Refactor downloader logging and add new options

Replaced print statements with logger calls for improved logging. Added support for sponsorblock categories and chapter embedding. Updated imports to use relative paths and improved command building logic for yt-dlp.
This commit is contained in:
Your Name
2025-08-16 15:05:15 +03:00
parent 4fdc815f70
commit 8ee436aa14
@@ -1,10 +1,11 @@
from PySide6.QtCore import QThread, Signal, QObject, QProcess, QTimer from PySide6.QtCore import QThread, Signal, QObject, QProcess, QTimer
from .ytsage_logging import logger
try: try:
import yt_dlp # Keep yt_dlp import here - only downloader uses it. import yt_dlp # Keep yt_dlp import here - only downloader uses it.
YT_DLP_AVAILABLE = True YT_DLP_AVAILABLE = True
except ImportError: except ImportError:
YT_DLP_AVAILABLE = False YT_DLP_AVAILABLE = False
print("Warning: yt-dlp not available at startup, will be downloaded at runtime") logger.warning("yt-dlp not available at startup, will be downloaded at runtime")
import time import time
import os import os
import re import re
@@ -12,7 +13,7 @@ import subprocess # For direct CLI command execution
import shlex # For safely parsing command arguments import shlex # For safely parsing command arguments
import sys # Added to get executable path information import sys # Added to get executable path information
from pathlib import Path from pathlib import Path
from ytsage_yt_dlp import get_yt_dlp_path # Import the new yt-dlp path function from .ytsage_yt_dlp import get_yt_dlp_path # Import the new yt-dlp path function
class SignalManager(QObject): class SignalManager(QObject):
update_formats = Signal(list) update_formats = Signal(list)
@@ -27,7 +28,7 @@ class DownloadThread(QThread):
file_exists_signal = Signal(str) # New signal for file existence file_exists_signal = Signal(str) # New signal for file existence
update_details = Signal(str) # New signal for filename, speed, ETA update_details = Signal(str) # New signal for filename, speed, ETA
def __init__(self, url, path, format_id, subtitle_langs=None, is_playlist=False, merge_subs=False, enable_sponsorblock=False, resolution='', playlist_items=None, save_description=False, cookie_file=None, rate_limit=None, download_section=None, force_keyframes=False): def __init__(self, url, path, format_id, subtitle_langs=None, is_playlist=False, merge_subs=False, enable_sponsorblock=False, sponsorblock_categories=None, resolution='', playlist_items=None, save_description=False, embed_chapters=False, cookie_file=None, rate_limit=None, download_section=None, force_keyframes=False):
super().__init__() super().__init__()
self.url = url self.url = url
self.path = path self.path = path
@@ -36,9 +37,11 @@ class DownloadThread(QThread):
self.is_playlist = is_playlist self.is_playlist = is_playlist
self.merge_subs = merge_subs self.merge_subs = merge_subs
self.enable_sponsorblock = enable_sponsorblock self.enable_sponsorblock = enable_sponsorblock
self.sponsorblock_categories = sponsorblock_categories if sponsorblock_categories else ['sponsor']
self.resolution = resolution self.resolution = resolution
self.playlist_items = playlist_items self.playlist_items = playlist_items
self.save_description = save_description self.save_description = save_description
self.embed_chapters = embed_chapters
self.cookie_file = cookie_file self.cookie_file = cookie_file
self.rate_limit = rate_limit self.rate_limit = rate_limit
self.download_section = download_section self.download_section = download_section
@@ -65,7 +68,7 @@ class DownloadThread(QThread):
if os.path.isfile(file_path): if os.path.isfile(file_path):
os.remove(file_path) os.remove(file_path)
except Exception as e: except Exception as e:
print(f"Error deleting {filename}: {str(e)}") logger.error(f"Error deleting {filename}: {str(e)}")
except Exception as e: except Exception as e:
self.error_signal.emit(f"Error cleaning partial files: {str(e)}") self.error_signal.emit(f"Error cleaning partial files: {str(e)}")
@@ -84,11 +87,11 @@ class DownloadThread(QThread):
if os.path.isfile(subtitle_file): if os.path.isfile(subtitle_file):
os.remove(subtitle_file) os.remove(subtitle_file)
deleted_count += 1 deleted_count += 1
print(f"DEBUG: Deleted tracked subtitle file: {os.path.basename(subtitle_file)}") logger.debug(f"Deleted tracked subtitle file: {os.path.basename(subtitle_file)}")
except Exception as e: except Exception as e:
print(f"Error deleting subtitle file {subtitle_file}: {str(e)}") logger.error(f"Error deleting subtitle file {subtitle_file}: {str(e)}")
print(f"DEBUG: Deleted {deleted_count} of {len(self.subtitle_files)} tracked subtitle files") logger.debug(f"Deleted {deleted_count} of {len(self.subtitle_files)} tracked subtitle files")
# Method 2: Find newly created subtitle files by comparing with initial set # Method 2: Find newly created subtitle files by comparing with initial set
try: try:
@@ -101,19 +104,17 @@ class DownloadThread(QThread):
new_subtitle_files.add(full_path) new_subtitle_files.add(full_path)
if new_subtitle_files: if new_subtitle_files:
print(f"DEBUG: Found {len(new_subtitle_files)} new subtitle files to delete") logger.debug(f"Found {len(new_subtitle_files)} new subtitle files to delete")
for subtitle_file in new_subtitle_files: for subtitle_file in new_subtitle_files:
try: try:
if os.path.isfile(subtitle_file): if os.path.isfile(subtitle_file):
os.remove(subtitle_file) os.remove(subtitle_file)
deleted_count += 1 deleted_count += 1
print(f"DEBUG: Deleted new subtitle file: {os.path.basename(subtitle_file)}") logger.debug(f"Deleted new subtitle file: {os.path.basename(subtitle_file)}")
except Exception as e: except Exception as e:
print(f"Error deleting new subtitle file {subtitle_file}: {str(e)}") logger.error(f"Error deleting new subtitle file {subtitle_file}: {str(e)}")
except Exception as e: except Exception as e:
print(f"Error in finding new subtitle files: {str(e)}") logger.error(f"Error in finding new subtitle files: {str(e)}") # Method 3: As a last resort, use timestamp-based approach for recently created files
# Method 3: As a last resort, use timestamp-based approach for recently created files
if self.last_file_path and deleted_count == 0: if self.last_file_path and deleted_count == 0:
target_dir = os.path.dirname(self.last_file_path) target_dir = os.path.dirname(self.last_file_path)
@@ -129,19 +130,19 @@ class DownloadThread(QThread):
try: try:
os.remove(file_path) os.remove(file_path)
deleted_count += 1 deleted_count += 1
print(f"DEBUG: Deleted subtitle file by timestamp: {filename}") logger.debug(f"Deleted subtitle file by timestamp: {filename}")
except Exception as e: except Exception as e:
print(f"Error deleting subtitle file {filename}: {str(e)}") logger.error(f"Error deleting subtitle file {filename}: {str(e)}")
print(f"DEBUG: Total subtitle files deleted: {deleted_count}") logger.debug(f"Total subtitle files deleted: {deleted_count}")
except Exception as e: except Exception as e:
print(f"Error cleaning subtitle files: {str(e)}") logger.error(f"Error cleaning subtitle files: {str(e)}")
def check_file_exists(self): def check_file_exists(self):
"""Check if the file already exists before downloading""" """Check if the file already exists before downloading"""
try: try:
print("DEBUG: Starting file existence check") logger.debug("Starting file existence check")
# Use yt-dlp to get the filename without downloading, suppressing warnings # Use yt-dlp to get the filename without downloading, suppressing warnings
ydl_opts_check = { ydl_opts_check = {
'quiet': True, 'quiet': True,
@@ -160,13 +161,13 @@ class DownloadThread(QThread):
# Handle cases where info extraction fails silently # Handle cases where info extraction fails silently
if not info: if not info:
print("DEBUG: Failed to extract info during file existence check. Skipping check.") logger.debug("Failed to extract info during file existence check. Skipping check.")
return False # Proceed with download attempt return False # Proceed with download attempt
# Get the title and sanitize it for filename # Get the title and sanitize it for filename
title = info.get('title', 'video') title = info.get('title', 'video')
# Don't remove colons and other special characters yet # Don't remove colons and other special characters yet
print(f"DEBUG: Original video title: {title}") logger.debug(f"Original video title: {title}")
# Get resolution for better matching # Get resolution for better matching
resolution = "" resolution = ""
@@ -175,9 +176,9 @@ class DownloadThread(QThread):
resolution = format_info.get('resolution', '') resolution = format_info.get('resolution', '')
break break
print(f"DEBUG: Resolution: {resolution}") logger.debug(f"Resolution: {resolution}")
else: else:
print("DEBUG: yt-dlp not available, skipping file existence check") logger.debug("yt-dlp not available, skipping file existence check")
return False # Proceed with download attempt return False # Proceed with download attempt
# Create the expected filename (more specific) # Create the expected filename (more specific)
@@ -189,7 +190,7 @@ class DownloadThread(QThread):
# Normalize the path to use consistent separators # Normalize the path to use consistent separators
base_path = os.path.normpath(base_path) base_path = os.path.normpath(base_path)
print(f"DEBUG: Base path: {base_path}") logger.debug(f"Base path: {base_path}")
# Instead of trying to predict the exact filename, scan the directory # Instead of trying to predict the exact filename, scan the directory
# and look for files that contain both the title and resolution # and look for files that contain both the title and resolution
@@ -204,16 +205,16 @@ class DownloadThread(QThread):
title_match = all(word in filename_lower for word in title_words[:3]) title_match = all(word in filename_lower for word in title_words[:3])
resolution_match = resolution.lower() in filename_lower resolution_match = resolution.lower() in filename_lower
print(f"DEBUG: Checking file: {filename}, Title match: {title_match}, Resolution match: {resolution_match}") logger.debug(f"Checking file: {filename}, Title match: {title_match}, Resolution match: {resolution_match}")
if title_match and resolution_match: if title_match and resolution_match:
print(f"DEBUG: Found matching file: {filename}") logger.debug(f"Found matching file: {filename}")
return filename return filename
print("DEBUG: No matching file found") logger.debug("No matching file found")
return None return None
except Exception as e: except Exception as e:
print(f"DEBUG: Error checking file existence: {str(e)}") logger.debug(f"Error checking file existence: {str(e)}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return None return None
@@ -223,7 +224,7 @@ class DownloadThread(QThread):
# Use the new yt-dlp path function from ytsage_yt_dlp module # Use the new yt-dlp path function from ytsage_yt_dlp module
yt_dlp_path = get_yt_dlp_path() yt_dlp_path = get_yt_dlp_path()
cmd = [yt_dlp_path] cmd = [yt_dlp_path]
print(f"DEBUG: Using yt-dlp from: {yt_dlp_path}") logger.debug(f"Using yt-dlp from: {yt_dlp_path}")
# Format selection strategy - use format ID if provided or fallback to resolution # Format selection strategy - use format ID if provided or fallback to resolution
if self.format_id: if self.format_id:
@@ -245,24 +246,24 @@ class DownloadThread(QThread):
if fmt.get('format_id') == clean_format_id: if fmt.get('format_id') == clean_format_id:
if fmt.get('vcodec') == 'none' or 'audio only' in fmt.get('format_note', '').lower(): if fmt.get('vcodec') == 'none' or 'audio only' in fmt.get('format_note', '').lower():
is_audio_format = True is_audio_format = True
print(f"DEBUG: Detected audio-only format for ID: {clean_format_id}") logger.debug(f"Detected audio-only format for ID: {clean_format_id}")
break break
except Exception as e: except Exception as e:
print(f"DEBUG: Error checking if format is audio-only: {e}") logger.debug(f"Error checking if format is audio-only: {e}")
# For audio-only formats, don't try to merge with video # For audio-only formats, don't try to merge with video
if is_audio_format: if is_audio_format:
cmd.extend(["-f", clean_format_id]) cmd.extend(["-f", clean_format_id])
print(f"DEBUG: Using audio-only format selection: {clean_format_id}") logger.debug(f"Using audio-only format selection: {clean_format_id}")
else: else:
cmd.extend(["-f", f"{clean_format_id}+bestaudio/best"]) cmd.extend(["-f", f"{clean_format_id}+bestaudio/best"])
print(f"DEBUG: Using video format selection with audio: {clean_format_id}+bestaudio/best") logger.debug(f"Using video format selection with audio: {clean_format_id}+bestaudio/best")
# Determine output format based on the selected format ID - only for video formats # Determine output format based on the selected format ID - only for video formats
if not is_audio_format: if not is_audio_format:
try: try:
format_ext = None format_ext = None
print(f"DEBUG: Getting format information for format ID: {self.format_id} (using: {clean_format_id})") logger.debug(f"Getting format information for format ID: {self.format_id} (using: {clean_format_id})")
if YT_DLP_AVAILABLE: if YT_DLP_AVAILABLE:
ydl_opts = { ydl_opts = {
'quiet': True, 'quiet': True,
@@ -284,11 +285,11 @@ class DownloadThread(QThread):
break break
if format_ext: if format_ext:
print(f"DEBUG: Detected format extension: {format_ext}") logger.debug(f"Detected format extension: {format_ext}")
# Ensure output matches the selected format - only for video formats # Ensure output matches the selected format - only for video formats
cmd.extend(["--merge-output-format", format_ext]) cmd.extend(["--merge-output-format", format_ext])
except Exception as e: except Exception as e:
print(f"DEBUG: Error detecting format extension: {e}") logger.debug(f"Error detecting format extension: {e}")
# If we can't determine the format, don't specify merge-output-format # If we can't determine the format, don't specify merge-output-format
pass pass
else: else:
@@ -327,7 +328,7 @@ class DownloadThread(QThread):
lang_code = sub_selection.split(' - ')[0] lang_code = sub_selection.split(' - ')[0]
lang_codes.append(lang_code) lang_codes.append(lang_code)
except Exception as e: except Exception as e:
print(f"Warning: Could not parse subtitle selection '{sub_selection}': {e}") logger.warning(f"Could not parse subtitle selection '{sub_selection}': {e}")
if lang_codes: if lang_codes:
cmd.extend(["--sub-langs", ",".join(lang_codes)]) cmd.extend(["--sub-langs", ",".join(lang_codes)])
@@ -338,14 +339,18 @@ class DownloadThread(QThread):
cmd.append("--embed-subs") cmd.append("--embed-subs")
# Add SponsorBlock if enabled # Add SponsorBlock if enabled
if self.enable_sponsorblock: if self.enable_sponsorblock and self.sponsorblock_categories:
cmd.append("--sponsorblock-remove") cmd.append("--sponsorblock-remove")
cmd.append("sponsor") cmd.append(",".join(self.sponsorblock_categories))
# Add description saving if enabled # Add description saving if enabled
if self.save_description: if self.save_description:
cmd.append("--write-description") cmd.append("--write-description")
# Add chapters embedding if enabled
if self.embed_chapters:
cmd.append("--embed-chapters")
# Add cookies if specified # Add cookies if specified
if self.cookie_file: if self.cookie_file:
cmd.extend(["--cookies", self.cookie_file]) cmd.extend(["--cookies", self.cookie_file])
@@ -362,7 +367,7 @@ class DownloadThread(QThread):
if self.force_keyframes: if self.force_keyframes:
cmd.append("--force-keyframes-at-cuts") cmd.append("--force-keyframes-at-cuts")
print(f"DEBUG: Added download section: {self.download_section}, Force keyframes: {self.force_keyframes}") logger.debug(f"Added download section: {self.download_section}, Force keyframes: {self.force_keyframes}")
# Add the URL as the final argument # Add the URL as the final argument
cmd.append(self.url) cmd.append(self.url)
@@ -371,16 +376,16 @@ class DownloadThread(QThread):
def run(self): def run(self):
try: try:
print("DEBUG: Starting download thread") logger.debug("Starting download thread")
# First check if file already exists using original method # First check if file already exists using original method
existing_file = self.check_file_exists() existing_file = self.check_file_exists()
if existing_file: if existing_file:
print(f"DEBUG: File exists, emitting signal: {existing_file}") logger.debug(f"File exists, emitting signal: {existing_file}")
self.file_exists_signal.emit(existing_file) self.file_exists_signal.emit(existing_file)
return return
print("DEBUG: No existing file found, proceeding with download") logger.debug("No existing file found, proceeding with download")
# Get initial list of subtitle files to compare later # Get initial list of subtitle files to compare later
self.initial_subtitle_files = set() self.initial_subtitle_files = set()
@@ -391,9 +396,9 @@ class DownloadThread(QThread):
for file in files: for file in files:
if file.endswith('.vtt') or file.endswith('.srt'): if file.endswith('.vtt') or file.endswith('.srt'):
self.initial_subtitle_files.add(os.path.join(root, file)) self.initial_subtitle_files.add(os.path.join(root, file))
print(f"DEBUG: Found {len(self.initial_subtitle_files)} existing subtitle files before download") logger.debug(f"Found {len(self.initial_subtitle_files)} existing subtitle files before download")
except Exception as e: except Exception as e:
print(f"Warning: Error scanning for initial subtitle files: {e}") logger.warning(f"Error scanning for initial subtitle files: {e}")
if self.use_direct_command: if self.use_direct_command:
# Use direct CLI command instead of Python API # Use direct CLI command instead of Python API
@@ -413,7 +418,7 @@ class DownloadThread(QThread):
try: try:
cmd = self._build_yt_dlp_command() cmd = self._build_yt_dlp_command()
cmd_str = " ".join(shlex.quote(str(arg)) for arg in cmd) cmd_str = " ".join(shlex.quote(str(arg)) for arg in cmd)
print(f"DEBUG: Executing command: {cmd_str}") logger.debug(f"Executing command: {cmd_str}")
self.status_signal.emit("🚀 Starting download...") self.status_signal.emit("🚀 Starting download...")
self.progress_signal.emit(0) self.progress_signal.emit(0)
@@ -491,7 +496,7 @@ class DownloadThread(QThread):
def _parse_output_line(self, line): def _parse_output_line(self, line):
"""Parse yt-dlp command output to update progress and status.""" """Parse yt-dlp command output to update progress and status."""
line = line.strip() line = line.strip()
# print(f"yt-dlp: {line}") # Log all output - OPTIONALLY UNCOMMENT FOR VERBOSE DEBUG # logger.info(f"yt-dlp: {line}") # Log all output - OPTIONALLY UNCOMMENT FOR VERBOSE DEBUG
# Extract filename when the destination line appears # Extract filename when the destination line appears
# Use a slightly more robust regex looking for the start of the line # Use a slightly more robust regex looking for the start of the line
@@ -501,7 +506,7 @@ class DownloadThread(QThread):
filepath = dest_match.group(1).strip() filepath = dest_match.group(1).strip()
self.current_filename = os.path.basename(filepath) self.current_filename = os.path.basename(filepath)
self.last_file_path = filepath # Store the full path for later cleanup self.last_file_path = filepath # Store the full path for later cleanup
print(f"DEBUG: Extracted filename: {self.current_filename}") # DEBUG logger.debug(f"Extracted filename: {self.current_filename}") # DEBUG
# Check if this is an audio-only download by looking in the previous lines # Check if this is an audio-only download by looking in the previous lines
is_audio_download = False is_audio_download = False
@@ -514,7 +519,7 @@ class DownloadThread(QThread):
format_match = re.search(r'Downloading format (\d+)', line) format_match = re.search(r'Downloading format (\d+)', line)
if format_match: if format_match:
format_id = format_match.group(1) format_id = format_match.group(1)
print(f"DEBUG: Detected format ID: {format_id}") logger.debug(f"Detected format ID: {format_id}")
# Format IDs for audio typically have different patterns # Format IDs for audio typically have different patterns
# (like 140, 251 for audio vs 137, 248 for video) # (like 140, 251 for audio vs 137, 248 for video)
# This is just a heuristic since format IDs can vary # This is just a heuristic since format IDs can vary
@@ -538,7 +543,7 @@ class DownloadThread(QThread):
else: else:
self.status_signal.emit(f"⏬ Downloading...") self.status_signal.emit(f"⏬ Downloading...")
except Exception as e: except Exception as e:
print(f"Error extracting filename from line '{line}': {e}") logger.error(f"Error extracting filename from line '{line}': {e}")
self.status_signal.emit("⚡ Downloading...") # Fallback status self.status_signal.emit("⚡ Downloading...") # Fallback status
return # Don't process this line further for speed/ETA return # Don't process this line further for speed/ETA
@@ -564,7 +569,7 @@ class DownloadThread(QThread):
# If it's a relative path, make it absolute based on current path # If it's a relative path, make it absolute based on current path
subtitle_file = os.path.join(self.path, subtitle_file) subtitle_file = os.path.join(self.path, subtitle_file)
self.subtitle_files.append(subtitle_file) self.subtitle_files.append(subtitle_file)
print(f"DEBUG: Tracking subtitle file for later cleanup: {subtitle_file}") logger.debug(f"Tracking subtitle file for later cleanup: {subtitle_file}")
return return
# Send status updates based on output line content # Send status updates based on output line content
@@ -617,7 +622,7 @@ class DownloadThread(QThread):
self.update_details.emit(status) self.update_details.emit(status)
except Exception as e: except Exception as e:
# If parsing fails, just show basic status (maybe log the error) # If parsing fails, just show basic status (maybe log the error)
print(f"Error parsing download details line: {line} -> {e}") logger.error(f"Error parsing download details line: {line} -> {e}")
pass # Keep basic status emission below if needed, or emit generic details pass # Keep basic status emission below if needed, or emit generic details
# Check for post-processing # Check for post-processing
@@ -648,7 +653,7 @@ class DownloadThread(QThread):
self.file_exists_signal.emit(filename) self.file_exists_signal.emit(filename)
else: else:
print(f"Could not extract filename from 'already downloaded' line: {line}") logger.info(f"Could not extract filename from 'already downloaded' line: {line}")
self.status_signal.emit("⚠️ File already exists") # Fallback status self.status_signal.emit("⚠️ File already exists") # Fallback status
elif 'Finished downloading' in line: elif 'Finished downloading' in line:
self.progress_signal.emit(100) self.progress_signal.emit(100)