From f2a6f3c3e6f6a126f2e5833cd4e427da2aa8c2b8 Mon Sep 17 00:00:00 2001 From: oop7 <110548351+oop7@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:33:25 +0200 Subject: [PATCH] Add analysis-dependent control logic and tooltips Introduces logic to enable or disable UI controls based on whether video analysis is complete, with appropriate tooltips for disabled states. Updates all language files to include new tooltip strings. Ensures controls like subtitle selection, SponsorBlock, and merge subtitles are only enabled after analysis, improving user guidance and preventing invalid actions. --- languages/ar.json | 5 +- languages/de.json | 5 +- languages/en.json | 5 +- languages/es.json | 5 +- languages/fr.json | 5 +- languages/hi.json | 5 +- languages/id.json | 5 +- languages/it.json | 5 +- languages/ja.json | 5 +- languages/pl.json | 5 +- languages/pt.json | 5 +- languages/ru.json | 5 +- languages/tr.json | 5 +- languages/zh.json | 5 +- src/gui/ytsage_gui_main.py | 125 ++++++++++++++++++++++++++++--- src/gui/ytsage_gui_video_info.py | 14 +++- 16 files changed, 184 insertions(+), 25 deletions(-) diff --git a/languages/ar.json b/languages/ar.json index a3ba2ff..0f21b5a 100644 --- a/languages/ar.json +++ b/languages/ar.json @@ -320,7 +320,10 @@ "select_subtitles": "اختر الترجمات...", "sponsorblock_categories": "فئات SponsorBlock...", "invalid_url_or_enter": "عنوان URL غير صالح أو الرجاء إدخال عنوان URL.", - "zero_selected": "تم اختيار 0" + "zero_selected": "تم اختيار 0", + "analyze_first_tooltip": "يرجى تحليل الفيديو أولاً", + "audio_mode_disabled": "غير متاح في وضع الصوت فقط", + "select_subtitles_first": "يرجى تحديد الترجمة أولاً" }, "sponsorblock": { "sponsor": "الراعي", diff --git a/languages/de.json b/languages/de.json index 579af68..173eada 100644 --- a/languages/de.json +++ b/languages/de.json @@ -320,7 +320,10 @@ "select_subtitles": "Untertitel auswählen...", "sponsorblock_categories": "SponsorBlock-Kategorien...", "invalid_url_or_enter": "Ungültige URL oder bitte geben Sie eine URL ein.", - "zero_selected": "0 ausgewählt" + "zero_selected": "0 ausgewählt", + "analyze_first_tooltip": "Bitte analysieren Sie zuerst das Video", + "audio_mode_disabled": "Nicht verfügbar im Nur-Audio-Modus", + "select_subtitles_first": "Bitte wählen Sie zuerst Untertitel aus" }, "sponsorblock": { "sponsor": "Sponsor", diff --git a/languages/en.json b/languages/en.json index f4b4bf3..ebb89a1 100644 --- a/languages/en.json +++ b/languages/en.json @@ -320,7 +320,10 @@ "select_subtitles": "Select Subtitles...", "sponsorblock_categories": "SponsorBlock Categories...", "invalid_url_or_enter": "Invalid URL or please enter a URL.", - "zero_selected": "0 selected" + "zero_selected": "0 selected", + "analyze_first_tooltip": "Please analyze the video first", + "audio_mode_disabled": "Not available in audio-only mode", + "select_subtitles_first": "Please select subtitles first" }, "sponsorblock": { "sponsor": "Sponsor", diff --git a/languages/es.json b/languages/es.json index 89cbb32..8fff5e9 100644 --- a/languages/es.json +++ b/languages/es.json @@ -303,7 +303,10 @@ "select_subtitles": "Seleccionar Subtítulos...", "sponsorblock_categories": "Categorías SponsorBlock...", "invalid_url_or_enter": "URL inválida o por favor ingresa una URL.", - "zero_selected": "0 seleccionados" + "zero_selected": "0 seleccionados", + "analyze_first_tooltip": "Por favor analiza el video primero", + "audio_mode_disabled": "No disponible en modo solo audio", + "select_subtitles_first": "Por favor selecciona subtítulos primero" }, "sponsorblock": { "sponsor": "Patrocinador", diff --git a/languages/fr.json b/languages/fr.json index 391f4ed..df5ca6d 100644 --- a/languages/fr.json +++ b/languages/fr.json @@ -320,7 +320,10 @@ "select_subtitles": "Sélectionner les sous-titres...", "sponsorblock_categories": "Catégories SponsorBlock...", "invalid_url_or_enter": "URL invalide ou veuillez entrer une URL.", - "zero_selected": "0 sélectionné" + "zero_selected": "0 sélectionné", + "analyze_first_tooltip": "Veuillez d'abord analyser la vidéo", + "audio_mode_disabled": "Non disponible en mode audio uniquement", + "select_subtitles_first": "Veuillez d'abord sélectionner les sous-titres" }, "sponsorblock": { "sponsor": "Sponsor", diff --git a/languages/hi.json b/languages/hi.json index b33846e..564a499 100644 --- a/languages/hi.json +++ b/languages/hi.json @@ -320,7 +320,10 @@ "select_subtitles": "उपशीर्षक चुनें...", "sponsorblock_categories": "SponsorBlock श्रेणियां...", "invalid_url_or_enter": "अमान्य URL या कृपया URL दर्ज करें।", - "zero_selected": "0 चयनित" + "zero_selected": "0 चयनित", + "analyze_first_tooltip": "कृपया पहले वीडियो का विश्लेषण करें", + "audio_mode_disabled": "केवल ऑडियो मोड में उपलब्ध नहीं", + "select_subtitles_first": "कृपया पहले उपशीर्षक चुनें" }, "sponsorblock": { "sponsor": "प्रायोजक", diff --git a/languages/id.json b/languages/id.json index 8114465..0bf3bf2 100644 --- a/languages/id.json +++ b/languages/id.json @@ -320,7 +320,10 @@ "select_subtitles": "Pilih subtitle...", "sponsorblock_categories": "Kategori SponsorBlock...", "invalid_url_or_enter": "URL tidak valid atau silakan masukkan URL.", - "zero_selected": "0 dipilih" + "zero_selected": "0 dipilih", + "analyze_first_tooltip": "Silakan analisis video terlebih dahulu", + "audio_mode_disabled": "Tidak tersedia dalam mode audio saja", + "select_subtitles_first": "Silakan pilih subtitle terlebih dahulu" }, "sponsorblock": { "sponsor": "Sponsor", diff --git a/languages/it.json b/languages/it.json index c0a3afc..a6bcf08 100644 --- a/languages/it.json +++ b/languages/it.json @@ -320,7 +320,10 @@ "select_subtitles": "Seleziona sottotitoli...", "sponsorblock_categories": "Categorie SponsorBlock...", "invalid_url_or_enter": "URL non valido o inserisci un URL.", - "zero_selected": "0 selezionati" + "zero_selected": "0 selezionati", + "analyze_first_tooltip": "Si prega di analizzare prima il video", + "audio_mode_disabled": "Non disponibile in modalità solo audio", + "select_subtitles_first": "Si prega di selezionare prima i sottotitoli" }, "sponsorblock": { "sponsor": "Sponsor", diff --git a/languages/ja.json b/languages/ja.json index 827fe96..6229ad6 100644 --- a/languages/ja.json +++ b/languages/ja.json @@ -320,7 +320,10 @@ "select_subtitles": "字幕を選択...", "sponsorblock_categories": "SponsorBlockカテゴリ...", "invalid_url_or_enter": "無効なURLまたはURLを入力してください。", - "zero_selected": "0個選択" + "zero_selected": "0個選択", + "analyze_first_tooltip": "最初に動画を分析してください", + "audio_mode_disabled": "音声のみモードでは利用できません", + "select_subtitles_first": "最初に字幕を選択してください" }, "sponsorblock": { "sponsor": "スポンサー", diff --git a/languages/pl.json b/languages/pl.json index c64e153..4e492e3 100644 --- a/languages/pl.json +++ b/languages/pl.json @@ -320,7 +320,10 @@ "select_subtitles": "Wybierz napisy...", "sponsorblock_categories": "Kategorie SponsorBlock...", "invalid_url_or_enter": "Nieprawidłowy URL lub wprowadź URL.", - "zero_selected": "0 wybranych" + "zero_selected": "0 wybranych", + "analyze_first_tooltip": "Najpierw przeanalizuj wideo", + "audio_mode_disabled": "Niedostępne w trybie tylko audio", + "select_subtitles_first": "Najpierw wybierz napisy" }, "sponsorblock": { "sponsor": "Sponsor", diff --git a/languages/pt.json b/languages/pt.json index 4d5503a..060bfe7 100644 --- a/languages/pt.json +++ b/languages/pt.json @@ -320,7 +320,10 @@ "select_subtitles": "Selecionar Legendas...", "sponsorblock_categories": "Categorias SponsorBlock...", "invalid_url_or_enter": "URL inválido ou por favor insira um URL.", - "zero_selected": "0 selecionados" + "zero_selected": "0 selecionados", + "analyze_first_tooltip": "Por favor analise o vídeo primeiro", + "audio_mode_disabled": "Não disponível no modo apenas áudio", + "select_subtitles_first": "Por favor selecione legendas primeiro" }, "sponsorblock": { "sponsor": "Patrocinador", diff --git a/languages/ru.json b/languages/ru.json index 8878d74..dac025e 100644 --- a/languages/ru.json +++ b/languages/ru.json @@ -320,7 +320,10 @@ "select_subtitles": "Выбрать субтитры...", "sponsorblock_categories": "Категории SponsorBlock...", "invalid_url_or_enter": "Неверный URL или пожалуйста введите URL.", - "zero_selected": "0 выбрано" + "zero_selected": "0 выбрано", + "analyze_first_tooltip": "Пожалуйста, сначала проанализируйте видео", + "audio_mode_disabled": "Недоступно в режиме только аудио", + "select_subtitles_first": "Пожалуйста, сначала выберите субтитры" }, "sponsorblock": { "sponsor": "Спонсор", diff --git a/languages/tr.json b/languages/tr.json index 14ae8a2..3daa985 100644 --- a/languages/tr.json +++ b/languages/tr.json @@ -320,7 +320,10 @@ "select_subtitles": "Altyazı seç...", "sponsorblock_categories": "SponsorBlock kategorileri...", "invalid_url_or_enter": "Geçersiz URL veya lütfen bir URL girin.", - "zero_selected": "0 seçildi" + "zero_selected": "0 seçildi", + "analyze_first_tooltip": "Lütfen önce videoyu analiz edin", + "audio_mode_disabled": "Yalnızca ses modunda kullanılamaz", + "select_subtitles_first": "Lütfen önce altyazıları seçin" }, "sponsorblock": { "sponsor": "Sponsor", diff --git a/languages/zh.json b/languages/zh.json index c232493..c24d56e 100644 --- a/languages/zh.json +++ b/languages/zh.json @@ -320,7 +320,10 @@ "select_subtitles": "选择字幕...", "sponsorblock_categories": "SponsorBlock 分类...", "invalid_url_or_enter": "无效的URL或请输入URL。", - "zero_selected": "已选择 0 个" + "zero_selected": "已选择 0 个", + "analyze_first_tooltip": "请先分析视频", + "audio_mode_disabled": "在纯音频模式下不可用", + "select_subtitles_first": "请先选择字幕" }, "sponsorblock": { "sponsor": "赞助商", diff --git a/src/gui/ytsage_gui_main.py b/src/gui/ytsage_gui_main.py index 28f3906..747c655 100644 --- a/src/gui/ytsage_gui_main.py +++ b/src/gui/ytsage_gui_main.py @@ -8,7 +8,7 @@ import markdown import pyglet import requests from packaging import version -from PySide6.QtCore import Q_ARG, QMetaObject, Qt, QTimer +from PySide6.QtCore import Q_ARG, QMetaObject, Qt, QTimer, Slot from PySide6.QtGui import QIcon from PySide6.QtWidgets import ( QApplication, @@ -127,6 +127,8 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from # Initialize output format settings self.force_output_format = ConfigManager.get("force_output_format") or False self.preferred_output_format = ConfigManager.get("preferred_output_format") or "mp4" + # Track if video analysis is completed + self.analysis_completed = False self.init_ui() self.setStyleSheet( @@ -708,6 +710,9 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from self.signals.playlist_select_btn_visible.connect(self.playlist_select_btn.setVisible) self.signals.playlist_select_btn_text.connect(self.playlist_select_btn.setText) + # Disable analysis-dependent controls until video is analyzed + self.toggle_analysis_dependent_controls(enabled=False) + def analyze_url(self) -> None: url = self.url_input.text().strip() if not url: @@ -720,6 +725,10 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from QMessageBox.warning(self, _("main_ui.error_title"), error_message) return + # Reset analysis state and disable controls + self.analysis_completed = False + self.toggle_analysis_dependent_controls(enabled=False) + self.signals.update_status.emit(_("main_ui.analyzing_preparing")) threading.Thread(target=self._analyze_url_thread, args=(url,), daemon=True).start() @@ -1532,39 +1541,128 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from self.status_label.setText(_("status.ready")) self.download_details_label.setText("") # Clear details label + @Slot(bool) + def toggle_analysis_dependent_controls(self, enabled=True) -> None: + """Enable or disable controls that require video analysis to be completed""" + # Determine tooltip text for disabled state + tooltip_text = "" if enabled else _("main_ui.analyze_first_tooltip") + + # Subtitle selection + if hasattr(self, "subtitle_select_btn"): + self.subtitle_select_btn.setEnabled(enabled) + if not enabled: + self.subtitle_select_btn.setToolTip(tooltip_text) + else: + self.subtitle_select_btn.setToolTip("") + + # SponsorBlock (only if not in audio mode) + if hasattr(self, "sponsorblock_select_btn"): + is_audio_mode = self.audio_button.isChecked() + self.sponsorblock_select_btn.setEnabled(enabled and not is_audio_mode) + if not enabled or is_audio_mode: + self.sponsorblock_select_btn.setToolTip(tooltip_text if not enabled else _("main_ui.audio_mode_disabled")) + else: + self.sponsorblock_select_btn.setToolTip("") + + # Save Thumbnail checkbox + if hasattr(self, "save_thumbnail_checkbox"): + self.save_thumbnail_checkbox.setEnabled(enabled) + if not enabled: + self.save_thumbnail_checkbox.setToolTip(tooltip_text) + else: + self.save_thumbnail_checkbox.setToolTip("") + + # Save Description checkbox + if hasattr(self, "save_description_checkbox"): + self.save_description_checkbox.setEnabled(enabled) + if not enabled: + self.save_description_checkbox.setToolTip(tooltip_text) + else: + self.save_description_checkbox.setToolTip("") + + # Embed Chapters checkbox + if hasattr(self, "embed_chapters_checkbox"): + self.embed_chapters_checkbox.setEnabled(enabled) + if not enabled: + self.embed_chapters_checkbox.setToolTip(tooltip_text) + else: + self.embed_chapters_checkbox.setToolTip("") + + # Merge Subtitles (only if subtitles are selected and not in audio mode) + if hasattr(self, "merge_subs_checkbox"): + has_subs = len(getattr(self, "selected_subtitles", [])) > 0 + is_audio_mode = self.audio_button.isChecked() + should_enable = enabled and has_subs and not is_audio_mode + self.merge_subs_checkbox.setEnabled(should_enable) + if not enabled: + self.merge_subs_checkbox.setToolTip(tooltip_text) + elif not has_subs: + self.merge_subs_checkbox.setToolTip(_("main_ui.select_subtitles_first")) + elif is_audio_mode: + self.merge_subs_checkbox.setToolTip(_("main_ui.audio_mode_disabled")) + else: + self.merge_subs_checkbox.setToolTip("") + def handle_format_selection(self, button) -> None: # Update formats self.filter_formats() def handle_mode_change(self) -> None: """Enable or disable features based on video/audio mode""" + # Only allow enabling if analysis is complete + can_enable = self.analysis_completed + if self.audio_button.isChecked(): # In Audio Only mode, disable video-specific features if hasattr(self, "sponsorblock_select_btn"): self.sponsorblock_select_btn.setEnabled(False) + self.sponsorblock_select_btn.setToolTip(_("main_ui.audio_mode_disabled")) if hasattr(self, "selected_sponsorblock_categories"): self.selected_sponsorblock_categories = [] # Clear selection when disabled if hasattr(self, "_update_sponsorblock_display"): self._update_sponsorblock_display() self.merge_subs_checkbox.setEnabled(False) self.merge_subs_checkbox.setChecked(False) # Uncheck when disabled + if not can_enable: + self.merge_subs_checkbox.setToolTip(_("main_ui.analyze_first_tooltip")) + else: + self.merge_subs_checkbox.setToolTip(_("main_ui.audio_mode_disabled")) - # Allow subtitle selection in Audio Only mode too + # Allow subtitle selection in Audio Only mode if analysis is complete if hasattr(self, "subtitle_select_btn"): - self.subtitle_select_btn.setEnabled(True) + self.subtitle_select_btn.setEnabled(can_enable) + if not can_enable: + self.subtitle_select_btn.setToolTip(_("main_ui.analyze_first_tooltip")) + else: + self.subtitle_select_btn.setToolTip("") else: - # In Video mode, enable video-specific features + # In Video mode, enable video-specific features (if analysis complete) if hasattr(self, "sponsorblock_select_btn"): - self.sponsorblock_select_btn.setEnabled(True) + self.sponsorblock_select_btn.setEnabled(can_enable) + if not can_enable: + self.sponsorblock_select_btn.setToolTip(_("main_ui.analyze_first_tooltip")) + else: + self.sponsorblock_select_btn.setToolTip("") # Don't automatically restore categories - let user choose when they open the dialog - # Enable merge_subs only if subtitles are selected + # Enable merge_subs only if subtitles are selected and analysis is complete has_subs_selected = len(getattr(self, "selected_subtitles", [])) > 0 - self.merge_subs_checkbox.setEnabled(has_subs_selected) + should_enable_merge = can_enable and has_subs_selected + self.merge_subs_checkbox.setEnabled(should_enable_merge) + if not can_enable: + self.merge_subs_checkbox.setToolTip(_("main_ui.analyze_first_tooltip")) + elif not has_subs_selected: + self.merge_subs_checkbox.setToolTip(_("main_ui.select_subtitles_first")) + else: + self.merge_subs_checkbox.setToolTip("") - # Re-enable subtitle selection button in Video mode + # Re-enable subtitle selection button in Video mode (if analysis complete) if hasattr(self, "subtitle_select_btn"): - self.subtitle_select_btn.setEnabled(True) + self.subtitle_select_btn.setEnabled(can_enable) + if not can_enable: + self.subtitle_select_btn.setToolTip(_("main_ui.analyze_first_tooltip")) + else: + self.subtitle_select_btn.setToolTip("") # Keep these methods for backwards compatibility - they just call the new dialog now def show_custom_command(self) -> None: @@ -1843,6 +1941,15 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from self.signals.update_status.emit(_("main_ui.analysis_complete")) + # Mark analysis as complete and enable analysis-dependent controls + self.analysis_completed = True + QMetaObject.invokeMethod( + self, + "toggle_analysis_dependent_controls", + Qt.ConnectionType.QueuedConnection, + Q_ARG(bool, True), + ) + except subprocess.TimeoutExpired: logger.error("Analysis timed out. Please try again.") self.signals.update_status.emit(_("errors.analysis_timeout")) diff --git a/src/gui/ytsage_gui_video_info.py b/src/gui/ytsage_gui_video_info.py index 8098588..fceaf02 100644 --- a/src/gui/ytsage_gui_video_info.py +++ b/src/gui/ytsage_gui_video_info.py @@ -300,11 +300,21 @@ class VideoInfoMixin: # Enable/disable the merge checkbox in the parent window if merge_checkbox: - # Only enable merge checkbox if we're not in Audio Only mode + # Only enable merge checkbox if we're not in Audio Only mode and analysis is complete is_audio_only = hasattr(self, "audio_button") and self.audio_button.isChecked() + has_analysis = getattr(self, "analysis_completed", False) # In audio-only mode, we still allow subtitle selection but not merging - should_enable = count > 0 and not is_audio_only + should_enable = count > 0 and not is_audio_only and has_analysis merge_checkbox.setEnabled(should_enable) + # Update tooltip + if not has_analysis: + merge_checkbox.setToolTip(_("main_ui.analyze_first_tooltip")) + elif is_audio_only: + merge_checkbox.setToolTip(_("main_ui.audio_mode_disabled")) + elif count == 0: + merge_checkbox.setToolTip(_("main_ui.select_subtitles_first")) + else: + merge_checkbox.setToolTip("") else: logger.warning("merge_subs_checkbox not found on parent window.")