From f6c813602c7334b916cfec699edb70ab9d27c467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deniz=20=C5=9Eafak?= Date: Fri, 4 Jul 2025 20:44:58 +0300 Subject: [PATCH 1/6] Use values instad of plain text in subtitle formats --- abogen/conversion.py | 12 ++++++------ abogen/gui.py | 41 ++++++++++++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/abogen/conversion.py b/abogen/conversion.py index 5761f9c..a3a95ad 100644 --- a/abogen/conversion.py +++ b/abogen/conversion.py @@ -693,8 +693,8 @@ class ConversionThread(QThread): if 'ass' in subtitle_format: # Generate ASS subtitle - is_centered = 'centered' in subtitle_format - is_narrow = 'narrow' in subtitle_format + is_centered = subtitle_format in ("ass_centered_wide", "ass_centered_narrow") + is_narrow = subtitle_format in ("ass_narrow", "ass_centered_narrow") self._write_ass_subtitle(chapter_subtitle_path, chapter_subtitle_entries, is_centered, is_narrow) else: # Generate SRT subtitle (default) @@ -772,10 +772,10 @@ class ConversionThread(QThread): subtitle_path = os.path.splitext(final_out_path)[0] + f".{file_extension}" if 'ass' in subtitle_format: - # Generate ASS subtitle with optional centering and margin - is_centered = 'centered' in subtitle_format - is_narrow = 'narrow' in subtitle_format - self._write_ass_subtitle(subtitle_path, subtitle_entries, is_centered, is_narrow) + # Generate ASS subtitle + is_centered = subtitle_format in ("ass_centered_wide", "ass_centered_narrow") + is_narrow = subtitle_format in ("ass_narrow", "ass_centered_narrow") + self._write_ass_subtitle(chapter_subtitle_path, chapter_subtitle_entries, is_centered, is_narrow) else: # Generate SRT subtitle (default) with open(subtitle_path, "w", encoding="utf-8", errors="replace") as srt_file: diff --git a/abogen/gui.py b/abogen/gui.py index e7b8a85..1e0f603 100644 --- a/abogen/gui.py +++ b/abogen/gui.py @@ -503,11 +503,25 @@ class TextboxDialog(QDialog): self.update_char_count() self.text_edit.setFocus() +def migrate_subtitle_format(config): + """Convert old subtitle_format values to new internal keys.""" + old_to_new = { + "srt": "srt", + "ass (wide)": "ass_wide", + "ass (narrow)": "ass_narrow", + "ass (centered wide)": "ass_centered_wide", + "ass (centered narrow)": "ass_centered_narrow", + } + val = config.get("subtitle_format") + if val in old_to_new: + config["subtitle_format"] = old_to_new[val] + save_config(config) class abogen(QWidget): def __init__(self): super().__init__() self.config = load_config() + migrate_subtitle_format(self.config) self.check_updates = self.config.get("check_updates", True) self.save_option = self.config.get("save_option", "Save next to input file") self.selected_output_folder = self.config.get("selected_output_folder", None) @@ -1336,7 +1350,7 @@ class abogen(QWidget): # Pass separate_chapters_format setting self.conversion_thread.separate_chapters_format = self.separate_chapters_format # Pass subtitle format setting - self.conversion_thread.subtitle_format = self.config.get("subtitle_format", "srt") + self.conversion_thread.subtitle_format = self.config.get("subtitle_format", "ass_centered_narrow") # Pass chapter count for EPUB or PDF files if self.selected_file_type in ["epub", "pdf"] and hasattr( self, "selected_chapters" @@ -1936,19 +1950,28 @@ class abogen(QWidget): # Add subtitle format option subtitle_format_menu = QMenu("Subtitle format", self) subtitle_format_menu.setToolTip("Choose the format for generated subtitles") - + subtitle_format_group = QActionGroup(self) subtitle_format_group.setExclusive(True) - - subtitle_format = self.config.get("subtitle_format", "srt") - for format_option in ["srt", "ass (wide)", "ass (narrow)", "ass (centered wide)", "ass (centered narrow)"]: - format_action = QAction(f"{format_option}", self) + + # Define mapping: (internal_value, display_text) + subtitle_formats = [ + ("srt", "SRT (standard)"), + ("ass_wide", "ASS (wide)"), + ("ass_narrow", "ASS (narrow)"), + ("ass_centered_wide", "ASS (centered wide)"), + ("ass_centered_narrow", "ASS (centered narrow)"), + ] + + subtitle_format = self.config.get("subtitle_format", "ass_centered_narrow") + for value, text in subtitle_formats: + format_action = QAction(text, self) format_action.setCheckable(True) - format_action.setChecked(subtitle_format == format_option) - format_action.triggered.connect(lambda checked, fmt=format_option: self.set_subtitle_format(fmt)) + format_action.setChecked(subtitle_format == value) + format_action.triggered.connect(lambda checked, fmt=value: self.set_subtitle_format(fmt)) subtitle_format_group.addAction(format_action) subtitle_format_menu.addAction(format_action) - + menu.addMenu(subtitle_format_menu) # Add separate chapters format option From 60b22c7731bf6451fe53ccf021073d07094c0a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deniz=20=C5=9Eafak?= Date: Sat, 5 Jul 2025 02:48:34 +0300 Subject: [PATCH 2/6] Add dark theme support, improvements --- CHANGELOG.md | 1 + abogen/assets/settings.png | Bin 585 -> 0 bytes abogen/assets/settings.svg | 31 +++++++ abogen/book_handler.py | 1 + abogen/constants.py | 16 ++++ abogen/gui.py | 170 +++++++++++++++++++++++++++++++++++- abogen/main.py | 20 +++++ abogen/voice_formula_gui.py | 70 ++++++++++----- 8 files changed, 287 insertions(+), 22 deletions(-) delete mode 100644 abogen/assets/settings.png create mode 100644 abogen/assets/settings.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 869c195..6954003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # v1.1.0 (pre-release) - Added a feature that allows selecting multiple items in book handler (in right click menu) by @jborza in #31, that fixes #28 +- Added dark theme support, allowing users to switch between light and dark themes in the settings. # v1.0.9 - Added chunking/segmenting system that fixes memory outage issues when processing large audio files. diff --git a/abogen/assets/settings.png b/abogen/assets/settings.png deleted file mode 100644 index bd6fb88ad3f20ec63e644c2f2925d6224828d958..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 585 zcmV-P0=E5$P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10oF-G zK~y-6m6APZ6hRcne{V+7D8kl)rJoRUMT$H8s02Bpg@s_J65HcuK>Mw1dMN> zwOO304{;nD@BQNR!I*A+w$`4j&EEU#p#!PKhmniP61eeR;zUhxD6m8hk?_;?nbr;fjt#-sNHUV&hva|0HP?`06sMY6JRJJ+s(Q~ zPJ=m2$TTQ@AsFDFXUzV6NIvvMjsxm)oj&0^q&B@0?TToO + + + + + + + + + \ No newline at end of file diff --git a/abogen/book_handler.py b/abogen/book_handler.py index 55476b6..e5bf8ac 100644 --- a/abogen/book_handler.py +++ b/abogen/book_handler.py @@ -161,6 +161,7 @@ class HandlerDialog(QDialog): self.treeWidget.expandAll() if self.treeWidget.topLevelItemCount() > 0: self.treeWidget.setCurrentItem(self.treeWidget.topLevelItem(0)) + self.treeWidget.setFocus() # Update checkbox states self._update_checkbox_states() diff --git a/abogen/constants.py b/abogen/constants.py index a8fdfef..fca17a0 100644 --- a/abogen/constants.py +++ b/abogen/constants.py @@ -102,3 +102,19 @@ SAMPLE_VOICE_TEXTS = { "p": "Este é um exemplo da voz selecionada.", "z": "这是所选语音的示例。", } + +COLORS = { + "BLUE": "#007dff", + "RED": "#c0392b", + "YELLOW_BACKGROUND": "rgba(255, 221, 51, 0.40)", + "GREY_BACKGROUND": "rgba(128, 128, 128, 0.15)", + "RED_BACKGROUND": "rgba(232, 78, 60, 0.15)", + # Theme palette colors + "DARK_BG": "#202326", + "DARK_BASE": "#141618", + "DARK_ALT": "#2c2f31", + "DARK_BUTTON": "#292c30", + "DARK_DISABLED": "#535353", + "LIGHT_BG": "#eff0f1", + "LIGHT_DISABLED": "#9a9999", +} \ No newline at end of file diff --git a/abogen/gui.py b/abogen/gui.py index 1e0f603..322d5cc 100644 --- a/abogen/gui.py +++ b/abogen/gui.py @@ -39,6 +39,7 @@ from PyQt5.QtCore import ( QIODevice, QSize, QTimer, + QEvent, ) from PyQt5.QtGui import ( QTextCursor, @@ -71,6 +72,7 @@ from constants import ( LANGUAGE_DESCRIPTIONS, VOICES_INTERNAL, SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION, + COLORS ) from threading import Thread from voice_formula_gui import VoiceFormulaDialog @@ -79,7 +81,22 @@ from voice_profiles import load_profiles # Import ctypes for Windows-specific taskbar icon if platform.system() == "Windows": import ctypes + import winreg +class DarkTitleBarEventFilter(QObject): + def __init__(self, is_windows, get_dark_mode_func, set_title_bar_dark_mode_func): + super().__init__() + self.is_windows = is_windows + self.get_dark_mode = get_dark_mode_func + self.set_title_bar_dark_mode = set_title_bar_dark_mode_func + + def eventFilter(self, obj, event): + if event.type() == QEvent.Show: + # Only apply to QWidget windows + if isinstance(obj, QWidget) and obj.isWindow(): + if self.is_windows and self.get_dark_mode(): + self.set_title_bar_dark_mode(obj, True) + return False class ShowWarningSignalEmitter(QObject): # New class to handle signal emission show_warning_signal = pyqtSignal(str, str) @@ -521,6 +538,7 @@ class abogen(QWidget): def __init__(self): super().__init__() self.config = load_config() + self.apply_theme(self.config.get("theme", "system")) migrate_subtitle_format(self.config) self.check_updates = self.config.get("check_updates", True) self.save_option = self.config.get("save_option", "Save next to input file") @@ -808,7 +826,7 @@ class abogen(QWidget): gpu_layout.addLayout(gpu_checkbox_layout) # Settings button with icon - settings_icon_path = get_resource_path("abogen.assets", "settings.png") + settings_icon_path = get_resource_path("abogen.assets", "settings.svg") self.settings_btn = QPushButton(self) if settings_icon_path and os.path.exists(settings_icon_path): self.settings_btn.setIcon(QIcon(settings_icon_path)) @@ -1931,10 +1949,160 @@ class abogen(QWidget): # If dialog is rejected, cancel the conversion self.cancel_conversion() + def apply_theme(self, theme): + from PyQt5.QtGui import QPalette, QColor + from PyQt5.QtWidgets import QStyleFactory + + app = QApplication.instance() + is_windows = platform.system() == "Windows" + available_styles = [s.lower() for s in QStyleFactory.keys()] + + def is_windows_dark_mode(): + try: + import winreg + with winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" + ) as key: + value, _ = winreg.QueryValueEx(key, "AppsUseLightTheme") + return value == 0 + except Exception: + return False + + # --- Theme selection logic --- + def set_dark_palette(): + palette = QPalette() + dark_bg = QColor(COLORS["DARK_BG"]) + base_bg = QColor(COLORS["DARK_BASE"]) + alt_bg = QColor(COLORS["DARK_ALT"]) + button_bg = QColor(COLORS["DARK_BUTTON"]) + disabled_fg = QColor(COLORS["DARK_DISABLED"]) + palette.setColor(QPalette.ColorRole.Window, dark_bg) + palette.setColor(QPalette.ColorRole.WindowText, Qt.GlobalColor.white) + palette.setColor(QPalette.ColorRole.Base, base_bg) + palette.setColor(QPalette.ColorRole.AlternateBase, alt_bg) + palette.setColor(QPalette.ColorRole.ToolTipBase, dark_bg) + palette.setColor(QPalette.ColorRole.ToolTipText, Qt.GlobalColor.white) + palette.setColor(QPalette.ColorRole.Text, Qt.GlobalColor.white) + palette.setColor(QPalette.ColorRole.Button, button_bg) + palette.setColor(QPalette.ColorRole.ButtonText, Qt.GlobalColor.white) + # Disabled roles + palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, disabled_fg) + palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, disabled_fg) + palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, disabled_fg) + palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, dark_bg) + palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Button, dark_bg) + app.setPalette(palette) + + + def set_light_palette(): + palette = QPalette() + disabled_fg = QColor(COLORS["LIGHT_DISABLED"]) + palette.setColor(QPalette.ColorRole.Window, QColor(COLORS["LIGHT_BG"])) + palette.setColor(QPalette.ColorRole.WindowText, Qt.GlobalColor.black) + palette.setColor(QPalette.ColorRole.Base, Qt.GlobalColor.white) + palette.setColor(QPalette.ColorRole.AlternateBase, Qt.GlobalColor.white) + palette.setColor(QPalette.ColorRole.ToolTipBase, Qt.GlobalColor.white) + palette.setColor(QPalette.ColorRole.ToolTipText, Qt.GlobalColor.black) + palette.setColor(QPalette.ColorRole.Text, Qt.GlobalColor.black) + palette.setColor(QPalette.ColorRole.Button, Qt.GlobalColor.white) + palette.setColor(QPalette.ColorRole.ButtonText, Qt.GlobalColor.black) + # Disabled roles + palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, disabled_fg) + palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, disabled_fg) + palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, disabled_fg) + palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, Qt.GlobalColor.white) + palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Button, Qt.GlobalColor.white) + app.setPalette(palette) + + # --- Dark title bar support for Windows --- + def set_title_bar_dark_mode(window, enable): + if is_windows: + try: + window.update() + DWMWA_USE_IMMERSIVE_DARK_MODE = 20 + set_window_attribute = ctypes.windll.dwmapi.DwmSetWindowAttribute + hwnd = int(window.winId()) + value = ctypes.c_int(2 if enable else 0) + set_window_attribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, ctypes.byref(value), ctypes.sizeof(value)) + except Exception: + pass + + # Main logic + dark_mode = theme == "dark" or (theme == "system" and is_windows and is_windows_dark_mode()) + if dark_mode: + app.setStyle("Fusion") + set_dark_palette() + elif (theme == "light" or theme == "system") and is_windows: + if "windowsvista" in available_styles: + app.setStyle("windowsvista") + else: + app.setStyle("Fusion") + app.setPalette(QPalette()) + elif theme == "light": + app.setStyle("Fusion") + set_light_palette() + else: + app.setStyle("Fusion") + app.setPalette(QPalette()) + + # Always set the title bar mode according to the current theme for all top-level widgets + for widget in app.topLevelWidgets(): + set_title_bar_dark_mode(widget, dark_mode) + + # Refresh all top-level widgets + style_name = app.style().objectName() + app.setStyle(style_name) + for widget in app.topLevelWidgets(): + app.style().polish(widget) + widget.update() + + # Remove old event filter if present, then install a new one for dark title bar on new windows + if hasattr(app, "_dark_titlebar_event_filter"): + app.removeEventFilter(app._dark_titlebar_event_filter) + delattr(app, "_dark_titlebar_event_filter") + + def get_dark_mode(): + return theme == "dark" or (theme == "system" and is_windows and is_windows_dark_mode()) + app._dark_titlebar_event_filter = DarkTitleBarEventFilter( + is_windows, get_dark_mode, set_title_bar_dark_mode + ) + app.installEventFilter(app._dark_titlebar_event_filter) + + # Save config if changed + if self.config.get("theme", "system") != theme: + self.config["theme"] = theme + save_config(self.config) + def show_settings_menu(self): """Show a dropdown menu for settings options.""" menu = QMenu(self) + theme_menu = QMenu("Theme", self) + theme_menu.setToolTip("Choose the application theme") + + theme_group = QActionGroup(self) + theme_group.setExclusive(True) + + # Theme options: (internal_value, display_text) + theme_options = [ + ("system", "System"), + ("light", "Light"), + ("dark", "Dark"), + ] + + # Get current theme from config, default to "system" + current_theme = self.config.get("theme", "system") + for value, text in theme_options: + theme_action = QAction(text, self) + theme_action.setCheckable(True) + theme_action.setChecked(current_theme == value) + theme_action.triggered.connect(lambda checked, v=value: self.apply_theme(v)) + theme_group.addAction(theme_action) + theme_menu.addAction(theme_action) + + menu.addMenu(theme_menu) + # Add replace single newlines option newline_action = QAction("Replace single newlines with spaces", self) newline_action.setCheckable(True) diff --git a/abogen/main.py b/abogen/main.py index ad59430..2a311a9 100644 --- a/abogen/main.py +++ b/abogen/main.py @@ -3,6 +3,7 @@ import sys import platform from PyQt5.QtWidgets import QApplication from PyQt5.QtGui import QIcon +from PyQt5.QtCore import qInstallMessageHandler, QtMsgType # Add the directory to Python path sys.path.insert(0, os.path.join(os.path.dirname(__file__))) @@ -24,6 +25,25 @@ if sys.stderr is None: if platform.system() == "Darwin" and platform.processor() == "arm": os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1" +# Custom message handler to filter out specific Qt warnings +def qt_message_handler(mode, context, message): + if "Wayland does not support QWindow::requestActivate()" in message: + return # Suppress this specific message + if "setGrabPopup called with a parent, QtWaylandClient" in message: + return + if mode == QtMsgType.QtWarningMsg: + print(f"Qt Warning: {message}") + elif mode == QtMsgType.QtCriticalMsg: + print(f"Qt Critical: {message}") + elif mode == QtMsgType.QtFatalMsg: + print(f"Qt Fatal: {message}") + elif mode == QtMsgType.QtInfoMsg: + print(f"Qt Info: {message}") + + +# Install the custom message handler +qInstallMessageHandler(qt_message_handler) + # Set application ID for Windows taskbar icon if platform.system() == "Windows": import ctypes diff --git a/abogen/voice_formula_gui.py b/abogen/voice_formula_gui.py index b182b09..a9d0d70 100644 --- a/abogen/voice_formula_gui.py +++ b/abogen/voice_formula_gui.py @@ -24,14 +24,18 @@ from PyQt5.QtWidgets import ( QMenu, QAction, QComboBox, + QApplication, ) from PyQt5.QtCore import Qt, QTimer, QPoint, QRect, QSize -from PyQt5.QtGui import QPixmap, QIcon, QColor +from PyQt5.QtGui import QPixmap, QIcon from constants import ( VOICES_INTERNAL, SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION, LANGUAGE_DESCRIPTIONS, + COLORS, ) +import re +import platform from utils import get_resource_path from voice_profiles import ( load_profiles, @@ -221,6 +225,19 @@ class VoiceMixer(QWidget): self.slider.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) self.slider.setFixedWidth(SLIDER_WIDTH) + # Fix slider in Windows + if platform.system() == "Windows": + appstyle = QApplication.instance().style().objectName().lower() + if appstyle != "windowsvista": + # Set custom groove color for disabled state using COLORS["GREY_BACKGROUND"] + self.slider.setStyleSheet(f""" + QSlider::groove:vertical:disabled {{ + background: {COLORS.get("GREY_BACKGROUND")}; + width: 4px; + border-radius: 4px; + }} + """) + # Connect controls self.slider.valueChanged.connect(lambda val: self.spin_box.setValue(val / 100)) self.spin_box.valueChanged.connect( @@ -271,9 +288,9 @@ class HoverLabel(QLabel): self.delete_button = QPushButton("×", self) self.delete_button.setFixedSize(16, 16) self.delete_button.setStyleSheet( - """ - QPushButton { - background-color: #ff5555; + f""" + QPushButton {{ + background-color: {COLORS.get("RED")}; color: white; border-radius: 7px; font-weight: bold; @@ -281,11 +298,11 @@ class HoverLabel(QLabel): border: none; padding: 0px; margin: 0px; - } - QPushButton:hover { + }} + QPushButton:hover {{ background-color: red; - } - """ + }} + """ ) # Make sure the entire button is clickable, not just the text self.delete_button.setFocusPolicy(Qt.NoFocus) @@ -350,6 +367,9 @@ class VoiceFormulaDialog(QDialog): profile_layout.addLayout(header_layout) # Profile list self.profile_list = QListWidget() + self.profile_list.setSelectionMode(QListWidget.SingleSelection) + self.profile_list.setSelectionBehavior(QListWidget.SelectRows) + self.profile_list.setStyleSheet("QListWidget::item:selected { background: palette(highlight); color: palette(highlighted-text); }") icon = QIcon(get_resource_path("abogen.assets", "profile.png")) if self._virtual_new_profile: item = QListWidgetItem(icon, "New profile") @@ -738,7 +758,7 @@ class VoiceFormulaDialog(QDialog): percentage = weight / total * 100 # Make the voice name bold and include percentage voice_label = HoverLabel( - f'{name}: {percentage:.1f}%', + f'{name}: {percentage:.1f}%', name, ) voice_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) @@ -953,6 +973,18 @@ class VoiceFormulaDialog(QDialog): return super().closeEvent(event) + def _parse_rgba_to_qcolor(self, rgba_str): + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QColor + """Helper to convert 'rgba(R,G,B,A_float)' string to QColor.""" + match = re.match(r"rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)", rgba_str) + if match: + r, g, b = int(match.group(1)), int(match.group(2)), int(match.group(3)) + a_float = float(match.group(4)) + a_int = int(a_float * 255) + return QColor(r, g, b, a_int) + return Qt.GlobalColor.transparent + def mark_profile_modified(self): item = self.profile_list.currentItem() if item and not item.text().startswith("*"): @@ -1339,23 +1371,20 @@ class VoiceFormulaDialog(QDialog): self.profile_list.setItemWidget(item, widget) def update_profile_list_colors(self): + from PyQt5.QtCore import Qt profiles = load_profiles() for i in range(self.profile_list.count()): item = self.profile_list.item(i) name = item.text().lstrip("*") if self._virtual_new_profile and name == "New profile": - color = QColor("#ffff00") # yellow - color.setAlpha(64) # Set transparency - item.setBackground(color) + color = self._parse_rgba_to_qcolor(COLORS.get("YELLOW_BACKGROUND")) + item.setData(Qt.BackgroundRole, color) elif item.text().startswith("*"): - color = QColor("#ffff00") # yellow - color.setAlpha(64) # Set transparency - item.setBackground(color) + color = self._parse_rgba_to_qcolor(COLORS.get("YELLOW_BACKGROUND")) + item.setData(Qt.BackgroundRole, color) else: - item.setBackground(self.profile_list.palette().base().color()) # Use default list item background - + item.setData(Qt.BackgroundRole, self.profile_list.palette().base().color()) weights = profiles.get(name, {}).get("voices", []) - # Defensive: only sum if weights is a list of (voice, weight) pairs total = 0 if isinstance(weights, list): for entry in weights: @@ -1366,9 +1395,8 @@ class VoiceFormulaDialog(QDialog): ): total += entry[1] if total == 0: - color = QColor("#ff0000") # red - color.setAlpha(64) # Set transparency - item.setBackground(color) + color = self._parse_rgba_to_qcolor(COLORS.get("RED_BACKGROUND")) + item.setData(Qt.BackgroundRole, color) self.update_profile_save_buttons() def preview_current_mix(self): From 0f025da06bd9f5a41830447d24db12d8f4b340a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deniz=20=C5=9Eafak?= Date: Sun, 6 Jul 2025 04:41:55 +0300 Subject: [PATCH 3/6] Fix "chapter_subtitle_path" error --- abogen/conversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abogen/conversion.py b/abogen/conversion.py index a3a95ad..1e6b262 100644 --- a/abogen/conversion.py +++ b/abogen/conversion.py @@ -775,7 +775,7 @@ class ConversionThread(QThread): # Generate ASS subtitle is_centered = subtitle_format in ("ass_centered_wide", "ass_centered_narrow") is_narrow = subtitle_format in ("ass_narrow", "ass_centered_narrow") - self._write_ass_subtitle(chapter_subtitle_path, chapter_subtitle_entries, is_centered, is_narrow) + self._write_ass_subtitle(subtitle_path, subtitle_entries, is_centered, is_narrow) else: # Generate SRT subtitle (default) with open(subtitle_path, "w", encoding="utf-8", errors="replace") as srt_file: From c1fb4541dcdfc649b1907ee257ed963d7532709b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deniz=20=C5=9Eafak?= Date: Sun, 6 Jul 2025 04:45:34 +0300 Subject: [PATCH 4/6] Replace single newlines by default --- abogen/gui.py | 2 +- abogen/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/abogen/gui.py b/abogen/gui.py index 322d5cc..cccdbc9 100644 --- a/abogen/gui.py +++ b/abogen/gui.py @@ -571,7 +571,7 @@ class abogen(QWidget): self.use_gpu = self.config.get( "use_gpu", True # Load GPU setting with default True ) - self.replace_single_newlines = self.config.get("replace_single_newlines", False) + self.replace_single_newlines = self.config.get("replace_single_newlines", True) self._pending_close_event = None self.gpu_ok = False # Initialize GPU availability status diff --git a/abogen/utils.py b/abogen/utils.py index 46f77b9..3638301 100644 --- a/abogen/utils.py +++ b/abogen/utils.py @@ -91,7 +91,7 @@ _sleep_procs = {"Darwin": None, "Linux": None} # Store sleep prevention process def clean_text(text, *args, **kwargs): # Load replace_single_newlines from config cfg = load_config() - replace_single_newlines = cfg.get("replace_single_newlines", False) + replace_single_newlines = cfg.get("replace_single_newlines", True) # Collapse all whitespace (excluding newlines) into single spaces per line and trim edges lines = [re.sub(r"[^\S\n]+", " ", line).strip() for line in text.splitlines()] text = "\n".join(lines) From 6b997d290956f5c8a5896bc0eaa679de15dba37e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deniz=20=C5=9Eafak?= Date: Sun, 6 Jul 2025 04:50:24 +0300 Subject: [PATCH 5/6] Revert defaults, disable workflow --- .github/workflows/test_publish.yml | 8 ++++---- abogen/gui.py | 2 +- abogen/utils.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_publish.yml b/.github/workflows/test_publish.yml index 3be0d9d..9b6b0fe 100644 --- a/.github/workflows/test_publish.yml +++ b/.github/workflows/test_publish.yml @@ -2,11 +2,11 @@ name: Build multi-arch Docker Image on: # Build and push - release: - types: [published] + #release: + # types: [published] # Build only - push: - branches: [main] + #push: + # branches: [main] # TODO - enable build on pull requests if build times can be reduced # pull_request: workflow_dispatch: diff --git a/abogen/gui.py b/abogen/gui.py index cccdbc9..322d5cc 100644 --- a/abogen/gui.py +++ b/abogen/gui.py @@ -571,7 +571,7 @@ class abogen(QWidget): self.use_gpu = self.config.get( "use_gpu", True # Load GPU setting with default True ) - self.replace_single_newlines = self.config.get("replace_single_newlines", True) + self.replace_single_newlines = self.config.get("replace_single_newlines", False) self._pending_close_event = None self.gpu_ok = False # Initialize GPU availability status diff --git a/abogen/utils.py b/abogen/utils.py index 3638301..46f77b9 100644 --- a/abogen/utils.py +++ b/abogen/utils.py @@ -91,7 +91,7 @@ _sleep_procs = {"Darwin": None, "Linux": None} # Store sleep prevention process def clean_text(text, *args, **kwargs): # Load replace_single_newlines from config cfg = load_config() - replace_single_newlines = cfg.get("replace_single_newlines", True) + replace_single_newlines = cfg.get("replace_single_newlines", False) # Collapse all whitespace (excluding newlines) into single spaces per line and trim edges lines = [re.sub(r"[^\S\n]+", " ", line).strip() for line in text.splitlines()] text = "\n".join(lines) From 3a87fb032f639a2c279faec3faa6f7f0aae20a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deniz=20=C5=9Eafak?= Date: Sun, 6 Jul 2025 05:17:45 +0300 Subject: [PATCH 6/6] Better GPU detection --- abogen/is_nvidia.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/abogen/is_nvidia.py b/abogen/is_nvidia.py index 2777f7b..78bd8cf 100644 --- a/abogen/is_nvidia.py +++ b/abogen/is_nvidia.py @@ -6,11 +6,15 @@ def check(): except Exception: return False + nvidia_keywords = ['nvidia', 'rtx', 'gtx', 'quadro', 'tesla', 'titan', 'mx'] for gpu in stats.gpus: - print(gpu.name) - if 'nvidia' in gpu.name.lower(): + name = gpu.name.lower() + if any(keyword in name for keyword in nvidia_keywords): return True return False if __name__ == "__main__": + stats = gpustat.new_query() + for gpu in stats.gpus: + print(gpu.name) print(check()) \ No newline at end of file