# core/video_handler.py """Video resolution detection and encoding logic.""" import subprocess from pathlib import Path from core.logger_helper import setup_logger logger = setup_logger(Path(__file__).parent.parent / "logs") def get_source_resolution(input_file: Path) -> tuple: """ Get source video resolution (width, height). Returns tuple: (width, height) """ try: cmd = [ "ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height", "-of", "default=noprint_wrappers=1:nokey=1:noprint_wrappers=1", str(input_file) ] result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore', check=False) if result.stdout: lines = result.stdout.strip().split("\n") width = int(lines[0]) if len(lines) > 0 and lines[0].strip() else 1920 height = int(lines[1]) if len(lines) > 1 and lines[1].strip() else 1080 logger.info(f"Source resolution detected: {width}x{height}") return (width, height) else: logger.warning(f"ffprobe returned no output for {input_file.name}. Defaulting to 1920x1080") return (1920, 1080) except Exception as e: logger.warning(f"Failed to detect source resolution: {e}. Defaulting to 1920x1080") return (1920, 1080) def get_source_bit_depth(input_file: Path) -> int: """ Detect source video bit depth (8, 10, or 12). Returns: 12, 10, or 8 (default) """ try: cmd = [ "ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=pix_fmt", "-of", "default=noprint_wrappers=1:nokey=1", str(input_file) ] result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore', check=False) if result.stdout: pix_fmt = result.stdout.strip().lower() # Check for 12-bit indicators first if any(x in pix_fmt for x in ["12le", "12be"]): logger.info(f"Source bit depth detected: 12-bit ({pix_fmt})") return 12 # Check for 10-bit indicators elif any(x in pix_fmt for x in ["10le", "10be", "p010", "yuv420p10"]): logger.info(f"Source bit depth detected: 10-bit ({pix_fmt})") return 10 else: logger.info(f"Source bit depth detected: 8-bit ({pix_fmt})") return 8 else: logger.debug(f"Could not detect bit depth for {input_file.name}. Defaulting to 8-bit") return 8 except Exception as e: logger.warning(f"Failed to detect source bit depth: {e}. Defaulting to 8-bit") return 8 def determine_target_resolution(src_width: int, src_height: int, explicit_resolution: str = None) -> tuple: """ Determine target resolution based on source and explicit override. Returns tuple: (res_width, res_height, target_resolution_label) Logic: If explicit_resolution specified: use it Else: - If source > 1080p: scale to 1080p - If source <= 1080p: preserve source resolution """ if explicit_resolution: # User explicitly specified resolution - always use it if explicit_resolution == "1080": return (1920, 1080, "1080") elif explicit_resolution == "720": return (1280, 720, "720") else: # 480 return (854, 480, "480") else: # No explicit resolution - use smart defaults if src_height > 1080: # Scale down anything above 1080p to 1080p logger.info(f"Source {src_width}x{src_height} detected. Scaling to 1080p.") return (1920, 1080, "1080") else: # Preserve source resolution (480p, 720p, 1080p, etc.) if src_height <= 720: logger.info(f"Source {src_width}x{src_height} (<=720p). Preserving source resolution.") return (src_width, src_height, "720") else: logger.info(f"Source {src_width}x{src_height} (<=1080p). Preserving source resolution.") return (src_width, src_height, "1080")