From 7a7b63b03b1c062698c15a580d67f129dd4ae320 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Tue, 1 Sep 2026 12:17:21 +0500 Subject: [PATCH] refact (core): simplifies constants.py, removes redundancy, updates dependent code --- audio_splitter/constants.py | 57 +++++++------------ audio_splitter/core.py | 7 ++- audio_splitter/ffmpeg.py | 12 ++-- audio_splitter/formats.py | 16 +++--- web/backend/api/info.py | 4 +- web/frontend/src/hooks/useFormatValidation.ts | 2 +- web/frontend/src/types/index.ts | 1 - 7 files changed, 42 insertions(+), 57 deletions(-) diff --git a/audio_splitter/constants.py b/audio_splitter/constants.py index e167b3e..af96789 100644 --- a/audio_splitter/constants.py +++ b/audio_splitter/constants.py @@ -11,34 +11,36 @@ This file serves as the single source of truth for: Codec != Container != File Extension. Example: Opus (codec) → Ogg (container) → .opus (extension) """ +from typing import Dict, Optional # ------------------------------------------------------------------------------ # Container information # ------------------------------------------------------------------------------ CONTAINER_INFO = [ # Audio-only containers - {'name': 'mp3', 'ffmpeg': 'mp3', 'extension': '.mp3', 'audio_only': False, 'supports_video': True, 'supports_subs': False}, - {'name': 'm4a', 'ffmpeg': 'mp4', 'extension': '.m4a', 'audio_only': True, 'supports_video': False, 'supports_subs': False}, - {'name': 'flac', 'ffmpeg': 'flac', 'extension': '.flac', 'audio_only': True, 'supports_video': False, 'supports_subs': False}, - {'name': 'wav', 'ffmpeg': 'wav', 'extension': '.wav', 'audio_only': True, 'supports_video': False, 'supports_subs': False}, - {'name': 'aac', 'ffmpeg': 'adts', 'extension': '.aac', 'audio_only': True, 'supports_video': False, 'supports_subs': False}, + {'name': 'mp3', 'ffmpeg': 'mp3', 'extension': '.mp3', 'supports_video': True, 'supports_subs': False}, + {'name': 'm4a', 'ffmpeg': 'mp4', 'extension': '.m4a', 'supports_video': False, 'supports_subs': False}, + {'name': 'flac', 'ffmpeg': 'flac', 'extension': '.flac', 'supports_video': False, 'supports_subs': False}, + {'name': 'wav', 'ffmpeg': 'wav', 'extension': '.wav', 'supports_video': False, 'supports_subs': False}, + {'name': 'aac', 'ffmpeg': 'adts', 'extension': '.aac', 'supports_video': False, 'supports_subs': False}, # Containers that support video and subtitles - {'name': 'mp4', 'ffmpeg': 'mp4', 'extension': '.mp4', 'audio_only': False, 'supports_video': True, 'supports_subs': True}, - {'name': 'mkv', 'ffmpeg': 'matroska', 'extension': '.mkv', 'audio_only': False, 'supports_video': True, 'supports_subs': True}, - {'name': 'matroska', 'ffmpeg': 'matroska', 'extension': '.mkv', 'audio_only': False, 'supports_video': True, 'supports_subs': True}, - {'name': 'ogg', 'ffmpeg': 'ogg', 'extension': '.ogg', 'audio_only': False, 'supports_video': True, 'supports_subs': True}, - {'name': 'webm', 'ffmpeg': 'webm', 'extension': '.webm', 'audio_only': False, 'supports_video': True, 'supports_subs': False}, + {'name': 'mp4', 'ffmpeg': 'mp4', 'extension': '.mp4', 'supports_video': True, 'supports_subs': True}, + {'name': 'mkv', 'ffmpeg': 'matroska', 'extension': '.mkv', 'supports_video': True, 'supports_subs': True}, + {'name': 'ogg', 'ffmpeg': 'ogg', 'extension': '.ogg', 'supports_video': True, 'supports_subs': True}, + {'name': 'webm', 'ffmpeg': 'webm', 'extension': '.webm', 'supports_video': True, 'supports_subs': False}, ] -# Legacy FORMAT_INFO for backward compatibility -FORMAT_INFO = { - container['name']: { - 'ffmpeg': container['ffmpeg'], - 'ext': container['extension'], - 'audio_only': container['audio_only'], - } - for container in CONTAINER_INFO -} +# Container name lookup set for fast membership checks +CONTAINER_NAMES = {container['name'] for container in CONTAINER_INFO} + + +def get_container_info(container_name: str) -> Optional[Dict]: + """Return container info dict or None if not found.""" + for container in CONTAINER_INFO: + if container['name'] == container_name: + return container + return None + # ------------------------------------------------------------------------------ # Codec information @@ -70,22 +72,6 @@ CODEC_TO_EXTENSION_MAP = {codec['name']: codec['recommended_extension'] for code # Map codec name → FFmpeg encoder name CODEC_NAME_TO_FFMPEG = {codec['name']: codec['ffmpeg'] for codec in CODEC_INFO} -# ------------------------------------------------------------------------------ -# Video codec support per container (legacy, will be superseded by compatibility matrix) -# ------------------------------------------------------------------------------ -CONTAINER_VIDEO_CODEC_SUPPORT = { - 'mp4': ['h264', 'h265', 'vp9', 'av1', 'mpeg4', 'hevc'], - 'mkv': ['*'], - 'matroska': ['*'], - 'ogg': ['theora', 'dirac', 'vp8', 'png', 'mjpeg'], - 'webm': ['vp8', 'vp9', 'av1'], - 'mp3': [], - 'm4a': [], - 'flac': [], - 'wav': [], - 'aac': [], -} - # ------------------------------------------------------------------------------ # Compatibility matrix: container → supported audio and video codecs # ------------------------------------------------------------------------------ @@ -100,7 +86,6 @@ COMPATIBILITY_MATRIX = { 'm4a': {'audio': ['aac', 'alac', 'opus', 'flac'], 'video': []}, 'mp4': {'audio': ['aac', 'alac', 'opus', 'flac', 'mp3'], 'video': ['h264', 'h265', 'vp9', 'av1']}, 'mkv': {'audio': None, 'video': None}, - 'matroska': {'audio': None, 'video': None}, 'ogg': {'audio': ['opus', 'vorbis', 'flac'], 'video': ['theora', 'vp8', 'png']}, 'webm': {'audio': ['opus', 'vorbis'], 'video': ['vp8', 'vp9', 'av1']}, 'flac': {'audio': ['flac'], 'video': []}, diff --git a/audio_splitter/core.py b/audio_splitter/core.py index cbe4c8b..6aa9865 100644 --- a/audio_splitter/core.py +++ b/audio_splitter/core.py @@ -7,9 +7,9 @@ import sys from typing import List, Dict, Any from .constants import ( - FORMAT_INFO, + CONTAINER_NAMES, CODEC_TO_EXTENSION_MAP, - CODEC_NAME_TO_FFMPEG, # <-- Add this + CODEC_NAME_TO_FFMPEG, ) from .ffmpeg import ( get_audio_duration, @@ -121,7 +121,8 @@ def split_audio(input_file: str, output_directory: str, tracks: List[Dict[str, A if output_audio_codec in CODEC_TO_EXTENSION_MAP: extension = CODEC_TO_EXTENSION_MAP[output_audio_codec] else: - extension = FORMAT_INFO.get(output_container, {}).get('ext', '.mkv') + container_info = next((c for c in CONTAINER_INFO if c['name'] == output_container), None) + extension = container_info['extension'] if container_info else '.mkv' print(f"Output extension: {extension}") diff --git a/audio_splitter/ffmpeg.py b/audio_splitter/ffmpeg.py index 2090bf2..8d4cb35 100644 --- a/audio_splitter/ffmpeg.py +++ b/audio_splitter/ffmpeg.py @@ -5,7 +5,7 @@ import json import subprocess from typing import Dict, List, Optional, Tuple -from .constants import FORMAT_INFO +from .constants import CONTAINER_INFO from .utils import format_time @@ -159,7 +159,7 @@ def get_metadata(input_file: str) -> Dict[str, any]: def get_container_format(input_file: str) -> Optional[str]: """ - Retrieve the container format name (e.g., 'mp4', 'mp3', 'matroska') from the input file. + Retrieve the container format name (e.g., 'mp4', 'mp3', 'mkv') from the input file. Args: input_file: Path to the media file. @@ -179,7 +179,7 @@ def get_container_format(input_file: str) -> Optional[str]: format_name = result.stdout.strip().split(',')[0] # take first if multiple if not format_name: return None - # Normalize common aliases to names used in FORMAT_INFO + # Normalize common aliases to names used in CONTAINER_INFO mapping = { 'mpeg': 'mp3', 'mp2': 'mp3', @@ -187,8 +187,8 @@ def get_container_format(input_file: str) -> Optional[str]: 'm4a': 'mp4', 'mov': 'mp4', '3gp': 'mp4', - 'matroska': 'matroska', - 'webm': 'matroska', + 'matroska': 'mkv', + 'webm': 'mkv', 'ogg': 'ogg', 'flac': 'flac', 'wav': 'wav', @@ -366,7 +366,7 @@ def _build_standard_mapping( def _build_format(cmd: List[str], format_opt: Optional[str]) -> None: """Add output format option if specified.""" if format_opt: - ffmpeg_format = FORMAT_INFO.get(format_opt, {}).get('ffmpeg', format_opt) + ffmpeg_format = next((c['ffmpeg'] for c in CONTAINER_INFO if c['name'] == format_opt), format_opt) cmd.extend(['-f', ffmpeg_format]) diff --git a/audio_splitter/formats.py b/audio_splitter/formats.py index 6260cd6..4d8939b 100644 --- a/audio_splitter/formats.py +++ b/audio_splitter/formats.py @@ -4,7 +4,7 @@ from typing import Dict, Optional from .constants import ( - FORMAT_INFO, + CONTAINER_NAMES, CONTAINER_INFO, CODEC_TO_CONTAINER_MAP, COMPATIBILITY_MATRIX, @@ -22,7 +22,7 @@ def determine_default_format(container: Optional[str], codec: Optional[str]) -> 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(). + container: Container name (e.g., 'ogg', 'mp4', 'mkv') as returned by get_container_format(). codec: Audio codec name (e.g., 'opus', 'aac', 'mp3') as returned by get_audio_codec(). Returns: @@ -33,10 +33,10 @@ def determine_default_format(container: Optional[str], codec: Optional[str]) -> if codec and codec in CODEC_TO_CONTAINER_MAP: fmt = CODEC_TO_CONTAINER_MAP[codec] - if fmt in FORMAT_INFO: + if fmt in CONTAINER_NAMES: return fmt - if container in FORMAT_INFO: + if container in CONTAINER_NAMES: return container return None @@ -67,14 +67,14 @@ def determine_output_format(stream_info: Dict, user_format: Optional[str], 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: + if fmt in CONTAINER_NAMES: return fmt except Exception: pass # Fallback if stream_info.get('has_video') or stream_info.get('has_subtitle'): - return 'matroska' + return 'mkv' audio_codec = stream_info.get('audio_codec', '') if audio_codec == 'mp3': return 'mp3' @@ -97,13 +97,13 @@ def validate_format_compatibility( Raises: ValueError: If the combination is incompatible. """ - info = FORMAT_INFO.get(container) + info = next((c for c in CONTAINER_INFO if c['name'] == container), None) 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: + if not info['supports_video'] 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)." diff --git a/web/backend/api/info.py b/web/backend/api/info.py index 3c5a609..385187b 100644 --- a/web/backend/api/info.py +++ b/web/backend/api/info.py @@ -9,7 +9,7 @@ from backend.services.task_manager import task_manager # Import core functions from audio_splitter.ffmpeg import get_stream_info, get_container_format, get_audio_codec from audio_splitter.formats import determine_default_format -from audio_splitter.constants import FORMAT_INFO +from audio_splitter.constants import CONTAINER_NAMES from audio_splitter.defaults import DEFAULT_FORMAT router = APIRouter(prefix="/api", tags=["info"]) @@ -63,7 +63,7 @@ async def get_recommended_format(task_id: str): # Fallback if detection fails or format is unsupported if fmt is None: fmt = DEFAULT_FORMAT - if fmt not in FORMAT_INFO: + if fmt not in CONTAINER_NAMES: fmt = "mp3" # ultimate fallback return {"format": fmt} diff --git a/web/frontend/src/hooks/useFormatValidation.ts b/web/frontend/src/hooks/useFormatValidation.ts index 17a015c..eb51112 100644 --- a/web/frontend/src/hooks/useFormatValidation.ts +++ b/web/frontend/src/hooks/useFormatValidation.ts @@ -16,7 +16,7 @@ export function useFormatValidation( ) { useEffect(() => { const selectedContainer = containers.find(c => c.name === options.container) - if (selectedContainer?.audio_only && hasVideo && !options.drop_video) { + if (!selectedContainer?.supports_video && hasVideo && !options.drop_video) { setFormatError( `Container '${options.container}' does not support video streams. Please enable "Drop video" or choose a container that supports video.` ) diff --git a/web/frontend/src/types/index.ts b/web/frontend/src/types/index.ts index 76437d2..b6c02cb 100644 --- a/web/frontend/src/types/index.ts +++ b/web/frontend/src/types/index.ts @@ -48,7 +48,6 @@ export interface ContainerInfo { name: string ffmpeg: string extension: string - audio_only: boolean supports_video: boolean supports_subs: boolean }