- 音频/视频转双语(英/中)SRT 字幕,Docker 容器化,CPU 开发/GPU 生产同一份代码 - faster-whisper ASR(词级时间戳) + 断句时间戳重算 + NLLB 翻译(模型不共驻) - 分片上传(断点续传) + SQLite 持久化 + 主页/历史/日志页面 - 历史页文件名搜索;缓存定时清理(默认保留7天,可配置) - 双 Dockerfile(cpu/gpu) + setup/start/stop 脚本
65 lines
1.9 KiB
Python
65 lines
1.9 KiB
Python
"""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
|