from __future__ import annotations import logging logger = logging.getLogger(__name__) SUPPORTED_AUDIO_FORMATS = frozenset({ "aac", "aiff", "alac", "amr", "caf", "flac", "m4a", "mp3", "oga", "wav", "webm", "wma", }) NATIVE_OPUS_EXTS = frozenset({".opus", ".ogg"}) FFMPEG_OPUS_CMD = ( "ffmpeg -i {input_path} -vn -ar 48000 -ac 1 -c:a libopus -b:a 64k {output_path}" ) def is_native_opus(filename: str) -> bool: ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else "" return f".{ext}" in NATIVE_OPUS_EXTS def requires_transcoding(filename: str) -> bool: ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else "" return ext in SUPPORTED_AUDIO_FORMATS and not is_native_opus(filename) def build_transcode_command(input_path: str, output_path: str) -> str: return FFMPEG_OPUS_CMD.format(input_path=input_path, output_path=output_path) async def transcode_to_opus( input_path: str, output_path: str, max_duration_secs: int = 300, ) -> dict: import asyncio if not requires_transcoding(input_path): return {"success": True, "path": input_path, "transcoded": False} cmd = build_transcode_command(input_path, output_path) try: proc = await asyncio.create_subprocess_shell( cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await asyncio.wait_for( proc.communicate(), timeout=max_duration_secs + 10 ) if proc.returncode == 0: return {"success": True, "path": output_path, "transcoded": True} else: error_msg = stderr.decode("utf-8", errors="replace")[:500] logger.error("FFmpeg transcoding failed: %s", error_msg) return {"success": False, "error": error_msg, "path": None, "transcoded": False} except asyncio.TimeoutError: return {"success": False, "error": "Transcoding timed out", "path": None, "transcoded": False} except FileNotFoundError: return {"success": False, "error": "ffmpeg not found in PATH", "path": None, "transcoded": False} except Exception: logger.exception("Voice transcoding error") return {"success": False, "error": "Internal error during transcoding", "path": None, "transcoded": False}