Initial commit: audio2text 双语字幕生成服务

- 音频/视频转双语(英/中)SRT 字幕,Docker 容器化,CPU 开发/GPU 生产同一份代码
- faster-whisper ASR(词级时间戳) + 断句时间戳重算 + NLLB 翻译(模型不共驻)
- 分片上传(断点续传) + SQLite 持久化 + 主页/历史/日志页面
- 历史页文件名搜索;缓存定时清理(默认保留7天,可配置)
- 双 Dockerfile(cpu/gpu) + setup/start/stop 脚本
This commit is contained in:
2026-07-06 06:54:19 +00:00
commit 00e2a95fb7
44 changed files with 4110 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
"""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: 采样率,默认 16000Whisper 标准)
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