Files
audio_splitter/audio_splitter/formats.py
T

128 lines
4.3 KiB
Python

# audio_splitter/formats.py
"""Container format decision and validation."""
from typing import Dict, Optional
from .constants import (
FORMAT_INFO,
CONTAINER_INFO,
CODEC_TO_CONTAINER_MAP,
COMPATIBILITY_MATRIX,
is_audio_codec_supported,
is_video_codec_supported,
)
from .defaults import DEFAULT_FORMAT
def determine_default_format(container: Optional[str], codec: Optional[str]) -> Optional[str]:
"""
Given the container format and audio codec, determine the recommended output format.
Uses the CODEC_TO_CONTAINER_MAP to map codec → container.
If the codec is not found, it falls back to the container.
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
if codec and codec in CODEC_TO_CONTAINER_MAP:
fmt = CODEC_TO_CONTAINER_MAP[codec]
if fmt in FORMAT_INFO:
return fmt
if container in FORMAT_INFO:
return container
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
"""
if user_format:
return user_format
if input_file:
try:
from .ffmpeg import get_container_format, get_audio_codec
container = get_container_format(input_file)
audio_codec = get_audio_codec(input_file)
fmt = determine_default_format(container, audio_codec)
if fmt in FORMAT_INFO:
return fmt
except Exception:
pass
# Fallback
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'
def validate_format_compatibility(
container: str,
stream_info: Dict,
drop_video: bool,
drop_subs: bool,
input_file: Optional[str] = None,
audio_codec: Optional[str] = None,
video_codec: Optional[str] = None,
) -> None:
"""
Ensure the chosen container and codec combination is valid.
Raises:
ValueError: If the combination is incompatible.
"""
info = FORMAT_INFO.get(container)
if not info:
print(f"Warning: Unknown container '{container}'. Proceeding, but may fail.")
return
# Check if container is audio-only and video is present (unless dropped)
if info['audio_only'] and stream_info.get('has_video') and not drop_video:
raise ValueError(
f"Container '{container}' does not support video streams. "
"Please use --drop-video or choose a container that supports video (e.g., MKV, MP4)."
)
# Check if audio codec is supported
if audio_codec and audio_codec != 'copy':
if not is_audio_codec_supported(container, audio_codec):
raise ValueError(
f"Container '{container}' does not support audio codec '{audio_codec}'. "
f"Please choose a different container or audio codec."
)
# Check if video codec is supported (if video is present and not dropped)
if stream_info.get('has_video') and not drop_video:
# Determine the video codec from input file if not provided
if video_codec is None and input_file:
from .ffmpeg import get_video_codec
video_codec = get_video_codec(input_file)
if video_codec and video_codec != 'copy':
if not is_video_codec_supported(container, video_codec):
raise ValueError(
f"Container '{container}' does not support video codec '{video_codec}'. "
f"Please choose a different container, drop video, or transcode video to a supported codec."
)