書譯
書譯

如何翻譯 ASS/SSA 字幕而不破壞樣式、位置或卡拉 OK 效果

只翻譯 .ass 檔案中可見的對白,然後檢查時間軸、標籤、卡拉 OK 和字型。內含範例檔案、免費檢查工具,以及 9 次本機 LLM 測試。

BookTranslator

BookTranslator Team

19 min read

ASS 或 SSA 字幕檔就像一支小程式:包含標頭、樣式表,以及一連串帶有時間碼的事件,每個事件的最後一個欄位混合了對白與格式代碼。翻譯時,只更動該欄位中可讀的文字,其餘部分保持位元組完全不變,然後檢查三件翻譯工具不會幫你處理的事:字型、間距代碼和卡拉 OK 行。

我們用本機模型將一個包含 14 個事件的範例翻譯成簡體中文九次,使用三種不同的提示詞,並用我們撰寫的腳本檢查每個結果。時間軸、位置、顏色、註解和繪圖在九次結果中都完好無損。出問題的是檔案中假設使用英文的部分:在全部九次結果中,樣式字型都沒有中文字元,因此檔案會依賴播放器替換字型;而英文行中的硬空格在中文裡變成了一個可見的間隙。卡拉 OK 行在每次執行中,時間軸都對應到錯誤的詞語,其中四次執行裡有些音節完全沒有醒目提示。

快速解答:該翻譯什麼、該保留什麼

檔案部分我們範例中的例子要翻譯嗎?
[Script Info] 標頭PlayResX: 1920, WrapStyle: 0不要。位置是根據這些數值計算的。
[V4+ Styles]Style: Default,Trebuchet MS,64,...不要,但字型名稱例外,你可能需要刻意變更它
事件欄位 1–90,0:00:01.00,0:00:03.20,Default,Jun,0,0,0,不要。這些是圖層、時間軸、樣式、講者、邊界和效果。
覆寫區塊{\i1}, {\an8\pos(960,120)}, {\k25}不要
繪圖{\p1}m 0 0 l 200 0 200 60 0 60{\p0}不要。這是向量圖形,不是文字。
Comment: 事件Comment: ...,Earlier draft: Try the stacks.選用。註解永遠不會顯示在畫面上。
大括號註記{TL note: "stacks" means the closed shelves}選用。播放器會忽略它們。
可見對白Did you find the second volume?要
行內代碼\N, \n, \h保留 \N。對於 \n 和 \h,請逐一判斷。

本指南其餘部分將說明每一列為何重要,以及如何檢查結果。

ASS 列的組成方式

[Events] 區段中的每一列都遵循其上方宣告的格式:

Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:03.40,0:00:06.10,Default,Mara,0,0,0,,{\i1}I thought it was{\i0} sold out.

只有第十個欄位 Text 包含觀眾會讀到的內容。libass 專案的 ASS 檔案格式指南 指出,這最後一個欄位「允許包含逗號」,這就是為什麼粗心地按每個逗號分割會破壞像 Well, if it's there, then, we go now. 這樣的列。安全的工具只會在前九個逗號處分割。

在 Text 內部,大括號中的任何內容都是覆寫區塊。Aegisub 的 覆寫標籤參考 定義了您最常遇到的代碼:

  • \N 在每種換行模式下都會強制換行。\n 僅在換行模式 2 中換行,否則為空格。\h 是不換行空格。
  • \an8 使用數字鍵盤佈局設定對齊方式,因此 8 是頂部居中。\pos(960,120) 將該列放置在腳本座標中。
  • \k25 是卡拉 OK 音節;持續時間以厘秒為單位,因此 25 表示四分之一秒。
  • 在 \p1 之後,文字「將被解釋為繪圖指令」,因此翻譯者若「翻譯」m 0 0 l 200 0,就會破壞圖形。
  • 大括號內無法辨識的文字「會被默默忽略」,這就是為什麼粉絲字幕檔案經常在大括號中攜帶翻譯註釋。

以 Comment: 開頭的列使用相同的欄位,但根據 libass 指南,「螢幕上不會顯示任何內容」。

我們測試了什麼

我們撰寫了 sample-source.ass,這是一個設定在書店的 14 事件場景,每個上述功能都有一個範例:斜體、硬換行、對話中的逗號、大括號註釋、註解列、定位標誌、向量方塊、淡入淡出、硬空格、使用 \r 的顏色變更、軟換行、滾動 Banner 效果,以及一個六音節的卡拉 OK 列。

然後我們要求本地模型以三種方式將其翻譯成簡體中文:

  1. 整個檔案。 貼上整個 .ass 檔案,並要求返回翻譯後的檔案。
  2. 僅文字。 僅將每個對話列的 Text 欄位作為 JSON 發送,標籤內聯,並指示保留每個 {...} 區塊以及每個 \N、\n 和 \h。
  3. 受保護。 相同,但先將每個大括號區塊和行代碼替換為編號佔位符,然後再將原始內容放回。

