模組五|發布與衍生物(Day 23–27)
我做的是一檔中文新聞 podcast,有些集會請來賓進來對談。錄音走線上多軌,主持人與來賓各自留下一條音軌,之後交給一條自動化管線組裝、上架。
剪輯收在一個沒有客觀標準的東西:好不好只能試聽。而節目做完之後是另一批東西——封面、影片、導流、文案。
而第一個問題是:視覺一致性能不能自動化?
我的答案有點反直覺:能,但不是用生圖模型。
我把所有封面產生腳本翻了一遍。
只有兩集用了 AI 生圖。 走的是 image edit(影像編輯)模式——不是給一段文字讓模型從零畫,而是餵一張既有的圖進去讓它改:把前一集的封面當風格參考,讓模型在同一個調性下畫新的主視覺(一張封面裡最主要的那張圖,每集不同),輸出 1024×1024,之後用 LANCZOS(放大影像時用的重取樣演算法,邊緣比最近鄰、雙線性那些做法乾淨)升到 2048。
其餘的每一張,都是程式畫的。 Pillow(Python 的影像處理函式庫,可以開畫布、貼圖、寫字、存檔)開一張畫布,貼底色、貼主視覺、用指定字型畫標題與集數編號、輸出。沒有模型參與。
而更早期的做法更土:用 headless 瀏覽器(沒有視窗、由程式驅動的瀏覽器)截圖一份 HTML,3000×3000。

因為一致性要的東西,剛好是生成模型最給不了的:
同一個字型、同一個位置、同一個比例、同一個留白。
生圖模型每次輸出都不一樣,這是它的特性,不是缺陷。用 image edit 模式加上前一集當參考,可以把「風格」壓在一個範圍內,但壓不住字的位置差 3 像素。
而人眼對「同一個節目的封面」的判斷,恰好非常依賴那 3 像素。風格接近但排版跳動,看起來比風格不同還糟。它會像同一個模板被人手動排過,而不是同一條產線出來的。
所以分工是這樣的:
| 誰做 | 為什麼 | |
|---|---|---|
| 主視覺 | 生圖模型(少數集數)/素材庫 | 需要變化 |
| 排版 | 程式 | 需要不變 |
要變的交給模型,要不變的交給程式。
這條可以推廣:任何「每次都要有點不同、但骨架必須一樣」的產出,都適用這個切法。
同一集內容會落在三個不同版位——方形的、橫的、直的——每個版位都要有自己的一張圖。所以一集節目要出三種尺寸:
方形封面 2560 × 2560 (dpi 144)
橫式封面 1280 × 720
直式影片 1080 × 1920
三種比例,三套排版邏輯。而它們共用同一份主視覺與字型。
字型只有兩個檔:一個中文黑體、一個拉丁可變字重字型(variable font,同一個檔就能無段調粗細,不必再另外裝一套粗體檔;集數編號用它,加粗到 700——字重刻度上 400 是一般內文的粗細,700 大約等於粗體)。兩個字型檔,五十集。 這是一致性最便宜的來源:限制選項本身就是一種一致性機制。
講完優點,講難看的部分。
我把不同時期的腳本參數抓出來對照,箭頭代表同一個參數在不同集數之間換過值:
橫式封面的主視覺卡片 540×540 → 600×600
方形封面的品牌字級 150pt → 144pt
主視覺區塊尺寸 2070 → 1900
主視覺頂部留白 240 → 330
沒有 design token(設計變數:把字級、間距、尺寸這些數字抽成一份具名的共用設定,所有產出都引用同一份)。 這些數字直接寫在各集的腳本裡,改了就往下傳,沒改的就留在舊值。
所以「同一個節目」這件事,實際上是分段的:某幾集是一組排版,另幾集是另一組。差異小到不容易發現,但它確實在。
而修法很簡單:把這些數字抽成一份共用設定,所有腳本引用它。我沒做,因為每一集寫腳本的時候,複製上一集再改幾個數字是最快的。
這句話你已經在這個系列裡看過好幾次了。今天它有一個更嚴重的版本。
我在比對腳本的時候,發現三個檔案有問題。
先說一下這些檔案放在哪:每一集在製作端都有自己的一個目錄,該集要用的腳本一集一份,就躺在那個目錄裡。
兩個集數目錄裡的橫式封面腳本,是 byte-identical(位元組完全相同,連一個字元都不差)的舊集腳本。
不是「很像」,是完全一樣:docstring(寫在 Python 檔案最上面、說明這支腳本在幹嘛的那段文字)寫著舊集號、讀取的檔名是舊集的主視覺、畫在圖上的字串也是舊集號。
還有四份直式圖腳本,md5(把一份檔案內容算成一串固定長度的指紋,內容差一個位元組指紋就完全不同)完全相同,全部指向同一個舊集的音檔。
也就是說:這些腳本現在跑起來,會產出錯的東西。
而它們躺在那裡沒有造成任何問題,因為沒有人跑它們。實際的封面是由另一支正確的腳本產的。

