← 筆記

DeepSRT 如何實作精準的雙語字幕

把破碎的字幕縫成完整的句子,編上號碼交給 LLM,再讓每一句翻譯對號入座——時間軸自始至終只有一條。

2026 年 7 月 · DeepSRT 開發筆記

DeepSRT 播放 SNL 影片:中文翻譯字幕與英文原文字幕落在同一秒,右側是 AI 摘要側欄
實際成果:翻譯行與原文行落在同一秒——整支影片的每一秒都是如此。

雙語字幕看起來是個 UI 功能:上面翻譯、下面原文。真正難的是「精準」這兩個字——每一句翻譯都必須對上正在說的那句話,整支影片如此,不管你怎麼拖進度條都如此。

雙語字幕上線後不久,我們收到一種很特別的回報:中文那行是通順的中文沒錯,但講的是三分鐘後的劇情。更慘的時候,同一句話連續出現八次,疊成一面字牆。這篇筆記講我們查到了什麼,以及終結這個問題的三個設計決定。

免費的午餐有毒

影片平台本身就提供機器翻譯字幕軌。免費、即時、一個請求就拿到——做字幕產品的人第一個想到的都是它,我們也一樣。直到我們拿它和原文軌逐格比對,結論是:文字勉強能用,時間完全不可信。翻譯與語音的偏差不是一個固定值,而是隨著播放不斷累積。會累積的誤差代表沒有「校正」這回事:它不是偏了多少的問題,是從來就沒有對齊過。

要用整個產品的可靠性來付帳的免費午餐,不是免費的。

從碎片到句子

跟平台要原文字幕軌,拿回來的其實不是句子,是幾百個按「語音停頓」切開的碎片,每個帶毫秒級時間:

RAW FRAGMENTS (split by speech pauses)
  382.1s  "I guess heterosexual women"
  383.3s  "aren't allowed to have hair anymore."
  385.7s  "They have to shave their heads instead..."
        |
        |  deterministic sentence merge
        v
SPINE CUES (split by sentences)
  #153   382.1s - 385.0s   "I guess heterosexual women
                            aren't allowed to have hair anymore."
  #154   385.7s - ...      "They have to shave their heads..."

timing = exact union of the fragments, never invented

前兩個碎片其實是同一句話。自動字幕到處都長這樣——一個念頭被切成兩三段是常態,不是例外。

這種東西不能直接餵給翻譯:半句話翻出來就是瞎猜,單獨一個 "I guess heterosexual women" 可以翻去任何地方。也不能直接上畫面:碎片閃得太快,根本來不及讀。所以第一步是一道確定性的合併,把碎片縫成句子形狀的 cue:一路往下併,直到句子結束才切,同時設幾個安全閥——塊太長、會在畫面上掛太久、或跨過一段明顯的靜默(停頓就是天然的斷點),都強制切開。合併也認得文字系統:中日韓不用空格連接,長度預算也比拉丁字母更緊。

其中一條規則比其他都重要:合併只發生在碎片的邊界上。合併後每個 cue 的起訖就是成員碎片時間的精確聯集——沒有任何一句戴著編出來的時間戳。

而因為這道合併是確定性的——同樣的輸入,永遠切出同樣的句子、同樣的編號——後面兩件事才成立:編號穩定到可以當配對鍵;翻譯完成後可以按「這條軌的精確形狀」來快取。這兩點馬上就會用到。

決定一:只有一條時間軸

我們把翻譯軌整個丟掉,重寫時只立一條規則:系統裡只能存在一條時間軸。原文字幕軌——抓一次、按句意合併——成為我們內部稱作 spine(脊椎)的結構。每一句有編號、起始時間、長度、原文:

YouTube native caption track        <-- the only source of time
        |
        |  parse -> merge into sentences
        v
SPINE
  #0    0.0s -  4.5s   "Apple is releasing eight new..."
  #1    4.5s -  8.2s   "Here to comment are the most..."
  #2    8.2s - 11.9s   "Red Heart and Aerial Tramway."
  ...
        |
        v  pushed to screen immediately (native text)

