mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
Imrpovements in queue manager gui and code
This commit is contained in:
@@ -106,9 +106,18 @@ SAMPLE_VOICE_TEXTS = {
|
|||||||
COLORS = {
|
COLORS = {
|
||||||
"BLUE": "#007dff",
|
"BLUE": "#007dff",
|
||||||
"RED": "#c0392b",
|
"RED": "#c0392b",
|
||||||
|
"GREEN": "#42ad4a",
|
||||||
|
"GREEN_BG": "rgba(66, 173, 73, 0.1)",
|
||||||
|
"GREEN_BG_HOVER": "rgba(66, 173, 73, 0.15)",
|
||||||
|
"GREEN_BORDER": "#42ad4a",
|
||||||
|
"BLUE_BG": "rgba(0, 102, 255, 0.05)",
|
||||||
|
"BLUE_BG_HOVER": "rgba(0, 102, 255, 0.1)",
|
||||||
|
"BLUE_BORDER_HOVER": "#6ab0de",
|
||||||
"YELLOW_BACKGROUND": "rgba(255, 221, 51, 0.40)",
|
"YELLOW_BACKGROUND": "rgba(255, 221, 51, 0.40)",
|
||||||
"GREY_BACKGROUND": "rgba(128, 128, 128, 0.15)",
|
"GREY_BACKGROUND": "rgba(128, 128, 128, 0.15)",
|
||||||
"RED_BACKGROUND": "rgba(232, 78, 60, 0.15)",
|
"RED_BACKGROUND": "rgba(232, 78, 60, 0.15)",
|
||||||
|
"RED_BG": "rgba(232, 78, 60, 0.10)",
|
||||||
|
"RED_BG_HOVER": "rgba(232, 78, 60, 0.15)",
|
||||||
# Theme palette colors
|
# Theme palette colors
|
||||||
"DARK_BG": "#202326",
|
"DARK_BG": "#202326",
|
||||||
"DARK_BASE": "#141618",
|
"DARK_BASE": "#141618",
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ class ConversionThread(QThread):
|
|||||||
if hasattr(self, "file_size_str"):
|
if hasattr(self, "file_size_str"):
|
||||||
self.log_updated.emit(f"- File size: {self.file_size_str}")
|
self.log_updated.emit(f"- File size: {self.file_size_str}")
|
||||||
|
|
||||||
self.log_updated.emit(f"- Total characters: {self.total_char_count:,}")
|
self.log_updated.emit(f"- Total characters: {int(self.total_char_count):,}")
|
||||||
|
|
||||||
self.log_updated.emit(
|
self.log_updated.emit(
|
||||||
f"- Language: {self.lang_code} ({LANGUAGE_DESCRIPTIONS.get(self.lang_code, 'Unknown')})"
|
f"- Language: {self.lang_code} ({LANGUAGE_DESCRIPTIONS.get(self.lang_code, 'Unknown')})"
|
||||||
|
|||||||
+94
-41
@@ -124,14 +124,14 @@ class IconProvider(QFileIconProvider):
|
|||||||
|
|
||||||
class InputBox(QLabel):
|
class InputBox(QLabel):
|
||||||
# Define CSS styles as class constants
|
# Define CSS styles as class constants
|
||||||
STYLE_DEFAULT = "border:2px dashed #aaa; border-radius:5px; padding:20px; background:rgba(0, 102, 255, 0.05); min-height:100px;"
|
STYLE_DEFAULT = f"border:2px dashed #aaa; border-radius:5px; padding:20px; background:{COLORS['BLUE_BG']}; min-height:100px;"
|
||||||
STYLE_DEFAULT_HOVER = "background:rgba(0, 102, 255, 0.1); border-color:#6ab0de;"
|
STYLE_DEFAULT_HOVER = f"background:{COLORS['BLUE_BG_HOVER']}; border-color:{COLORS['BLUE_BORDER_HOVER']};"
|
||||||
|
|
||||||
STYLE_ACTIVE = "border:2px dashed #42ad4a; border-radius:5px; padding:20px; background:rgba(66, 173, 73, 0.1); min-height:100px;"
|
STYLE_ACTIVE = f"border:2px dashed {COLORS['GREEN']}; border-radius:5px; padding:20px; background:{COLORS['GREEN_BG']}; min-height:100px;"
|
||||||
STYLE_ACTIVE_HOVER = "background:rgba(66, 173, 73, 0.15); border-color:#42ad4a;"
|
STYLE_ACTIVE_HOVER = f"background:{COLORS['GREEN_BG_HOVER']}; border-color:{COLORS['GREEN_BORDER']};"
|
||||||
|
|
||||||
STYLE_ERROR = "border:2px dashed #e74c3c; border-radius:5px; padding:20px; background:rgba(232, 78, 60, 0.10); min-height:100px; color:#c0392b;"
|
STYLE_ERROR = f"border:2px dashed {COLORS['RED']}; border-radius:5px; padding:20px; background:{COLORS['RED_BG']}; min-height:100px; color:{COLORS['RED']};"
|
||||||
STYLE_ERROR_HOVER = "background:rgba(232, 78, 60, 0.15); border-color:#e74c3c;"
|
STYLE_ERROR_HOVER = f"background:{COLORS['RED_BG_HOVER']}; border-color:{COLORS['RED']};"
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@@ -206,7 +206,7 @@ class InputBox(QLabel):
|
|||||||
|
|
||||||
size_str = self._human_readable_size(os.path.getsize(file_path))
|
size_str = self._human_readable_size(os.path.getsize(file_path))
|
||||||
name = os.path.basename(file_path)
|
name = os.path.basename(file_path)
|
||||||
char_count = "N/A"
|
char_count = 0
|
||||||
|
|
||||||
def parse_size(size_str):
|
def parse_size(size_str):
|
||||||
# Use regex to extract the numeric part
|
# Use regex to extract the numeric part
|
||||||
@@ -653,6 +653,9 @@ class abogen(QWidget):
|
|||||||
self.selected_lang = entry[0][0] if entry and entry[0] else None
|
self.selected_lang = entry[0][0] if entry and entry[0] else None
|
||||||
if self.save_option == "Choose output folder" and self.selected_output_folder:
|
if self.save_option == "Choose output folder" and self.selected_output_folder:
|
||||||
self.save_path_label.setText(self.selected_output_folder)
|
self.save_path_label.setText(self.selected_output_folder)
|
||||||
|
self.save_path_label.setStyleSheet(
|
||||||
|
f"QLabel {{ color: {COLORS['GREEN']}; }}"
|
||||||
|
)
|
||||||
self.save_path_label.show()
|
self.save_path_label.show()
|
||||||
self.subtitle_combo.setCurrentText(self.subtitle_mode)
|
self.subtitle_combo.setCurrentText(self.subtitle_mode)
|
||||||
# Enable/disable subtitle options based on selected language (profile or voice)
|
# Enable/disable subtitle options based on selected language (profile or voice)
|
||||||
@@ -689,6 +692,26 @@ class abogen(QWidget):
|
|||||||
container_layout.setSpacing(15)
|
container_layout.setSpacing(15)
|
||||||
self.input_box = InputBox(self)
|
self.input_box = InputBox(self)
|
||||||
container_layout.addWidget(self.input_box, 1)
|
container_layout.addWidget(self.input_box, 1)
|
||||||
|
# Manage queue button, start queue button
|
||||||
|
self.queue_row_widget = QWidget(self) # Make queue_row a QWidget
|
||||||
|
queue_row = QHBoxLayout(self.queue_row_widget)
|
||||||
|
queue_row.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.btn_add_to_queue = QPushButton("Add to Queue", self)
|
||||||
|
self.btn_add_to_queue.setFixedHeight(40)
|
||||||
|
self.btn_add_to_queue.setEnabled(False)
|
||||||
|
self.btn_add_to_queue.clicked.connect(self.add_to_queue)
|
||||||
|
queue_row.addWidget(self.btn_add_to_queue)
|
||||||
|
self.btn_manage_queue = QPushButton("Manage Queue", self)
|
||||||
|
self.btn_manage_queue.setFixedHeight(40)
|
||||||
|
self.btn_manage_queue.setEnabled(True)
|
||||||
|
self.btn_manage_queue.clicked.connect(self.manage_queue)
|
||||||
|
queue_row.addWidget(self.btn_manage_queue)
|
||||||
|
self.btn_start_queue = QPushButton("Clear Queue", self)
|
||||||
|
self.btn_start_queue.setFixedHeight(40)
|
||||||
|
self.btn_start_queue.setEnabled(False)
|
||||||
|
self.btn_start_queue.clicked.connect(self.clear_queue)
|
||||||
|
queue_row.addWidget(self.btn_start_queue)
|
||||||
|
container_layout.addWidget(self.queue_row_widget)
|
||||||
self.log_text = QTextEdit(self)
|
self.log_text = QTextEdit(self)
|
||||||
self.log_text.setReadOnly(True)
|
self.log_text.setReadOnly(True)
|
||||||
self.log_text.setFrameStyle(QTextEdit.NoFrame)
|
self.log_text.setFrameStyle(QTextEdit.NoFrame)
|
||||||
@@ -877,29 +900,6 @@ class abogen(QWidget):
|
|||||||
|
|
||||||
controls_layout.addLayout(gpu_layout)
|
controls_layout.addLayout(gpu_layout)
|
||||||
|
|
||||||
|
|
||||||
# Manage queue button, start queue button
|
|
||||||
queue_row = QHBoxLayout()
|
|
||||||
# Add to queue button
|
|
||||||
self.btn_add_to_queue = QPushButton("Add to Queue", self)
|
|
||||||
self.btn_add_to_queue.setFixedHeight(40)
|
|
||||||
self.btn_add_to_queue.setEnabled(False)
|
|
||||||
self.btn_add_to_queue.clicked.connect(self.add_to_queue)
|
|
||||||
queue_row.addWidget(self.btn_add_to_queue)
|
|
||||||
# Manage queue button
|
|
||||||
self.btn_manage_queue = QPushButton("Manage Queue", self)
|
|
||||||
self.btn_manage_queue.setFixedHeight(40)
|
|
||||||
self.btn_manage_queue.setEnabled(False)
|
|
||||||
self.btn_manage_queue.clicked.connect(self.manage_queue)
|
|
||||||
queue_row.addWidget(self.btn_manage_queue)
|
|
||||||
# Start queue button
|
|
||||||
self.btn_start_queue = QPushButton("Start Queue", self)
|
|
||||||
self.btn_start_queue.setFixedHeight(40)
|
|
||||||
self.btn_start_queue.setEnabled(False)
|
|
||||||
self.btn_start_queue.clicked.connect(self.start_queue)
|
|
||||||
queue_row.addWidget(self.btn_start_queue)
|
|
||||||
controls_layout.addLayout(queue_row)
|
|
||||||
|
|
||||||
# Start button
|
# Start button
|
||||||
self.btn_start = QPushButton("Start", self)
|
self.btn_start = QPushButton("Start", self)
|
||||||
self.btn_start.setFixedHeight(60)
|
self.btn_start.setFixedHeight(60)
|
||||||
@@ -1334,11 +1334,34 @@ class abogen(QWidget):
|
|||||||
def enable_disable_queue_buttons(self):
|
def enable_disable_queue_buttons(self):
|
||||||
enabled = bool(self.queued_items)
|
enabled = bool(self.queued_items)
|
||||||
self.btn_start_queue.setEnabled(enabled)
|
self.btn_start_queue.setEnabled(enabled)
|
||||||
self.btn_manage_queue.setEnabled(enabled)
|
# Update Manage Queue button text with count
|
||||||
|
if enabled:
|
||||||
|
self.btn_manage_queue.setText(f"Manage Queue ({len(self.queued_items)})")
|
||||||
|
self.btn_manage_queue.setStyleSheet(
|
||||||
|
f"QPushButton {{ color: {COLORS['GREEN']}; }}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.btn_manage_queue.setText("Manage Queue")
|
||||||
|
self.btn_manage_queue.setStyleSheet("")
|
||||||
|
# Change main Start button to 'Start queue' if queue has items
|
||||||
|
if enabled:
|
||||||
|
self.btn_start.setText("Start queue")
|
||||||
|
try:
|
||||||
|
self.btn_start.clicked.disconnect()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.btn_start.clicked.connect(self.start_queue)
|
||||||
|
else:
|
||||||
|
self.btn_start.setText("Start")
|
||||||
|
try:
|
||||||
|
self.btn_start.clicked.disconnect()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.btn_start.clicked.connect(self.start_conversion)
|
||||||
|
|
||||||
def enqueue(self, item : QueuedItem):
|
def enqueue(self, item : QueuedItem):
|
||||||
self.queued_items.append(item)
|
self.queued_items.append(item)
|
||||||
self.update_log((f"Enqueued: {item.file_name}", True))
|
#self.update_log((f"Enqueued: {item.file_name}", True))
|
||||||
# enable start queue button, manage queue button
|
# enable start queue button, manage queue button
|
||||||
self.enable_disable_queue_buttons()
|
self.enable_disable_queue_buttons()
|
||||||
|
|
||||||
@@ -1362,17 +1385,46 @@ class abogen(QWidget):
|
|||||||
output_folder=self.selected_output_folder,
|
output_folder=self.selected_output_folder,
|
||||||
subtitle_mode=actual_subtitle_mode,
|
subtitle_mode=actual_subtitle_mode,
|
||||||
output_format=self.selected_format,
|
output_format=self.selected_format,
|
||||||
total_char_count=self.char_count,
|
total_char_count=self.char_count
|
||||||
use_gpu=self.gpu_ok
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Prevent adding duplicate items to the queue
|
||||||
|
for queued_item in self.queued_items:
|
||||||
|
if (
|
||||||
|
queued_item.file_name == item.file_name and
|
||||||
|
queued_item.lang_code == item.lang_code and
|
||||||
|
queued_item.speed == item.speed and
|
||||||
|
queued_item.voice == item.voice and
|
||||||
|
queued_item.save_option == item.save_option and
|
||||||
|
queued_item.output_folder == item.output_folder and
|
||||||
|
queued_item.subtitle_mode == item.subtitle_mode and
|
||||||
|
queued_item.output_format == item.output_format and
|
||||||
|
queued_item.total_char_count == item.total_char_count
|
||||||
|
):
|
||||||
|
QMessageBox.warning(self, "Duplicate Item", "This item is already in the queue.")
|
||||||
|
return
|
||||||
|
|
||||||
self.enqueue(item)
|
self.enqueue(item)
|
||||||
# Clear input after adding to queue
|
# Clear input after adding to queue
|
||||||
self.input_box.clear_input()
|
self.input_box.clear_input()
|
||||||
|
self.enable_disable_queue_buttons()
|
||||||
|
|
||||||
|
def clear_queue(self):
|
||||||
|
# Warn user if more than 1 item in the queue before clearing
|
||||||
|
if len(self.queued_items) > 1:
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Confirm Clear Queue",
|
||||||
|
f"Are you sure you want to clear {len(self.queued_items)} items from the queue?",
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
QMessageBox.No
|
||||||
|
)
|
||||||
|
if reply != QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
self.queued_items = []
|
||||||
|
self.enable_disable_queue_buttons()
|
||||||
|
|
||||||
def manage_queue(self):
|
def manage_queue(self):
|
||||||
if not self.queued_items:
|
|
||||||
self.input_box.set_error("Queue is empty.")
|
|
||||||
return
|
|
||||||
# show a dialog to manage the queue
|
# show a dialog to manage the queue
|
||||||
dialog = QueueManager(self, self.queued_items)
|
dialog = QueueManager(self, self.queued_items)
|
||||||
if dialog.exec_() == QDialog.Accepted:
|
if dialog.exec_() == QDialog.Accepted:
|
||||||
@@ -1381,9 +1433,6 @@ class abogen(QWidget):
|
|||||||
self.enable_disable_queue_buttons()
|
self.enable_disable_queue_buttons()
|
||||||
|
|
||||||
def start_queue(self):
|
def start_queue(self):
|
||||||
if not self.queued_items:
|
|
||||||
self.input_box.set_error("Queue is empty.")
|
|
||||||
return
|
|
||||||
self.current_queue_index = 0 # Start from the first item
|
self.current_queue_index = 0 # Start from the first item
|
||||||
self.start_next_queued_item()
|
self.start_next_queued_item()
|
||||||
|
|
||||||
@@ -1399,7 +1448,6 @@ class abogen(QWidget):
|
|||||||
self.subtitle_mode = item.subtitle_mode
|
self.subtitle_mode = item.subtitle_mode
|
||||||
self.selected_format = item.output_format
|
self.selected_format = item.output_format
|
||||||
self.char_count = item.total_char_count
|
self.char_count = item.total_char_count
|
||||||
self.gpu_ok = item.use_gpu
|
|
||||||
self.start_conversion()
|
self.start_conversion()
|
||||||
else:
|
else:
|
||||||
# Queue finished, reset index
|
# Queue finished, reset index
|
||||||
@@ -1455,6 +1503,7 @@ class abogen(QWidget):
|
|||||||
self.progress_bar.setFormat("%p%") # Reset format initially
|
self.progress_bar.setFormat("%p%") # Reset format initially
|
||||||
self.etr_label.hide() # Hide ETR label initially
|
self.etr_label.hide() # Hide ETR label initially
|
||||||
self.controls_widget.hide()
|
self.controls_widget.hide()
|
||||||
|
self.queue_row_widget.hide() # Hide queue row when process starts
|
||||||
self.progress_bar.show()
|
self.progress_bar.show()
|
||||||
self.btn_cancel.show()
|
self.btn_cancel.show()
|
||||||
QApplication.processEvents()
|
QApplication.processEvents()
|
||||||
@@ -1635,6 +1684,7 @@ class abogen(QWidget):
|
|||||||
if self.open_file_btn:
|
if self.open_file_btn:
|
||||||
self.open_file_btn.show()
|
self.open_file_btn.show()
|
||||||
self.controls_widget.show()
|
self.controls_widget.show()
|
||||||
|
self.queue_row_widget.show() # Show queue row on reset
|
||||||
self.finish_widget.hide()
|
self.finish_widget.hide()
|
||||||
self.btn_start.setText("Start")
|
self.btn_start.setText("Start")
|
||||||
# Disconnect only if connected, then reconnect
|
# Disconnect only if connected, then reconnect
|
||||||
@@ -1643,12 +1693,14 @@ class abogen(QWidget):
|
|||||||
except TypeError:
|
except TypeError:
|
||||||
pass # Ignore error if not connected
|
pass # Ignore error if not connected
|
||||||
self.btn_start.clicked.connect(self.start_conversion)
|
self.btn_start.clicked.connect(self.start_conversion)
|
||||||
|
self.enable_disable_queue_buttons()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._show_error_message_box("Reset Error", f"Could not reset UI:\n{e}")
|
self._show_error_message_box("Reset Error", f"Could not reset UI:\n{e}")
|
||||||
|
|
||||||
def go_back_ui(self):
|
def go_back_ui(self):
|
||||||
self.finish_widget.hide()
|
self.finish_widget.hide()
|
||||||
self.controls_widget.show()
|
self.controls_widget.show()
|
||||||
|
self.queue_row_widget.show() # Show queue row on go back
|
||||||
self.progress_bar.hide()
|
self.progress_bar.hide()
|
||||||
self.restore_input_box()
|
self.restore_input_box()
|
||||||
|
|
||||||
@@ -2023,6 +2075,7 @@ class abogen(QWidget):
|
|||||||
self.progress_bar.hide()
|
self.progress_bar.hide()
|
||||||
self.btn_cancel.hide()
|
self.btn_cancel.hide()
|
||||||
self.controls_widget.show()
|
self.controls_widget.show()
|
||||||
|
self.queue_row_widget.show() # Show queue row on cancel
|
||||||
self.finish_widget.hide()
|
self.finish_widget.hide()
|
||||||
self.restore_input_box()
|
self.restore_input_box()
|
||||||
display_path = (
|
display_path = (
|
||||||
|
|||||||
+285
-23
@@ -5,35 +5,83 @@
|
|||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QDialog,
|
QDialog,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
|
QHBoxLayout, # added
|
||||||
QDialogButtonBox,
|
QDialogButtonBox,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QListWidget,
|
QListWidget,
|
||||||
|
QListWidgetItem,
|
||||||
|
QFileIconProvider,
|
||||||
|
QLabel,
|
||||||
)
|
)
|
||||||
|
from PyQt5.QtCore import QFileInfo, Qt
|
||||||
|
from constants import COLORS
|
||||||
|
|
||||||
class QueueManager(QDialog):
|
class QueueManager(QDialog):
|
||||||
def __init__(self, parent, queue: list, title="Queue Manager", size=(400, 300)):
|
def __init__(self, parent, queue: list, title="Queue Manager", size=(600, 700)):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
layout.setContentsMargins(15, 15, 15, 15) # set main layout margins
|
||||||
|
layout.setSpacing(12) # set spacing between widgets in main layout
|
||||||
# list of queued items
|
# list of queued items
|
||||||
self.listwidget = QListWidget()
|
self.listwidget = QListWidget()
|
||||||
self.listwidget.setSelectionMode(QListWidget.SingleSelection)
|
self.listwidget.setSelectionMode(QListWidget.ExtendedSelection)
|
||||||
self.listwidget.setAlternatingRowColors(True)
|
self.listwidget.setAlternatingRowColors(True)
|
||||||
|
self.listwidget.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
|
self.listwidget.customContextMenuRequested.connect(self.show_context_menu)
|
||||||
|
# Add informative instructions at the top
|
||||||
|
instructions = QLabel(
|
||||||
|
"<h2>How Queue Works?</h2>"
|
||||||
|
"You can add text files (.txt) directly using the '<b>Add files</b>' button below. "
|
||||||
|
"To add PDF or EPUB files, use the input box in the main window and click the <b>'Add to Queue'</b> button. "
|
||||||
|
"Each file in the queue keeps the configuration settings active when it was added. "
|
||||||
|
"Changing the main window configuration afterward <b>does not</b> affect files already in the queue. "
|
||||||
|
"You can view each file's configuration by hovering over them."
|
||||||
|
)
|
||||||
|
instructions.setAlignment(Qt.AlignLeft)
|
||||||
|
instructions.setWordWrap(True)
|
||||||
|
instructions.setStyleSheet("margin-bottom: 8px;")
|
||||||
|
layout.addWidget(instructions)
|
||||||
|
# Overlay label for empty queue
|
||||||
|
self.empty_overlay = QLabel(
|
||||||
|
"No items in the queue.",
|
||||||
|
self.listwidget
|
||||||
|
)
|
||||||
|
self.empty_overlay.setAlignment(Qt.AlignCenter)
|
||||||
|
self.empty_overlay.setStyleSheet(f"color: {COLORS['LIGHT_DISABLED']}; background: transparent;")
|
||||||
|
self.empty_overlay.setWordWrap(True)
|
||||||
|
self.empty_overlay.hide()
|
||||||
# add queue items to the list
|
# add queue items to the list
|
||||||
self.process_queue()
|
self.process_queue()
|
||||||
|
|
||||||
layout.addWidget(self.listwidget)
|
button_row = QHBoxLayout()
|
||||||
# add remove item button
|
button_row.setContentsMargins(0, 0, 0, 0) # optional: no margins for button row
|
||||||
remove_button = QPushButton("Remove")
|
button_row.setSpacing(7) # set spacing between buttons
|
||||||
remove_button.clicked.connect(self.remove_item)
|
# Add files button
|
||||||
layout.addWidget(remove_button)
|
add_files_button = QPushButton("Add files")
|
||||||
# add clear queue button
|
add_files_button.setFixedHeight(40)
|
||||||
clear_button = QPushButton("Clear Queue")
|
add_files_button.clicked.connect(self.add_more_files)
|
||||||
clear_button.clicked.connect(self.clear_queue)
|
button_row.addWidget(add_files_button)
|
||||||
layout.addWidget(clear_button)
|
|
||||||
|
|
||||||
|
# Remove button
|
||||||
|
self.remove_button = QPushButton("Remove selected")
|
||||||
|
self.remove_button.setFixedHeight(40)
|
||||||
|
self.remove_button.clicked.connect(self.remove_item)
|
||||||
|
button_row.addWidget(self.remove_button)
|
||||||
|
|
||||||
|
# Clear button
|
||||||
|
self.clear_button = QPushButton("Clear Queue")
|
||||||
|
self.clear_button.setFixedHeight(40)
|
||||||
|
self.clear_button.clicked.connect(self.clear_queue)
|
||||||
|
button_row.addWidget(self.clear_button)
|
||||||
|
|
||||||
|
layout.addLayout(button_row)
|
||||||
|
layout.addWidget(self.listwidget)
|
||||||
|
|
||||||
|
# Connect selection change to update button state
|
||||||
|
self.listwidget.currentItemChanged.connect(self.update_button_states)
|
||||||
|
self.listwidget.itemSelectionChanged.connect(self.update_button_states)
|
||||||
|
|
||||||
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
|
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
|
||||||
buttons.accepted.connect(self.accept)
|
buttons.accepted.connect(self.accept)
|
||||||
@@ -44,30 +92,244 @@ class QueueManager(QDialog):
|
|||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
self.setMinimumSize(*size)
|
self.resize(*size)
|
||||||
|
|
||||||
|
self.update_button_states()
|
||||||
|
|
||||||
def process_queue(self):
|
def process_queue(self):
|
||||||
"""Process the queue items."""
|
"""Process the queue items."""
|
||||||
|
self.listwidget.clear()
|
||||||
if not self.queue:
|
if not self.queue:
|
||||||
|
self.empty_overlay.resize(self.listwidget.size())
|
||||||
|
self.empty_overlay.show()
|
||||||
|
self.update_button_states()
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
self.empty_overlay.hide()
|
||||||
|
icon_provider = QFileIconProvider()
|
||||||
for item in self.queue:
|
for item in self.queue:
|
||||||
# TODO take just the file name from the item, no directories
|
# Only show the file name, not the full path
|
||||||
self.listwidget.addItem(item.file_name)
|
file_name = item.file_name
|
||||||
|
display_name = file_name
|
||||||
|
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))
|
||||||
|
list_item = QListWidgetItem(icon, display_name)
|
||||||
|
# Set tooltip with detailed info
|
||||||
|
output_folder = getattr(item, 'output_folder', '')
|
||||||
|
tooltip = (
|
||||||
|
f"<b>Path:</b> {file_name}<br>"
|
||||||
|
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>"
|
||||||
|
f"<b>Save Option:</b> {getattr(item, 'save_option', '')}<br>"
|
||||||
|
)
|
||||||
|
if output_folder not in (None, '', 'None'):
|
||||||
|
tooltip += f"<b>Output Folder:</b> {output_folder}<br>"
|
||||||
|
tooltip += (
|
||||||
|
f"<b>Subtitle Mode:</b> {getattr(item, 'subtitle_mode', '')}<br>"
|
||||||
|
f"<b>Output Format:</b> {getattr(item, 'output_format', '')}<br>"
|
||||||
|
f"<b>Characters:</b> {getattr(item, 'total_char_count', '')}"
|
||||||
|
)
|
||||||
|
list_item.setToolTip(tooltip)
|
||||||
|
self.listwidget.addItem(list_item)
|
||||||
|
|
||||||
|
self.update_button_states()
|
||||||
|
|
||||||
def remove_item(self):
|
def remove_item(self):
|
||||||
item = self.listwidget.currentItem()
|
items = self.listwidget.selectedItems()
|
||||||
if not item:
|
if not items:
|
||||||
return
|
return
|
||||||
found_item = [q for q in self.queue if q.file_name==item.text()][0]
|
import os
|
||||||
self.queue.remove(found_item)
|
display_names = [item.text() for item in items]
|
||||||
# rebind the queue to the listwidget
|
to_remove = []
|
||||||
self.listwidget.clear()
|
for q in self.queue:
|
||||||
|
if os.path.basename(q.file_name) in display_names:
|
||||||
|
to_remove.append(q)
|
||||||
|
for item in to_remove:
|
||||||
|
self.queue.remove(item)
|
||||||
self.process_queue()
|
self.process_queue()
|
||||||
pass
|
self.update_button_states()
|
||||||
|
|
||||||
def clear_queue(self):
|
def clear_queue(self):
|
||||||
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
if len(self.queue) > 1:
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Confirm Clear Queue",
|
||||||
|
f"Are you sure you want to clear {len(self.queue)} items from the queue?",
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
QMessageBox.No
|
||||||
|
)
|
||||||
|
if reply != QMessageBox.Yes:
|
||||||
|
return
|
||||||
self.queue.clear()
|
self.queue.clear()
|
||||||
self.listwidget.clear()
|
self.listwidget.clear()
|
||||||
|
self.empty_overlay.resize(self.listwidget.size()) # Ensure overlay is sized correctly
|
||||||
|
self.empty_overlay.show() # Show the overlay when queue is empty
|
||||||
|
self.update_button_states()
|
||||||
|
|
||||||
def get_queue(self):
|
def get_queue(self):
|
||||||
return self.queue
|
return self.queue
|
||||||
|
|
||||||
|
def get_current_attributes(self):
|
||||||
|
# Fetch current attribute values from the parent abogen GUI
|
||||||
|
attrs = {}
|
||||||
|
parent = self.parent
|
||||||
|
if parent is not None:
|
||||||
|
# lang_code: use parent's get_voice_formula and get_selected_lang
|
||||||
|
if hasattr(parent, 'get_voice_formula') and hasattr(parent, 'get_selected_lang'):
|
||||||
|
voice_formula = parent.get_voice_formula()
|
||||||
|
attrs['lang_code'] = parent.get_selected_lang(voice_formula)
|
||||||
|
attrs['voice'] = voice_formula
|
||||||
|
else:
|
||||||
|
attrs['lang_code'] = getattr(parent, 'selected_lang', '')
|
||||||
|
attrs['voice'] = getattr(parent, 'selected_voice', '')
|
||||||
|
# speed
|
||||||
|
if hasattr(parent, 'speed_slider'):
|
||||||
|
attrs['speed'] = parent.speed_slider.value() / 100.0
|
||||||
|
else:
|
||||||
|
attrs['speed'] = getattr(parent, 'speed', 1.0)
|
||||||
|
# save_option
|
||||||
|
attrs['save_option'] = getattr(parent, 'save_option', '')
|
||||||
|
# output_folder
|
||||||
|
attrs['output_folder'] = getattr(parent, 'selected_output_folder', '')
|
||||||
|
# subtitle_mode
|
||||||
|
if hasattr(parent, 'get_actual_subtitle_mode'):
|
||||||
|
attrs['subtitle_mode'] = parent.get_actual_subtitle_mode()
|
||||||
|
else:
|
||||||
|
attrs['subtitle_mode'] = getattr(parent, 'subtitle_mode', '')
|
||||||
|
# output_format
|
||||||
|
attrs['output_format'] = getattr(parent, 'selected_format', '')
|
||||||
|
# total_char_count
|
||||||
|
attrs['total_char_count'] = getattr(parent, 'char_count', '')
|
||||||
|
else:
|
||||||
|
# fallback: empty values
|
||||||
|
attrs = {k: '' for k in [
|
||||||
|
'lang_code', 'speed', 'voice', 'save_option',
|
||||||
|
'output_folder', 'subtitle_mode', 'output_format', 'total_char_count']}
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def add_more_files(self):
|
||||||
|
from PyQt5.QtWidgets import QFileDialog, QMessageBox
|
||||||
|
import os
|
||||||
|
from utils import calculate_text_length # import the function
|
||||||
|
# Only allow .txt files
|
||||||
|
files, _ = QFileDialog.getOpenFileNames(self, "Select .txt files", "", "Text Files (*.txt)")
|
||||||
|
if not files:
|
||||||
|
return
|
||||||
|
# Get current attribute values from GUI
|
||||||
|
current_attrs = self.get_current_attributes()
|
||||||
|
duplicates = []
|
||||||
|
for file_path in files:
|
||||||
|
# Create a dummy item with the current GUI attributes
|
||||||
|
class QueueItem:
|
||||||
|
pass
|
||||||
|
item = QueueItem()
|
||||||
|
item.file_name = file_path
|
||||||
|
for attr, value in current_attrs.items():
|
||||||
|
setattr(item, attr, value)
|
||||||
|
# Read file content and calculate total_char_count using calculate_text_length
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
|
file_content = f.read()
|
||||||
|
item.total_char_count = calculate_text_length(file_content)
|
||||||
|
except Exception:
|
||||||
|
item.total_char_count = 0
|
||||||
|
# Prevent adding duplicate items to the queue (check all attributes)
|
||||||
|
is_duplicate = False
|
||||||
|
for queued_item in self.queue:
|
||||||
|
if (
|
||||||
|
getattr(queued_item, 'file_name', None) == getattr(item, 'file_name', None) and
|
||||||
|
getattr(queued_item, 'lang_code', None) == getattr(item, 'lang_code', None) and
|
||||||
|
getattr(queued_item, 'speed', None) == getattr(item, 'speed', None) and
|
||||||
|
getattr(queued_item, 'voice', None) == getattr(item, 'voice', None) and
|
||||||
|
getattr(queued_item, 'save_option', None) == getattr(item, 'save_option', None) and
|
||||||
|
getattr(queued_item, 'output_folder', None) == getattr(item, 'output_folder', None) and
|
||||||
|
getattr(queued_item, 'subtitle_mode', None) == getattr(item, 'subtitle_mode', None) and
|
||||||
|
getattr(queued_item, 'output_format', None) == getattr(item, 'output_format', None) and
|
||||||
|
getattr(queued_item, 'total_char_count', None) == getattr(item, 'total_char_count', None)
|
||||||
|
):
|
||||||
|
is_duplicate = True
|
||||||
|
break
|
||||||
|
if is_duplicate:
|
||||||
|
duplicates.append(os.path.basename(file_path))
|
||||||
|
continue
|
||||||
|
self.queue.append(item)
|
||||||
|
if duplicates:
|
||||||
|
QMessageBox.warning(self, "Duplicate Item(s)", "The following file(s) with the same attributes are already in the queue and were not added:\n" + '\n'.join(duplicates))
|
||||||
|
self.process_queue()
|
||||||
|
self.update_button_states()
|
||||||
|
|
||||||
|
def resizeEvent(self, event):
|
||||||
|
super().resizeEvent(event)
|
||||||
|
if hasattr(self, 'empty_overlay'):
|
||||||
|
self.empty_overlay.resize(self.listwidget.size())
|
||||||
|
|
||||||
|
def update_button_states(self):
|
||||||
|
# Enable Remove if at least one item is selected, else disable
|
||||||
|
if hasattr(self, 'remove_button'):
|
||||||
|
selected_count = len(self.listwidget.selectedItems())
|
||||||
|
self.remove_button.setEnabled(selected_count > 0)
|
||||||
|
if selected_count > 1:
|
||||||
|
self.remove_button.setText(f"Remove selected ({selected_count})")
|
||||||
|
else:
|
||||||
|
self.remove_button.setText("Remove selected")
|
||||||
|
# Disable Clear if queue is empty
|
||||||
|
if hasattr(self, 'clear_button'):
|
||||||
|
self.clear_button.setEnabled(bool(self.queue))
|
||||||
|
|
||||||
|
def show_context_menu(self, pos):
|
||||||
|
from PyQt5.QtWidgets import QMenu, QAction
|
||||||
|
from PyQt5.QtGui import QDesktopServices
|
||||||
|
from PyQt5.QtCore import QUrl
|
||||||
|
import os
|
||||||
|
global_pos = self.listwidget.viewport().mapToGlobal(pos)
|
||||||
|
selected_items = self.listwidget.selectedItems()
|
||||||
|
menu = QMenu(self)
|
||||||
|
if len(selected_items) == 1:
|
||||||
|
remove_action = QAction("Remove this item", self)
|
||||||
|
remove_action.triggered.connect(self.remove_item)
|
||||||
|
menu.addAction(remove_action)
|
||||||
|
go_to_folder_action = QAction("Go to folder", self)
|
||||||
|
def go_to_folder():
|
||||||
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
item = selected_items[0]
|
||||||
|
display_name = item.text()
|
||||||
|
for q in self.queue:
|
||||||
|
if os.path.basename(q.file_name) == display_name:
|
||||||
|
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))
|
||||||
|
break
|
||||||
|
go_to_folder_action.triggered.connect(go_to_folder)
|
||||||
|
menu.addAction(go_to_folder_action)
|
||||||
|
# Add Open file action
|
||||||
|
open_file_action = QAction("Open file", self)
|
||||||
|
def open_file():
|
||||||
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
item = selected_items[0]
|
||||||
|
display_name = item.text()
|
||||||
|
for q in self.queue:
|
||||||
|
if os.path.basename(q.file_name) == display_name:
|
||||||
|
if not os.path.exists(q.file_name):
|
||||||
|
QMessageBox.warning(self, "File Not Found", f"The file does not exist.")
|
||||||
|
return
|
||||||
|
QDesktopServices.openUrl(QUrl.fromLocalFile(q.file_name))
|
||||||
|
break
|
||||||
|
open_file_action.triggered.connect(open_file)
|
||||||
|
menu.addAction(open_file_action)
|
||||||
|
elif len(selected_items) > 1:
|
||||||
|
remove_action = QAction(f"Remove selected ({len(selected_items)})", self)
|
||||||
|
remove_action.triggered.connect(self.remove_item)
|
||||||
|
menu.addAction(remove_action)
|
||||||
|
# Always add Clear Queue
|
||||||
|
clear_action = QAction("Clear Queue", self)
|
||||||
|
clear_action.triggered.connect(self.clear_queue)
|
||||||
|
menu.addAction(clear_action)
|
||||||
|
menu.exec_(global_pos)
|
||||||
@@ -11,5 +11,4 @@ class QueuedItem:
|
|||||||
output_folder: str
|
output_folder: str
|
||||||
subtitle_mode: str
|
subtitle_mode: str
|
||||||
output_format: str
|
output_format: str
|
||||||
total_char_count: int
|
total_char_count: int
|
||||||
use_gpu: bool
|
|
||||||
Reference in New Issue
Block a user