From 9786d011c29431a837704779bc7dd5ab3ad4aec8 Mon Sep 17 00:00:00 2001 From: qsethuk Date: Thu, 4 Dec 2025 00:48:49 +0300 Subject: [PATCH] Update yt-dlp option config for download best quality video with pc/phone support --- bot/modules/media_loader/ytdlp.py | 153 +++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 2 deletions(-) diff --git a/bot/modules/media_loader/ytdlp.py b/bot/modules/media_loader/ytdlp.py index ece0747..00c3430 100644 --- a/bot/modules/media_loader/ytdlp.py +++ b/bot/modules/media_loader/ytdlp.py @@ -8,10 +8,144 @@ import asyncio import threading import logging import time +import shutil +import json logger = logging.getLogger(__name__) +async def fix_video_aspect_ratio(video_path: str) -> Optional[str]: + """ + Fix video aspect ratio metadata for mobile compatibility + + This function ensures that video has correct aspect ratio metadata + so it displays correctly on mobile devices (not as square) + + Args: + video_path: Path to video file + + Returns: + Path to fixed video file (same file if fixed in place, or new file) + """ + try: + # Check if ffmpeg is available + if not shutil.which('ffmpeg'): + logger.warning("ffmpeg not found, skipping aspect ratio fix") + return None + + # Check if ffprobe is available + if not shutil.which('ffprobe'): + logger.warning("ffprobe not found, skipping aspect ratio fix") + return None + + video_file = Path(video_path) + if not video_file.exists(): + logger.warning(f"Video file not found: {video_path}") + return None + + # Get video information to check aspect ratio + probe_cmd = [ + 'ffprobe', '-v', 'error', + '-select_streams', 'v:0', + '-show_entries', 'stream=width,height,display_aspect_ratio,sample_aspect_ratio', + '-of', 'json', + str(video_path) + ] + + proc = await asyncio.create_subprocess_exec( + *probe_cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await proc.communicate() + + if proc.returncode != 0: + logger.warning(f"Failed to probe video: {stderr.decode()}") + return None + + try: + probe_data = json.loads(stdout.decode()) + streams = probe_data.get('streams', []) + if not streams: + logger.warning("No video streams found") + return None + + stream = streams[0] + width = stream.get('width') + height = stream.get('height') + dar = stream.get('display_aspect_ratio') # Display Aspect Ratio + sar = stream.get('sample_aspect_ratio') # Sample Aspect Ratio + + if not width or not height: + logger.warning("Could not get video dimensions") + return None + + # Calculate expected aspect ratio + expected_dar = f"{width}:{height}" + + # If aspect ratio metadata is missing or incorrect, fix it + needs_fix = False + if not dar or dar == 'N/A': + needs_fix = True + logger.info(f"Video missing aspect ratio metadata (DAR), will fix to {expected_dar}") + elif dar != expected_dar: + # Check if the difference is significant + try: + dar_parts = dar.split(':') + if len(dar_parts) == 2: + dar_ratio = float(dar_parts[0]) / float(dar_parts[1]) + expected_ratio = width / height + if abs(dar_ratio - expected_ratio) > 0.01: # More than 1% difference + needs_fix = True + logger.info(f"Video has incorrect aspect ratio {dar}, will fix to {expected_dar}") + except (ValueError, ZeroDivisionError): + needs_fix = True + + if not needs_fix: + logger.debug(f"Video aspect ratio is correct: {dar} (dimensions: {width}x{height})") + return None + + # Fix aspect ratio by remuxing with correct metadata + # Use temp file to avoid corruption if process fails + temp_file = video_file.with_suffix('.tmp' + video_file.suffix) + + fix_cmd = [ + 'ffmpeg', '-i', str(video_path), + '-c', 'copy', # Copy streams without re-encoding + '-aspect', expected_dar, # Set correct aspect ratio + '-movflags', '+faststart', # Optimize for streaming + '-y', # Overwrite + str(temp_file) + ] + + proc = await asyncio.create_subprocess_exec( + *fix_cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + await proc.communicate() + + if proc.returncode == 0 and temp_file.exists(): + # Replace original file with fixed one + video_file.unlink() + temp_file.rename(video_file) + logger.info(f"Video aspect ratio fixed: {video_path} (DAR: {expected_dar})") + return str(video_file) + else: + logger.warning(f"Failed to fix aspect ratio, keeping original file") + if temp_file.exists(): + temp_file.unlink() + return None + + except json.JSONDecodeError as e: + logger.warning(f"Failed to parse ffprobe output: {e}") + return None + + except Exception as e: + logger.error(f"Error fixing video aspect ratio: {e}", exc_info=True) + return None + + def create_progress_hook(progress_callback: Optional[Callable] = None, event_loop=None, cancel_event: Optional[threading.Event] = None, last_update_time: list = None): """ Create progress hook for tracking download progress @@ -165,8 +299,15 @@ async def download_media( 'merge_output_format': 'mp4', # FFmpeg options for merging to ensure compatibility # Copy streams when possible (no re-encoding), only encode if necessary + # Add proper metadata for mobile compatibility 'postprocessor_args': { - 'ffmpeg': ['-c:v', 'copy', '-c:a', 'aac', '-movflags', '+faststart'] + 'ffmpeg': [ + '-c:v', 'copy', + '-c:a', 'aac', + '-movflags', '+faststart', + '-aspect', 'auto', # Preserve aspect ratio + '-metadata:s:v:0', 'rotate=0', # Ensure no rotation metadata issues + ] }, # Don't prefer free formats (they may be lower quality) 'prefer_free_formats': False, @@ -330,12 +471,20 @@ async def download_media( if file_path.exists(): file_size = file_path.stat().st_size logger.info(f"File found: {file_path}, size: {file_size / (1024*1024):.2f} MB") + + # Post-process video to fix aspect ratio for mobile compatibility + if ext.lower() in ['mp4', 'mov', 'avi', 'mkv', 'webm']: + fixed_file_path = await fix_video_aspect_ratio(str(file_path)) + if fixed_file_path: + file_path = Path(fixed_file_path) + logger.info(f"Video aspect ratio fixed: {file_path}") + return { 'file_path': str(file_path), 'title': title, 'duration': info.get('duration'), 'thumbnail': info.get('thumbnail'), - 'size': file_size + 'size': file_path.stat().st_size } else: # Output list of all files in directory for debugging