HTML to Video 轉換難題解決方案:HyperFrames 透過 seek 機制實現確定性渲染

HTML to Video 轉換難題解決方案:HyperFrames 透過 seek 機制實現確定性渲染。
一年多前,團隊嘗試讓大型語言模型(LLM)生成 HTML 程式碼來製作影片,初期面臨巨量提示、反覆互動與手寫補丁等問題,即便加入 Agent 輔助,仍未達生產級水準。後續測試 Remotion 雖具備 React 元件與生產渲染工具,但框架限制了 Agent 創意;回歸純 HTML/CSS/JS 後,創意回歸,進而開發「HyperFrames」,解決 HTML 自由度與確定性 MP4 渲染的矛盾。
核心創新:seek 而非 play
HyperFrames 的每個組合僅暴露單一運行時介面 window.__hf,包含 duration: 10(秒)與 seek(timeSeconds) 函式,渲染器不呼叫 play(),而是依序呼叫 seek(0)、截圖、seek(1/30)、截圖,直至產生 10 秒 30fps 影片的 300 幀。
- 時間不自動推進,避免依賴 requestAnimationFrame。
- 瀏覽器僅維持固定幀,直至下一個請求。
- 工作室預覽透過 iframe 與 postMessage 橋接實現 play/pause/scrub;無頭渲染則經 Puppeteer 與 CDP 呼叫相同
window.__hf,確保相同程式碼路徑與輸出。
動畫函式庫透過三方法 FrameAdapter 整合:
interface FrameAdapter {
id: string;
init?: (ctx) => Promise<void> | void;
getDurationFrames: () => number;
seekFrame: (frame: number) => Promise<void> | void;
}
- GSAP 為預設,因其 timeline 已支援 pause() 與 totalTime(t, false),完美契合。
- Lottie、CSS WAAPI、Three.js 時鐘皆易適配。
- 不相容者如無控制器 CSS 關鍵影格、video 元件、多數 canvas 函式庫,需包裝適配器剝離時鐘,或離線渲染成幀後以圖像重播。
此抽象將工作室預覽與無頭渲染合併單一程式碼庫。
擷取挑戰:逐幀控制 Chrome
初期 Puppeteer 擷取僅四行程式碼:
await page.evaluate(t => window.__hf.seek(t), time);
await page.screenshot({ path: `frame_${i}.jpg` });
但遭遇四項問題:
Page.captureScreenshot 競爭條件:截圖僅捕捉合成器就緒畫面,非「佈局完成、字型載入、GSAP tween 提交樣式、GPU 繪製結束」時刻,導致文字未渲染、SVG 填充預設、video 顯示 300x150 預設尺寸等錯誤幀,重試才正常。團隊花費大量時間寫入「幀是否落地」啟發式:輪詢 fonts.ready、等待計算樣式、比較像素雜湊。此法於 macOS/Windows 使用,但不適合大規模生產。
HeadlessExperimental.beginFrame 提供原子控制:CDP 方法執行單一 layout→paint→composite→screenshot 週期:
await cdp.send("HeadlessExperimental.beginFrame", {
frameTimeTicks,
interval,
screenshot: { format: "jpeg", quality: 80, optimizeForSpeed: true }
});
- 回應含 hasDamage,確認視覺變化,無背景競爭條件。
- 需特定 Chrome 組建 chrome-headless-shell 與旗標:
--deterministic-mode
--enable-begin-frame-control
--run-all-compositor-stages-before-draw
--disable-threaded-animation
--disable-threaded-scrolling
--disable-checker-imaging
--disable-image-animation-resync
--enable-surface-synchronization
這些旗標關閉非同步排程來源:執行緒合成器、滾動、漸進圖像解碼、圖像動畫重同步、vsync 表面計時。啟用後,合成器於主執行緒同步運行,performance.now() 由 frameTimeTicks 驅動,而非系統時鐘。僅 Linux 相容,macOS/Windows 回退啟發式。
- Chrome 事件迴圈停止推進:啟用 --enable-begin-frame-control 後,主執行緒不自動滴答,無 frame callbacks、setTimeout、microtask 排空。頁面載入時 document.fonts.ready 永遠懸掛。解決以暖身迴圈:載入中每 33ms 發 beginFrame(noDisplayUpdates: true),推進事件迴圈無產生幀:
while (warmupRunning) {
await cdp.send("HeadlessExperimental.beginFrame", {
frameTimeTicks: warmupFrameTime,
interval: 33,
noDisplayUpdates: true,
});
warmupFrameTime += 33;
await sleep(33);
}
暖身結束後,從超出範圍幀時開始真實擷取,避免時間倒退。
- Puppeteer waitForFunction 失效:依賴 requestAnimationFrame 輪詢,beginFrame 模式下 rAF 不觸發,導致懸掛。改用自訂輪詢:
while (Date.now() < deadline) {
const ready = await page.evaluate(
"!!(window.__hf && typeof window.__hf.seek === 'function' && window.__hf.duration > 0)"
);
if (ready) break;
await new Promise(r => setTimeout(r, 100));
}
最終擷取迴圈確定性無懈:
for (let i = 0; i < totalFrames; i++) {
const time = quantizeTimeToFrame(i / fps, fps);
await page.evaluate(t => window.__hf.seek(t), time);
const { buffer } = await beginFrameCapture(page, options, frameTicks, interval);
writeFileSync(`frame_${i}.jpg`, buffer);
}
無重試、無抖動幀。
影片內嵌問題:預先解碼取代播放
瀏覽器於渲染時播放
解決:擷取前 FFmpeg 預先將每個
<!-- before -->
<video data-start="2" data-duration="5" src="clip.mp4" />
<!-- at capture time, frame 60 of 150 -->
<video style="visibility: hidden" ... />
<img src="data:image/jpeg;base64,..." class="__render_frame__" />
注入 複製原元素計算樣式,確保 GSAP tween、CSS transform、opacity、object-fit 等無變:
img.style.position = computedStyle.position;
img.style.transform = computedStyle.transform;
img.style.opacity = computedStyle.opacity;
img.style.objectFit = computedStyle.objectFit;
// ...約十餘項
動畫函式庫視為無變元素,僅幀切換如翻書。相較 Remotion(Rust 合成器即時解碼 HTTP 供
其他確定性陷阱
控制時間與渲染解決大部分,非全部:
- 字型變異:Google Fonts @import url(fonts.googleapis.com/...) 受網路影響不穩。編譯 HTML 時改寫為本地 base64 嵌入 @fontsource 複本,消除網路延遲與不穩。
- 時間量化:30fps 每幀 33.3333ms,若 seek(0.0333333) 與 seek(0.0333334) 差異,需量化:
function quantizeTimeToFrame(time, fps) {
return Math.round(time * fps) / fps;
}
一行程式碼,避免像素差異。
- 作者規則:組合程式碼禁 Date.now()、無種子 Math.random()、渲染時網路擷取。違規即破壞確定性,此為契約。
最終成果與生產考量
相同 window.__hf 運行時 bundle 於工作室預覽(iframe)與無頭渲染通用,渲染器驗證 bundle sha256 對照清單,強制預覽=渲染程式碼一致。
- 長影片拆 N Chrome 程序並行,每 worker 渲染份額,FFmpeg 末端串接 MP4 片段。
- 缺點:影片密集組合並行易超時(Chrome 解碼器不足),回退單 worker。
HyperFrames 建基既有:GSAP timeline 設計、Remotion HTML 影片格式。HyperFrames 採限縮創作模型與 seekable 運行契約,致敬先驅。
建置動機與開放
團隊建 AI 模型與 Agent,認為 Agent 將以此法製作影片,並透過影片溝通。目前 HyperFrames 專案已開源,開發者可透過 GitHub 存取相關程式碼與說明,歡迎貢獻 adapter 系統,期待上層產品應用。Repo:github.com/heygen-com/hyperframes。
— James Russo (@Rames_Jusso) April 17, 2026