109 lines
4.2 KiB
Python
109 lines
4.2 KiB
Python
# 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")
|