在第二和第三種條件下,腳本從未將向量繪圖或註解列發送給模型,因此這些列只能在整個檔案條件下被損壞。

模型是 Qwen 3.6 27B (Q4_K_M),在 Apple M4 Max 搭配 64 GB 記憶體的 LM Studio 0.4.8 中運行,關閉思考功能,溫度設為 0.3。我們每種條件運行三次,總共九次。第三次運行的第一次嘗試因本地伺服器斷開連接而結束,所以我們重新啟動並再次運行第三輪。腳本是 run_llm_ass_test.py,所有九個輸出都未經編輯地與其一起發布;README 列出了每個檔案。如果您從未設定過本地模型,我們的 本地 LLM 翻譯指南 涵蓋了相關設定。

這是一個小型測試,僅涉及一個模型、一個語言對和一個樣本。它展示了哪些問題會發生以及如何捕捉它們。它並不對工具或提示進行排名。

結果:機制倖存;英語假設未能倖免

我們使用 check_ass_translation.py 檢查了每個輸出。完整輸出在 check-results.txt 中,而 results.csv 每次運行有一列,包括腳本無法看到的問題。

檢查整個檔案僅文字受保護
時間軸、樣式與欄位 1–9 維持不變3 / 33 / 33 / 3
覆寫標籤維持不變3 / 32 / 33 / 3
繪圖與註解行未受更動3 / 33 / 33 / 3
樣式字型可繪製譯文0 / 30 / 30 / 3
\h 與 \n 在中文文本中保留為間隙3 / 33 / 33 / 3
所有卡拉 OK 標籤均保留,且各自附有文字3 / 30 / 31 / 3

有兩個結果讓我們感到意外。貼上整個檔案在三次執行中都保持了結構完整,不過其中兩次也翻譯了標頭中的 Title 行。而聽起來更安全的純文字提示詞,卻產生了唯一一次標籤損壞。在其中一次執行中,它為一行帶有六個卡拉 OK 標籤的文字返回了 {\k25}翻{\k30}页,{\k45}开{\k60}灯。最後兩個標籤和 1.20 秒的高亮時間消失了,因此高亮會在該行結束前就提前結束。同一次執行還把 Room\h12 is upstairs. 變成了 12\h12号房间在楼上。,房間號碼出現了兩次。

所以有用的教訓不是「使用提示詞 X」。而是你所擔心的結構損壞很容易偵測,而全部九次執行中真正漏掉的損壞,位於 ASS 翻譯工具頁面沒有討論到的三個地方。

1. 字型無法繪製新語言

範例的樣式使用 Trebuchet MS 作為對白、Georgia 作為標誌,這對英文發行版來說很正常。這兩種字型都不包含中文字元。當我們將檢查器的字形測試指向這兩個字型檔案時,每次執行中全部 12 個翻譯文字事件都失敗了:

ERROR   event 1: font 'Trebuchet MS' has no glyph for 9 characters, e.g. 了二你到卷吗找第?
ERROR   event 7: font 'Georgia' has no glyph for 10 characters, e.g. 书人仅作员工库架闭限

播放器可能會替換成其他已安裝的字型,所以字幕可能還是會顯示,但發行版不再控制它的外觀,而系統缺少合適字型的觀眾可能會看到空白方塊。修正方法是刻意編輯每個樣式的 Fontname 欄位。當我們將一個輸出中的所有三個樣式改為 Hiragino Sans GB(macOS 隨附的中文字型),並對該字型執行字形測試時,字形錯誤降為零。

選擇觀眾實際會有的字型,或隨影片一起提供。libass 指南說發行版應附加它們使用的每個字型,最好附加到 MKV 容器。由於目標語言字型是新的,附加它現在成了翻譯工作的一部分。

2. 間距代碼帶有英文習慣

Room\h12 將「Room」黏在「12」上,使它們永遠不會跨行分割。中文詞語之間沒有空格分隔,但每次執行都將 \h 保留在某處:五次執行將它放在 12号房间 和 在楼上 之間,三次放在數字之前,一次放在兩個數字副本之間。無論哪種方式,觀眾都會看到一個多餘的空格。Wait\nhere. 也有同樣的問題。使用 WrapStyle: 0 時,\n 會渲染為空格,所以 在\n这里等 會在三個詞的句子中間顯示一個間隙。

這裡的結構沒問題;語言有問題。對於詞語之間沒有空格的目標語言,請從翻譯文字中移除 \h 和 \n。只在需要強制換行的地方保留 \N,並將其放在目標句子中的自然位置。檢查器將變更的 \h 和 \n 計數報告為警告而非錯誤,原因就在於此,並且當其中一個在中文、日文或韓文文字中倖存時會發出警告。

