gfx-html
圖形是由無頭 Chrome 逐幀渲染的 HTML 頁面,並被編碼成帶有 Alpha 通道的影片。完整請求文字位於 $ARGUMENTS 中;第一個 Token 通常是專案 slug。動詞名稱遵循建構合約;python engine/ve.py --help 在衝突時具有最終決定權。
讓這條生產線運作的規則
每個圖形都是 ?t=<seconds> 的純函數。 不允許 CSS 動畫、GSAP 時間軸、requestAnimationFrame、轉場效果。頁面從 location.search 讀取 t,計算其佈局,並渲染一個靜態影格。
這不是風格偏好。chrome --headless --screenshot 在「頁面看起來已載入」時觸發——在此建構中沒有虛擬時鐘,沒有 Emulation.setVirtualTimePolicy,沒有 beginFrame。以這種方式取樣的 CSS 動畫頁面每次執行都會返回不同的像素。這會破壞內容可尋址快取(相同的鍵,不同的像素)並產生可見的抖動。
由相同簡單迴圈取樣的 t 純函數是確定性且可快取的,這就是全部的區別。
預先說明的預算
Chrome 在穩定狀態下以約 430-470 毫秒/幀 的速度渲染,冷啟動需要 7.9 秒。一個 5 秒、30 fps 的圖形有 150 幀,大約需要 65 秒(單執行緒)。引擎會並行化四個 Chrome 進程並快取每幀的 PNG,因此未改變的圖形可以免費重新渲染——但首次渲染並非即時。
在開始長時間渲染前,請告知使用者影格數量和估計時間。偏好短圖形:一個持續 6 秒的下方第三條不需要 6 秒的運動,它需要 0.4 秒入場、停頓和 0.4 秒出場。
步驟 1 - 挑選或編寫範本
範本位於 presets/graphics/*.html。在編寫之前先重複使用。範本是單一自包含的 HTML 檔案,它必須:
- 從
location.search讀取t(秒)及其屬性。 - 使用純算術從
t計算所有位置/不透明度。緩動函數是可以的——它們是純函數。 - 在透明背景上渲染。絕不繪製背景顏色。
- 使用來自
assets/fonts/的字型,透過帶有file:///URL 的@font-face。不要依賴系統字型——Chrome 在此機器上解析的字型與 QC 控制渲染假設的字型不同。 - 在 1920x1080 解析度下且位於平台的安全字幕區域內可讀。檢查
output.platform:一個位於底部三分之一處的下方第三條會被 TikTok UI 遮擋。
佈局細節、緩動慣例和屬性合約在 docs/graphics.md 中。此處不再重述。
步驟 2 - 渲染影格
python engine/ve.py gfx <slug>
每幀引擎將 Chrome 作為 argv 列表調用,大致如下:
chrome.exe --headless=new --disable-gpu --no-sandbox --hide-scrollbars
--user-data-dir=<win-path>\prof --default-background-color=00000000
--window-size=1920,1080 --screenshot=<win-path>\frame_0007.png
"file:///C:/.../template.html?t=0.233&title=..."
兩個標誌承載了整條生產線:
--default-background-color=00000000是產生真正 Alpha 通道的關鍵。已驗證:角落像素讀取為0 0 0 0。沒有它,你會得到不透明的黑色,疊加層會覆蓋影片。file:///URL 必須是真實的 Windows 路徑。Git Bash 的$PWD路徑會靜默地失敗——三個不同的 HTML 檔案產生了相同的空白深色模式錯誤頁面的 md5,退出碼為 0,且檔案大小看似合理。這就是為什麼 QC 檢查 (g) 會比對已知的空白雜湊值。
影格快取在 .cache/html/ 下,鍵值為 sha256(html_content + t + params + renderer + renderer_version)。更改範本會使每個影格失效,這是正確的行為。
步驟 3 - 保留 Alpha 進行編碼
相同的命令。ve gfx 會渲染影格並在一個步驟中編碼——沒有單獨的編碼動詞,因為一個沒有後續影片的 PNG 序列是半成品,下游無法使用。
PNG 序列編碼為 QuickTime RLE (qtrle,argb)。ProRes 4444 和原始 PNG 序列也能正確保持 Alpha 通道。在此建構中,VP9-in-WebM 會靜默地捨棄 Alpha,即使使用了 -metadata alpha_mode=1——請勿使用它,且在未經探測時也不要接受使用者的此類資產。
輸出位於 .cache/ovl/<xx>/<key>.mov,並附帶一個 <key>.json 側車檔案。
步驟 4 - 錨定疊加層
撰寫 overlays[] 條目:id、kind: "html"、template、props、anchor、duration_frames、layer、render。
anchor 始終是 {segment_id, offset_frames}——絕不是絕對時間軸位置。 時間軸位置是根據啟用區段的累加和推導出來的。一旦上游有人停用某個區段,每個絕對位置都會出錯,每個圖形都會偏移。錨定的疊加層會自動跟隨其區段移動,這是編輯者期望的行為。
layer 控制合成順序;數字越大越在上面。字幕最後燃燒,位於圖形之上。
步驟 5 - 在宣告完成前進行驗證
三個檢查,全部都很便宜:
- Alpha 探針。 從角落影格讀取一個原始 rgba 像素並斷言
a = 0。QC (d)。 - 空白頁面檢查。 斷言影格並非全都是單一顏色。QC (g)。這能捕捉到錯誤的
file:///URL、載入失敗的字型以及 JS 錯誤,這些情況都會以看似合理的檔案大小並以退出碼 0 結束。它會對第一個、中間和四分之三處的影格進行取樣,並且僅在所有影格都均勻時才失敗——此處的每個範本都以不透明度 0 開頭,因此正常工作圖形的第 0 影格是合法的空白,僅根據它來判斷會使正確的渲染失敗。
ve gfx 會自動對其剛剛製作的內容執行這兩個檢查,如果任一失敗則以退出碼 2 結束。
3. 確定性。 將一個影格渲染兩次並比較雜湊值。如果它們不同,則範本中的某些東西不是 t 的純函數——找到它。此檢查存在是因為它捕捉到的失敗是不可見的,直到快取的渲染與新的渲染不一致。
然後向使用者顯示中點處的靜止幀。當一個螢幕截圖只需要一個命令時,不要用散文描述圖形。
失敗模式
| 症狀 | 原因 | 修正 |
|---|---|---|
| 疊加層以黑色覆蓋影片 | --default-background-color 缺失,或範本繪製了背景 | 添加該標誌;使 body 透明 |
| 每個影格都相同,檔案大小看似合理 | 來自錯誤 file:/// URL 的空白錯誤頁面 | 使用真實的 Windows 路徑;比對空白雜湊值 |
| 相同的鍵,不同執行之間像素不同 | 範本不是 t 的純函數 | 移除所有 CSS 動畫和 rAF |
| 文字使用錯誤的字體渲染 | 系統字型回退 | 使用帶有 file:/// URL 的 @font-face 從 assets/fonts/ 載入 |
| 編碼後 Alpha 遺失 | VP9/WebM | 使用 qtrle argb 或 ProRes 4444 |
| 上游編輯後圖形偏移 | 絕對時間軸錨點 | 錨定到 {segment_id, offset_frames} |
| 渲染需要數分鐘 | 430 毫秒/幀是下限 | 縮短圖形或接受它;快取使第二次執行免費 |