展示影片字幕工具
問題
螢幕錄製的產品展示影片常因三個與產品本身無關的原因而未能觸及目標觀眾:UI 文字語言錯誤、字體太小難以閱讀,以及影片通常完全沒有旁白(靜音錄製)。觀眾看著操作發生,卻無法理解其意義。字幕可以解決這個問題——但前提是字幕的樣式必須在雜亂的 UI 上清晰可讀、放置的位置不能遮擋正在示範的內容,而且時間點必須準確,不會被轉場效果截斷。
此技能是一個完整的流程管線,並涵蓋那些若非親身經歷、往往會耗費數小時才能發現的非顯而易見的失敗模式。
使用時機
- 「為這段展示影片加上字幕」/「為我們的韓國團隊為此錄影加入說明文字」
- 靜音螢幕錄影(.mp4 格式,無音軌,或僅有背景音樂的音軌),其故事必須完全透過字幕文字來傳達
- 僅有英文的產品展示,需呈現給非英語觀眾
- 已有字幕,但置中於底部且遮擋了展示面板——需要改為跟隨焦點的位置
- 為將在影片上進行解說的簡報者產生閱讀腳本(請參閱「搭配使用」)
請勿將此技能用於:使用合成語音進行配音(那是 TTS 管線),或擷取平台上已存在的字幕。
安裝(單一指令)
scripts/bootstrap.sh --dry-run # 僅檢查:印出確切缺少的項目
scripts/bootstrap.sh # 安裝缺少的項目(會詢問一次)
scripts/bootstrap.sh --yes --lang ja # 自動安裝;同時準備日文字型
bootstrap.sh 是冪等的——在已經準備好的機器上不會做任何事。它會偵測平台並選用正確的安裝工具(Homebrew / apt / dnf / pacman / winget / scoop),若無可用的套件管理器,則會退而將靜態 ffmpeg 放入 ~/.local/bin。請在任何需要燒錄字幕的工作階段中,將其作為第一個動作執行;技能沒有安裝階段的掛鉤,因此這個腳本就是安裝程式。
先決條件
請先執行內建檢查——它會檢查機器並告訴您確切缺少什麼:
scripts/doctor.sh "Hiragino Sans GB" # 傳入您打算使用的 CJK 字型家族名稱
檢查項目,以及缺少時的安裝指令:
| 需求 | 原因 | 安裝指令 |
|---|---|---|
ffmpeg 需以 libass 編譯 | 用於燒錄 .ass 字幕(-vf ass=...) | macOS brew install ffmpeg;Windows:gyan.dev 完整版本;Debian apt install ffmpeg |
ffmpeg 編譯中需包含 --enable-fontconfig | 可依字型家族名稱解析字型;否則會無聲地使用替代字型 | 與上述相同版本的編譯皆已包含 |
| 系統可解析的 CJK 字型家族 | 字幕文字本身 | macOS:Hiragino Sans GB(預先安裝)· Windows:Microsoft YaHei(預先安裝)· Linux:apt install fonts-noto-cjk |
python3(可選) | 僅供 export_cues.py 使用;使用標準函式庫,無需 pip 安裝 | macOS:xcode-select --install;Linux:apt install python3 |
bash、zip(可選) | 用於腳本及分發打包 | 所有平台皆已預先安裝 |
無需 Node、無需 ImageMagick、無需 pip 套件、無需 API 金鑰。具有視覺能力的模型(即代理本身)負責讀取影格——這並非需要安裝的項目。
兩個需要注意的環境事項:
- 將
ffmpeg加入PATH,或將它的位置匯出給腳本使用:FFMPEG=/path/to/ffmpeg scripts/burn.sh ...。 - Windows:libass 會根據編譯版本透過 fontconfig/DirectWrite 解析字型。如果字幕以錯誤的字體顯示,請安裝 fontconfig(
scoop install fontconfig或使用 MSYS2),並以fc-match "Microsoft YaHei:bold"重新檢查。
管線流程
請由上至下依序進行。請勿跳過階段 2——後續每個階段都依賴於了解 UI 幾何結構。
階段 1 — 盤點(2 分鐘)
scripts/probe.sh INPUT.mp4
記錄:時長、解析度、FPS、碼率、是否存在音軌。在腳本內執行音訊檢查(平均音量 + 靜音映射)。
根據音訊檢查結果的決定:
| 音訊發現 | 字幕類型 |
|---|---|
| 完全無音軌 | 敘述性字幕 — 由您編寫故事(「發生了什麼事 / 為什麼重要」) |
| 有音訊但無語音(僅音樂) | 同上。可透過轉錄一段 10 秒的音訊片段來確認:一個 ASR 工具在吵雜音訊上回傳空白或近乎空白的結果,表示是音樂而非語音 |
| 有語音出現 | 對話式字幕 — 先附時間戳記轉錄,然後再進行清理;不需自行編寫內容 |
階段 2 — 視覺分析與幾何結構(人們常常跳過的階段)
以 2 FPS 提取影格(60 秒片段約產生 120 幀;先每 4 幀檢視一次以掌握整體脈絡,再放大檢視文字區域):
scripts/split_frames.sh INPUT.mp4 frames/ 2
在編寫任何一句字幕前,先建立三份成果:
-
節拍表 —
| 時間 | 畫面上改變了什麼 | 螢幕文字(逐字) |。一個「節拍」代表任何改變:通知出現、面板滑入、按鈕被點擊、發生滾動、頁面切換、轉場。請注意轉場類型(白色閃爍、直接切換、變黑)——字幕不可跨越它們。 -
UI 文字(逐字),以原始解析度讀取。縮小的預覽影格會讓較小的 UI 文字模糊,導致轉錄錯誤。請從原始檔案中裁切:
ffmpeg -i IN.mp4 -ss 12.5 -frames:v 1 -vf "crop=W:H:X:Y,scale=2*iw:-1" out.jpg(-ss置於-i之後以精確定位影格;請參閱陷阱。) -
幾何映射圖 — 以來源像素為單位,測量「即時」面板(正在展示的內容)的像素邊界。請實際測量,不要僅憑目測:
scripts/find_edge.sh INPUT.mp4 30 2880 300 # t=30s,從 x=2880 開始擷取一條 300px 寬的垂直條然後讀取回傳的條狀圖像,注意面板左邊緣落在條狀圖內的位置;將該偏移量加上條狀圖的 x 座標,即可得到絕對邊界。若後續場景的佈局發生變化(例如稍後開啟了一個聊天面板),請為其他場景重複此步驟。
由幾何映射圖得出的規則: 字幕應位於展示面板相鄰的自由區域內,絕不覆蓋在其上方。請參閱階段 5。
階段 3 — 編寫字幕稿
有兩項規則主導一切:
- 每個提示一個想法。 最多 2–3 行短文。一個提示是一個句子,而非段落。
- 閱讀速度預算。 中日韓文(CJK):舒適範圍為每秒 3.5–5.5 個字元;超過 6 會感到急促;低於 3 則覺得冗長。拉丁文:每秒 12–18 個字元。請計算,不要猜測。
scripts/export_cues.py會在您有了.ass檔案後,印出每個提示的字元密度——請根據這個數字進行迭代調整。
提示時間規則:
- 提示應在視覺節拍出現時開始,而非之前。如果一個通知在 1.0 秒時滑入,提示就應在 1.0 秒開始。
- 提示應在轉場(白色閃爍 / 直接切換)之前結束,並在轉場期間至少保留 0.8 秒的空白。跨越轉場的字幕看起來像一個錯誤。
- 保留真正的間隔。每分鐘留下三到五秒的空白,能讓影片有呼吸感;從頭到尾塞滿字幕會讓觀眾疲憊。
- 對於敘述性字幕,請將原因放入字幕,而不是機械描述:「代理器自行發起聯繫」勝過「右上角出現了一個通知」。
請在此處暫停,讓稿件獲得核准。 文字被核准後再進行樣式設定。樣式設定完成後才修改文字,只會導致一次無謂的重新渲染。
階段 3b — 選擇字幕語言
此管線與語言無關,但在進行樣式設定之前,每種語言都必須決定以下三件事。
1. 字型家族 — 選擇平台能夠解析的字型(使用 scripts/doctor.sh <lang> 驗證):
| 字幕語言 | macOS | Windows | Linux |
|---|---|---|---|
| 中文 (zh) | Hiragino Sans GB / PingFang SC | Microsoft YaHei | Noto Sans CJK SC |
| 日文 (ja) | Hiragino Sans | Yu Gothic / Meiryo | Noto Sans CJK JP |
| 韓文 (ko) | Apple SD Gothic Neo | Malgun Gothic | Noto Sans CJK KR |
| 拉丁文 (en/fr/de/es…) | Helvetica Neue | Segoe UI | Inter / DejaVu Sans |
2. 閱讀速度 — 預算依書寫系統而異;scripts/export_cues.py 會自動選擇正確的規則(--lang 可覆蓋),並標記超出預算的提示:
| 書寫系統 | 舒適範圍 | 標記為過快 |
|---|---|---|
| 漢字 / 假名 (zh, ja) | 3.5–5.5 字元/秒 | > 6.0 |
| 諺文 (ko) | 4.0–6.0 字元/秒 | > 6.5 |
| 拉丁文 | 12–18 字元/秒 | > 19 |
3. 換行規則 — 中文和日文沒有詞間空格:請在標點符號處(U+FF0C、U+3001、U+3002、U+FF1A)斷行,絕不拆開產品名稱。韓文和拉丁文在空格處斷行;韓文的助詞請保持與其名詞相連。如果一行放不下,請在縮小字型前先重新換行——較小的字型比多一行文字更容易損失可讀性。
4. 按書寫系統估算行寬 — 不同書寫系統的字寬不同,因此相同的 Fontsize 會產生不同的行長度。請以 floor(available_px / (fontsize * k)) 預估行數,其中 k 對漢字約為 1.0,對諺文和假名約為 1.05–1.1(它們設定得較寬),而對拉丁文平均約為 0.5(會隨單字變化)。
以 4K 解析度和 Fontsize 165 為例:13 個韓文字元約為 2,150 像素(在 2,450 像素的空間中安全),而 18 個字元約為 2,970 像素——這會溢出並將該行推出畫面的左邊緣。當一行放不下時,請重新換行;不要為了讓它放進去而縮小字型。
雙語字幕(原文 + 翻譯):僅限於兩行文字都短的時候。兩種語言的行數會使閱讀負擔加倍,而且在展示中會與 UI 競爭。除非客戶另有要求,否則偏好每個檔案版本使用一種語言;若必須使用雙語,請將原文疊在上方、翻譯放在下方,並將整個區塊的預算設定為翻譯語言的閱讀速度。
從右至左書寫的語言(阿拉伯語、希伯來語):libass 支援 RTL,但字型塑形品質取決於編譯版本中的 harfbuzz。在提交前請先渲染一個預覽影格進行檢查——確認字母正確連接,且對齊錨點仍然合理。
字體粗細因書寫系統而異。 日文和韓文字型家族在相同的 Fontsize 下,通常看起來比中文字型家族視覺上更大;請透過一個預覽影格進行驗證,並根據視覺效果調整,而非依照規則。
階段 4 — 樣式(雙層光暈)
單一的 ASS 樣式無法產生「帶有厚實彩色輪廓和柔和彩色光暈的白色文字」——Outline 只能繪製一個環。請在兩個圖層上,為每個提示使用兩個事件:
| 圖層 | 填色 | 輪廓 | 模糊 | 目的 |
|---|---|---|---|---|
| 0 | 透明 (&HFF000000) | 寬、半透明紫色 | \blur 20–30 | 光暈 |
| 1 | 白色 | 較窄、實心紫色 | 無 / \blur1 | 可讀文字 |
起點(請參閱 assets/caption-template.ass)。顏色格式為 &HAABBGGRR——Alpha 00 為不透明,FF 為完全透明,且位元組順序為 BGR:
紫色 RGB(124,46,240) → &H00F02E7C
光暈,35% 不透明 → &H55F02E7C
請根據 PlayRes 縮放,而不是憑感覺:一個 4K 渲染使用了 fontsize 180 / outline 16 / halo 28 / blur 24;在 1080p 下達到相同外觀則為 80 / 7 / 12 / 12。將 PlayResX/Y 設為輸出解析度,並保持 ScaledBorderAndShadow: yes。
階段 5 — 跟隨焦點的放置位置
對展示影片來說,置中底部是錯誤的:底部通常是 UI 區域,而展示面板是唯一必須保持可見的部分。將每個提示錨定在正在顯示的內容相鄰的自由區域:
| 此提示的焦點 | 錨點 | 原因 |
|---|---|---|
| 側邊面板 / 聊天面板開啟 | \an6\pos(<面板左側 - 40>, <面板垂直中>) | 右邊緣緊貼面板,區塊向左側生長,寬度改變時無需重新計算 |
| 右上角的快訊/通知 | \an3\pos(<通知右側>, <通知底部 + 80>) | 右下角錨點將區塊正好置於通知下方 |
| 全寬頁面,無面板 | \an5\pos(<可視中心_x>, <兩個內容區塊之間的 y>) | 置中,但放置於測量出的空隙中——而非幾何中心 |
| 帶有大型內建文字的標題卡 | \an5\pos(中心_x, 標題下方) | 幾何中心會與影片本身的標題衝突 |
在開始書寫前,先計算最大行寬:max_line_px ≈ fontsize × 字元數。如果一行會超過自由區域,請重新換行(絕不要只為了一個提示而縮小字型)——在標點處斷行,並保持產品名稱完整不拆分。
對於右側錨定的區塊,溢出一、二十個像素到平坦背景上是可接受的,而且看起來像是有意為之;溢到展示面板上則不行。
讓錨點遠離畫面邊緣。 輪廓和光暈會超出字型框大約 outline + blur 像素(在 4K 解析度、outline 15 / blur 22 下,約為 40 像素,尚需加上抗鋸齒)。因此,一個距離右邊緣 50 像素的 \an3 錨點,其光暈會被裁切,看起來像一個破損的單字。請將錨點設在距離畫面邊緣至少 150 像素的位置,並在預覽影格中檢查結果——裁切在 .ass 檔案中是不可見的,但在螢幕上卻很明顯。
階段 6 — 燒錄
scripts/burn.sh INPUT.mp4 subs.ass OUT.mp4 [crf]
此指令等同於 -i IN -vf "ass=subs.ass" -c:v libx264 -crf 17~18 -pix_fmt yuv420p -movflags +faststart,並保留原始音軌。對於展示影片而言,內嵌式(硬)字幕是正確的預設選項——它們能在所有播放器、上傳平台和螢幕分享中倖存;僅在有人要求時,才隨附一個 .ass/.srt 檔案。
階段 7 — 驗證
先渲染便宜的靜止幀,然後驗證編碼後的檔案:
scripts/preview_frame.sh INPUT.mp4 subs.ass 16.0 /tmp/check1.jpg
檢查每個影格是否有:文字覆蓋於展示面板、文字超出畫面、意外的換行、應該已結束但未結束的提示、在明亮背景上變成一團模糊的光暈。然後從已完成的檔案中重新提取 3 個影格(字幕已燒入,品質已是最終狀態),並再次確認。
陷阱(每個都曾耗費真實時間才發現)
-ss置於-i前方會使ass濾鏡失效。 輸入定位會將時間戳記重設為零,因此濾鏡會為每個影格渲染 t≈0 時的字幕狀態。結果:影片燒錄完成但完全沒有字幕,而且 ffmpeg 不會印出任何錯誤。請務必將-ss置於-i之後(輸出定位),或加上-copyts。已捆綁的腳本已預設這麼做。- 預覽影格的座標會騙人。 一個縮放至 1280×720 的影格,但顯示給您看時……(原文遭截斷)