"""Container format decision and validation.""" from typing import Dict, Optional from .constants import FORMAT_INFO def determine_default_format(container: Optional[str], codec: Optional[str]) -> Optional[str]: """ Given the container format and audio codec, determine the recommended output format. This is used when the user has not explicitly specified a format. It prioritizes the codec to choose the most appropriate container/extension. Args: container: Container name (e.g., 'ogg', 'mp4', 'matroska') as returned by get_container_format(). codec: Audio codec name (e.g., 'opus', 'aac', 'mp3') as returned by get_audio_codec(). Returns: Format name (e.g., 'opus', 'm4a', 'mp3') or None if unknown. """ if not container: return None # Codec-based decisions (highest priority) if codec == 'opus': return 'opus' if codec in ('aac', 'alac', 'he-aac'): return 'm4a' if codec == 'mp3': return 'mp3' if codec == 'vorbis': return 'ogg' if codec == 'flac': return 'flac' # Container-based fallback (lower priority) if container in ('mp4', 'm4a', 'mov', '3gp'): return 'mp4' if container in ('matroska', 'webm'): return 'matroska' if container in ('ogg',): return 'ogg' if container in ('mp3', 'mpeg'): return 'mp3' if container == 'flac': return 'flac' if container == 'wav': return 'wav' if container == 'aac': return 'aac' if container == 'opus': return 'opus' if container == 'amr': return 'amr' return None def determine_output_format(stream_info: Dict, user_format: Optional[str], transcode_audio: Optional[str], input_file: Optional[str] = None) -> str: """ Decide which container format to use. If user_format is provided, use it. Else, try to detect the input file's container and codec, and use the recommended format. If detection fails or format is not supported, fallback to: - MKV if video/subtitles exist - MP3 if the audio codec is MP3 - MP4 (M4A) otherwise Args: stream_info: Dict from get_stream_info(). user_format: User‑requested format (or None). transcode_audio: Audio codec to transcode to (or None) (unused in this function). input_file: Path to the input file (optional, used to detect container and codec). Returns: A format name that exists in FORMAT_INFO. """ if user_format: return user_format # If input_file is provided, try to detect container and codec if input_file: try: from .ffmpeg import get_container_format, get_audio_codec container = get_container_format(input_file) codec = get_audio_codec(input_file) fmt = determine_default_format(container, codec) if fmt in FORMAT_INFO: return fmt except Exception: # If detection fails, fall through to legacy logic pass # Fallback: legacy behavior if stream_info.get('has_video') or stream_info.get('has_subtitle'): return 'matroska' audio_codec = stream_info.get('audio_codec', '') if audio_codec == 'mp3': return 'mp3' else: return 'mp4' # .m4a def validate_format_compatibility(format_name: str, stream_info: Dict, drop_video: bool, drop_subs: bool) -> None: """ Ensure the chosen container can accommodate the streams we intend to keep. Raises: ValueError: If the format is incompatible with the intended streams. """ info = FORMAT_INFO.get(format_name) if not info: print(f"Warning: Unknown format '{format_name}'. Proceeding, but may fail.") return if info['audio_only']: if stream_info.get('has_video') and not drop_video: raise ValueError( f"Format '{format_name}' does not support video streams. " "Please use --drop-video or choose a container that supports video." ) if stream_info.get('has_subtitle') and not drop_subs: raise ValueError( f"Format '{format_name}' does not support subtitle streams. " "Please use --drop-subs or choose a container that supports subtitles." )