mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 05:40:26 +02:00
Fixed save options not working correctly, better indicators
This commit is contained in:
+3
-1
@@ -1,7 +1,9 @@
|
||||
# 1.1.8 (pre-release)
|
||||
- Added `.md` (Markdown) file extension support by @brianxiadong in PR #75
|
||||
- Added new option `Configure silence between chapters` that lets you configure the silence between chapters, mentioned by @lfperez1982 in #79
|
||||
- Improved the markdown logic to better handle various markdown structures and edge cases.
|
||||
- Better indicators while displaying and managing the input and processing files.
|
||||
- Improved the markdown logic to better handle various markdown structures and cases.
|
||||
- Fixed save options not working correctly in queue mode, mentioned by @jborza in #78
|
||||
- Fixed `No Qt platform plugin could be initialized` error, mentioned by @sunrainxyz in #59
|
||||
- Potentially fixed subtitle generation stucks at 9:59:59, mentioned by @bolaykim in #73
|
||||
- Improvements in code and documentation.
|
||||
|
||||
+33
-25
@@ -148,6 +148,7 @@ class ConversionThread(QThread):
|
||||
total_char_count,
|
||||
use_gpu=True,
|
||||
from_queue=False,
|
||||
save_base_path=None,
|
||||
): # Add use_gpu parameter
|
||||
super().__init__()
|
||||
self._chapter_options_event = threading.Event()
|
||||
@@ -169,6 +170,7 @@ class ConversionThread(QThread):
|
||||
self.total_char_count = total_char_count # Use passed total character count
|
||||
self.processed_char_count = 0 # Initialize processed character count
|
||||
self.display_path = None # Add variable for display path
|
||||
self.save_base_path = save_base_path # Store the save base path
|
||||
self.is_direct_text = (
|
||||
False # Flag to indicate if input is from textbox rather than file
|
||||
)
|
||||
@@ -238,15 +240,23 @@ class ConversionThread(QThread):
|
||||
# Show configuration
|
||||
self.log_updated.emit("Configuration:")
|
||||
|
||||
# Determine input file and processing file
|
||||
if getattr(self, "from_queue", False):
|
||||
input_file = self.save_base_path or self.file_name
|
||||
processing_file = self.file_name
|
||||
else:
|
||||
input_file = self.display_path if self.display_path else self.file_name
|
||||
processing_file = self.file_name
|
||||
|
||||
self.log_updated.emit(f"- Input File: {input_file}")
|
||||
if input_file != processing_file:
|
||||
self.log_updated.emit(f"- Processing File: {processing_file}")
|
||||
|
||||
# Use file_name for logs if from_queue, otherwise use display_path if available
|
||||
if getattr(self, "from_queue", False):
|
||||
display_file = self.file_name
|
||||
base_path = self.save_base_path or self.file_name # Use save_base_path if available
|
||||
else:
|
||||
display_file = (
|
||||
self.display_path if self.display_path else self.file_name
|
||||
)
|
||||
|
||||
self.log_updated.emit(f"- Input File: {display_file}")
|
||||
base_path = self.display_path if self.display_path else self.file_name
|
||||
|
||||
# Use file size string passed from GUI
|
||||
if hasattr(self, "file_size_str"):
|
||||
@@ -402,7 +412,7 @@ class ConversionThread(QThread):
|
||||
|
||||
# Use file_name for logs if from_queue, otherwise use display_path if available
|
||||
if getattr(self, "from_queue", False):
|
||||
base_path = self.file_name
|
||||
base_path = self.save_base_path or self.file_name # Use save_base_path if available
|
||||
else:
|
||||
base_path = self.display_path if self.display_path else self.file_name
|
||||
|
||||
@@ -607,6 +617,10 @@ class ConversionThread(QThread):
|
||||
# Instead of processing the whole text, process by chapter
|
||||
for chapter_idx, (chapter_name, chapter_text) in enumerate(chapters, 1):
|
||||
chapter_out_path = None
|
||||
chapter_out_file = None
|
||||
chapter_ffmpeg_proc = None
|
||||
chapter_subtitle_file = None
|
||||
chapter_subtitle_path = None
|
||||
if total_chapters > 1:
|
||||
self.log_updated.emit(
|
||||
(
|
||||
@@ -720,27 +734,21 @@ class ConversionThread(QThread):
|
||||
is_narrow = subtitle_format in (
|
||||
"ass_narrow",
|
||||
"ass_centered_narrow",
|
||||
)
|
||||
chapter_subtitle_margin = "90" if is_narrow else ""
|
||||
chapter_subtitle_alignment_tag = (
|
||||
f"{{\\an5}}" if is_centered else ""
|
||||
)
|
||||
else:
|
||||
chapter_subtitle_file = open(
|
||||
chapter_subtitle_path,
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
)
|
||||
chapter_subtitle_margin = "90" if is_narrow else ""
|
||||
chapter_subtitle_alignment_tag = (
|
||||
f"{{\\an5}}" if is_centered else ""
|
||||
)
|
||||
else:
|
||||
chapter_subtitle_path = None
|
||||
chapter_subtitle_file = None
|
||||
chapter_subtitle_file = open(
|
||||
chapter_subtitle_path,
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
else:
|
||||
chapter_out_file = None
|
||||
chapter_out_path = None
|
||||
chapter_ffmpeg_proc = None
|
||||
chapter_subtitle_file = None
|
||||
chapter_subtitle_path = None
|
||||
chapter_subtitle_file = None
|
||||
for result in tts(
|
||||
chapter_text,
|
||||
voice=loaded_voice,
|
||||
|
||||
+74
-5
@@ -486,15 +486,63 @@ class InputBox(QLabel):
|
||||
# win.selected_file holds the path to the text that is converted.
|
||||
file_to_check = win.selected_file
|
||||
|
||||
# If this is a converted document (epub/pdf/markdown) that was written to the
|
||||
# user's cache directory, show a menu letting the user jump to either the
|
||||
# processed (cached .txt) file or the original input file (epub/pdf/md).
|
||||
try:
|
||||
cache_dir = get_user_cache_path()
|
||||
except Exception:
|
||||
cache_dir = None
|
||||
|
||||
is_cached_doc = False
|
||||
if (
|
||||
file_to_check
|
||||
and os.path.exists(file_to_check)
|
||||
and os.path.isfile(file_to_check)
|
||||
and cache_dir
|
||||
):
|
||||
folder_path = os.path.dirname(file_to_check)
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(folder_path))
|
||||
# Consider it cached when the file is under the cache directory and is a .txt
|
||||
if file_to_check.endswith('.txt') and os.path.commonpath([os.path.abspath(file_to_check), os.path.abspath(cache_dir)]) == os.path.abspath(cache_dir):
|
||||
# Only treat as document-cache when original type was a document
|
||||
if getattr(win, 'selected_file_type', None) in ['epub', 'pdf', 'md', 'markdown']:
|
||||
is_cached_doc = True
|
||||
|
||||
if is_cached_doc:
|
||||
menu = QMenu(self)
|
||||
act_processed = QAction("Go to processed file", self)
|
||||
|
||||
def open_processed():
|
||||
folder_path = os.path.dirname(file_to_check)
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(folder_path))
|
||||
|
||||
act_processed.triggered.connect(open_processed)
|
||||
menu.addAction(act_processed)
|
||||
|
||||
act_input = QAction("Go to input file", self)
|
||||
# Prefer displayed_file_path (original input path) then selected_book_path
|
||||
input_path = getattr(win, 'displayed_file_path', None) or getattr(win, 'selected_book_path', None)
|
||||
if input_path and os.path.exists(input_path):
|
||||
def open_input():
|
||||
folder_path = os.path.dirname(input_path)
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(folder_path))
|
||||
|
||||
act_input.triggered.connect(open_input)
|
||||
else:
|
||||
act_input.setEnabled(False)
|
||||
|
||||
menu.addAction(act_input)
|
||||
# Show the menu anchored to the button
|
||||
menu.exec_(self.go_to_folder_btn.mapToGlobal(QPoint(0, self.go_to_folder_btn.height())))
|
||||
else:
|
||||
QMessageBox.warning(win, "Error", "Converted file not found.")
|
||||
if (
|
||||
file_to_check
|
||||
and os.path.exists(file_to_check)
|
||||
and os.path.isfile(file_to_check)
|
||||
):
|
||||
folder_path = os.path.dirname(file_to_check)
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(folder_path))
|
||||
else:
|
||||
QMessageBox.warning(win, "Error", "Converted file not found.")
|
||||
|
||||
|
||||
class TextboxDialog(QDialog):
|
||||
@@ -1644,13 +1692,16 @@ class abogen(QWidget):
|
||||
# For epub/pdf, always use the converted txt file (selected_file)
|
||||
if self.selected_file_type in ["epub", "pdf", "md", "markdown"]:
|
||||
file_to_queue = self.selected_file
|
||||
# Use the original file path for save location
|
||||
save_base_path = self.displayed_file_path if self.displayed_file_path else file_to_queue
|
||||
else:
|
||||
file_to_queue = (
|
||||
self.displayed_file_path
|
||||
if self.displayed_file_path
|
||||
else self.selected_file
|
||||
)
|
||||
|
||||
save_base_path = file_to_queue # For non-EPUB, it's the same
|
||||
|
||||
if not file_to_queue:
|
||||
self.input_box.set_error("Please add a file.")
|
||||
return
|
||||
@@ -1669,6 +1720,7 @@ class abogen(QWidget):
|
||||
output_format=self.selected_format,
|
||||
total_char_count=self.char_count,
|
||||
replace_single_newlines=self.replace_single_newlines,
|
||||
save_base_path=save_base_path,
|
||||
)
|
||||
|
||||
# Prevent adding duplicate items to the queue
|
||||
@@ -1684,6 +1736,8 @@ class abogen(QWidget):
|
||||
and queued_item.output_format == item_queue.output_format
|
||||
and getattr(queued_item, "replace_single_newlines", False)
|
||||
== item_queue.replace_single_newlines
|
||||
and getattr(queued_item, "save_base_path", None)
|
||||
== item_queue.save_base_path
|
||||
):
|
||||
QMessageBox.warning(
|
||||
self, "Duplicate Item", "This item is already in the queue."
|
||||
@@ -1743,6 +1797,8 @@ class abogen(QWidget):
|
||||
self.replace_single_newlines = getattr(
|
||||
queued_item, "replace_single_newlines", False
|
||||
)
|
||||
# Restore the original file path for save location
|
||||
self.displayed_file_path = queued_item.save_base_path or queued_item.file_name
|
||||
self.start_conversion(from_queue=True)
|
||||
else:
|
||||
# Queue finished, reset index
|
||||
@@ -1786,6 +1842,18 @@ class abogen(QWidget):
|
||||
if not self.selected_file:
|
||||
self.input_box.set_error("Please add a file.")
|
||||
return
|
||||
|
||||
# Ensure we honor the currently selected save option when not running from queue
|
||||
if not from_queue:
|
||||
current_option = self.save_combo.currentText()
|
||||
self.save_option = current_option
|
||||
self.config["save_option"] = current_option
|
||||
# If user is not choosing a specific folder, clear any residual folder
|
||||
if current_option != "Choose output folder":
|
||||
self.selected_output_folder = None
|
||||
self.config["selected_output_folder"] = None
|
||||
save_config(self.config)
|
||||
|
||||
prevent_sleep_start()
|
||||
self.is_converting = True
|
||||
self.convert_input_box_to_log()
|
||||
@@ -1858,6 +1926,7 @@ class abogen(QWidget):
|
||||
total_char_count=self.char_count,
|
||||
use_gpu=self.gpu_ok,
|
||||
from_queue=from_queue,
|
||||
save_base_path=self.displayed_file_path, # Pass the save base path (original file for EPUB)
|
||||
) # Use gpu_ok status
|
||||
# Pass the displayed file path to the log_updated signal handler in ConversionThread
|
||||
self.conversion_thread.display_path = display_path
|
||||
@@ -2981,7 +3050,7 @@ class abogen(QWidget):
|
||||
def open_cache_directory(self):
|
||||
"""Open the cache directory used by the program."""
|
||||
try:
|
||||
# Get the cache directory path
|
||||
# Get the abogen cache directory
|
||||
cache_dir = get_user_cache_path()
|
||||
|
||||
# Create the directory if it doesn't exist
|
||||
|
||||
+125
-30
@@ -217,20 +217,33 @@ class QueueManager(QDialog):
|
||||
self.empty_overlay.hide()
|
||||
icon_provider = QFileIconProvider()
|
||||
for item in self.queue:
|
||||
# Determine display file path (prefer save_base_path for original file)
|
||||
display_file_path = getattr(item, "save_base_path", None) or item.file_name
|
||||
processing_file_path = item.file_name
|
||||
|
||||
# Only show the file name, not the full path
|
||||
file_name = item.file_name
|
||||
display_name = file_name
|
||||
display_name = display_file_path
|
||||
import os
|
||||
|
||||
if os.path.sep in file_name:
|
||||
display_name = os.path.basename(file_name)
|
||||
# Get icon for the file
|
||||
icon = icon_provider.icon(QFileInfo(file_name))
|
||||
if os.path.sep in display_file_path:
|
||||
display_name = os.path.basename(display_file_path)
|
||||
# Get icon for the display file
|
||||
icon = icon_provider.icon(QFileInfo(display_file_path))
|
||||
list_item = QListWidgetItem()
|
||||
# Set tooltip with detailed info
|
||||
output_folder = getattr(item, "output_folder", "")
|
||||
tooltip = (
|
||||
f"<b>Path:</b> {file_name}<br>"
|
||||
# For plain .txt inputs we don't need to show a separate processing file
|
||||
show_processing = True
|
||||
try:
|
||||
if isinstance(display_file_path, str) and display_file_path.lower().endswith('.txt'):
|
||||
show_processing = False
|
||||
except Exception:
|
||||
show_processing = True
|
||||
|
||||
tooltip = f"<b>Input File:</b> {display_file_path}<br>"
|
||||
if show_processing and processing_file_path and processing_file_path != display_file_path:
|
||||
tooltip += f"<b>Processing File:</b> {processing_file_path}<br>"
|
||||
tooltip += (
|
||||
f"<b>Language:</b> {getattr(item, 'lang_code', '')}<br>"
|
||||
f"<b>Speed:</b> {getattr(item, 'speed', '')}<br>"
|
||||
f"<b>Voice:</b> {getattr(item, 'voice', '')}<br>"
|
||||
@@ -246,10 +259,14 @@ class QueueManager(QDialog):
|
||||
)
|
||||
list_item.setToolTip(tooltip)
|
||||
list_item.setIcon(icon)
|
||||
list_item.setData(Qt.UserRole, file_name)
|
||||
# Store both paths for context menu
|
||||
list_item.setData(Qt.UserRole, {
|
||||
'display_path': display_file_path,
|
||||
'processing_path': processing_file_path
|
||||
})
|
||||
# Use custom widget for display
|
||||
char_count = getattr(item, "total_char_count", 0)
|
||||
widget = QueueListItemWidget(file_name, char_count)
|
||||
widget = QueueListItemWidget(display_file_path, char_count)
|
||||
self.listwidget.addItem(list_item)
|
||||
self.listwidget.setItemWidget(list_item, widget)
|
||||
self.update_button_states()
|
||||
@@ -372,6 +389,7 @@ class QueueManager(QDialog):
|
||||
|
||||
item = QueueItem()
|
||||
item.file_name = file_path
|
||||
item.save_base_path = file_path # For .txt files, processing and save paths are the same
|
||||
for attr, value in current_attrs.items():
|
||||
setattr(item, attr, value)
|
||||
# Read file content and calculate total_char_count using calculate_text_length
|
||||
@@ -405,6 +423,8 @@ class QueueManager(QDialog):
|
||||
== getattr(item, "total_char_count", None)
|
||||
and getattr(queued_item, "replace_single_newlines", False)
|
||||
== getattr(item, "replace_single_newlines", False)
|
||||
and getattr(queued_item, "save_base_path", None)
|
||||
== getattr(item, "save_base_path", None)
|
||||
):
|
||||
is_duplicate = True
|
||||
break
|
||||
@@ -473,42 +493,117 @@ class QueueManager(QDialog):
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
item = selected_items[0]
|
||||
file_path = item.data(Qt.UserRole)
|
||||
paths = item.data(Qt.UserRole)
|
||||
if isinstance(paths, dict):
|
||||
file_path = paths.get('display_path', paths.get('processing_path', ''))
|
||||
else:
|
||||
file_path = paths # Fallback for old format
|
||||
|
||||
# Find the queue item
|
||||
for q in self.queue:
|
||||
if q.file_name == file_path:
|
||||
if not os.path.exists(q.file_name):
|
||||
if (getattr(q, "save_base_path", None) == file_path or
|
||||
q.file_name == file_path):
|
||||
target_path = getattr(q, "save_base_path", None) or q.file_name
|
||||
if not os.path.exists(target_path):
|
||||
QMessageBox.warning(
|
||||
self, "File Not Found", f"The file does not exist."
|
||||
)
|
||||
return
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(q.file_name))
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(target_path))
|
||||
break
|
||||
|
||||
open_file_action.triggered.connect(open_file)
|
||||
menu.addAction(open_file_action)
|
||||
|
||||
# Add Go to folder action
|
||||
go_to_folder_action = QAction("Go to folder", self)
|
||||
# If the queued item represents a converted document (markdown, pdf, epub)
|
||||
# show two actions: Go to processed file (the cached .txt) and Go to input file (original source)
|
||||
item = selected_items[0]
|
||||
paths = item.data(Qt.UserRole)
|
||||
if isinstance(paths, dict):
|
||||
display_path = paths.get('display_path', '')
|
||||
processing_path = paths.get('processing_path', '')
|
||||
else:
|
||||
display_path = paths
|
||||
processing_path = paths
|
||||
|
||||
def go_to_folder():
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
doc_exts = ('.md', '.markdown', '.pdf', '.epub')
|
||||
is_document_input = (
|
||||
isinstance(display_path, str) and display_path.lower().endswith(doc_exts)
|
||||
) or (
|
||||
isinstance(processing_path, str) and processing_path.lower().endswith(doc_exts)
|
||||
)
|
||||
|
||||
item = selected_items[0]
|
||||
file_path = item.data(Qt.UserRole)
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
def open_folder_for(path_label: str):
|
||||
# path_label should be either 'display' or 'processing'
|
||||
p = display_path if path_label == 'display' else processing_path
|
||||
if not p:
|
||||
QMessageBox.warning(self, "File Not Found", "Path is not available.")
|
||||
return
|
||||
# If the stored path is the display path (original) but the actual file may be
|
||||
# stored on the queue object differently, try to resolve via the queue entry.
|
||||
target_path = None
|
||||
for q in self.queue:
|
||||
if q.file_name == file_path:
|
||||
if not os.path.exists(q.file_name):
|
||||
QMessageBox.warning(
|
||||
self, "File Not Found", f"The file does not exist."
|
||||
)
|
||||
return
|
||||
folder = os.path.dirname(q.file_name)
|
||||
if os.path.exists(folder):
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(folder))
|
||||
if getattr(q, 'save_base_path', None) == display_path or q.file_name == display_path:
|
||||
if path_label == 'display':
|
||||
target_path = getattr(q, 'save_base_path', None) or q.file_name
|
||||
else:
|
||||
target_path = q.file_name
|
||||
break
|
||||
if getattr(q, 'save_base_path', None) == processing_path or q.file_name == processing_path:
|
||||
if path_label == 'display':
|
||||
target_path = getattr(q, 'save_base_path', None) or q.file_name
|
||||
else:
|
||||
target_path = q.file_name
|
||||
break
|
||||
# Fallback to the raw path if resolution failed
|
||||
if not target_path:
|
||||
target_path = p
|
||||
|
||||
go_to_folder_action.triggered.connect(go_to_folder)
|
||||
menu.addAction(go_to_folder_action)
|
||||
if not os.path.exists(target_path):
|
||||
QMessageBox.warning(self, "File Not Found", f"The file does not exist: {target_path}")
|
||||
return
|
||||
folder = os.path.dirname(target_path)
|
||||
if os.path.exists(folder):
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(folder))
|
||||
|
||||
if is_document_input:
|
||||
processed_action = QAction("Go to processed file", self)
|
||||
processed_action.triggered.connect(lambda: open_folder_for('processing'))
|
||||
menu.addAction(processed_action)
|
||||
|
||||
input_action = QAction("Go to input file", self)
|
||||
input_action.triggered.connect(lambda: open_folder_for('display'))
|
||||
menu.addAction(input_action)
|
||||
else:
|
||||
# Default behavior for non-document inputs: single "Go to folder" action
|
||||
go_to_folder_action = QAction("Go to folder", self)
|
||||
|
||||
def go_to_folder():
|
||||
item = selected_items[0]
|
||||
paths = item.data(Qt.UserRole)
|
||||
if isinstance(paths, dict):
|
||||
file_path = paths.get('display_path', paths.get('processing_path', ''))
|
||||
else:
|
||||
file_path = paths # Fallback for old format
|
||||
# Find the queue item
|
||||
for q in self.queue:
|
||||
if (getattr(q, "save_base_path", None) == file_path or q.file_name == file_path):
|
||||
target_path = getattr(q, "save_base_path", None) or q.file_name
|
||||
if not os.path.exists(target_path):
|
||||
QMessageBox.warning(
|
||||
self, "File Not Found", f"The file does not exist."
|
||||
)
|
||||
return
|
||||
folder = os.path.dirname(target_path)
|
||||
if os.path.exists(folder):
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(folder))
|
||||
break
|
||||
|
||||
go_to_folder_action.triggered.connect(go_to_folder)
|
||||
menu.addAction(go_to_folder_action)
|
||||
|
||||
elif len(selected_items) > 1:
|
||||
remove_action = QAction(f"Remove selected ({len(selected_items)})", self)
|
||||
|
||||
@@ -14,3 +14,4 @@ class QueuedItem:
|
||||
output_format: str
|
||||
total_char_count: int
|
||||
replace_single_newlines: bool = False
|
||||
save_base_path: str = None
|
||||
|
||||
Reference in New Issue
Block a user