死碼(dead code,留在專案裡但實際上不會被執行到的程式)是怎麼長出來的?這件事的形狀值得看清楚。開新一集的時候,有一支建目錄的腳本負責替新集數開一個資料夾、把該集要用的檔案備好,而它的實作方式就是整包複製上一集:
複製上一集的整個目錄 ← 快
↓
改了「今天要用的」那幾支腳本
↓
沒改「今天沒用到的」那幾支
↓
它們留在那裡,內容是錯的,而且沒有任何徵兆
沒有錯誤訊息,因為它們沒有被執行。
而問題會在未來某一天發生:我(或任何人)打開那個目錄,看到一支叫「產出橫式封面」的腳本,跑下去,得到一張別集的封面。
死碼不會報錯,它只是躺在那裡等人踩。
Day 27 那篇專門講「複製整個資料夾然後忘記改」這件事,會講這個複製機制造成的更大災難:集數撞號、素材放錯目錄,至少 5 組加 9 起。今天這三個 stale copy 是同一個病最輕微的症狀。
有意思的是,這三個檔案其實留下了可偵測的痕跡:它們跟來源檔案的 md5 完全相同。
也就是說,一個很簡單的檢查就能抓到:
對每個集數目錄裡的腳本,算 md5
如果它跟另一個目錄的同名腳本 md5 相同
→ 這支腳本沒有被改過,可能是複製殘留
→ 檢查它的 docstring 與硬編路徑是否指向本集
十幾行程式碼。而我沒寫,直到寫這個系列做比對時,才發現有東西可以抓。
線索一直在磁碟上,只是沒有人去問那個問題。
視覺一致性我做到了八成,而剩下兩成沒人在管。
那些漂移的排版數字(卡片從 540 變 600、字級從 150 變 144),我是寫這篇的時候才發現的。它們沒有造成任何抱怨,但它們讓「同一條產線」這件事變成半真的。
第二個代價:兩集用了 AI 生圖,而我沒有記錄那次的成本與 prompt 演進。 那兩張封面的 prompt 直接寫死在腳本裡,沒有版本、沒有「試過什麼被否決」的註解,Day 20 講的是一個背景音樂音量參數改了七次的故事,那個參數旁邊的註解把現在的值、它跟人聲的相對關係、上一個值、為什麼改全寫了;跟那行註解比起來,這裡的紀錄品質差很多。
同一個人,在不同環節的紀錄紀律差這麼多,本身就是一個發現。 音訊我會留理由,圖像我不會,大概因為音訊我調得比較痛。
第三個:那三個 stale copy 我沒刪。 又一項。
要變的交給模型,要不變的交給程式。
視覺一致性不是「風格接近」,是排版完全相同。而排版是程式的強項、模型的弱項。把這兩件事切開,比要求模型「保持一致」有效得多。
第二條,關於一致性最便宜的來源:
限制選項。 兩個字型檔、三種輸出尺寸、一組固定的留白。選項越少,跑五十次的結果越像同一個東西。而這比任何 style guide(設計規範文件,寫明字型、顏色、間距該怎麼用)都有效,因為它不依賴人記得。
第三條,可以今天就做的:
複製出來的檔案,去比對它跟來源的 md5。 相同代表它沒被改過。如果它應該要被改(例如裡面有硬編的識別字串),那它就是一個等著被踩的地雷。
這個檢查十幾行,而它抓的是「複製貼上式初始化」必然產生的殘留。只要你的 scaffold(開新專案/新單位時用來架好初始檔案的那套腳手架)是用複製實作的,你就一定有這種東西。
明天講一個 12 分鐘裡修了六次的 bug,以及為什麼前四次都在猜。