ForcePilot/backend/package/yuxi/channels/adapters/matrix/media_meta.py
Kris 2063145ce1 feat(matrix): 新增完整的Matrix适配器工具集
新增了20+个Matrix相关工具模块,包含线程处理、自动加入、消息去重、配置管理、安全策略、媒体处理、命令解析等功能,完善Matrix适配器基础能力
2026-05-12 00:45:54 +08:00

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