translations attach later, keyed by id -- never by time

這條規則的牙齒在這裡:翻譯不攜帶任何時間。翻譯是掛在編號上的註記,不是第二條軌。畫面上任何一秒該顯示什麼,由 spine 獨自決定;翻譯永遠只回答「#21 的中文是什麼」,從不回答「現在該顯示什麼」。

決定二:帶編號送出去,按編號收回來

spine 就緒後,我們把句子分批送給 LLM,每句帶著編號,並附上前後幾句當語境(標明不用翻),讓慣用語和連續的哏不會斷掉:

SEND                                RECEIVE
  context    #18, #19 (do not translate)
  translate  #20 "They have to..."      #20 -> translated
             #21 "Under what..."        #21 -> translated
             #22 "Let me think..."      #23 -> translated
             #23 "[Laughs] I..."
                                        set check: #22 missing
                                                   |
                                        next round: resend #22 ONLY

LLM 的輸出在結構上是不可信的:它可能漏句、把兩句併成一句、甚至捏造編號。所以每批收回時做集合驗證——回來的編號集合必須等於送出的編號集合。多出來的丟棄;缺席的下一輪用更小的批次單獨補送;補幾次仍失敗就放棄那一句,讓它顯示原文。錯誤從結構上就被隔離了。

排程跟著播放位置走:你正在看的那一批最先翻,下一批預先翻好,其餘在背景填滿。拖進度條只是換一個優先目標——翻好的永不重翻,翻完的影片永久快取在你的 Mac 上。

DeepSRT 播放 Jimmy O. Yang 站立喜劇,時間軸停在 0:04——開播四秒,中文翻譯和英文原文已經同時在畫面上
播放頭優先排程的實際效果:影片開播四秒,雙語字幕已經就位。

決定三:為什麼保證不會漂移

JOIN BY POSITION (fragile)          JOIN BY ID (cannot slip)

native      translated              native        translations
[0] ------- [0]                     #0 --lookup-- {#0: "..."}
[1] ------- [1]                     #1 --lookup-- {#1: "..."}
[2] --+     (blank -- dropped)      #2 --lookup-- missing -> native
[3]   +---- [2]   <- off by one     #3 --lookup-- {#3: "..."}
[4] --+
[5]   +---- [3]   <- drift grows

one hole shifts every line          a hole stays a hole:
after it, forever                   one native line, nothing else

左邊是所有依賴「順序」或「時間對齊」的配對的通病:一個空洞讓後面每一句都位移,而且誤差複利成長——這正是我們在平台翻譯軌上量到的行為。右邊的配對鍵是編號本身:明確寫在請求裡,也要求原樣寫回回應裡。錯的編號不會讓任何東西位移,它只會通不過驗證、被重送。不存在「錯位但被接受」的狀態。

我們沒有修好漂移,我們刪掉了漂移成立的前提。漂移需要「翻譯自帶時間」這個假設,而我們的翻譯什麼時間都不帶。

DeepSRT 播放 KARINA 在 WATERBOMB 首爾的現場演出:快節奏歌詞連同句中的 [音樂] 標記,翻譯行與原文落在同一秒
最嚴苛的場景:音樂祭現場——快節奏歌詞、觀眾聲、句中的聲音標記——雙語依然落在同一秒。

你拿到的東西

影片一開,原文字幕就在。翻譯幾秒內逐段跟上,一分鐘內整支影片翻完,之後永久快取。因為 LLM 看得到前後文,口語和玩笑翻出來是人話,不是逐字機翻。一如既往地沒有伺服器:翻譯用你自己的 Gemini key 從你的 Mac 直連——我們沒有任何地方可以看到你在看什麼。

做字幕產品有一課要學兩次才記得住:平台給的資料,先驗證它的時鐘,再相信它的內容。這筆學費我們付過了,這篇筆記就是收據。