diff --git a/bot/modules/media_loader/ytdlp.py b/bot/modules/media_loader/ytdlp.py index 0860eaa..b165ba4 100644 --- a/bot/modules/media_loader/ytdlp.py +++ b/bot/modules/media_loader/ytdlp.py @@ -14,6 +14,34 @@ import json logger = logging.getLogger(__name__) +class YtDlpErrorFilter: + """Filter to suppress non-critical yt-dlp errors from stderr""" + + def __init__(self, original_stderr): + self.original_stderr = original_stderr + self.buffer = [] + + def write(self, text): + """Filter stderr output from yt-dlp""" + # Suppress "Unable to extract title" errors - they're not critical + if "Unable to extract title" in text: + # Log as debug instead of error + logger.debug(f"yt-dlp: {text.strip()}") + return + + # Suppress other non-critical extraction errors + if "Unable to extract" in text and ("title" in text.lower() or "metadata" in text.lower()): + logger.debug(f"yt-dlp: {text.strip()}") + return + + # Write everything else to original stderr + self.original_stderr.write(text) + self.original_stderr.flush() + + def flush(self): + self.original_stderr.flush() + + async def fix_video_aspect_ratio(video_path: str) -> Optional[str]: """ Fix video aspect ratio metadata for mobile compatibility @@ -311,7 +339,7 @@ async def download_media( # Additional options for better quality 'writesubtitles': False, 'writeautomaticsub': False, - 'ignoreerrors': False, + 'ignoreerrors': True, # Continue on extraction errors (e.g., missing title) # Network settings for better reliability 'socket_timeout': 60, # Increase socket timeout to 60 seconds 'retries': 3, # Retry failed downloads up to 3 times @@ -396,7 +424,15 @@ async def download_media( # This function runs in a separate thread (ThreadPoolExecutor) # progress hook will be called from this thread and use # run_coroutine_threadsafe for safe call in main event loop + import sys + original_stderr = sys.stderr + error_filter = None + try: + # Redirect stderr to filter non-critical errors + error_filter = YtDlpErrorFilter(original_stderr) + sys.stderr = error_filter + with yt_dlp.YoutubeDL(ydl_opts) as ydl: # Check for cancellation before start if cancel_event and cancel_event.is_set(): @@ -418,8 +454,16 @@ async def download_media( error_msg = str(download_error) error_lower = error_msg.lower() + # Check if it's a non-critical extraction error (e.g., missing title) + # These errors don't prevent download, just metadata extraction + if "Unable to extract" in error_msg and ("title" in error_lower or "metadata" in error_lower): + logger.debug( + f"Non-critical extraction error (metadata may be missing): {error_msg}. " + f"Video file should still be available. Will check file existence." + ) + # Don't raise - video is likely already downloaded # Check if it's just a postprocessing error (video is already downloaded) - if "Postprocessing" in error_msg or "aspect ratio" in error_lower: + elif "Postprocessing" in error_msg or "aspect ratio" in error_lower: logger.warning( f"Postprocessing error (non-critical): {error_msg}. " f"Video file should still be available. Will check file existence." @@ -466,6 +510,10 @@ async def download_media( # Interrupt download on cancellation logger.info("Download interrupted") raise + finally: + # Restore original stderr + if error_filter: + sys.stderr = original_stderr # Execute in executor for non-blocking download # None uses ThreadPoolExecutor by default @@ -806,4 +854,3 @@ async def get_videos_list(url: str, cookies_file: Optional[str] = None) -> Optio except Exception as e: logger.error(f"Error getting videos list: {e}", exc_info=True) return None -