/video-subtitle — 视频字幕工具
使用本地 Whisper 模型将视频音频转录为 SRT 字幕,然后将其嵌入视频中。
用户可调用
当用户输入 /video-subtitle 时,运行此技能。
先决条件
此技能需要两个工具。在继续之前检查可用性:
1. mlx-whisper (Apple Silicon) 或 openai-whisper
检测哪个可用:
which mlx_whisper whisper 2>/dev/null
如果两者都未找到,引导用户安装其中一个:
- Apple Silicon Mac (推荐):
pip install mlx-whisper - 任何平台:
pip install openai-whisper
2. 带 libass 的 ffmpeg
检查 libass 支持:
ffmpeg -filters 2>&1 | grep subtitles
如果没有输出(缺少 libass),则 subtitles 滤镜不可用:
- macOS:
brew install ffmpeg-full(仅 keg 模式,使用/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg) - Linux:
sudo apt install ffmpeg(通常包含 libass) - 备用方案: 如果用户有常规的不带 libass 的 ffmpeg,则将 SRT 作为字幕轨道软嵌入,而不是烧录。
Whisper 模型选择
发现缓存的模型:
ls ~/.cache/huggingface/hub/ 2>/dev/null | grep whisper
优先使用最大的可用模型。如果未缓存任何模型,则使用:
- mlx-whisper:
mlx-community/whisper-large-v3-mlx - openai-whisper:
large-v3
模式
/video-subtitle path/to/video.mov→ 完整流水线(转录 + 修正 + 烧录)/video-subtitle srt path/to/video.mov→ 仅 SRT(转录,不烧录)/video-subtitle burn path/to/video.mov path/to/sub.srt→ 仅烧录(使用现有 SRT)/video-subtitle(无参数)→ 交互式,询问视频路径
完整流水线
第 1 步:验证视频
如果未提供路径,使用 AskUserQuestion 询问。确认文件存在并读取元数据:
ffmpeg -i "<video_path>" 2>&1 | grep -E "Duration|Stream.*(Video|Audio)"
向用户报告时长、分辨率和文件大小。
第 2 步:提取音频
ffmpeg -i "<video_path>" -vn -acodec pcm_s16le -ar 16000 -ac 1 "<output_dir>/<basename>_audio.wav" -y
16kHz 单声道 WAV 是 Whisper 的最佳输入格式。
第 3 步:确认语言
使用 AskUserQuestion 询问用户。常见选项:
| 语言 | 标志 |
|---|---|
| 中文 | --language zh |
| 英语 | --language en |
| 日语 | --language ja |
| 自动检测 | 省略 --language |
第 4 步:转录
mlx-whisper:
mlx_whisper --model "<model>" --language <lang> --output-format srt --output-dir "<output_dir>" "<audio_wav>"
openai-whisper:
whisper "<audio_wav>" --model large-v3 --language <lang> --output_format srt --output_dir "<output_dir>"
告诉用户:“正在转录,请稍候...”
第 5 步:清理 SRT
读取生成的 SRT 并修复:
- 移除幻觉:删除零时长时间戳(例如
00:00:57,640 --> 00:00:57,640)或空文本的条目 - 修正专有名词:Whisper 常误识别品牌名称和技术术语 — 使用上下文纠错
- 合并片段:合并时间短于 0.5 秒且语义上相连的条目
- 重新编号:运行此 Python 片段重新编号所有条目:
python3 -c "
import re
with open('<srt_path>', 'r') as f:
content = f.read()
blocks = re.split(r'\n\n+', content.strip())
out = []
for i, block in enumerate(blocks, 1):
lines = block.strip().split('\n')
if len(lines) >= 2:
lines[0] = str(i)
out.append('\n'.join(lines))
with open('<srt_path>', 'w') as f:
f.write('\n\n'.join(out) + '\n')
"
向用户展示修正后的 SRT,并在烧录前询问是否需要进一步编辑。
第 6 步:烧录字幕
确定正确的 ffmpeg 二进制文件(如果常规 ffmpeg 缺少 libass,则优先使用 ffmpeg-full):
FFMPEG="ffmpeg"
if ! ffmpeg -filters 2>&1 | grep -q subtitles; then
if [ -x /opt/homebrew/opt/ffmpeg-full/bin/ffmpeg ]; then
FFMPEG="/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg"
fi
fi
然后烧录:
$FFMPEG \
-i "<video_path>" \
-vf "subtitles=<srt_path>:force_style='FontSize=24,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2,MarginV=30'" \
-c:v libx264 -preset medium -crf 23 \
-c:a aac -b:a 128k \
-pix_fmt yuv420p \
"<output_dir>/<basename>_subtitled.mp4" -y
force_style 选项:
| 参数 | 默认值 | 描述 |
|---|---|---|
| FontSize | 24 | 字体大小 |
| PrimaryColour | &H00FFFFFF | 白色文本 |
| OutlineColour | &H00000000 | 黑色轮廓 |
| Outline | 2 | 轮廓厚度 |
| MarginV | 30 | 底部边距 |
重要提示: SRT 文件路径不得包含会混淆 ffmpeg 滤镜解析器的特殊字符。必要时使用相对路径或符号链接。
第 7 步:完成
报告输出文件路径和大小。建议使用 open <path> 预览。提醒用户中间文件 _audio.wav 可以删除。
故障排除
| 问题 | 原因 | 修复 |
|---|---|---|
No such filter: 'subtitles' | ffmpeg 缺少 libass | 使用 ffmpeg-full 或安装 libass |
No such filter: 'drawtext' | ffmpeg 缺少 libfreetype | 使用 ffmpeg-full |
| 重复/空 SRT 条目 | Whisper 幻觉 | 在第 5 步清理 |
| 中文字符显示为方框 | 缺少 CJK 字体 | macOS:自动回退到 PingFang。Linux:apt install fonts-noto-cjk |
Error parsing filterchain | SRT 路径中的特殊字符 | 使用相对路径,避免空格 |
质量规则
- 忠实转录:只转录实际所说的内容 — 不要添加或改写含义
- 专有名词修正:修正常见的 Whisper 误识别,但需与用户确认
- 字幕节奏:每个条目最多两行,每行最多约 20 个 CJK 字符
- 清理:完成后提醒用户删除中间文件(_audio.wav)