/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. ffmpeg 搭配 libass
檢查 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 個中日韓字元
- 清理環境:完成後提醒使用者刪除中間檔案(_audio.wav)