"""ffmpeg 服务:从视频/音频文件提取 16kHz 单声道 PCM wav。 16kHz mono PCM 正是 Whisper 的标准输入,省去模型内重采样。 """ from __future__ import annotations import logging import shutil import subprocess from pathlib import Path logger = logging.getLogger("audio2text.ffmpeg") class FFmpegError(RuntimeError): pass def extract_audio( src: Path, out_wav: Path, sample_rate: int = 16000, ) -> Path: """提取音频为 16kHz 单声道 PCM wav。 Args: src: 输入视频/音频文件 out_wav: 输出 wav 路径 sample_rate: 采样率,默认 16000(Whisper 标准) Returns: out_wav 路径 Raises: FFmpegError: ffmpeg 不可用或提取失败 """ if shutil.which("ffmpeg") is None: raise FFmpegError("ffmpeg 未安装;容器内应通过 apt 装好。") if not src.is_file(): raise FFmpegError(f"源文件不存在:{src}") out_wav.parent.mkdir(parents=True, exist_ok=True) # -vn 去视频;-ac 1 单声道;-ar 16k 采样率;-c:a pcm_s16le 16bit PCM cmd = [ "ffmpeg", "-y", "-loglevel", "error", "-i", str(src), "-vn", "-ac", "1", "-ar", str(sample_rate), "-c:a", "pcm_s16le", str(out_wav), ] logger.debug("提取音频:%s -> %s", src.name, out_wav.name) logger.debug("ffmpeg 命令:%s", " ".join(cmd)) try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=3600) except subprocess.TimeoutExpired as exc: raise FFmpegError(f"ffmpeg 超时(>1h):{src}") from exc if result.returncode != 0: raise FFmpegError( f"ffmpeg 失败 (code={result.returncode}): {result.stderr.strip()[:500]}" ) if not out_wav.is_file(): raise FFmpegError(f"ffmpeg 未生成输出文件:{out_wav}") return out_wav