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.")