from __future__ import annotations from typing import Any from yuxi.utils.logging_config import logger def extract_media_metadata( filepath: str, mime_type: str | None = None, ) -> dict[str, Any]: result: dict[str, Any] = { "duration_ms": 0, "width": 0, "height": 0, "sample_rate": 0, "channels": 0, "bitrate": 0, "codec": "", } _try_mutagen(filepath, result, mime_type) return result def _try_mutagen(filepath: str, result: dict[str, Any], mime_type: str | None) -> None: try: from mutagen import File audio = File(filepath) if audio is None: return if hasattr(audio, "info"): info = audio.info result["duration_ms"] = int(getattr(info, "length", 0) * 1000) result["sample_rate"] = getattr(info, "sample_rate", 0) result["channels"] = getattr(info, "channels", 0) result["bitrate"] = getattr(info, "bitrate", 0) result["codec"] = _detect_codec(audio, mime_type) except ImportError: logger.debug("mutagen not installed, skipping audio metadata extraction") except Exception as e: logger.debug(f"Media metadata extraction failed: {e}") def _detect_codec(audio: Any, mime_type: str | None) -> str: if mime_type: parts = mime_type.split("/") if len(parts) > 1: return parts[1] audio_class = type(audio).__name__.lower() if "mp3" in audio_class or "mpeg" in audio_class: return "mp3" if "ogg" in audio_class or "vorbis" in audio_class: return "vorbis" if "flac" in audio_class: return "flac" if "wave" in audio_class or "wav" in audio_class: return "pcm" if "opus" in audio_class: return "opus" if "aac" in audio_class: return "aac" return audio_class def extract_duration_bytes(audio_data: bytes, mime_type: str = "audio/ogg") -> int: import struct if "wav" in mime_type and len(audio_data) > 44: try: sample_rate = struct.unpack_from(" 0: return int(data_size / byte_rate * 1000) except (struct.error, IndexError): pass if "mp3" in mime_type: return _estimate_mp3_duration(audio_data) if "ogg" in mime_type: return _estimate_ogg_duration(audio_data) return 0 def _estimate_mp3_duration(data: bytes) -> int: frame_sizes = {0: 0, 1: 0, 2: 0, 3: 0} return len(data) * 8 // (128000) * 1000 if len(data) > 0 else 0 def _estimate_ogg_duration(data: bytes) -> int: return len(data) * 8 // (64000) * 1000 if len(data) > 0 else 0