Add progress signals and fake progress timer

Emit progress updates during URL analysis and simulate gradual progress for long-running yt-dlp operations. Added QTimer import and a progress_update Signal on AnalysisThread, with emits at multiple analysis milestones (e.g. 15, 30, 60, 70, 85, 90, 92, 95, 100). In AnalysisMixin introduced _analysis_timer and _fake_progress, connected thread progress to _handle_analysis_progress, and implemented a _update_fake_progress timer that slowly advances the progress bar between real updates. Timers are stopped on real progress, completion or error and the UI progress is reset appropriately to improve UX during long extraction steps.
This commit is contained in:
oop7
2026-02-03 19:17:30 +02:00
parent 0b0e3add3c
commit 49dde84481
+67 -1
View File
@@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
import json import json
import subprocess import subprocess
from PySide6.QtCore import QMetaObject, Qt, Q_ARG, QThread, Signal from PySide6.QtCore import QMetaObject, Qt, Q_ARG, QThread, Signal, QTimer
from PySide6.QtWidgets import QMessageBox from PySide6.QtWidgets import QMessageBox
from ..core.ytsage_utils import validate_video_url, parse_yt_dlp_error from ..core.ytsage_utils import validate_video_url, parse_yt_dlp_error
@@ -22,6 +22,7 @@ class AnalysisThread(QThread):
""" """
# Signals for status updates # Signals for status updates
status_update = Signal(str) status_update = Signal(str)
progress_update = Signal(int) # Signal for progress bar updates
# Signals for playlist UI # Signals for playlist UI
playlist_info_visible = Signal(bool) playlist_info_visible = Signal(bool)
@@ -63,6 +64,7 @@ class AnalysisThread(QThread):
"""Main thread execution - performs URL analysis.""" """Main thread execution - performs URL analysis."""
try: try:
self.status_update.emit(_("main_ui.analyzing_extracting_basic")) self.status_update.emit(_("main_ui.analyzing_extracting_basic"))
self.progress_update.emit(15)
url = self.url url = self.url
# Clean up the URL to handle both playlist and video URLs # Clean up the URL to handle both playlist and video URLs
@@ -107,6 +109,7 @@ class AnalysisThread(QThread):
return return
self.status_update.emit(_("main_ui.analyzing_extracting_ytdlp")) self.status_update.emit(_("main_ui.analyzing_extracting_ytdlp"))
self.progress_update.emit(30) # This will trigger fake progress in UI
# Build command for basic info extraction # Build command for basic info extraction
cmd = [yt_dlp_path, "--dump-single-json", "--flat-playlist", "--no-warnings", url] cmd = [yt_dlp_path, "--dump-single-json", "--flat-playlist", "--no-warnings", url]
@@ -156,6 +159,7 @@ class AnalysisThread(QThread):
return return
self.status_update.emit(_("main_ui.analyzing_processing_data")) self.status_update.emit(_("main_ui.analyzing_processing_data"))
self.progress_update.emit(60)
# Prepare result data # Prepare result data
result_data: Dict[str, Any] = { result_data: Dict[str, Any] = {
@@ -184,6 +188,7 @@ class AnalysisThread(QThread):
# Fetch full info for the first video to get formats # Fetch full info for the first video to get formats
self.status_update.emit(_("main_ui.analyzing_fetching_first_video")) self.status_update.emit(_("main_ui.analyzing_fetching_first_video"))
self.progress_update.emit(70)
first_video_entry = playlist_entries[0] first_video_entry = playlist_entries[0]
first_video_url = first_video_entry.get("url") first_video_url = first_video_entry.get("url")
@@ -235,20 +240,24 @@ class AnalysisThread(QThread):
return return
self.status_update.emit(_("main_ui.analyzing_processing_formats_ytdlp")) self.status_update.emit(_("main_ui.analyzing_processing_formats_ytdlp"))
self.progress_update.emit(85)
result_data["all_formats"] = video_info.get("formats", []) result_data["all_formats"] = video_info.get("formats", [])
# Get thumbnail URL # Get thumbnail URL
self.status_update.emit(_("main_ui.analyzing_loading_thumbnail_ytdlp")) self.status_update.emit(_("main_ui.analyzing_loading_thumbnail_ytdlp"))
self.progress_update.emit(90)
playlist_info = result_data.get("playlist_info") or {} playlist_info = result_data.get("playlist_info") or {}
thumbnail_url = playlist_info.get("thumbnail") or video_info.get("thumbnail") thumbnail_url = playlist_info.get("thumbnail") or video_info.get("thumbnail")
result_data["thumbnail_url"] = thumbnail_url result_data["thumbnail_url"] = thumbnail_url
# Handle subtitles # Handle subtitles
self.status_update.emit(_("main_ui.analyzing_processing_subtitles_ytdlp")) self.status_update.emit(_("main_ui.analyzing_processing_subtitles_ytdlp"))
self.progress_update.emit(92)
result_data["available_subtitles"] = video_info.get("subtitles", {}) result_data["available_subtitles"] = video_info.get("subtitles", {})
result_data["available_automatic_subtitles"] = video_info.get("automatic_captions", {}) result_data["available_automatic_subtitles"] = video_info.get("automatic_captions", {})
self.status_update.emit(_("main_ui.analyzing_updating_table")) self.status_update.emit(_("main_ui.analyzing_updating_table"))
self.progress_update.emit(95)
# Emit all results at once # Emit all results at once
self.analysis_complete.emit(result_data) self.analysis_complete.emit(result_data)
@@ -259,6 +268,8 @@ class AnalysisMixin:
# Track the current analysis thread # Track the current analysis thread
_analysis_thread: Optional[AnalysisThread] = None _analysis_thread: Optional[AnalysisThread] = None
_analysis_timer: Optional[QTimer] = None
_fake_progress: int = 0
def analyze_url(self) -> None: def analyze_url(self) -> None:
"""Start URL analysis in a background thread.""" """Start URL analysis in a background thread."""
@@ -293,11 +304,18 @@ class AnalysisMixin:
self.toggle_analysis_dependent_controls(enabled=False) self.toggle_analysis_dependent_controls(enabled=False)
self.signals.update_status.emit(_("main_ui.analyzing_preparing")) self.signals.update_status.emit(_("main_ui.analyzing_preparing"))
self.signals.update_progress.emit(0) # Reset progress
self.is_analyzing = True self.is_analyzing = True
# Stop any existing timer
if self._analysis_timer:
self._analysis_timer.stop()
self._analysis_timer = None
# Create and configure the analysis thread # Create and configure the analysis thread
self._analysis_thread = AnalysisThread( self._analysis_thread = AnalysisThread(
url=url, url=url,
# ... arguments will be filled by ... usage below ...
cookie_file_path=self.cookie_file_path, cookie_file_path=self.cookie_file_path,
browser_cookies_option=self.browser_cookies_option, browser_cookies_option=self.browser_cookies_option,
proxy_url=self.proxy_url, proxy_url=self.proxy_url,
@@ -307,6 +325,7 @@ class AnalysisMixin:
# Connect signals to handlers # Connect signals to handlers
self._analysis_thread.status_update.connect(self.signals.update_status.emit) self._analysis_thread.status_update.connect(self.signals.update_status.emit)
self._analysis_thread.progress_update.connect(self._handle_analysis_progress)
self._analysis_thread.playlist_info_visible.connect(self.signals.playlist_info_label_visible.emit) self._analysis_thread.playlist_info_visible.connect(self.signals.playlist_info_label_visible.emit)
self._analysis_thread.playlist_info_text.connect(self.signals.playlist_info_label_text.emit) self._analysis_thread.playlist_info_text.connect(self.signals.playlist_info_label_text.emit)
self._analysis_thread.playlist_select_btn_visible.connect(self.signals.playlist_select_btn_visible.emit) self._analysis_thread.playlist_select_btn_visible.connect(self.signals.playlist_select_btn_visible.emit)
@@ -318,10 +337,53 @@ class AnalysisMixin:
# Start the thread # Start the thread
self._analysis_thread.start() self._analysis_thread.start()
def _handle_analysis_progress(self, value: int) -> None:
"""Handle progress updates from analysis thread."""
self = cast("YTSageApp", self)
# Stop fake timer if running on any real update
if self._analysis_timer:
self._analysis_timer.stop()
self._analysis_timer = None
self.signals.update_progress.emit(value)
# If we hit the extraction phase (30%), start fake progress
if value == 30:
self._fake_progress = 30
self._analysis_timer = QTimer(self)
self._analysis_timer.timeout.connect(self._update_fake_progress)
self._analysis_timer.start(200) # Every 200ms
def _update_fake_progress(self) -> None:
"""Increment progress bar slowly during long operations."""
self = cast("YTSageApp", self)
# Asymptotically approach 85%
if self._fake_progress < 85:
# Slow down as we get higher
increment = 1
if self._fake_progress > 60:
if self._fake_progress % 3 == 0: # Slower
increment = 1
else:
increment = 0
if increment > 0:
self._fake_progress += increment
self.signals.update_progress.emit(self._fake_progress)
def _on_analysis_complete(self, result_data: Dict[str, Any]) -> None: def _on_analysis_complete(self, result_data: Dict[str, Any]) -> None:
"""Handle successful analysis completion - runs in main thread.""" """Handle successful analysis completion - runs in main thread."""
self = cast("YTSageApp", self) self = cast("YTSageApp", self)
# Stop fake timer
if self._analysis_timer:
self._analysis_timer.stop()
self._analysis_timer = None
self.signals.update_progress.emit(100)
# Update instance variables with results (safe - we're in main thread) # Update instance variables with results (safe - we're in main thread)
self.is_playlist = result_data["is_playlist"] self.is_playlist = result_data["is_playlist"]
self.playlist_info = result_data["playlist_info"] self.playlist_info = result_data["playlist_info"]
@@ -362,6 +424,10 @@ class AnalysisMixin:
def _on_analysis_error(self, error_message: str) -> None: def _on_analysis_error(self, error_message: str) -> None:
"""Handle analysis error - runs in main thread.""" """Handle analysis error - runs in main thread."""
self = cast("YTSageApp", self) self = cast("YTSageApp", self)
if self._analysis_timer:
self._analysis_timer.stop()
self._analysis_timer = None
self.signals.update_progress.emit(0)
self.signals.update_status.emit(error_message) self.signals.update_status.emit(error_message)
def _on_analysis_finished(self) -> None: def _on_analysis_finished(self) -> None: