CHANGE: maps codec/container/file_extension more accurate
This commit is contained in:
@@ -1,16 +1,79 @@
|
||||
"""Container format decision and validation."""
|
||||
|
||||
from typing import Dict, Optional
|
||||
|
||||
from .constants import FORMAT_INFO
|
||||
|
||||
|
||||
def determine_output_format(stream_info, user_format, transcode_audio):
|
||||
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).
|
||||
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.
|
||||
@@ -18,11 +81,22 @@ def determine_output_format(stream_info, user_format, transcode_audio):
|
||||
if user_format:
|
||||
return user_format
|
||||
|
||||
# If video or subtitles exist, use MKV (which supports everything).
|
||||
if stream_info['has_video'] or stream_info['has_subtitle']:
|
||||
return 'matroska'
|
||||
# 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
|
||||
|
||||
# Audio‑only: choose based on the current audio codec.
|
||||
# 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'
|
||||
@@ -30,7 +104,8 @@ def determine_output_format(stream_info, user_format, transcode_audio):
|
||||
return 'mp4' # .m4a
|
||||
|
||||
|
||||
def validate_format_compatibility(format_name, stream_info, drop_video, drop_subs):
|
||||
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.
|
||||
|
||||
@@ -43,12 +118,12 @@ def validate_format_compatibility(format_name, stream_info, drop_video, drop_sub
|
||||
return
|
||||
|
||||
if info['audio_only']:
|
||||
if stream_info['has_video'] and not drop_video:
|
||||
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['has_subtitle'] and not drop_subs:
|
||||
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."
|
||||
|
||||
Reference in New Issue
Block a user