3. 卡拉 OK 時間屬於原始詞語

卡拉 OK 行 {\k25}Turn {\k30}the {\k45}page, {\k60}turn {\k40}the {\k80}light 將每個英文音節與歌曲計時。翻譯無法有意義地保留該時間,因為中文詞語不會落在相同的節拍上。我們的執行顯示了它出錯的不同方式:

  • 在四次執行中,全部六個標籤都被保留,每個都落在與之前不同的詞上。其中一次以 {\k40}打{\k80}开(「打開」)結尾,並將「light」從該行完全丟棄。
  • 在四次執行中,一個音節沒有高亮任何內容,例如 打开{\k40}{\k80}灯(在空音節上高亮 0.40 秒)。在其中兩次中,一個 0.45 秒的音節也只高亮了一個逗號。
  • 在一次執行中,兩個標籤消失了,如上所述。

檢查器會標記就地翻譯的卡拉 OK 行以及任何沒有文字的音節。更好的修正方法是編輯性的:不要就地翻譯卡拉 OK。保留原始計時行用於跟唱,並將翻譯添加為單獨的事件:複製卡拉 OK 事件,給它自己的樣式(例如,更小並放置在底部),移除 \k 標籤,並翻譯文字。只有當你準備好自己根據音訊計時時,才逐音節重新計時。

經得起考驗的工作流程

  1. 先讓來源可讀。 如果檔案顯示亂碼字元,請先修正編碼,再做其他事。我們的編碼指南 使用 SRT 範例,但相同的位元組層級步驟也適用於 .ass 檔案,並保持原始位元組安全。檢查器只讀取 UTF-8 和 UTF-16。

  2. 決定範圍。 列出樣式並決定哪些是對白、哪些是螢幕標誌、哪些是卡拉 OK。標誌通常需要翻譯;卡拉 OK 通常需要單獨一行;註解和大括號備註由你決定。

  3. 只翻譯 Text 欄位。 使用解析 ASS 而非將其視為純文字的工具,或者像我們的第三個條件那樣自行保護標籤。將來源檔案保留在輸出旁邊。

  4. 執行結構檢查。

    python3 check_ass_translation.py source.ass translated.ass
    

    它比較事件數量、事件類型、欄位 1–9、樣式值、覆寫區塊、繪圖、註解行、\N、卡拉 OK 音節和持續時間,以及返回為空或未變更的行。它不評判翻譯品質。

  5. 刻意修正字型。 變更每個現在包含目標語言文字的樣式中的 Fontname,然後對你將要交付的字型檔案執行字符測試:

    pip install fonttools
    python3 check_ass_translation.py source.ass translated.ass --font "Noto Sans SC=/path/to/NotoSansSC-Regular.otf"
    

    變更的字型名稱會顯示為警告,因此你可以確認每項變更都是有意為之。

  6. 清理間距代碼和卡拉 OK,如上所述。

  7. 搭配影片觀看。 在 Aegisub 或你的播放器中開啟檔案。檢查標誌是否仍符合其後方繪製的形狀,因為 \p 方塊會保持英文大小,而翻譯後的標誌可能長得多或短得多。檢查台詞是否因其螢幕時間而過長。這兩個問題在檔案本身都看不出來。

BookTranslator 的適用場景

BookTranslator 的字幕翻譯工具 接受 SRT、VTT、ASS 和 SSA 檔案。根據產品頁面說明,它會保留時間戳和字幕順序,並將支援的 ASS 和 SSA 樣式附加到每個字幕項目上,因此你不必自行擷取和重新插入文字。它會以周圍台詞為上下文來翻譯對話,這有助於處理整集出現的名字和反覆出現的笑話。

我們並未為本文將此樣本交由 BookTranslator 處理。無論你使用什麼工具,都請對結果執行步驟 4 到 7。保留英文字型的樣式仍然是無法繪製中文的樣式,而就地翻譯的卡拉 OK 仍然需要編輯決策。關於字幕工作流程的其他部分,請參閱我們的 AI 字幕翻譯指南、字幕翻譯軟體比較,以及如果你的翻譯要上傳到 YouTube,請參閱 YouTube 字幕工作流程。

何時應改為轉換成 SRT

在翻譯前將 ASS 轉換為 SRT 會移除樣式、位置、繪圖和卡拉 OK,這也消除了上述大部分問題。如果你的目標平台只顯示純字幕,或者樣式對該發佈版本從不重要,那麼這是合理的取捨。對於動畫和音樂發佈作品,標誌和卡拉 OK 是作品的一部分,這樣做就是錯誤的取捨。請根據檔案將在何處顯示來決定,而不是根據哪種格式更容易翻譯。

取得測試檔案

本次測試的所有內容均可免費重複使用:

相關文章