102 lines
2.8 KiB
Python
102 lines
2.8 KiB
Python
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("<I", audio_data, 24)[0]
|
|
byte_rate = struct.unpack_from("<I", audio_data, 28)[0]
|
|
data_size = struct.unpack_from("<I", audio_data, 40)[0]
|
|
if byte_rate > 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
|