From 9759fa565f88ec2f093f0af3232c9705c289f23b Mon Sep 17 00:00:00 2001 From: oop7 <110548351+oop7@users.noreply.github.com> Date: Sat, 24 Jan 2026 13:35:02 +0200 Subject: [PATCH] Refactor UI startup and stylesheet handling Moved the main application stylesheet to a new src/gui/ytsage_stylesheet.py module and applied it via StyleSheet.MAIN. Deferred blocking startup checks (FFmpeg, yt-dlp, Deno, updates) until after the UI is shown to improve responsiveness. Improved subtitle file deletion logic and added destination filename capture in downloader. Added custom style for auto-update checkbox in updater dialog. --- main.py | 17 -- src/core/ytsage_downloader.py | 19 +- .../ytsage_dialogs_updater.py | 26 ++ src/gui/ytsage_gui_main.py | 228 +++--------------- src/gui/ytsage_stylesheet.py | 126 ++++++++++ 5 files changed, 198 insertions(+), 218 deletions(-) create mode 100644 src/gui/ytsage_stylesheet.py diff --git a/main.py b/main.py index e0ceba7..ce49b44 100644 --- a/main.py +++ b/main.py @@ -3,8 +3,6 @@ import sys from PySide6.QtWidgets import QApplication, QMessageBox from src.utils.ytsage_logger import logger -from src.core.ytsage_yt_dlp import check_ytdlp_binary, setup_ytdlp # Import the new yt-dlp setup functions -from src.core.ytsage_deno import check_deno_binary, setup_deno # Import the new Deno setup functions from src.gui.ytsage_gui_main import YTSageApp # Import the main application class from ytsage_gui_main @@ -22,21 +20,6 @@ def main(): logger.info("Starting YTSage application") app = QApplication(sys.argv) - # Get the expected binary path and check if it exists - if not check_ytdlp_binary(): - # No app-specific binary found, show setup dialog regardless of Python package - logger.warning("No yt-dlp binary found, starting setup process") - yt_dlp_path = setup_ytdlp() - if yt_dlp_path == "yt-dlp": # If user canceled or something went wrong - logger.warning("yt-dlp not configured properly") - - # Check for Deno binary - if not check_deno_binary(): - logger.warning("No Deno binary found, starting setup process") - deno_path = setup_deno() - if deno_path == "deno": # If user canceled or something went wrong - logger.warning("Deno not configured properly") - window = YTSageApp() # Instantiate the main application class window.show() logger.info("Application window shown, entering main loop") diff --git a/src/core/ytsage_downloader.py b/src/core/ytsage_downloader.py index 12f1ed2..48b99b6 100644 --- a/src/core/ytsage_downloader.py +++ b/src/core/ytsage_downloader.py @@ -189,9 +189,12 @@ class DownloadThread(QThread): def safe_delete(path: Path) -> bool: try: - path.unlink(missing_ok=True) - logger.debug(f"Deleted subtitle file: {path.name}") - return True + # Check if file exists before trying to delete + if path.exists(): + path.unlink(missing_ok=True) + logger.debug(f"Deleted subtitle file: {path.name}") + return True + return False except Exception as e: logger.exception(f"Error deleting subtitle file {path}: {e}") return False @@ -410,8 +413,6 @@ class DownloadThread(QThread): self._terminate_process_tree(self.process) # Add delay before cleanup to allow file handles to be released - # Force garbage collection to help release resources - gc.collect() time.sleep(2) self.cleanup_partial_files() self.status_signal.emit(_("download.cancelled")) @@ -622,6 +623,14 @@ class DownloadThread(QThread): if "Downloading webpage" in line or "Extracting URL" in line: self.status_signal.emit(_("download.fetching_info")) self.progress_signal.emit(0) + elif "[download] Destination:" in line: + # Extract the destination filename + match = re.search(r"Destination: (.+)", line) + if match: + dest_path = match.group(1).strip() + self.current_filename = Path(dest_path).name + self.last_file_path = dest_path + logger.debug(f"Captured destination filename: {self.current_filename}") elif "Downloading API JSON" in line: self.status_signal.emit(_("download.processing_playlist")) self.progress_signal.emit(0) diff --git a/src/gui/ytsage_gui_dialogs/ytsage_dialogs_updater.py b/src/gui/ytsage_gui_dialogs/ytsage_dialogs_updater.py index 9f74457..06853cd 100644 --- a/src/gui/ytsage_gui_dialogs/ytsage_dialogs_updater.py +++ b/src/gui/ytsage_gui_dialogs/ytsage_dialogs_updater.py @@ -492,6 +492,32 @@ class UpdaterTabWidget(QWidget): # Enable/Disable auto-update checkbox self.auto_update_enabled = QCheckBox(_("settings.enable_auto_updates")) + self.auto_update_enabled.setStyleSheet( + """ + QCheckBox { + color: #ffffff; + spacing: 5px; + padding: 3px; + } + QCheckBox::indicator { + width: 18px; + height: 18px; + border-radius: 9px; + } + QCheckBox::indicator:unchecked { + border: 2px solid #666666; + background: #1d1e22; + border-radius: 9px; + } + QCheckBox::indicator:checked { + border: 2px solid #c90000; + background: #c90000; + border-radius: 9px; + } + QCheckBox:disabled { color: #888888; } + QCheckBox::indicator:disabled { border-color: #555555; background: #444444; } + """ + ) auto_update_layout.addWidget(self.auto_update_enabled) # Frequency options diff --git a/src/gui/ytsage_gui_main.py b/src/gui/ytsage_gui_main.py index e08ff05..d5852c4 100644 --- a/src/gui/ytsage_gui_main.py +++ b/src/gui/ytsage_gui_main.py @@ -51,6 +51,7 @@ from src.utils.ytsage_logger import logger from src.utils.ytsage_config_manager import ConfigManager from src.utils.ytsage_localization import LocalizationManager, _ from src.utils.ytsage_history_manager import HistoryManager +from src.gui.ytsage_stylesheet import StyleSheet class UpdateCheckThread(QThread): @@ -105,30 +106,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from saved_language = ConfigManager.get("language") or "en" LocalizationManager.initialize(saved_language) - # Check for FFmpeg before proceeding - if not check_ffmpeg(): - self.show_ffmpeg_dialog() - - # Check for yt-dlp in our app's bin directory or system PATH - ytdlp_path = get_yt_dlp_path() - if ytdlp_path == "yt-dlp": # Not found in app dir or PATH - self.show_ytdlp_setup_dialog() - else: - logger.info(f"Using yt-dlp from: {ytdlp_path}") - - # Check for Deno in our app's bin directory - deno_path = get_deno_path() - if deno_path == "deno": # Not found in app dir - self.show_deno_setup_dialog() - else: - logger.info(f"Using Deno from: {deno_path}") - self.version = APP_VERSION - self.check_for_updates() - - # Check for auto-updates if enabled - self.check_auto_update_ytdlp() - load_saved_path(self) # Load custom icon if ICON_PATH.exists(): @@ -176,178 +154,11 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from self.analysis_completed = False self.init_ui() - self.setStyleSheet( - """ - QMainWindow { - background-color: #15181b; - } - QWidget { - background-color: #15181b; - color: #ffffff; - } - QLineEdit { - padding: 5px 15px; - border: 2px solid #2a2d2e; - border-radius: 6px; - background-color: #1b2021; - color: #ffffff; - font-size: 13px; - } - QLineEdit:focus { - border-color: #ff6b6b; - } - QPushButton { - padding: 8px 15px; - background-color: #c90000; - border: none; - border-radius: 4px; - color: white; - font-weight: bold; - } - QPushButton:hover { - background-color: #a50000; - } - QPushButton:pressed { - background-color: #800000; - } - QPushButton:disabled { - background-color: #3d3d3d; - color: #888888; - } - QTableWidget { - border: 2px solid #1b2021; - border-radius: 4px; - background-color: #1b2021; - gridline-color: #1b2021; - } - QHeaderView::section { - background-color: #15181b; - padding: 5px; - border: 1px solid #1b2021; - color: #ffffff; - } - QProgressBar { - border: 2px solid #1b2021; - border-radius: 4px; - text-align: center; - color: white; - } - QProgressBar::chunk { - background-color: #c90000; - border-radius: 2px; - } - QLabel { - color: #ffffff; - } - /* Style for filter buttons */ - QPushButton.filter-btn { - background-color: #1b2021; - padding: 5px 10px; - margin: 0 5px; - } - QPushButton.filter-btn:checked { - background-color: #c90000; - } - QPushButton.filter-btn:hover { - background-color: #444444; - } - QPushButton.filter-btn:checked:hover { - background-color: #a50000; - } - /* Modern Scrollbar Styling */ - QScrollBar:vertical { - border: none; - background: #15181b; - width: 14px; - margin: 15px 0 15px 0; - border-radius: 7px; - } - QScrollBar::handle:vertical { - background: #404040; - min-height: 30px; - border-radius: 7px; - } - QScrollBar::handle:vertical:hover { - background: #505050; - } - QScrollBar::sub-line:vertical { - border: none; - background: #15181b; - height: 15px; - border-top-left-radius: 7px; - border-top-right-radius: 7px; - subcontrol-position: top; - subcontrol-origin: margin; - } - QScrollBar::add-line:vertical { - border: none; - background: #15181b; - height: 15px; - border-bottom-left-radius: 7px; - border-bottom-right-radius: 7px; - subcontrol-position: bottom; - subcontrol-origin: margin; - } - QScrollBar::sub-line:vertical:hover, - QScrollBar::add-line:vertical:hover { - background: #404040; - } - QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { - background: none; - width: 0; - height: 0; - } - QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { - background: none; - } - /* Horizontal Scrollbar */ - QScrollBar:horizontal { - border: none; - background: #15181b; - height: 14px; - margin: 0 15px 0 15px; - border-radius: 7px; - } - QScrollBar::handle:horizontal { - background: #404040; - min-width: 30px; - border-radius: 7px; - } - QScrollBar::handle:horizontal:hover { - background: #505050; - } - QScrollBar::sub-line:horizontal { - border: none; - background: #15181b; - width: 15px; - border-top-left-radius: 7px; - border-bottom-left-radius: 7px; - subcontrol-position: left; - subcontrol-origin: margin; - } - QScrollBar::add-line:horizontal { - border: none; - background: #15181b; - width: 15px; - border-top-right-radius: 7px; - border-bottom-right-radius: 7px; - subcontrol-position: right; - subcontrol-origin: margin; - } - QScrollBar::sub-line:horizontal:hover, - QScrollBar::add-line:horizontal:hover { - background: #404040; - } - QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal { - background: none; - width: 0; - height: 0; - } - QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { - background: none; - } - """ - ) + + # Defer heavy start-up tasks to ensure UI renders immediately + QTimer.singleShot(100, self._perform_startup_checks) + + self.setStyleSheet(StyleSheet.MAIN) self.signals.update_progress.connect(self.update_progress_bar) # After adding format buttons @@ -361,7 +172,32 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from # Initialize UI state based on current mode self.handle_mode_change() - # Init_sound method is removed, serve no purpose. + + def _perform_startup_checks(self): + """Perform potentially blocking startup checks after UI is shown.""" + + # Check for FFmpeg before proceeding + if not check_ffmpeg(): + self.show_ffmpeg_dialog() + + # Check for yt-dlp in our app's bin directory or system PATH + ytdlp_path = get_yt_dlp_path() + if ytdlp_path == "yt-dlp": # Not found in app dir or PATH + self.show_ytdlp_setup_dialog() + else: + logger.info(f"Using yt-dlp from: {ytdlp_path}") + + # Check for Deno in our app's bin directory + deno_path = get_deno_path() + if deno_path == "deno": # Not found in app dir + self.show_deno_setup_dialog() + else: + logger.info(f"Using Deno from: {deno_path}") + + self.check_for_updates() + + # Check for auto-updates if enabled + QTimer.singleShot(2000, self.check_auto_update_ytdlp) # Further delay auto-update check def play_notification_sound(self) -> None: """Play notification sound asynchronously (non-blocking).""" diff --git a/src/gui/ytsage_stylesheet.py b/src/gui/ytsage_stylesheet.py new file mode 100644 index 0000000..99dfcbc --- /dev/null +++ b/src/gui/ytsage_stylesheet.py @@ -0,0 +1,126 @@ + +class StyleSheet: + MAIN = """ + QMainWindow { + background-color: #15181b; + } + QWidget { + background-color: #15181b; + color: #ffffff; + } + QLineEdit { + padding: 5px 15px; + border: 2px solid #2a2d2e; + border-radius: 6px; + background-color: #1b2021; + color: #ffffff; + font-size: 13px; + } + QLineEdit:focus { + border-color: #ff6b6b; + } + QPushButton { + padding: 8px 15px; + background-color: #c90000; + border: none; + border-radius: 4px; + color: white; + font-weight: bold; + } + QPushButton:hover { + background-color: #a50000; + } + QPushButton:pressed { + background-color: #800000; + } + QPushButton:disabled { + background-color: #3d3d3d; + color: #888888; + } + QTableWidget { + border: 2px solid #1b2021; + border-radius: 4px; + background-color: #1b2021; + gridline-color: #1b2021; + } + QHeaderView::section { + background-color: #15181b; + padding: 5px; + border: 1px solid #1b2021; + color: #ffffff; + } + QProgressBar { + border: 2px solid #1b2021; + border-radius: 4px; + text-align: center; + color: white; + } + QProgressBar::chunk { + background-color: #c90000; + border-radius: 2px; + } + QLabel { + color: #ffffff; + } + /* Style for filter buttons */ + QPushButton.filter-btn { + background-color: #1b2021; + padding: 5px 10px; + margin: 0 5px; + } + QPushButton.filter-btn:checked { + background-color: #c90000; + } + QPushButton.filter-btn:hover { + background-color: #444444; + } + QPushButton.filter-btn:checked:hover { + background-color: #a50000; + } + /* Modern Scrollbar Styling */ + QScrollBar:vertical { + border: none; + background: #15181b; + width: 14px; + margin: 15px 0 15px 0; + border-radius: 7px; + } + QScrollBar::handle:vertical { + background: #404040; + min-height: 30px; + border-radius: 7px; + } + QScrollBar::handle:vertical:hover { + background: #505050; + } + QScrollBar::sub-line:vertical { + border: none; + background: #15181b; + height: 15px; + border-top-left-radius: 7px; + border-top-right-radius: 7px; + subcontrol-position: top; + subcontrol-origin: margin; + } + QScrollBar::add-line:vertical { + border: none; + background: #15181b; + height: 15px; + border-bottom-left-radius: 7px; + border-bottom-right-radius: 7px; + subcontrol-position: bottom; + subcontrol-origin: margin; + } + QScrollBar::sub-line:vertical:hover, + QScrollBar::add-line:vertical:hover { + background: #404040; + } + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + background: none; + width: 0; + height: 0; + } + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + """