# 策展 · X (Twitter) 🔥

> 📖 本站完整內容索引（documentation index）：[llms.txt](/llms.txt)

> 作者：James Russo (@Rames_Jusso) · 平台：X (Twitter) · 日期：2026-04-18

> 原始來源：https://x.com/i/article/2045222338724257792

## 中文摘要

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 整合：
```javascript
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 擷取僅四行程式碼：
```javascript
await page.evaluate(t => window.__hf.seek(t), time);
await page.screenshot({ path: `frame_${i}.jpg` });
```
但遭遇四項問題：

1. **Page.captureScreenshot 競爭條件**：截圖僅捕捉合成器就緒畫面，非「佈局完成、字型載入、GSAP tween 提交樣式、GPU 繪製結束」時刻，導致文字未渲染、SVG 填充預設、video 顯示 300x150 預設尺寸等錯誤幀，重試才正常。團隊花費大量時間寫入「幀是否落地」啟發式：輪詢 fonts.ready、等待計算樣式、比較像素雜湊。此法於 macOS/Windows 使用，但不適合大規模生產。

2. **HeadlessExperimental.beginFrame 提供原子控制**：CDP 方法執行單一 layout→paint→composite→screenshot 週期：
```javascript
await cdp.send("HeadlessExperimental.beginFrame", {
  frameTimeTicks,
  interval,
  screenshot: { format: "jpeg", quality: 80, optimizeForSpeed: true }
});
```
- 回應含 hasDamage，確認視覺變化，無背景競爭條件。
- 需特定 Chrome 組建 chrome-headless-shell 與旗標：
```bash
--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 回退啟發式。

3. **Chrome 事件迴圈停止推進**：啟用 --enable-begin-frame-control 後，主執行緒不自動滴答，無 frame callbacks、setTimeout、microtask 排空。頁面載入時 document.fonts.ready 永遠懸掛。解決以暖身迴圈：載入中每 33ms 發 beginFrame（noDisplayUpdates: true），推進事件迴圈無產生幀：
```javascript
while (warmupRunning) {
  await cdp.send("HeadlessExperimental.beginFrame", {
    frameTimeTicks: warmupFrameTime,
    interval: 33,
    noDisplayUpdates: true,
  });
  warmupFrameTime += 33;
  await sleep(33);
}
```
暖身結束後，從超出範圍幀時開始真實擷取，避免時間倒退。

4. **Puppeteer waitForFunction 失效**：依賴 requestAnimationFrame 輪詢，beginFrame 模式下 rAF 不觸發，導致懸掛。改用自訂輪詢：
```javascript
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));
}
```

最終擷取迴圈確定性無懈：
```javascript
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);
}
```
無重試、無抖動幀。

**影片內嵌問題：預先解碼取代播放**

瀏覽器於渲染時播放 <video> 失效：無頭模式下 BeginFrame 導致解碼器跳幀、失敗或 readyState: 0 逾時；即使無 BeginFrame，不同機器/編解碼路徑產生不同輸出。

解決：擷取前 FFmpeg 預先將每個 <video> 提取為目標 fps 的編號 JPEG（如 5 秒 30fps 成 150 檔）。渲染時，為當前活躍影片注入 <img> 兄弟元素（data URI 載入正確幀），隱藏原 <video>：
```
<!-- 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__" />
```
注入 <img> 複製原元素計算樣式，確保 GSAP tween、CSS transform、opacity、object-fit 等無變：
```javascript
img.style.position = computedStyle.position;
img.style.transform = computedStyle.transform;
img.style.opacity = computedStyle.opacity;
img.style.objectFit = computedStyle.objectFit;
// ...約十餘項
```
動畫函式庫視為無變元素，僅幀切換如翻書。相較 Remotion（Rust 合成器即時解碼 HTTP 供 <OffscreenVideo>）、Replit（mp4box.js 瀏覽器 demux + WebCodecs 解碼至 canvas），HyperFrames 最簡：FFmpeg 預解碼存盤 JPEG，犧牲彈性（blob URL、串流、動態 src 較難）換短管道。目前基底運作優異，後續可改善。

**其他確定性陷阱**

控制時間與渲染解決大部分，非全部：

- **字型變異**：Google Fonts @import url(fonts.googleapis.com/...) 受網路影響不穩。編譯 HTML 時改寫為本地 base64 嵌入 @fontsource 複本，消除網路延遲與不穩。
- **時間量化**：30fps 每幀 33.3333ms，若 seek(0.0333333) 與 seek(0.0333334) 差異，需量化：
```javascript
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。

## 標籤

AIGC, 開源專案, Web, HyperFrames, Remotion
