name: captions description: 此技能在用戶要求生成字幕或畫面文字時使用 - "brenn die Untertitel ein", "mach Untertitel", "Untertitel drauf", "leg Untertitel drüber", "brenn die Subs ein", "mach die Captions", "Wort für Wort Untertitel", "Karaoke-Untertitel", "korrigier den Untertitel-Text", "burn in the captions", "add subtitles", "burn the subs", "word-by-word captions", "fix a caption typo"。涵蓋 ASS 生成、德文的像素寬度分組規則、字體表,以及透過 libass 進行字幕燒錄。 argument-hint: [專案代碼] [預設名稱] arguments: [project, preset]
captions
根據逐字轉錄稿生成 ASS 字幕檔案,並將其燒錄到渲染輸出中。
參數:$project 專案代碼,$preset 來自 presets/captions/*.json 的預設名稱。完整的請求內容在
$ARGUMENTS 中。空的 $ 佔位符表示技能是自動觸發而非透過斜線指令呼叫:此時應從工作階段的 PIPELINE_STATE 中取得專案,並告知你選擇了哪一個。動詞名稱遵循建置合約;以 python engine/ve.py --help 為最終準則。
兩個決定此功能是否正常運作的德文特定規則
1. 切勿使用 max_chars 進行分組
德語複合名詞是無法分割的單一詞元,會超出任何字符數限制。
Geschwindigkeitsbegrenzung 有 26 個字符,在 1920 幀寬度、90 px 字體大小下約佔 60% 的寬度。max_chars: 22 的規則無法拆分它,導致它要麼超出畫面邊框,要麼分組器默默地產生一個它承諾不產生的行。
應改為依據 像素寬度預算 進行分組,並對單獨超過預算的單詞設置自動縮小規則。estimate_text_width_px() 負責測量;group_words() 負責預算分配。詳細資訊位於 docs/fonts.md 和 engine/vedit/captions.py 的模組文件字串中。
這是德文字幕最常見的錯誤,且完全是可以避免的。
2. 字體必須是靜態的單一粗細檔案,並以其精確的 nameID 1 命名
可變字體無法在 ASS 中選擇粗細。InterVariable.ttf 透過 libass 渲染時,其位元組與 Arial 完全一致 - 擁有相同的 md5 - 同時只記錄它載入了該檔案。
退出碼為 0,沒有任何警告。Style: D,Inter,...,Bold=-1 會讓你得到 DirectWrite 的 Arial,在輸出檔上傳到 YouTube 之前沒有人會注意到。
僅使用下列字體,並使用精確的 Fontname 字串和 Bold 值:
| 檔案 | ASS Fontname | Bold |
|---|---|---|
Inter-Regular.ttf | Inter | 0 |
Inter-Bold.ttf | Inter | -1 |
Inter-SemiBold.ttf | Inter SemiBold | 0 |
Inter-ExtraBold.ttf | Inter ExtraBold | 0 |
Montserrat-Bold.ttf | Montserrat | -1 |
Montserrat-ExtraBold.ttf | Montserrat ExtraBold | 0 |
assets/fonts/ 目錄僅應包含字體檔案。 libass 會載入在其中找到的每個檔案,並對任何其他檔案報錯 - 一個多餘的 README.md 會產生 Error opening memory font 'README.md' 錯誤。
基於這個原因,該目錄在設定中屬於 ask 權限;如果文檔需要與字體放在一起,應放在 docs/fonts.md 中。
步驟 1 - 選擇預設
presets/captions/*.json 包含樣式、分組預算、位置和高亮行為。
根據 output.platform 選擇,而不是個人喜好:
- 垂直平台需要大尺寸、置中、1-3 個單詞,並位於安全區域的高處 - 底部三分之一被平台自身 UI 佔據。
- YouTube 長片需要兩行、底部錨定,尺寸需讓手機用戶能閱讀。
在生成前向用戶確認選擇,並說明哪些內容會變更。
步驟 2 - 生成 ASS 檔案
python engine/ve.py captions $project --preset $preset
兩個結構性要求,違反時都會默默失敗:
- 23 欄位的 V4+
Style: Format:行(從Name到Encoding,libass 自己的欄位列表 - 第 24 個欄位AlphaLevel是 SSA v4,而非 ASS V4+)以及 10 欄位的 EventsFormat:行必須是標準的,並且各自下方的資料行必須有完全相同的欄位數量。欄位數量不匹配會導致 libass 以退出碼 0 結束,在渲染的文字前方插入一個多餘的逗號,並默默地移除輪廓和粗體效果。 已透過 5 欄位的 Format 對應 6 欄位的 Dialogue 重現此問題。captions.validate_ass()在任何燒錄前會檢查這兩個計數;它不可省略,且burn_in()在檢查失敗時會拒絕執行。 - 檔案必須是 UTF-8 編碼。在該編碼下,德語變音符號可以透過 libass 正確地來回轉換 -
Größe Straße Übermäßig äöüÄÖÜß已透過端到端測試驗證。
側車檔案寫入 projects/<slug>/out/captions.ass,並從 captions.sidecar_path 引用。
時間碼來自與剪輯相同的詞彙列表,並透過 _computed 中的時間軸位置進行映射。因此,任何剪輯變更都會導致字幕失效 - 請在重新剪輯後重新生成,而非手動修補時間碼。
步驟 3 - 修正文字,而非轉錄稿
數字是可預測的問題。轉錄時必須使用 --suppress_numerals,因此當說話者說「20」時,轉錄稿會是「zwanzig」。應在
captions.overrides[] 中將其修正為 {word_index, text},切勿透過編輯轉錄稿來修正 - 轉錄稿是專案中所有詞彙範圍的錨點,重寫它會導致剪輯不同步。
專有名詞、產品名稱和「GmbH」風格的縮寫也應如此處理。透過掃描低 score 詞彙一次性收集它們:對齊分數作為填充詞信號用處不大,但它是「對齊器不確定這個詞是什麼」的良好信號,這正是一個專有名詞的特徵。
步驟 4 - 燒錄字幕
python engine/ve.py render $project
字幕燒錄在最終的封裝階段進行,只做一次,作用於拼接後的完整影片 - 絕對不在每個片段單獨燒錄。逐片段燒錄會在每個拼接點重新渲染相同的字幕,導致剪輯兩側出現不完整的行。
subtitles= 和 fontsdir= 的跳脫方式是精確且不直觀的。處處使用正斜線,並且數值需要用帶有單一跳脫磁碟冒號的引號括起來:
subtitles='C\:/Users/me/.../out/captions.ass':fontsdir='C\:/Users/me/.../assets/fonts'
vedit/ffmpeg.py 中的 escape_filter_path() 負責此轉義並返回包含其引號的字串 - 請勿自行添加引號。決定成敗的是引號,而不是巢狀深度:裸的單一跳脫形式會產生 No option name near '/Users/...' 錯誤,只有帶引號的形式才能在路徑中包含逗號、分號或括號時正常工作,這些字元在 Windows 上都是合法的。
escape_filter_path_nested() 為需要的人返回雙重反斜槓、不帶引號的 C\\:/... 變體,並記錄了該限制。
所有這些都通過 Python 的 argv 列表傳遞。同樣的路徑在 Git Bash 中建置會變成 C\;C:\Program Files\Git\Users\... - 這是現場重現的,而非理論假設。
設定 captions.enabled 和 captions.burn_in,並將 stage 設置為 "captions"。
步驟 5 - 證明字體已正確載入
QC 檢查 (e) 是強制的,這是捕捉靜默回退的唯一方法。 使用專案樣式渲染一個字幕幀,並使用 Arial 對照樣式渲染另一個幀,然後斷言兩者的雜湊值不同。相同的雜湊值意味著 libass 已回退,輸出將使用錯誤的字體。
然後也確認:Format: 行的欄位計數、沒有行超出像素預算、以及沒有字幕跨越片段邊界而留下殘片。
失敗模式
| 症狀 | 原因 | 修正方法 |
|---|---|---|
| 字幕看起來像 Arial | 可變字體,或錯誤的 Fontname | 使用靜態檔案 + 精確的 nameID 1 字串;執行 QC (e) |
| 文字中出現多餘逗號,無輪廓 | 錯誤的 Format: 行 | 輸出標準的 24/10 欄位行 |
Error opening memory font 'README.md' | assets/fonts/ 中有非字體檔案 | 該目錄僅放字體 |
| 一個長單詞超出畫面邊框 | 使用 max_chars 分組 | 使用像素寬度預算加上單詞自動縮小 |
No option name near '/Users/...' | fontsdir 使用了單一跳脫 | 在冒號前使用雙反斜槓 |
| 字幕拼寫出數字 | --suppress_numerals,為設計行為 | 在 captions.overrides 中新增條目 |
| 重新剪輯後字幕不同步 | 舊的 ASS 檔案 | 重新生成;切勿手動修補時間碼 |