61 lines
1.7 KiB
Python
61 lines
1.7 KiB
Python
"""
|
||
音频时长探测工具
|
||
================
|
||
|
||
基于 mutagen,支持从内存或文件路径探测音频时长。
|
||
不依赖 ffprobe,纯 Python 实现。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import io
|
||
import logging
|
||
|
||
import httpx
|
||
from mutagen.mp3 import MP3
|
||
from mutagen.wave import WAVE
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
async def get_audio_duration(url: str, timeout: float = 10.0) -> float:
|
||
"""
|
||
探测远程音频文件的时长(秒)。
|
||
|
||
下载音频到内存后,用 mutagen 读取文件头获取时长。
|
||
不写入磁盘,全部在内存中完成。
|
||
|
||
:param url: 音频文件 URL(需可访问)
|
||
:param timeout: HTTP 下载超时(秒)
|
||
:return: 音频时长(秒,float)
|
||
:raises ValueError: 无法解析时长
|
||
"""
|
||
async with httpx.AsyncClient() as client:
|
||
resp = await client.get(url, timeout=timeout)
|
||
resp.raise_for_status()
|
||
data = io.BytesIO(resp.content)
|
||
|
||
# 根据文件头判断格式
|
||
header = data.read(12)
|
||
data.seek(0)
|
||
|
||
try:
|
||
if header[:3] == b"ID3" or header[:2] == b"\xff\xfb" or header[:2] == b"\xff\xf3":
|
||
audio: MP3 | WAVE = MP3(data)
|
||
elif header[:4] == b"RIFF":
|
||
audio = WAVE(data)
|
||
else:
|
||
# fallback:先尝试 MP3(大多数 TTS 返回 mp3)
|
||
audio = MP3(data)
|
||
|
||
if audio.info is None:
|
||
raise ValueError("音频信息解析失败")
|
||
duration = audio.info.length
|
||
if duration is None or duration <= 0:
|
||
raise ValueError("音频时长解析失败")
|
||
return float(duration)
|
||
|
||
except Exception as e:
|
||
logger.error(f"探测音频时长失败: url={url[:60]}..., error={e}")
|
||
raise ValueError(f"无法解析音频时长: {e}") from e
|