From 05fcf00a9c646bf561b1bc0c7704de95b9613473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deniz=20=C5=9Eafak?= Date: Fri, 11 Jul 2025 10:26:33 +0300 Subject: [PATCH] Beter canceling logic, improve quque summary and overall code --- abogen/conversion.py | 13 +++-- abogen/gui.py | 111 +++++++++++++++++++++++++------------------ abogen/utils.py | 2 +- 3 files changed, 77 insertions(+), 49 deletions(-) diff --git a/abogen/conversion.py b/abogen/conversion.py index da25dcf..0a31e60 100644 --- a/abogen/conversion.py +++ b/abogen/conversion.py @@ -166,6 +166,8 @@ class ConversionThread(QThread): self.output_folder = output_folder self.subtitle_mode = subtitle_mode self.cancel_requested = False + self.should_cancel = False + self.process = None self.output_format = output_format self.from_queue = from_queue self.start_time = start_time # Store start_time @@ -1163,9 +1165,14 @@ class ConversionThread(QThread): def cancel(self): self.cancel_requested = True - self.waiting_for_user_input = ( - False # Also release the wait if we're waiting for input - ) + self.should_cancel = True + self.waiting_for_user_input = False + # Terminate subprocess if running + if self.process: + try: + self.process.terminate() + except Exception: + pass class VoicePreviewThread(QThread): diff --git a/abogen/gui.py b/abogen/gui.py index a0ea427..2aec2a0 100644 --- a/abogen/gui.py +++ b/abogen/gui.py @@ -76,7 +76,7 @@ from abogen.constants import ( SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION, COLORS ) -from threading import Thread +import threading from abogen.voice_formula_gui import VoiceFormulaDialog from abogen.voice_profiles import load_profiles @@ -687,10 +687,7 @@ class abogen(QWidget): 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: 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_row_widget.show() self.subtitle_combo.setCurrentText(self.subtitle_mode) # Enable/disable subtitle options based on selected language (profile or voice) enable = self.selected_lang in SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION @@ -946,7 +943,7 @@ class abogen(QWidget): # Save location save_layout = QHBoxLayout() save_layout.setSpacing(7) - save_label = QLabel("Save Location:", self) + save_label = QLabel("Save location:", self) save_layout.addWidget(save_label) self.save_combo = QComboBox(self) save_options = [ @@ -962,10 +959,24 @@ class abogen(QWidget): self.save_combo.setCurrentText(self.save_option) self.save_combo.currentTextChanged.connect(self.on_save_option_changed) save_layout.addWidget(self.save_combo) - self.save_path_label = QLabel("", self) - self.save_path_label.hide() - save_layout.addWidget(self.save_path_label) controls_layout.addLayout(save_layout) + + # Save path label + self.save_path_row_widget = QWidget(self) + save_path_row = QHBoxLayout(self.save_path_row_widget) + save_path_row.setSpacing(7) + save_path_row.setContentsMargins(0, 0, 0, 0) + selected_folder_label = QLabel("Selected folder:", self.save_path_row_widget) + save_path_row.addWidget(selected_folder_label) + self.save_path_label = QLabel("", self.save_path_row_widget) + self.save_path_label.setStyleSheet( + f"QLabel {{ color: {COLORS['GREEN']}; }}" + ) + self.save_path_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + save_path_row.addWidget(self.save_path_label) + self.save_path_row_widget.hide() # Hide the whole row by default + controls_layout.addWidget(self.save_path_row_widget) + # GPU Acceleration Checkbox with Settings button gpu_layout = QHBoxLayout() gpu_checkbox_layout = QVBoxLayout() @@ -1393,35 +1404,36 @@ class abogen(QWidget): self._update_log_main_thread(message) def _update_log_main_thread(self, message): - # Efficient log: keep only last 10 lines in a Python list if not hasattr(self, '_log_lines'): self._log_lines = [] - # Prepare the new log line as HTML + # Always produce a single HTML line (with color if tuple) if isinstance(message, tuple): text, spec = message color = "green" if spec is True else ("red" if spec is False else spec) - html = self.color_text(text, color) + html_line = self.color_text(text, color) else: - html = str(message).replace("\n", "
") + "
" + html_line = f"{str(message).replace(chr(10), '
')}
" - # Split incoming html into lines (preserving blank lines) - new_lines = html.split("
") - # Remove the last empty string if html ends with
- if new_lines and new_lines[-1] == "": - new_lines = new_lines[:-1] + self._log_lines.append(html_line) - # Append new lines to the buffer - self._log_lines.extend(new_lines) - # Keep only the last log_window_max_lines lines - self._log_lines = self._log_lines[-self.log_window_max_lines:] - - # Join and set as HTML - self.log_text.setHtml("
".join(self._log_lines)) - - # Scroll to bottom if needed + # Check if user is at bottom BEFORE inserting sb = self.log_text.verticalScrollBar() - sb.setValue(sb.maximum()) + at_bottom = sb.value() >= sb.maximum() - 2 + + # Trim buffer if needed + if len(self._log_lines) > self.log_window_max_lines: + self._log_lines = self._log_lines[-self.log_window_max_lines:] + self.log_text.clear() + for line in self._log_lines: + self.log_text.insertHtml(line) + else: + self.log_text.insertHtml(html_line) + + # If user was at bottom before, scroll to new bottom + if at_bottom: + sb.setValue(sb.maximum()) + QApplication.processEvents() def _get_queue_progress_format(self, value=None): @@ -1755,7 +1767,7 @@ class abogen(QWidget): load_thread = LoadPipelineThread(pipeline_loaded_callback) load_thread.start() - Thread(target=gpu_and_load, daemon=True).start() + threading.Thread(target=gpu_and_load, daemon=True).start() def show_queue_summary(self): """Show a styled, resizable summary dialog after queue finishes.""" @@ -1764,17 +1776,24 @@ class abogen(QWidget): # Build HTML summary for better styling summary_html = "" + summary_html += ( + f"

Queue completed

" + f"Processed {len(self.queued_items)} items:

" + ) + + for idx, item in enumerate(self.queued_items, 1): - status = "Completed" output = getattr(item, "output_path", None) if not output: output = "Unknown" summary_html += ( - f"{idx}) {os.path.basename(item.file_name)}
" - f"Language: {item.lang_code}
" - f"Voice: {item.voice}
" - f"Output: {output}
" - f"Status: {status}" + f"{idx}) {os.path.basename(item.file_name)}
" + f"Language: {item.lang_code}
" + f"Voice: {item.voice}
" + f"Speed: {item.speed * 100:.1f}%
" + f"Characters: {item.total_char_count}
" + f"Input: {item.file_name}
" + f"Output: {output}" f"

" ) summary_html += "" @@ -1858,8 +1877,8 @@ class abogen(QWidget): # Queue finished, show finish screen self.controls_widget.hide() self.finish_widget.show() - self.log_text.moveCursor(QTextCursor.End) - self.log_text.ensureCursorVisible() + sb = self.log_text.verticalScrollBar() + sb.setValue(sb.maximum()) save_config(self.config) # Show queue summary if more than one item if len(self.queued_items) > 1: @@ -1942,7 +1961,7 @@ class abogen(QWidget): if folder: self.selected_output_folder = folder self.save_path_label.setText(folder) - self.save_path_label.show() + self.save_path_row_widget.show() self.config["selected_output_folder"] = folder else: self.save_option = "Save next to input file" @@ -1956,7 +1975,7 @@ class abogen(QWidget): self.save_combo.setCurrentText(self.save_option) self.config["save_option"] = self.save_option else: - self.save_path_label.hide() + self.save_path_row_widget.hide() self.selected_output_folder = None self.config["selected_output_folder"] = None save_config(self.config) @@ -2280,7 +2299,14 @@ class abogen(QWidget): hasattr(self, "conversion_thread") and self.conversion_thread.isRunning() ): - self.conversion_thread.cancel() + if not hasattr(self, "_conversion_lock"): + self._conversion_lock = threading.Lock() + def _cancel(): + with self._conversion_lock: + self.conversion_thread.cancel() # <-- Use cancel() method + self.conversion_thread.wait() + threading.Thread(target=_cancel, daemon=True).start() + self.is_converting = False self.etr_label.hide() # Hide ETR label self.progress_bar.hide() @@ -2304,11 +2330,6 @@ class abogen(QWidget): self.input_box.clear_input() else: self.input_box.clear_input() - if ( - hasattr(self, "conversion_thread") - and self.conversion_thread.isRunning() - ): - self.conversion_thread.wait() prevent_sleep_end() except Exception as e: self._show_error_message_box( diff --git a/abogen/utils.py b/abogen/utils.py index fc27734..fd24176 100644 --- a/abogen/utils.py +++ b/abogen/utils.py @@ -230,7 +230,7 @@ def get_gpu_acceleration(enabled): cuda_error = torch.cuda.get_device_name(0) if cuda_devices > 0 else "No devices found" except Exception as e: cuda_error = str(e) - return f"CUDA GPU is not available. Using CPU.\nDetails: {cuda_error}", False + return f"CUDA GPU is not available. Using CPU. ({cuda_error})", False except Exception as e: return f"Error checking CUDA GPU: {e}", False