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.
|
||||
- Improved process handling for subpprocess calls, ensuring better management of subprocesses.
|
||||
- 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.
|
||||
|
||||
# v1.0.6
|
||||
|
||||
+23
-2
@@ -37,6 +37,7 @@ class HandlerDialog(QDialog):
|
||||
# Class variables to remember checkbox states between dialog instances
|
||||
_save_chapters_separately = False
|
||||
_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):
|
||||
super().__init__(parent)
|
||||
@@ -64,6 +65,7 @@ class HandlerDialog(QDialog):
|
||||
# Initialize save chapters flags from class variables
|
||||
self.save_chapters_separately = HandlerDialog._save_chapters_separately
|
||||
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
|
||||
try:
|
||||
@@ -1099,7 +1101,7 @@ class HandlerDialog(QDialog):
|
||||
self.previewEdit.setStyleSheet("QTextEdit { border: none; }")
|
||||
|
||||
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.previewInfoLabel.setWordWrap(True)
|
||||
@@ -1174,7 +1176,6 @@ class HandlerDialog(QDialog):
|
||||
self.save_chapters_checkbox.setChecked(self.save_chapters_separately)
|
||||
self.save_chapters_checkbox.stateChanged.connect(self.on_save_chapters_changed)
|
||||
leftLayout.addWidget(self.save_chapters_checkbox)
|
||||
|
||||
self.merge_chapters_checkbox = QCheckBox(
|
||||
"Create a merged version at the end", self
|
||||
)
|
||||
@@ -1184,6 +1185,19 @@ class HandlerDialog(QDialog):
|
||||
)
|
||||
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)
|
||||
|
||||
leftWidget = QWidget()
|
||||
@@ -1696,6 +1710,10 @@ class HandlerDialog(QDialog):
|
||||
self.merge_chapters_at_end = bool(state)
|
||||
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):
|
||||
return (
|
||||
self.save_chapters_separately
|
||||
@@ -1706,6 +1724,9 @@ class HandlerDialog(QDialog):
|
||||
def get_merge_chapters_at_end(self):
|
||||
return self.merge_chapters_at_end
|
||||
|
||||
def get_save_as_project(self):
|
||||
return self.save_as_project
|
||||
|
||||
def on_tree_context_menu(self, pos):
|
||||
item = self.treeWidget.itemAt(pos)
|
||||
if (
|
||||
|
||||
+69
-3
@@ -128,6 +128,13 @@ class InputBox(QLabel):
|
||||
self.edit_btn.clicked.connect(self.on_edit_clicked)
|
||||
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):
|
||||
super().resizeEvent(event)
|
||||
margin = 12
|
||||
@@ -138,6 +145,11 @@ class InputBox(QLabel):
|
||||
# Position textbox button at top left
|
||||
self.textbox_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):
|
||||
# get icon without resizing using custom provider
|
||||
@@ -227,6 +239,7 @@ class InputBox(QLabel):
|
||||
should_show_edit = True
|
||||
|
||||
self.edit_btn.setVisible(should_show_edit)
|
||||
self.go_to_folder_btn.show()
|
||||
|
||||
def set_error(self, message):
|
||||
self.setText(message)
|
||||
@@ -249,6 +262,7 @@ class InputBox(QLabel):
|
||||
# Show textbox and hide edit when input is cleared
|
||||
self.textbox_btn.show()
|
||||
self.edit_btn.hide()
|
||||
self.go_to_folder_btn.hide()
|
||||
|
||||
def _human_readable_size(self, size, decimal_places=2):
|
||||
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
||||
@@ -319,6 +333,17 @@ class InputBox(QLabel):
|
||||
# For regular txt files
|
||||
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):
|
||||
def __init__(self, parent=None):
|
||||
@@ -861,9 +886,7 @@ class abogen(QWidget):
|
||||
)
|
||||
dialog.setWindowModality(Qt.NonModal)
|
||||
dialog.setModal(False)
|
||||
dialog.show()
|
||||
|
||||
# We'll handle the dialog result asynchronously
|
||||
dialog.show() # We'll handle the dialog result asynchronously
|
||||
def on_dialog_finished(result):
|
||||
if result != QDialog.Accepted:
|
||||
return False
|
||||
@@ -878,6 +901,7 @@ class abogen(QWidget):
|
||||
self.selected_chapters = all_checked_hrefs
|
||||
self.save_chapters_separately = dialog.get_save_chapters_separately()
|
||||
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
|
||||
if book_path.lower().endswith(".pdf"):
|
||||
@@ -886,8 +910,50 @@ class abogen(QWidget):
|
||||
# Use "abogen" prefix for temporary files
|
||||
# Extract base name without extension
|
||||
base_name = os.path.splitext(os.path.basename(book_path))[0]
|
||||
|
||||
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(
|
||||
prefix=f"abogen_{base_name}_", suffix=".txt", dir=temp_dir
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user