模組四|剪輯管線(Day 17–22)
模組三講怎麼驗一份稿子。從今天開始講錄完之後的事,而這一段是整條產線裡 AI 參與最深、也最沒有出過大事的一段。
除了一件事。
逐字稿是這樣產的:
model = WhisperModel("large-v3", device="cpu", compute_type="int8")
segments, info = model.transcribe(
audio_path,
language="zh",
vad_filter=True,
vad_parameters=dict(min_silence_duration_ms=500),
beam_size=5,
condition_on_previous_text=False, # 避免長音檔重複/漂移
initial_prompt=DOMAIN_PROMPT,
)
跑在 CPU 上、int8 量化,Mac 上大約是 0.5 到 2 倍實時:一小時的音檔跑半小時到兩小時。沒有 GPU 路徑。
沒寫 GPU 版本不是因為不會,是因為這一步不趕。錄完之後我不會坐在旁邊等,跑一晚上跟跑十分鐘對我來說沒差。而寫一條 GPU 路徑要處理環境、驅動、量化格式,那些成本是實時的。
不急的東西不要優化。 這條在只有一個人的產線上特別重要,因為每一條你優化過的路徑,之後都要由你自己維護。
condition_on_previous_text=False 是一個伏筆這個參數的意思是:辨識每一段的時候,不要把前面已經辨識出來的文字當成上下文餵進去。
預設是 True,因為有上下文通常辨識得更準。而關掉它的理由寫在註解裡:避免長音檔重複/漂移。
這是 Whisper 家族的一個已知行為:當它把自己的輸出當上下文,遇到一段沒有語音的區間,它可能會繼續生成,因為上下文告訴它「剛剛在講話,所以現在應該也在講話」。
於是它會憑空生出一段話,通常是把前面某句重複好幾次。
我關掉了這個參數,所以這個問題被壓下來了。但沒有被消滅,明天講一個誤砍六分多鐘的事故,成因正是這個。

initial_prompt 是一段文字,作用是告訴模型「這段音訊大概是什麼領域」,讓它在同音字之間選對。
我的是 95 個字元。
裡面塞的是:節目類型、本集會出現的專有名詞、來賓的職涯領域相關詞彙,結尾一句「繁體中文。」
95 個字元很短。而它的效果不是「讓辨識變好」,是讓特定幾個詞不要錯,那些會反覆出現、錯一次就要改幾十處的詞。
這件事的投資報酬率極高:寫 95 個字元,換掉幾十次尋找取代。
而它的限制也很明顯:它是逐集手寫的。 每一集的專有名詞不同,所以每一集要重寫一次。這意味著它永遠不會累積,上一集學到的詞,下一集不會自動帶過去。
一個共用詞庫加上每集的增量,會好很多。我沒做。
這一點我一開始沒想通,走了一段冤枉路。
毛片的逐字稿跟成品的逐字稿,是兩份不同的東西。
毛片逐字稿是用來決定剪哪裡的,它的時間碼對應原始錄音。
成品逐字稿是用來上字幕、產文案的,它的時間碼要對應剪完之後的檔案。
剪輯把中間切掉了,所以毛片的時間碼在成品上全部位移。不能直接沿用。
所以這支腳本實際上跑兩次:一次在剪之前(給剪點用),一次在剪之後(給字幕用)。
多跑一次的成本是幾十分鐘的 CPU 時間,而它省掉的是「手動重算所有時間碼」。那件事我試過一次,錯誤率高到不如重跑。
當一個計算比手動修正更便宜的時候,就重算,不要修補。 這條在 Day 22 會再出現一次,那次是整份成品重建。
現在講不好的部分。
我把所有提到 ASR 模型的地方列出來,發現三個不同的值,散落在三種載體:
| 出處 | 寫的是什麼 |
|---|---|
| 那支轉錄腳本的預設值 | large-v3 |
| 兩支剪輯腳本的註解 | large-v3-turbo |
| 某份剪輯記錄 | small,int8 |
三個值,三個地方,沒有一個是權威來源。
哪一個是現在實際在用的?要回答這個問題,我得去看最近一集實際跑的是哪支腳本。而這件事應該是一行設定就能回答的。
這是 Day 16 那個「同一份東西有多個副本」在參數層級的版本:參數被寫在註解裡、寫在記錄裡、寫在預設值裡,然後各自演化。
修法很簡單:一個共用的設定檔,所有地方引用它。我沒做,因為每一集寫腳本的當下,複製上一集然後改幾個數字是最快的(Day 27 會講這個複製機制造成的更大災難)。

現在講這一整篇最難看的數字。
字幕、逐字稿、錯別字相關的 commit,佔製作端全部 commit 的 13.1%(155 筆)。
把所有 fix / 修正 類算進來是 187 筆、15.8%。
每七個 commit 就有一個在修字。
而它的形狀比數字更難看。最密集的一段是 85 分鐘內連續 10 筆,全部在修同一批字幕的錯字,其中一句 commit message 一模一樣重複了三次。
那不是工程,那是鬼打牆。
更糟的是它沒有收斂。那一串的最後一筆只是「修正文本中的錯別字」,沒有任何機制性的改變。然後幾個月後同樣的事情又爆一次,那個月的字幕相關 commit 是全期最高。
Day 12 到 16 講了四種檢查,沒有一種對這件事有用。
這是整條產線裡唯一一個「只能靠人聽」的環節,而它佔了 13% 的 commit。
Day 5 說過「AI 省的是產出時間,不是決定時間」。這裡是更精確的版本:ASR 省的是打字時間,不是校對時間。 而校對佔的比重,遠超過我當初的預期。
我到現在沒有做任何機制化的改善。
至少有三件事可以做,全部不難:
initial_prompt 從它生成)三件事加起來大概兩小時。而 155 筆 commit 的時間遠遠超過兩小時。
我知道這件事,而且我在寫這篇的時候又確認了一次數字,然後還是沒做。 這已經是這個系列第五次出現同一句話了。
第二個代價:condition_on_previous_text=False 壓下了幻聽,但也讓辨識失去上下文,長句、跨段的專有名詞會辨識得比較差。這是一個我選了的取捨,而我沒有量過它的代價有多大。
用最小的輸入,換掉最大的重複工作。
95 個字元的提示詞,換掉幾十次尋找取代。這種投報比在 AI 工具上很常見,而它的形狀通常是:找出那個「錯一次要改很多次」的東西,然後在最前面把它固定住。
第二條:當重算比修補便宜的時候,重算。
剪完之後的時間碼,我試過手動換算,錯誤率高到不如重跑一次 ASR。而重跑的成本是幾十分鐘的 CPU,不是我的時間。
你的時間跟機器的時間不是同一種資源,而人很容易為了省機器的時間而花自己的時間。
第三條,也是最該記住的:
在導入一個 AI 工具之前,先想清楚它省的是哪一段,以及那一段原本佔多少。
ASR 省的是打字。而在我的流程裡,打字從來不是瓶頸,校對才是。所以導入之後,總時間沒有變少多少,只是從「打字 + 校對」變成「等機器 + 校對」。
如果我當初問過這個問題,我會在第一天就開始建那個詞庫。
明天講那個誤砍六分多鐘的事故,它的成因,就是今天那個被壓下來但沒消滅的參數。