mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
Added Save in a project folder with metadata option, added Go to folder button
This commit is contained in:
+3
-1
@@ -4,7 +4,9 @@
|
|||||||
- Prevent cancellation if process is at 99%, ensuring the process is not interrupted at the last moment.
|
- Prevent cancellation if process is at 99%, ensuring the process is not interrupted at the last moment.
|
||||||
- Improved process handling for subpprocess calls, ensuring better management of subprocesses.
|
- Improved process handling for subpprocess calls, ensuring better management of subprocesses.
|
||||||
- Improved PDF handling, ignoring empty pages/chapters and better chapter handling.
|
- Improved PDF handling, ignoring empty pages/chapters and better chapter handling.
|
||||||
- Added `.opus` as output format for generated audio files.
|
- Added `Save in a project folder with metadata` option in the book handler, allowing users to save the converted items in a project folder with available metadata files. Useful if you want to work with the converted files in the future, issue mentioned by @Darthagnon in #15
|
||||||
|
- Added `Go to folder` button in input box, allowing users to open the folder containing the converted file.
|
||||||
|
- Added `.opus` as output format for generated audio files, which is a more efficient format for audio files.
|
||||||
- Added "Playing..." indicator for "Preview" button in the voice mixer.
|
- Added "Playing..." indicator for "Preview" button in the voice mixer.
|
||||||
|
|
||||||
# v1.0.6
|
# v1.0.6
|
||||||
|
|||||||
+24
-3
@@ -37,6 +37,7 @@ class HandlerDialog(QDialog):
|
|||||||
# Class variables to remember checkbox states between dialog instances
|
# Class variables to remember checkbox states between dialog instances
|
||||||
_save_chapters_separately = False
|
_save_chapters_separately = False
|
||||||
_merge_chapters_at_end = True
|
_merge_chapters_at_end = True
|
||||||
|
_save_as_project = False # New class variable for save_as_project option
|
||||||
|
|
||||||
def __init__(self, book_path, file_type=None, checked_chapters=None, parent=None):
|
def __init__(self, book_path, file_type=None, checked_chapters=None, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@@ -64,6 +65,7 @@ class HandlerDialog(QDialog):
|
|||||||
# Initialize save chapters flags from class variables
|
# Initialize save chapters flags from class variables
|
||||||
self.save_chapters_separately = HandlerDialog._save_chapters_separately
|
self.save_chapters_separately = HandlerDialog._save_chapters_separately
|
||||||
self.merge_chapters_at_end = HandlerDialog._merge_chapters_at_end
|
self.merge_chapters_at_end = HandlerDialog._merge_chapters_at_end
|
||||||
|
self.save_as_project = HandlerDialog._save_as_project
|
||||||
|
|
||||||
# Load the book based on file type
|
# Load the book based on file type
|
||||||
try:
|
try:
|
||||||
@@ -1099,7 +1101,7 @@ class HandlerDialog(QDialog):
|
|||||||
self.previewEdit.setStyleSheet("QTextEdit { border: none; }")
|
self.previewEdit.setStyleSheet("QTextEdit { border: none; }")
|
||||||
|
|
||||||
self.previewInfoLabel = QLabel(
|
self.previewInfoLabel = QLabel(
|
||||||
'*Note: You can modify the content later using the "Edit" button in the input box or by accessing the temporary files directory through settings.',
|
'*Note: You can modify the content later using the "Edit" button in the input box or by accessing the temporary files directory through settings (if not saved in a project folder).',
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
self.previewInfoLabel.setWordWrap(True)
|
self.previewInfoLabel.setWordWrap(True)
|
||||||
@@ -1173,8 +1175,7 @@ class HandlerDialog(QDialog):
|
|||||||
self.save_chapters_checkbox = QCheckBox(checkbox_text, self)
|
self.save_chapters_checkbox = QCheckBox(checkbox_text, self)
|
||||||
self.save_chapters_checkbox.setChecked(self.save_chapters_separately)
|
self.save_chapters_checkbox.setChecked(self.save_chapters_separately)
|
||||||
self.save_chapters_checkbox.stateChanged.connect(self.on_save_chapters_changed)
|
self.save_chapters_checkbox.stateChanged.connect(self.on_save_chapters_changed)
|
||||||
leftLayout.addWidget(self.save_chapters_checkbox)
|
leftLayout.addWidget(self.save_chapters_checkbox)
|
||||||
|
|
||||||
self.merge_chapters_checkbox = QCheckBox(
|
self.merge_chapters_checkbox = QCheckBox(
|
||||||
"Create a merged version at the end", self
|
"Create a merged version at the end", self
|
||||||
)
|
)
|
||||||
@@ -1183,6 +1184,19 @@ class HandlerDialog(QDialog):
|
|||||||
self.on_merge_chapters_changed
|
self.on_merge_chapters_changed
|
||||||
)
|
)
|
||||||
leftLayout.addWidget(self.merge_chapters_checkbox)
|
leftLayout.addWidget(self.merge_chapters_checkbox)
|
||||||
|
|
||||||
|
self.save_as_project_checkbox = QCheckBox(
|
||||||
|
"Save in a project folder with metadata", self
|
||||||
|
)
|
||||||
|
self.save_as_project_checkbox.setToolTip(
|
||||||
|
"Save the converted item in a project folder with metadata files. "
|
||||||
|
"(Useful if you want to work with converted items in the future.)"
|
||||||
|
)
|
||||||
|
self.save_as_project_checkbox.setChecked(self.save_as_project)
|
||||||
|
self.save_as_project_checkbox.stateChanged.connect(
|
||||||
|
self.on_save_as_project_changed
|
||||||
|
)
|
||||||
|
leftLayout.addWidget(self.save_as_project_checkbox)
|
||||||
|
|
||||||
leftLayout.addWidget(buttons)
|
leftLayout.addWidget(buttons)
|
||||||
|
|
||||||
@@ -1696,6 +1710,10 @@ class HandlerDialog(QDialog):
|
|||||||
self.merge_chapters_at_end = bool(state)
|
self.merge_chapters_at_end = bool(state)
|
||||||
HandlerDialog._merge_chapters_at_end = self.merge_chapters_at_end
|
HandlerDialog._merge_chapters_at_end = self.merge_chapters_at_end
|
||||||
|
|
||||||
|
def on_save_as_project_changed(self, state):
|
||||||
|
self.save_as_project = bool(state)
|
||||||
|
HandlerDialog._save_as_project = self.save_as_project
|
||||||
|
|
||||||
def get_save_chapters_separately(self):
|
def get_save_chapters_separately(self):
|
||||||
return (
|
return (
|
||||||
self.save_chapters_separately
|
self.save_chapters_separately
|
||||||
@@ -1706,6 +1724,9 @@ class HandlerDialog(QDialog):
|
|||||||
def get_merge_chapters_at_end(self):
|
def get_merge_chapters_at_end(self):
|
||||||
return self.merge_chapters_at_end
|
return self.merge_chapters_at_end
|
||||||
|
|
||||||
|
def get_save_as_project(self):
|
||||||
|
return self.save_as_project
|
||||||
|
|
||||||
def on_tree_context_menu(self, pos):
|
def on_tree_context_menu(self, pos):
|
||||||
item = self.treeWidget.itemAt(pos)
|
item = self.treeWidget.itemAt(pos)
|
||||||
if (
|
if (
|
||||||
|
|||||||
+71
-5
@@ -128,6 +128,13 @@ class InputBox(QLabel):
|
|||||||
self.edit_btn.clicked.connect(self.on_edit_clicked)
|
self.edit_btn.clicked.connect(self.on_edit_clicked)
|
||||||
self.edit_btn.hide()
|
self.edit_btn.hide()
|
||||||
|
|
||||||
|
# Add Go to folder button
|
||||||
|
self.go_to_folder_btn = QPushButton("Go to folder", self)
|
||||||
|
self.go_to_folder_btn.setStyleSheet("QPushButton { padding: 6px 10px; }")
|
||||||
|
self.go_to_folder_btn.setToolTip("Open the folder that contains the converted file")
|
||||||
|
self.go_to_folder_btn.clicked.connect(self.on_go_to_folder_clicked)
|
||||||
|
self.go_to_folder_btn.hide()
|
||||||
|
|
||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event):
|
||||||
super().resizeEvent(event)
|
super().resizeEvent(event)
|
||||||
margin = 12
|
margin = 12
|
||||||
@@ -138,6 +145,11 @@ class InputBox(QLabel):
|
|||||||
# Position textbox button at top left
|
# Position textbox button at top left
|
||||||
self.textbox_btn.move(margin, margin)
|
self.textbox_btn.move(margin, margin)
|
||||||
self.edit_btn.move(margin, margin)
|
self.edit_btn.move(margin, margin)
|
||||||
|
# Position go to folder button at bottom right
|
||||||
|
self.go_to_folder_btn.move(
|
||||||
|
self.width() - self.go_to_folder_btn.width(),
|
||||||
|
self.height() - self.go_to_folder_btn.height() - margin,
|
||||||
|
)
|
||||||
|
|
||||||
def set_file_info(self, file_path):
|
def set_file_info(self, file_path):
|
||||||
# get icon without resizing using custom provider
|
# get icon without resizing using custom provider
|
||||||
@@ -227,6 +239,7 @@ class InputBox(QLabel):
|
|||||||
should_show_edit = True
|
should_show_edit = True
|
||||||
|
|
||||||
self.edit_btn.setVisible(should_show_edit)
|
self.edit_btn.setVisible(should_show_edit)
|
||||||
|
self.go_to_folder_btn.show()
|
||||||
|
|
||||||
def set_error(self, message):
|
def set_error(self, message):
|
||||||
self.setText(message)
|
self.setText(message)
|
||||||
@@ -249,6 +262,7 @@ class InputBox(QLabel):
|
|||||||
# Show textbox and hide edit when input is cleared
|
# Show textbox and hide edit when input is cleared
|
||||||
self.textbox_btn.show()
|
self.textbox_btn.show()
|
||||||
self.edit_btn.hide()
|
self.edit_btn.hide()
|
||||||
|
self.go_to_folder_btn.hide()
|
||||||
|
|
||||||
def _human_readable_size(self, size, decimal_places=2):
|
def _human_readable_size(self, size, decimal_places=2):
|
||||||
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
||||||
@@ -319,6 +333,17 @@ class InputBox(QLabel):
|
|||||||
# For regular txt files
|
# For regular txt files
|
||||||
win.open_textbox_dialog()
|
win.open_textbox_dialog()
|
||||||
|
|
||||||
|
def on_go_to_folder_clicked(self):
|
||||||
|
win = self.window()
|
||||||
|
# win.selected_file holds the path to the text that is converted.
|
||||||
|
file_to_check = win.selected_file
|
||||||
|
|
||||||
|
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):
|
class TextboxDialog(QDialog):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
@@ -861,9 +886,7 @@ class abogen(QWidget):
|
|||||||
)
|
)
|
||||||
dialog.setWindowModality(Qt.NonModal)
|
dialog.setWindowModality(Qt.NonModal)
|
||||||
dialog.setModal(False)
|
dialog.setModal(False)
|
||||||
dialog.show()
|
dialog.show() # We'll handle the dialog result asynchronously
|
||||||
|
|
||||||
# We'll handle the dialog result asynchronously
|
|
||||||
def on_dialog_finished(result):
|
def on_dialog_finished(result):
|
||||||
if result != QDialog.Accepted:
|
if result != QDialog.Accepted:
|
||||||
return False
|
return False
|
||||||
@@ -878,6 +901,7 @@ class abogen(QWidget):
|
|||||||
self.selected_chapters = all_checked_hrefs
|
self.selected_chapters = all_checked_hrefs
|
||||||
self.save_chapters_separately = dialog.get_save_chapters_separately()
|
self.save_chapters_separately = dialog.get_save_chapters_separately()
|
||||||
self.merge_chapters_at_end = dialog.get_merge_chapters_at_end()
|
self.merge_chapters_at_end = dialog.get_merge_chapters_at_end()
|
||||||
|
self.save_as_project = dialog.get_save_as_project()
|
||||||
|
|
||||||
# Store if the PDF has bookmarks for button text display
|
# Store if the PDF has bookmarks for button text display
|
||||||
if book_path.lower().endswith(".pdf"):
|
if book_path.lower().endswith(".pdf"):
|
||||||
@@ -886,8 +910,50 @@ class abogen(QWidget):
|
|||||||
# Use "abogen" prefix for temporary files
|
# Use "abogen" prefix for temporary files
|
||||||
# Extract base name without extension
|
# Extract base name without extension
|
||||||
base_name = os.path.splitext(os.path.basename(book_path))[0]
|
base_name = os.path.splitext(os.path.basename(book_path))[0]
|
||||||
temp_dir = os.path.join(tempfile.gettempdir(), PROGRAM_NAME)
|
|
||||||
os.makedirs(temp_dir, exist_ok=True)
|
if self.save_as_project:
|
||||||
|
# Get project directory from user
|
||||||
|
project_dir = QFileDialog.getExistingDirectory(
|
||||||
|
self, "Select Project Folder", "", QFileDialog.ShowDirsOnly
|
||||||
|
)
|
||||||
|
if not project_dir:
|
||||||
|
# User cancelled, fallback to temp
|
||||||
|
self.save_as_project = False
|
||||||
|
temp_dir = os.path.join(tempfile.gettempdir(), PROGRAM_NAME)
|
||||||
|
else:
|
||||||
|
# Create project folder structure
|
||||||
|
project_name = f"{base_name}_project"
|
||||||
|
project_dir = os.path.join(project_dir, project_name)
|
||||||
|
temp_dir = os.path.join(project_dir, "text")
|
||||||
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Save metadata if available
|
||||||
|
meta_dir = os.path.join(project_dir, "metadata")
|
||||||
|
os.makedirs(meta_dir, exist_ok=True) # Save book metadata if available
|
||||||
|
if hasattr(dialog, "book_metadata"):
|
||||||
|
meta_path = os.path.join(meta_dir, "book_info.txt")
|
||||||
|
with open(meta_path, "w", encoding="utf-8") as f:
|
||||||
|
# Clean HTML tags from metadata
|
||||||
|
title = re.sub(r'<[^>]+>', '', str(dialog.book_metadata.get('title', 'Unknown')))
|
||||||
|
publisher = re.sub(r'<[^>]+>', '', str(dialog.book_metadata.get('publisher', 'Unknown')))
|
||||||
|
authors = [re.sub(r'<[^>]+>', '', str(author)) for author in dialog.book_metadata.get('authors', ['Unknown'])]
|
||||||
|
|
||||||
|
f.write(f"Title: {title}\n")
|
||||||
|
f.write(f"Authors: {', '.join(authors)}\n")
|
||||||
|
f.write(f"Publisher: {publisher}\n")
|
||||||
|
if dialog.book_metadata.get('description'):
|
||||||
|
description = re.sub(r'<[^>]+>', '', str(dialog.book_metadata.get('description')))
|
||||||
|
f.write(f"\nDescription:\n{description}\n")
|
||||||
|
|
||||||
|
# Save cover image if available
|
||||||
|
if dialog.book_metadata.get("cover_image"):
|
||||||
|
cover_path = os.path.join(meta_dir, "cover.png")
|
||||||
|
with open(cover_path, "wb") as f:
|
||||||
|
f.write(dialog.book_metadata["cover_image"])
|
||||||
|
else:
|
||||||
|
temp_dir = os.path.join(tempfile.gettempdir(), PROGRAM_NAME)
|
||||||
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
|
||||||
fd, tmp = tempfile.mkstemp(
|
fd, tmp = tempfile.mkstemp(
|
||||||
prefix=f"abogen_{base_name}_", suffix=".txt", dir=temp_dir
|
prefix=f"abogen_{base_name}_", suffix=".txt", dir=temp_dir
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user