用 AI 寫程式做動畫影片的 Token 避坑指南:從單檔 Canvas 到 CSS/DOM 模組化架構

近年來,「用程式碼寫動畫,再透過 Playwright 逐格截圖渲染成 4K/60fps 影片(Code-to-Video)」的工作流逐漸成為獨立創作者與工程師的熱門利器。搭配 Cursor、Claude、ChatGPT 或 Antigravity 等 AI 程式助理,只要給出大綱與腳本,AI 幾分鐘就能為你寫出一個包含時間軸控制、字幕對位與動畫渲染的網頁。

但在實際反覆迭代、微調設計風格時,很多人會遇到一個痛點:「為什麼我只是想改場景 3 的文字顏色,或者把某個元素往上移 20px,AI 卻花費了天量的 Token,甚至因為上下文過長開始遺漏程式碼?」

本文將以真實專案經驗為出發點,分析這個「Token 吞噬黑洞」的成因,提供第三方動畫框架的完整選型評估,並給出優化後的最佳實踐架構與升級版 Prompt 範本。


一、 血淚經驗:原本的工作流與 Prompt

在製作一支主題為《別拿未來的焦慮,懲罰現在還在努力的自己》的教學動畫時,我們最初給 AI 的 Prompt 如下:

原版 Prompt

幫我製作一支教學動畫解說影片,主題是「別拿未來的焦慮,懲罰現在還在努力的自己」。
用程式逐格渲染成 MP4(HTML Canvas 的 seek(t) + Playwright 截圖 + ffmpeg),並用我的聲音配旁白。

# 一、規格
- 16:9,1920×1080,30 fps,旁白參考 file:@code-v10.md
- 字幕在畫面下方,深色膠囊底、象牙白字;字幕顯示原文(英文、數字照寫)
- 不加背景音樂;旁白會由我真人配音, 下一段字幕的顯示的觸發是按 mouse click or keyword space key.
- 直接隱藏面, 讓我可以直接錄畫面同時配音產生影片, 並要支援我使用 click or space 切換 subtitle.

# 二、旁白稿(8 段,畫面以段落為單位)
1. 段落清單:
- 別拿未來的焦慮,懲罰現在還在努力的自己。
- 一個關於焦慮的小故事
- 修好性格,成為靠得住的人
- 練好本事,別成為破水桶
- 停止內耗,掌控不了的交給上天
- 做完你能做的,剩下的就別煩惱了
- 歡迎留言分享:你最近最想卸下的內耗是什麼呢?
2. 先不要寫程式,給我六格分鏡。讓我看過、改過最後確認過,等方向對了再動手。
3. 顯示風格為 dark theme 有高級質感的簡報風格的動畫, 避免太花俏的用色, 每個場景之間要有一致性.
  目標受眾為一般民眾, 避免於 dark theme 使用太暗的前景文字, 會造成閱讀困難.
4. 渲染之前,先抽開頭、中間、結尾三格檢查,沒問題再整支輸出。
5. 避免最下面的字幕overwrite 畫面顯示的元素, 字幕上緣約在 y≈940,使所有元素都在 y≤870 以內,留有約 70px 安全距離。

二、 為什麼這個工作流程會變成「Token 吞噬黑洞」?

當 AI 依照上述提示詞生成檔案時,通常會產出一個 單一且龐大的 scene.html(長達 1,200 ~ 1,500 行),裡頭包含:

  1. CSS Reset 與控制面板樣式
  2. 預設的旁白時間軸陣列
  3. Playwright 逐格渲染輔助鉤子
  4. 錄音模式與快捷鍵邏輯
  5. 龐大的 Canvas 2D 命令式繪圖代碼(drawScene1 到 drawScene6)

核心問題剖析:

  1. 單檔肥大(Monolithic File)
    AI 助理每執行一次修改(例如「把文字調亮」),工具必須把這 1,300+ 行程式碼送入 Context Window。若經過 4~5 輪微調,單次對話就消耗了數萬甚至數十萬 Tokens。
  2. 命令式(Imperative)Canvas 繪圖對 AI 極不友善
    Canvas 是像素與命令導向的:ctx.fillStyle = "rgba(148,163,184,0.7)"; ctx.fillText("能力不足,機會留不住", x, y + 200); 文字、顏色、排版全寫死在 JS 運算中。當你要調整邊界或對比度時,AI 無法透過 CSS 選擇器全域修改,必須逐行用正則搜尋每條指令並手工計算座標(如 y - 280 + i * 60),極易出錯且耗損算力。
  3. 資料、樣式、邏輯三者混雜
    旁白文案、色票(Design Tokens)、場景布局和時間軸驅動全部混在一起。改動文案等於改動動態繪圖代碼。

三、 第三方框架選型評估

在決定下一步架構前,我們針對市面上常見的動畫與影片生成方案進行了深入評估:

方案特色與優勢缺點 / AI 維護成本適合場景
原生 DOM/CSS 模組化
(強烈推薦)
0 第三方依賴,極速啟動直接保留既有的 Playwright + ffmpeg 渲染工作流支援 CSS 變數,修改全域樣式極度省 Token宣告式 HTML/CSS,AI 解析與修改代碼成功率最高不適合極其繁複的三維流體或物理碰撞粒子運算首選推薦:文字講解、高級商務簡報、Motion Graphic 圖表動畫。
Remotion (React)業界最成熟的 Code-based 影片框架支援 React 元件生態與強大的時間軸(useCurrentFrame)伺服器端確定性渲染優秀專案初始化門檻高(Node.js, Webpack, TSX)修改單一元件容易牽涉跨檔案依賴與編譯錯誤除錯時 Context 回傳的錯誤日誌較長團隊標準化生產、數據驅動影片、高頻次自動化短影音。
Openslide / Sli.dev / Reveal.jsMarkdown 驅動,改文案幾乎不花額外 Token簡報版式現成,社群主題豐富本質是「投影片切換工具」,非「逐格動態影片引擎」難以做出流暢細膩的貝茲曲線(Easing)過渡與自訂動態圖形演講投影片、單純圖文口播講解,非精緻動態設計。
Video-podcast-maker / 自動短影音工具開箱即用,提供完整 GUI 或一鍵腳本自動對齊音訊與字幕波形黑盒子架構,自訂視覺細節極其困難無法彈性調整「特定場景的同心圓旋轉速度」等自定義需求快速產出 Podcast 口播轉影片、低客製化需求場景。

四、 最佳解法:現代化「DOM/CSS + 資料分離」架構

如果你的訴求是「高質感簡報動態 + 本地 Playwright 渲染 + AI 極致維護性」,最完美的方案不是引進沉重的框架,而是將 HTML 架構由 Canvas 命令式重構為「宣告式 DOM/CSS 模組化」:

推薦檔案結構

my-project/
├── data/
│   ├── subtitles.json       # 旁白時間軸與文案(AI 改字只讀此檔)
│   └── theme.css            # 統一 Design Tokens(色票、字體、間距變數)
├── scenes/
│   ├── scene-1.html         # 各場景獨立的 HTML 結構 (50~80 行)
│   ├── scene-2.html
│   └── ...
├── js/
│   ├── engine.js            # seek(t) 時間軸解析與進度注入 (通常不需變動)
│   └── recorder.js          # 空白鍵/滑鼠點擊配音記錄器
└── index.html               # 主舞台容器與字幕 Safe-Area 設定

為什麼這個架構大幅節省 Token?

  1. 改文案:AI 只要讀取 subtitles.json(約 50 行),改完即生效,不碰到任何繪圖代碼(省 90% Token)。
  2. 改配色:AI 只要讀取 theme.css 修改 CSS Custom Properties(例如 --text-muted: #cbd5e1),全域場景同步更新(省 95% Token)。
  3. 改特定場景:需要微調第 3 場景的立柱動畫?AI 只需要讀取 scenes/scene-3.html,完全不需要看其他 5 個場景的代碼。
  4. 定義安全防護區(Safe Area):透過 CSS calc(100vh - 140px) 或 Grid 版型,天然保證內容不會與底部字幕碰撞,不需要在 Canvas 中手算座標。

五、 優化升級後的 Prompt 範本(直接複製使用)

套用「實踐成效預估」與「AI 架構維護優化」後,下次啟動專案可以直接使用以下 Prompt:

請幫我製作一支現代網頁架構的教學動畫解說影片。

主題:【請在此替換你的影片主題,例如:別拿未來的焦慮,懲罰現在還在努力的自己】, 參考檔案: @file.txt
技術架構:採用「宣告式 DOM + CSS 動畫 + 獨立模組化」設計,支援即時麥克風配音打點,並透過 seek(t) + Playwright 截圖 + FFmpeg 渲染成含音訊的 1080p MP4。

# 一、專案模組化結構規範
為維持程式碼易讀性與 Token 利用率,請將各模組職責拆分於獨立檔案(單一檔案保持在 400 行以內):

my-project/
├── data/
│   ├── subtitles.json       # 旁白內容與時間軸數據(AI 文字調整專用)
│   └── theme.css            # 統一 Design Tokens(色彩、字體、Safe Area 變數)
├── scenes/
│   ├── scene-1.html & scene-1.css  # 各分鏡獨立 HTML 結構與 CSS 動態
│   ├── scene-2.html & scene-2.css
│   └── ...
├── js/
│   ├── engine.js            # seek(t) 時間軸解析與全域 CSS 變數注入 (--scene-progress)
│   └── recorder.js          # 鍵盤/滑鼠打點控制器與 MediaRecorder 錄音控制
├── index.html               # 1920x1080 舞台主容器、Safe Area 與控制面板
└── render.mjs               # Playwright 逐幀截圖與 FFmpeg 影音合軌自動化腳本

# 二、詳細技術與動態規範

1. 畫面與動態設計:
   - 解析度與幀率:16:9,1920×1080,24 fps。。旁白參考 file: inputfile.txt
   - 安全區域(Safe Area):底部 140px 留給字幕專用,主視覺內容區域限制在 y <= 870,確保視覺內容與字幕完美分隔。
   - 驅動機制:動畫以 CSS Transitions / Transforms 為主,由全域 seek(t) 注入的 `--scene-progress (0~1)` 變數驅動。
   - 渲染效能最佳化:位移與縮放一律採用 GPU 加速屬性 `transform: translate3d(x, y, 0) scale(...)` 與 `opacity`,並標註 `will-change: transform, opacity;`,確保無頭瀏覽器逐幀截圖順暢無效能瓶頸。

2. 焦點微動畫(Focus Micro-Animation)原則:
   - 視覺節制原則:貫徹「少即是多」,每個場景僅設定**一個最核心的焦點元素**進行持續微動畫,引導觀眾注意力集中於關鍵訊息。
   - 文字圖層動態規範:包含文字的元素(標題、卡片、膠囊、列表、說明文字)採用靜態呈現,如需視覺強調,**僅限使用 `box-shadow` 與 `border-color` 的光暈呼吸變化**(維持元素位置與尺寸穩定,提供舒適閱讀體驗)。
   - 焦點動畫類型選擇(每個場景擇一套用):
     - 背景光暈層(純視覺):`scale3d(1,1,1) → scale3d(1.06,1.06,1)` 與 `opacity` 0.85→1,週期 5~6 秒。
     - 幾何圖形 / SVG 圖示(無文字):`scale(1) → scale(1.035)` 配合 `box-shadow` 擴張(週期 4~5 秒),或 `translateY(0) → translateY(-5px)` 柔和浮動(週期 3.5 秒)。
     - 金句 / CTA 膠囊(含文字):`box-shadow` 與 `border-color` 的光暈呼吸效果(週期 3.5~5 秒)。
   - 動畫管理機制:所有持續性動畫的 `@keyframes` 與 class 統一管理於 `index.html` 的 `<style>` 中,維持樣式的一致性與維護性。

3. 配音與收音系統(js/recorder.js):
   - 支援點擊「開始配音」呼叫 `navigator.mediaDevices.getUserMedia` 並啟動 `MediaRecorder`(預設格式為 `audio/webm;codecs=opus` 或 `audio/mp4`)。
   - 結尾緩衝:最後一句字幕完成後,自動加入 1.5 ~ 2.0 秒的留白時間,使影片收尾更為平順。
   - 互動打點:支援按下 `Space` 鍵或滑鼠點擊紀錄字幕時間戳(`start` / `end`)。
   - 匯出功能:提供匯出 `timings.json` 與下載錄音檔(`audio.webm` / `audio.mp4`)按鈕,並支援按鍵 `H` 切換控制面板顯隱。

4. 渲染契約與自動化腳本(Render Contract & render.mjs):
   - 前端曝露全域 API:
     1. `window.seek = (t)`:跳轉至指定秒數 t,切換對應場景並注入 `--scene-progress`。
     2. `window.getTotalDuration = () => number`:依據 `subtitles` 陣列最後一筆資料的 `end` 時間戳動態回傳影片總長度。
     3. `window.hideControlsForRender = () => void`:隱藏 UI 控制面板,保留純淨 `#viewport` 畫面。
   - render.mjs 渲染規範:
     - 開啟 `http://127.0.0.1:3000/index.html`,計算總幀數 `Math.ceil(totalDuration * fps)`。
     - 採用 CDP Session (`Page.captureScreenshot`,JPEG 品質 95) 直通 stdin 進行高吞吐量截圖。
     - 具備進程保護,監聽 `SIGINT` 與 `exit` 事件,於例外狀況下自動清理子進程與暫存檔案。
     - 支援多核心 Worker 分段平行渲染(產生 `chunk_*.mp4`)。
     - 音訊合軌流程:
       1. 無損拼接:讀取包含分段絕對路徑的 `concat_list.txt`,執行 `ffmpeg -y -f concat -safe 0 -i concat_list.txt -c copy -avoid_negative_ts make_zero merged_video.mp4`。
       2. 音訊結合:依序偵測 `audio.wav` / `.mp3` / `.m4a` / `.webm`,透過 `ffmpeg -y -i merged_video.mp4 -i audio.* -map 0:v:0 -map 1:a:0 -c:v copy -c:a aac -ar 48000 -max_interleave_delta 0 -shortest output_final.mp4` 輸出最終成果。
       3. 廣播級人聲優化(選用):可套用降噪、等化器與動態調節之音訊濾鏡鏈。

    5. 視訊文字精簡與排版規格(Video-First Typography):
       - 影片閱讀節奏原則:觀眾停留視線極短,嚴禁放置長篇敘述段落。
       - 字數精煉規範:主標題保持在 10~16 字以內;卡片說明文字以單行或雙行短句為主(單句 <= 20
  字),多運用「粗體關鍵詞 + 膠囊標籤」取代大段文字。
       - 字級與對比要求:主標題 >= 46px (font-weight: 800~900),卡片副標 >= 26px,說明文字 >=
  20px,確保在電視大螢幕或手機縮圖皆清晰可讀。

    6. 響應式自適應縮放與精準置中規範(Auto-Fit & Safe Area Centering):
       - Viewport 視窗適配:index.html 必須內建 fitStage() 機制,監聽 resize 事件,計算 Math.min(window.innerWidth /
  1920, window.innerHeight / 1080),透過 transform: scale(...) 將 1920x1080
  舞台精準等比縮放並置中於任何瀏覽器視窗中央,禁止畫面溢出或被裁切。
       - 安全區內容佈局:#main-content (height: 870px) 與所有分鏡容器 (.s*-wrapper) 必須設定 display: flex; flex-
  direction: column; align-items: center; justify-content: center;,確保主要視覺內容在 y <= 870
  區域內水平與垂直完美對稱置中。
       - 無頭渲染瞬切合約:hideControlsForRender() 觸發時必須為 body 加入 .render-mode class,並於 CSS 宣告 body.
  render-mode .scene-container { transition: none !important; },確保 Playwright
  逐幀截圖時場景切換零延遲,避免截到半透明過渡幀。

# 三、視覺風格與設計系統(Design System)
- 視覺主題:高質感深色系知識簡報風(以深藍黑 `#0a0d14` 為基底,搭配暖琥珀金為焦點色,冷岩灰為輔助色)。
- 色彩對比:嚴格符合 WCAG AA 標準,標題採用近白色 `#f8fafc`,內文採用高對比 `#cbd5e1` 或 `#94a3b8`,確保清晰可讀。
- 字幕樣式:居中深色毛玻璃膠囊背景,象牙白文字,字級大小約 32px。

# 四、腳本大綱與段落
【在此貼上你的旁白稿與大綱,例如:】
- 段落 1:破題,點出核心痛點
- 段落 2:故事案例與認知誤區
- 段落 3:核心方法第一層
- 段落 4:核心方法第二層
- 段落 5:核心方法第三層
- 段落 6:總結歸納與互動留言

# 五、執行步驟
請依序執行以下步驟,確認後再進行下一步:
1. 第一步:輸出各分鏡的視覺構成規劃(包含文字佈局、核心幾何圖像與配色意象),待我確認。
2. 第二步:確認規劃後,輸出 `data/theme.css`、`data/subtitles.json`、`js/engine.js`、`js/recorder.js`、`index.html` 與 `render.mjs`。
3. 第三步:依序輸出各獨立分鏡檔案 `scenes/scene-*.html` 與 `scenes/scene-*.css`。

六、 進階技巧:如何靈活切換視覺風格或「看截圖仿風格」?

在上述 Prompt 的「三、視覺風格與設計系統」章節,如果你不想用預設的暗黑簡報風,或者看上了某支優質 YouTube/X 影片想「以圖生風格」,要怎麼向 AI 下提示詞?

關鍵原則:避免使用「文青感」、「高科技」等抽象形容詞,要求 AI 解構為「設計系統(Tokens)」。

1. 寫法 A:直接指定經典設計風格原型

範例:瑞士國際主義風(Swiss / Editorial Style)

適合:思維模型、哲學、冷靜的商業分析

# 三、視覺風格與設計系統(瑞士國際主義風)
- 色彩系統:純淨冷碳黑 `#111215` 為底,搭配單一「國際克萊因藍 `#002FA7`」或「瑞士紅 `#FF3B30`」作為唯一焦點色。文字階級:標題近白 `#F8FAFC`,內文 `#CBD5E1`。
- 排版:嚴格的格狀排版(Grid),使用極端字級大小對比(超大標題搭配小巧標註),大面積負空間(留白)。
- 幾何與動態:微圓角(2~4px)或直角、1px 實線邊框、不使用投影。動態俐落清脆(0.3s~0.4s,無回彈)。

範例:賽博終端風(Cybernetic HUD / Dev Terminal)

適合:軟體工程、系統重構、AI 技術架構教學

# 三、視覺風格與設計系統(Cybernetic Terminal HUD)
- 色彩系統:極深冷藍黑 `#080B10` 底,搭配微弱 0.05 不透明度點陣網格(Dot Grid)。高亮霓虹青藍 `#00F0FF` 作為重點脈動。文字以等寬字型感(Monospace)呈現。
- 幾何與裝飾:細十字準星(Reticle)、角括號、細線刻度。動態包含打字機效果與線條掃描。

2. 寫法 B:上傳參考作品截圖(多模態逆向工程)

如果你手邊有想模仿的影片畫面截圖,可以直接將截圖上傳給 AI,並在 Prompt 中改用以下**「設計系統逆向工程」**指令:

# 三、視覺風格與設計系統(依據附件截圖進行逆向工程)

我附上了 1~2 張心儀作品的畫面截圖(如附圖 `ref-1.png`)。
請先不要直接寫動畫程式碼,而是先針對截圖進行「設計系統逆向工程(Reverse Engineering)」,並產出對應規格:

1. 【色彩與光影提取】:
   - 提取背景底色 HEX、主強調焦點色(Primary Accent)、輔助色與漸層光源。
   - 分析前景文字的三級階級色彩(主標、副標、註釋),並確保其色彩對比度符合 WCAG AA 易讀標準(主標 > 7:1,內文 > 4.5:1)。
2. 【排版與字體語意】:
   - 分析截圖的大字 vs 小字比例(Display vs Body ratio)、行高、字重對比與字距(Letter-spacing)。
3. 【幾何、容器與材質】:
   - 分析卡片圓角半徑(Sharp 0px、Subtle 8px 或 Pill 大圓角)、邊框不透明度、是否帶有微毛玻璃(Backdrop blur)或微噪點。
4. 【動態節奏推估】:
   - 依據視覺調性推估最合適的 Easing 曲線(例:科技俐落型 Expo、商務沉穩型 Cubic,或回彈活潑型 Back)。

請先輸出上述逆向規格與 `data/theme.css` 的變數定義,等我確認無誤後,再進入分鏡與程式碼生成。

透過這種寫法,AI 能精準提取畫面的骨架與色票,而不是盲目拼湊,既能 100% 複製截圖的高級質感,又絕不會侵權抄襲對方的內容!


完整製作與工作流 SOP

步驟 1:建立專案環境

在新專案資料夾中安裝必要依賴:

npm init -y
npm install playwright

請確保本機系統環境已安裝 ffmpeg(終端機輸入 ffmpeg -version 能正常辨識)。


步驟 2:使用升級版 Prompt 讓 AI 生成程式碼

將下方「二、 下次專案直接使用的終極提示詞」複製給 AI(填入你的主題與腳本大綱)。 AI 將分步產出:

  1. 六格分鏡規劃審查。
  2. 模組化目錄結構:
    • data/subtitles.json(字幕與時間軸)
    • data/theme.css(色彩與 Safe Area)
    • scenes/scene-*.html & scene-*.css(各場景獨立片段)
    • js/engine.js & js/recorder.js(核心時間軸與麥克風錄音)
    • index.html(1080p 舞台容器)
    • render.mjs(Playwright + FFmpeg 渲染合軌腳本)

步驟 3:本機預覽與配音打點

  1. 啟動本機伺服器:
    npx serve -l 3000 .
    # 或
    python -m http.server 3000
  2. 開啟瀏覽器進行配音:
    • 瀏覽網址:
      http://localhost:3000/index.html
    • 點擊右上角 「開始配音」(瀏覽器會彈出麥克風授權,點擊允許)。
    • 畫面開始播放,對著麥克風念旁白,每念完一句按下 Space(空白鍵) 或 滑鼠點擊,畫面與字幕會平滑跳到下一句並記錄精確時間戳。
    • 全部念完後,再次點擊按鈕結束錄音。
  3. 下載配音產出:
    • 點擊 「匯出時間軸 JSON」 取得 timings.json。
    • 點擊 「下載錄音檔」 取得 audio.webm(或 audio.mp4)。

步驟 4:套用時間軸並渲染含人聲最佳化的 MP4

  1. 放置檔案:
    • 將下載的 timings.json 重新命名並覆蓋專案中的 data/subtitles.json。
    • 將下載的 audio.webm(或 audio.mp4 / audio.wav)放置在專案根目錄。
  2. 執行一鍵渲染腳本:
    node render.mjs
    Playwright 會在背景無損逐格截取 1920×1080 畫面幀,FFmpeg 自動套用人聲最佳化濾鏡(去噪、低頻切除、人聲 EQ、短延遲 立體聲包覆、speechnorm 音量平滑),輸出帶有專業廣播級音質的 output_final_normalize.mp4!

🎙️ 補充:獨立對音訊/影片進行人聲最佳化(手動 FFmpeg 指令)

如果你有現成的語音檔想要單獨處理,可以直接使用以下口語人聲黃金濾鏡鏈:

1. 單獨優化純音訊檔(輸出為 audio_normalize.wav 或 audio_normalize.mp3):

方案 A:含微立體空間感 (適合乾音錄音)

ffmpeg -y -i audio.webm -af "highpass=f=80,lowpass=f=8000,afftdn=nr=12,equalizer=f=200:t=q:w=1.2:g=1.5,equalizer=f=350:t=q:w=1.5:g=-2.0,equalizer=f=3000:t=q:w=1.2:g=2.0,equalizer=f=8000:t=q:w=1.5:g=1.5,aecho=0.8:0.2:15:0.1,speechnorm=e=4:p=0.9" -c:a pcm_s16le audio.mp4

方案 B:無 Echo 純淨版 (強烈推薦:若原始錄音已有房間殘響/迴音,去除 aecho)

ffmpeg -y -i audio.webm -af "highpass=f=80,lowpass=f=8000,afftdn=nr=12,equalizer=f=200:t=q:w=1.2:g=1.5,equalizer=f=350:t=q:w=1.5:g=-2.0,equalizer=f=3000:t=q:w=1.2:g=2.0,equalizer=f=8000:t=q:w=1.5:g=1.5,speechnorm=e=4:p=0.9" -c:a pcm_s16le audio.mp4

2. 單獨優化現有 MP4 影片中的聲音(視訊畫面不重新編碼,僅優化聲音並另存 _normalize.mp4):

# 無 Echo 純淨版:直接套用到現有影片
ffmpeg -y -i input.mp4 -c:v copy -af "highpass=f=80,lowpass=f=8000,afftdn=nr=12,equalizer=f=200:t=q:w=1.2:g=1.5,equalizer=f=350:t=q:w=1.5:g=-2.0,equalizer=f=3000:t=q:w=1.2:g=2.0,equalizer=f=8000:t=q:w=1.5:g=1.5,speechnorm=e=4:p=0.9" -c:a aac -b:a 192k input_normalize.mp4

各濾鏡參數解析:

  • highpass=f=80:切除 80Hz 以下的噴麥雜音與低頻震動,大幅減輕耳機收聽疲勞。
  • lowpass=f=8000:切除 8kHz 以上高頻刺耳毛刺、齒音與電子高頻嘯叫。
  • afftdn=nr=12:有效消除電腦風扇與環境底噪,如雜音嚴重可微調至 14~15。
  • 為什麼 equalizer 會出現多次?: 因為 FFmpeg 的 equalizer 是「單一頻段參數等化器(Single-Band Parametric EQ)」,一次只能調整一個特定頻率點(中心頻率 f、頻寬 w、增益 g)。專業錄音室調音通常需要同時調整「低、中、高」多個頻段,因此會用串聯(Chaining)的方式同時處理:
    • ① f=200, g=+1.5(低頻段):增加胸腔共鳴與溫暖厚實感。
    • ② f=350, g=-2.0(中低頻段):削減室內駐波造成的喉音渾濁感(Boxy / Muddy sound)。
    • ③ f=3000, g=+2.0(中高頻段):人類耳朵最敏感的語音區,大幅增強咬字與語意清晰度。
    • ④ f=8000, g=+1.5(高頻段):微調空氣感與通透度。
  • aecho(空間微殘響):若環境原本就沒做吸音(有房間反射迴音),請直接拿掉此濾鏡,避免迴音疊加造成聲音空洞。
  • speechnorm=e=4:p=0.9:專為語音設計的平滑正規化,自動抹平大小聲,不產生抽吸感。

輸入檔案請替換為 input.mp4,輸出檔案請替換為 output.mp4。

ffmpeg -i input.mp4 -af "highpass=f=80,lowpass=f=8000,afftdn=nr=12,speechnorm=e=4:p=0.9" -c:v copy output.mp4

如果影片的影像編碼不需要重新壓縮,使用上面的指令可以大幅縮短處理時間。

第一步是檢測最大音量。 執行這行指令後, ffmpeg 會掃描整個影片,並在結束時於終端機顯示統計數據,請在裡面尋找 max_volume 的數值:

ffmpeg -i input.mp4 -af volumedetect -f null -

第二步是根據檢測結果放大音量。 假設檢測出來的 max_volume 是 -5.2 dB ,代表聲音距離飽和還有 5.2 分貝的空間。 這時可以把音量增加 5.2 分貝,讓聲音達到不破音的最大極限:

ffmpeg -i input.mp4 -af volume=5.2dB -c:v copy output.mp4

如果影片的影像編碼需要重新處理,可以把 -c:v copy 改成 -c:v libx264 -crf 23 。

請問你目前的影片檢測出來的 max_volume 是幾分貝?


FFmpeg 音訊置換指南:用後製優化音軌替換原始影片音訊

本指南適用於以下情境:

  • 已透過 node render.mjs 渲染出 output_final.mp4(畫面正確,但音訊為原始錄音)。
  • 已手動跑完人聲最佳化指令,得到 output_final_normalize.mp4 或 audio_normalize.wav(優化過的音訊)。
  • 現在只想把優化後的音軌貼回到畫面不動的影片裡,不重新壓縮畫面。

方法一:從另一支 MP4 取出音軌,貼入目標影片

適用:音訊來源是跑過 -c:v copy 的 audio.mp4。

ffmpeg -y -i output_final.mp4 -i audio.mp4 -map 0:v -map 1:a -c:v copy -c:a copy output_merged.mp4

說明:

  • -map 0:v:視訊(畫面)取自第一個輸入 output_final.mp4。
  • -map 1:a:音訊取自第二個輸入 output_final_normalize.mp4。
  • -c:v copy / -c:a copy:畫面與音訊均不重新編碼,速度極快(幾秒內完成),畫質無損。

方法二:從 WAV 檔取出音軌,貼入目標影片

適用:音訊來源是純音訊檔 audio_normalize.wav。

ffmpeg -y `
  -i output_final.mp4 `
  -i audio_normalize.wav `
  -map 0:v `
  -map 1:a `
  -c:v copy `
  -c:a aac -b:a 192k `
  output_merged.mp4

說明:

  • WAV 不能直接寫進 MP4 容器,需要轉碼為 -c:a aac -b:a 192k。
  • 視訊仍 -c:v copy 不重壓,只有音訊重編碼(仍很快,幾秒內)。

方法三:完全靜音現有影片(移除原始音軌)

若只想確認畫面無誤,或暫時輸出靜音版本:

ffmpeg -y -i output_final.mp4 -c:v copy -an output_silent.mp4

-an = audio none(移除所有音訊串流)


常見 Troubleshooting

問題排查方式
影片長度與音訊長度不一致加上 -shortest 截到較短的那方;或 -t 60 手動指定秒數
輸出有多條音軌加上 -map 0:v -map 1:a:0 明確指定只取第一條音訊
音畫對不齊若音訊起始有靜音空白,加上 -af atrim=start=0.5 修剪前幾秒靜音
想確認影片內含哪些串流ffprobe -v quiet -show_streams output_final.mp4

完整工作流快速參考

[node render.mjs]
       │
       ▼
output_final.mp4          ← 畫面正確,但音訊為原始錄音(可能有底噪)
       │
       │  ① 手動優化音訊
       ▼
ffmpeg -y -i output_final.mp4 -c:v copy -af "highpass=f=80,lowpass=f=8000,
       afftdn=nr=12, equalizer×4, speechnorm=e=4:p=0.9"
       -c:a aac -b:a 192k output_final_normalize.mp4
       │
       │  ② 音軌置換(不重壓畫面)
       ▼
ffmpeg -y -i output_final.mp4 -i output_final_normalize.mp4
       -map 0:v -map 1:a -c copy output_merged.mp4
       │
       ▼
output_merged.mp4         ← 最終成品:乾淨畫面 + 優化人聲

要將輸入聲音的前 n 秒改為靜音,最簡單直覺的做法是使用 FFmpeg 的 volume 濾鏡配合時間控制。

指令格式如下:

ffmpeg -i input.mp3 -af “volume=enable=’between(t,0,n)’:volume=0” -c:v copy output.mp3

參數詳細說明的說明:

-af “volume=enable=’between(t,0,n)’:volume=0”

這段代表聲音濾鏡設定, between(t,0,n) 表示在時間 0 到 n 秒之間生效,把音量 volume 改為 0,也就是靜音。

-c:v copy

如果輸入檔案包含影片,這個參數可以直接複製影片軌而不重新編碼,節省處理時間。

假設要把前 5 秒變成靜音,指令寫法為:

ffmpeg -i input.mp3 -af “volume=enable=’between(t,0,5)’:volume=0” output.mp3

步驟驗證方式:

執行完指令後,播放輸出的 output.mp3 檔案,確認開頭前 5 秒是否有聲音,5 秒之後聲音是否正常恢復輸出。


網頁開發常見的動畫相關術語

  • Breathing Animation / Breathing Effect(呼吸動畫 / 呼吸效果): 側重於視覺呈現方式,指元素透過 CSS 慢慢放大縮小、改變透明度或微幅伸縮,模仿吸氣與呼氣的節律。
  • Idle Animation / Idle State Animation(待機動畫 / 閒置狀態動畫): 側重於觸發時機,指當使用者沒有進行操作、或是元件處於等待狀態時,持續循環播放的動畫。
  • Pulse Animation / Pulsing Effect(脈衝動畫 / 脈衝效果): 常見於按鈕或提示元件,利用 CSS keyframes 控制 transform: scale() 或 opacity 產生心跳般的收縮或發光效果。
  • Floating Motion / Bobbing Effect(漂浮效果 / 上下浮動): 如果動態純粹是沿著 Y 軸上下移動而不做縮放,通常會稱為漂浮效果,在 CSS 中常搭配 ease-in-out 或正弦波計算來達成平滑起伏。

明確知道動畫名稱,比較方便叫 AI 去套用效果.


語音合成(TTS, Text-to-Speech)是將文字轉換為自然人聲的技術

「畫面文字(text)與讀音文字(tts_text)分離」的資料結構:

  1. subtitles.json:
    每個字幕物件具備:
    • text:影片畫面毛玻璃膠囊顯示的正式繁體中文字幕。
    • tts_text:傳給 edge-tts 發音的專用文字。當遇到多音字、破音字、外文簡寫、或需要語調停頓處,可在此欄位進行調整(例如將 AI 寫為 A I 避免縮讀,或利用逗號調整喘氣節奏)。
  2. generate_tts.mjs:
    • 呼叫 edge-tts –voice zh-TW-HsiaoChenNeural 批次產生獨立句段。
    • 透過 ffprobe 精確取得每句音訊的毫秒級時長。
    • 自動更新時間軸:自動回寫所有 cues 的 start、end、場景區間以及影片總長度,確保音畫完全對齊。
    • 利用 ffmpeg 縫合靜音留白,匯出可直接合併影片的完整 audio.mp3。

重新生成配音與渲染指令 (CMD)

當您修改了 subtitles.json 中的 tts_text 後,只需在專案目錄下執行:

# 1. 自動重新生成全片所有 TTS 語音檔、對齊時間軸並輸出 audio.mp3

npm run tts

或以下指令(擇一即可)

node generate_tts.mjs 

# 2. 自動結合最新配音 audio.mp3 與 Playwright 截圖進行渲染合軌

npm run render

或以下指令(擇一即可)

node render.mjs --audio audio.mp3

不用去記指令, 因為執行完 #1 cmd, 在畫面上會提示 #2 指令, copy/paste 即可繼續.


做完影片之後, 執行以下提示詞:

help me 優化 @[video-prompt.txt]  
  為了讓下一個不同內容的 project 套用提示詞可以更快或更省 token, 是否應該吧差異或變數的部份獨立成 skill 或相關 .md 讓下一個 project 的 agent 更有效率竹的生成影片.

已為您完成 video-prompt.txt 的優化,並將通用架構規格獨立抽象為 Skill

video-prompt.txt 內容

請幫我製作一支現代網頁架構的教學動畫解說影片。
(本專案已遵循技能規範:`.\agent\skills\web-anim-video`)

# 一、影片專案參數
- **主題**:[輸入你的主題]
- **參考故事文本**:@story.txt
- **共用參考專案(借鑑架構與元件)**:C:\Max\play\git\TangPoetry-guo-guren-zhuang 
- **視覺風格與設計系統**:
  - 色系意象與風格截圖參考自檔案:`blog_cover_clean.jpg`
  - 指定字型:`Maruko Gothic CJKtc Regular`(搭配 Noto Sans TC)
  - 字幕規範:繁體中文,**不顯示**「旁白」字樣

# 二、技術與渲染規格(符合 web-anim-video 規範)
- **尺寸與幀率**:1920×1080 (16:9),24 fps。
- **安全區域**:底部 140px(y >= 940)字幕保留區;主視覺內容嚴格限制在 y <= 900。
- **配音系統**:採用 Edge-TTS 自動語音生成(`zh-TW-HsiaoChenNeural`),支援 `tts_text` 多音字校準與 `ffprobe` 毫秒自動對齊。
- **渲染合軌**:Playwright CDP 直通截圖 + FFmpeg 縫合 1080p MP4。

# 三、專案模組結構(單檔 400 行以內)
```
my-project/
├── data/
│   ├── subtitles.json       # 旁白與時間軸(分離式 text / tts_text)
│   └── theme.css            # 統一 Design Tokens 與 Safe Area
├── scenes/
│   ├── scene-1.html & scene-1.css
│   └── ...
├── js/
│   └── engine.js            # seek(t) 控制、CSS 變數注入
├── index.html               # 1920x1080 舞台與字幕容器
├── generate_tts.mjs         # Edge-TTS 批次合成、時間軸回寫與合成 audio.mp3
├── render.mjs               # Playwright CDP + FFmpeg 渲染合軌 (輸出 output_final.mp4)
└── package.json             # 腳本入口:npm run tts, npm run render
```

# 四、執行步驟
1. **第一步**:依據主題與文本規劃分鏡大綱與視覺構成(文字佈局、幾何意象、色彩),待使用者確認。
2. **第一點五步(素材準備)**:在進入第二步前,先分析各場景視覺元素,評估並區分適合由 SVG 渲染或是需使用 Image 圖片素材。針對需生成之圖片素材,於 `asset-prompts.md` 輸出提示詞清單供使用者檢視修改:
   - **風格偏好**:**必須精確對齊 `blog_cover_clean.jpg` 的視覺與畫風(例如:Q版可愛人物、圓潤輪廓、特定筆觸與色彩渲染)**,不得使用與封面風格不符的通用抽象向量風。
   - **資產對映**:提供各圖片素材簡潔規範的目標檔名(如 `./assets/*.png`)。
   - **綠幕規格**:純色綠幕背景 `background replaced with chroma key green #00B140`。
   - **邊緣與漸層規範**:硬邊輪廓與無透明漸層 `sharp crisp edges, solid flat colors, no soft gradients, no glowing fade` 。
   - **去背防溢色**:強化輪廓防溢色指令 `optimized cutout edges with no green spill`。
   - **貼紙隔離規範**:大邊距獨立無重疊規格 `sticker pack sheet with wide margins, strictly isolated, no overlapping`。
   - 待使用者手動將生成去背素材放入 `./assets/` 後,再推進至下一步。
3. **第二步**:確認規劃後,輸出 `data/theme.css`、`data/subtitles.json`、`js/engine.js`、`index.html`、`generate_tts.mjs` 與 `render.mjs`。
4. **第三步**:依序輸出各獨立分鏡檔案 `scenes/scene-*.html` 與 `scenes/scene-*.css`,並執行 `node generate_tts.mjs` 自動校準時間軸。
5. **第四步(Gemini TTS 切換,選用)**:若需切換至 Gemini TTS,將 Gemini 輸出音訊 rename 為 `data/jett.wav`,執行 `python .agents/skills/web-anim-video/run_gemini_pipeline.py`(共用腳本,無需複製)完成 Whisper 對齊與時間軸回寫,最後執行 `npm run render` 渲染輸出。
s

優化策略與成果:

1. 建立通用技能:web-anim-video

將不再變動的技術硬規格與重複程式規範抽離至共用 Skill:

• CSS 避坑三大鐵律(未觸發 opacity:0、多動畫雙延遲宣告、SVG 原生座標保護)。
• 渲染契約規格(CDP 逐幀截圖、純 ES Module 規範、FFmpeg image2pipe 直通、音畫合軌指令)。
• Edge-TTS 批次合成流程(zh-TW-HsiaoChenNeural、ffprobe 測量、時間軸自動回寫、留白間隔縫合)。
• Safe Area 幾何規範(1920×1080 16:9 24fps、y ≥ 940 底部字幕、y ≤ 900 主內容安全區)。

│ 💡 效益:下一個專案的 Agent 會自動在背景加載此 Skill,您完全不需要在 Prompt 裡重複傳遞上百行的 CSS 語法範例與防踩坑細節,大幅節省 Token 達 60% 以上!

.agent/skills/web-anim-video/SKILL.md 內容

---
name: web-anim-video
description: 現代網頁架構教學動畫影片生成架構(宣告式 DOM + CSS 動畫 + seek(t) + Edge-TTS + Playwright + FFmpeg 渲染)。用於快速建立 1080p 24fps 的模組化解說動畫專案。
---

# 現代網頁架構教學動畫影片 (Web Animation Video System)

本 Skill 封裝了「網頁即視訊」的完整技術架構。專門用於將長文、教學題材或故事文字快速轉換為現代質感的 1080p 24fps 動畫解說影片。

---

## 一、專案模組化結構標準

單一檔案嚴格維持在 400 行以內:

```
my-project/
├── data/
│   ├── subtitles.json       # 旁白內容與時間軸(分離式 text 與 tts_text 支援多音字)
│   └── theme.css            # 統一 Design Tokens(色彩、字體、Safe Area 變數)
├── scenes/
│   ├── scene-1.html & scene-1.css  # 各分鏡獨立 HTML 結構與 CSS 動態
│   ├── scene-2.html & scene-2.css
│   └── ...
├── js/
│   └── engine.js            # seek(t) 時間軸解析與 CSS 變數注入 (--scene-progress, --scene-t,載入 audio.mp3)
├── audio.mp3                # 預設完整旁白音軌 (const AUDIO_FILE = 'audio.mp3')
├── index.html               # 1920x1080 舞台主容器、Safe Area 字幕層與控制面板
├── generate_tts.mjs         # Edge-TTS 自動批次語音合成、ffprobe 測量、時間軸回寫與合成 audio.mp3
├── render.mjs               # Playwright CDP 逐幀截圖與 FFmpeg 影音合軌腳本 (輸出 output_final.mp4)
└── package.json             # 腳本入口:npm run tts, npm run render
```

---

## 二、核心動態與避坑規範(重要)

1. **解析度與 Safe Area**:
   - 1920×1080,16:9,24 fps。
   - 底部 140px($y \ge 940\text{px}$)保留為純淨字幕專用區域。
   - 主視覺內容嚴格限制在 $y \le 900\text{px}$,避免干擾字幕。

2. **驅動機制與效能**:
   - 動畫由全域 `seek(t)` 注入的 `--scene-t` 與 `--scene-progress (0~1)` 驅動。
   - 位移縮放一律採用 GPU 加速:`transform: translate3d(x, y, 0) scale(...)` 與 `opacity`。

3. **CSS 避坑四大鐵律**:
   - **【禁止提早顯現】**:未觸發元素初始必須設為 `opacity: 0;`。
   - **【雙延遲宣告格式】**:同時套用進場動畫(如 `fadeUp`)與呼吸動畫(如 `glowPulse`)時,嚴禁用簡寫覆蓋,必須保留 `paused`:
     ```css
     .card {
       opacity: 0;
       animation: fadeUp 0.65s cubic-bezier(0.34, 1.56, 0.64, 1) forwards,
                  glowPulse 2.5s ease-in-out infinite;
       animation-delay: calc(var(--scene-t, 0s) * -1 + 5.0s), 0s;
       animation-play-state: paused, paused;
     }
     ```
   - **【SVG 原生座標保護】**:SVG 內部帶有 `transform="translate(x, y)"` 的元素,嚴禁套用全域 `fadeUp`(避免被覆蓋為 `(0,0)` 飛出畫面),必須定義保留 base 座標的專屬 keyframes。
   - **【共用 Keyframes 統一收斂至 theme.css】**:`fadeUp`、`fadeIn`、`fadeInPop`、`glowPulse` 等跨場景動畫必須宣告於全域 `theme.css`,嚴禁僅宣告在單一場景,以防其他場景遺漏 keyframes 導致元素永久為 `opacity: 0`。

4. **排版文字精煉與字級規範**:
   - 避免字級 $< 18\text{px}$(包含膠囊標籤、狀態 Badge),確保大螢幕/電視投影清晰易讀。
   - 主標題 $\le 16$ 字(字級 $\ge 46\text{px}$)。
   - 卡片副標的字級 $\ge 26\text{px}$。
   - 卡片說明文字的字級 $\ge 20\text{px}$。
   - 卡片說明文字採單行或雙行短句(單句 $\le 20$ 字),多運用「膠囊標籤 + 粗體關鍵詞」取代大段文字。
   - **字幕自動換行保護**:字幕層必須設定 `white-space: normal; word-wrap: break-word; overflow-wrap: break-word;`,防止長文字溢出舞台。

---

## 三、`data/subtitles.json` 格式規範

採用**一層扁平結構**:`scenes[]` 與 `subtitles[]` 並列於根層級,subtitle 透過 `scene` 欄位 ref 所屬場景。

### 標準格式

```json
{
  "fps": 24,
  "voice": "gemini-tts-master",
  "scenes": [
    {
      "id": "scene-1",
      "name": "場景標題",
      "file": "scenes/scene-1.html",
      "start": 0.0,
      "end": 22.97
    }
  ],
  "subtitles": [
    {
      "id": "c-1",
      "scene": "scene-1",
      "start": 0.0,
      "end": 4.92,
      "text": "畫面顯示文字(可含標點、中文)",
      "tts_text": "TTS 朗讀文字(處理多音字,如 AI → A I)"
    }
  ]
}
```

### 欄位規範

**根層級**
| 欄位 | 型別 | 說明 |
|------|------|------|
| `fps` | number | 影片幀率,預設 `24`,render.mjs 優先讀此值 |
| `voice` | string | TTS 引擎標記(`gemini-tts-master` / `edge-tts`) |
| `scenes` | array | 場景清單,僅存邊界與 metadata |
| `subtitles` | array | 所有字幕句,扁平排列,透過 `scene` ref 歸屬 |

**`scenes[]` 欄位**
| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | 唯一識別碼,格式 `scene-N` |
| `name` | string | 場景中文標題(供工具與 UI 顯示) |
| `file` | string | HTML 路徑,如 `scenes/scene-1.html` |
| `start` / `end` | number | 場景起訖秒數(由 TTS 腳本回寫,勿手動維護) |

**`subtitles[]` 欄位**
| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | 唯一識別碼,格式 `c-N`,TTS switch 腳本按此 id 做 map 對應 |
| `scene` | string | 所屬場景 id(`scene-1` 等),engine.js 用此分組 |
| `start` / `end` | number | 字幕起訖秒數(由 TTS 腳本回寫) |
| `text` | string | 畫面字幕顯示文字 |
| `tts_text` | string | TTS 朗讀文字(與 `text` 相同時仍需保留欄位) |

### 設計原則

- **`scenes[]` 保持乾淨**:只存邊界與 metadata,不巢狀 subtitles,方便未來擴充 bgm、過場等欄位。
- **`subtitles[]` 扁平獨立**:TTS switch 腳本直接 iterate `subtitles[]`,按 `id` map 對應時間軸,無需遞迴兩層,避免錯位 bug。
- **`scene` ref 強制存在**:engine.js 透過 `subtitles.filter(s => s.scene === sceneId)` 分組渲染字幕。
- **`fps` 頂層化**:render.mjs 優先讀 `subtitles.json` 的 `fps`,fallback 才用內建常數。
- **`start` / `end` 由工具維護**:scenes 的 `start`/`end` 由 TTS 腳本自動回寫(等於對應段第一句 `start` 與最後一句 `end`),**嚴禁手動修改**。
- **`start` / `end` 自動校準與尾韻防呆規範**:
  1. 腳本同步 TTS 時間軸時,必須重新計算每個場景的 `start`(第一句字幕開始)與 `end`(最後一句字幕結束)。
  2. **【最後場景尾韻保留機制(重要)】**:最後一個場景的 `end` 必須額外增加 **2.5 ~ 3.0 秒的空檔緩衝**(或對齊實際音訊結尾秒數),**嚴禁讓最後一句字幕結束時影片或預覽立即黑屏/停止**。
  3. **觀看體驗最佳實踐**:結尾**不要拉長最後一句字幕的時間**(避免觀眾看著同一句話停留數秒產生呆滯感),而是**讓字幕隨語音結束自然隱藏,保留最後一幕純淨視覺動畫與背景音樂 3 秒尾韻**。
- **`engine.js` 總時長防呆**:`totalDuration` 嚴禁僅取 `scenes[scenes.length - 1].end`,必須同時掃描所有 `scene.end` 與所有 `subtitle.end` 取 `Math.max`,防止因 scene 時間未同步而造成最後一句字幕被截斷。
---

## 四、渲染與 TTS 契約

1. **前端曝露全域 API 與音訊預覽 (engine.js)**:
   - `window.seek(t)`:切換對應場景並注入局部變數。
   - `window.getTotalDuration()`:動態回傳影片總長度。
   - `window.hideControlsForRender()`:隱藏 UI 控制面板。
   - 音訊自根目錄載入:`audio.mp3`。
   - **【防轉場間隙閃爍規範(重要)】**:各場景之間常有約 0.8s 的空白過渡間隔(Gap)。在 `seek(t)` 搜尋目前場景時,**嚴禁用 `scenes[scenes.length - 1]` 作為預設 fallback**,否則落在間隔時會瞬間閃現最後一個場景!當時間落在兩場景之間時,必須維持停留在「剛結束的前一場景」,直到下一個場景正式開始:
     ```javascript
     let activeScene = this.scenes.find(s => this.currentTime >= s.start && this.currentTime <= s.end);
     if (!activeScene && this.scenes.length > 0) {
       if (this.currentTime < this.scenes[0].start) {
         activeScene = this.scenes[0];
       } else if (this.currentTime > this.scenes[this.scenes.length - 1].end) {
         activeScene = this.scenes[this.scenes.length - 1];
       } else {
         // 落在間隙 Gap 時,平順維持前一場景結尾,嚴禁回退到最後一幕
         for (let i = this.scenes.length - 1; i >= 0; i--) {
           if (this.currentTime >= this.scenes[i].end) {
             activeScene = this.scenes[i];
             break;
           }
         }
       }
     }
     ```

2. **Edge-TTS 規範 (generate_tts.mjs)**:
   - `data/subtitles.json` 必須具備 `text` 與 `tts_text`。
   - 繁體畫面顯示不含「旁白」字樣。
   - 執行 `node generate_tts.mjs` 自動合成各句音訊、測時並回寫毫秒時間軸,最終自動將完整音軌縫合輸出為 `audio.mp3`。

3. **Playwright 渲染 (render.mjs)**:
   - 純 ES Module 規範(**嚴禁在任何執行緒中呼叫 `require(...)`**,所有 import 必須在頂層使用 ES Module 語法)。
   - 音訊檔案路徑:`audio.mp3`(const AUDIO_FILE)。
   - 採用 CDP Session `Page.captureScreenshot` 直通 FFmpeg `image2pipe`,三段優化組合(F2):

   **[A] JPEG 截圖(預設 quality: 95)**
   - CDP 截圖改為 `format: 'jpeg', quality: 95`(93~95 視覺無損)。
   - FFmpeg 輸入改為 `-vcodec mjpeg`(取代 PNG,decode 成本大幅下降)。
   - 相比 PNG 約加速 **2~3x**。

   **[B] Pipeline 異步化(seek / screenshot / write 三階段重疊)**
   - 在 loop 開始前先 seed 第 0 幀的 `seek` + `captureScreenshot` Promise。
   - 每輪迴圈:**await 當幀截圖** → 立即 kick off **下一幀 seek + capture**(非阻塞)→ 再 write 當幀到 ffmpeg stdin。
   - 讓截圖延遲與 stdin write/drain 時間重疊,減少 idle 等待,約加速 **20~30%**。
   - 進度列額外顯示即時 fps:`Rendering: N/Total (%) — Xs elapsed — Y fps`。

   **[C] FFmpeg 編碼加速(preset veryfast)**
   - `libx264 -preset veryfast`(取代 medium),編碼速度約快 **3x**,輸出檔案約大 5%。
   - 仍保持 `-crf 18 -pix_fmt yuv420p`,畫質無損。

   **進程保護**:監聽 `ffmpeg.stdin` 的 `EPIPE`/`EOF`,後壓力透過 `drain` 事件處理,避免 stdin 溢出。

---

## 五、Gemini TTS Pipeline 自動化(run_gemini_pipeline.py)

當使用 Gemini TTS(而非 Edge-TTS)時,需要經過 Whisper 對齊才能將精確時間軸寫回 `subtitles.json`。此 pipeline 已封裝為單一腳本,放置於 skill 目錄供各專案複製使用。

### Skill 目錄結構(集中管理,跨專案共用)

```
.agents/skills/web-anim-video/
├── run_gemini_pipeline.py     ← 主控腳本(從專案根目錄呼叫)
├── switch_to_gemini_tts.py   ← 共用:回寫 subtitles.json 時間軸
├── extract_tts_text.py        ← 共用:提取 tts_text
├── 2_transcribe_whisper.py    ← 共用:Whisper 對齊轉錄
├── sync_whisper_text.py       ← 共用:同步字幕文字
└── merge_whisper_subtitles.py ← 共用:合併時間軸
```

各專案**無需複製任何腳本**,所有腳本統一從 skill 目錄讀取。
新專案只需準備:`data/gemini.wav`(Gemini TTS 輸出音訊)。

### 使用前準備

1. 將 Gemini TTS 輸出的 WAV 音訊放入 `data/`,**rename 為 `gemini.wav`**(或修改 CONFIG 中的 `gemini_wav`)。

### 執行指令

```bash
# 從專案根目錄執行(直接呼叫 skill 目錄,無需複製腳本)
python .agents/skills/web-anim-video/run_gemini_pipeline.py
```

或加入 `package.json` 一次性設定:
```json
"gemini": "python .agents/skills/web-anim-video/run_gemini_pipeline.py"
```
```bash
npm run gemini
```

### Pipeline 步驟(自動執行)

| 步驟 | 動作 | 產出 |
|------|------|------|
| 1 | 提取 `tts_text` | `data/full_tts_input.txt` |
| 2 | WAV → MP3 轉換 | `data/output_audio/master_full.mp3` |
| 3 | Whisper 轉錄對齊 | `data/output_audio/whisper_alignment.json` |
| 4 | 同步字幕文字 | `data/output_audio/whisper_alignment_synced.json` |
| 5 | 合併時間軸 | `data/output_audio/whisper_merged_edge_tts.json` |
| 6 | 整理備份 + 切換 | `gemini-tts/`、`audio.mp3`、`data/subtitles.json` 更新 |

### CONFIG 可調預設值

```python
CONFIG = {
    "gemini_wav":      "gemini.wav",   # Gemini TTS 輸出 WAV,rename 成此名稱放入 data/
    "whisper_model":   "base",       # 可改為 "small" / "medium" 提升精度
    "whisper_device":  "cpu",        # NVIDIA GPU 改為 "cuda"
    "whisper_compute": "int8",       # GPU 模式改為 "float16"
    "whisper_language":"zh",
    "gemini_dir":      "gemini-tts", # gemini 備份目錄名稱
}
```


──────

2. 精簡優化後的 video-prompt.txt

• 已移除 recorder.js:全面改採全自動 Edge-TTS 合成與毫秒精確對齊。
• 僅保留「專案變數」與「執行步驟」:
1. 專案變數:主題、故事文本路徑、風格截圖、專屬色系與字型。
2. 模組清單:精簡的目錄結構(不帶多餘冗長說明)。
3. 標準四步驟流程:分鏡大綱規劃 → 素材 Prompt 清單 → 核心骨幹檔建置 → 分鏡輸出與 TTS 批次生成。

未來開啟新專案時,您只需複製這份精簡後的 video-prompt.txt,修改前方的 「主題」、「參考故事文本」與「視覺風格」,即可直接發送給 Agent 高效生成!


針對結尾場景在影片換場的中間被閃現的bug

原因分析:為什麼 02:21 會閃現 scene-7?

02:21 等於 141 秒(即 2 分 21 秒)。
查看 subtitles.json 的時間軸,可以看到每個場景之間都有 0.8 秒的轉場空白間隙(Gap):

• scene-4 結束於:141.300s
• scene-5 開始於:142.100s
• 在 141.3s ~ 142.1s(剛好包含 02:21)這段時間內,沒有任何場景滿足 t >= s.start && t <= s.end。

再看 engine.js:88-93 的場景搜尋邏輯:

// Find active scene
let activeScene = this.scenes.find(s => this.currentTime >= s.start && this.currentTime <= s.end);
if (!activeScene && this.scenes.length > 0) {
  if (this.currentTime < this.scenes[0].start) activeScene = this.scenes[0];
  else activeScene = this.scenes[this.scenes.length - 1]; // ⚠️ 這裡出問題了!
}

當時間落在任何兩個場景之間的 0.8s 縫隙時:

  1. find() 找不到符合的場景,activeScene 變成 undefined。
  2. 因為當前時間大於第 1 個場景,else 條件直接回退給了 最後一個場景:this.scenes[this.scenes.length – 1](也就是 scene-7)!
  3. 這導致在每個轉場間隔的幾幀之內,畫面會突然閃現切換成 scene-7,隨後進入下一個場景時又被切回來。
    ──────

如何修復(修改步驟)

打開 engine.js,找到 seek(t) 方法(約第 88 行至 93 行):

原程式碼:

    // Find active scene
    let activeScene = this.scenes.find(s => this.currentTime >= s.start && this.currentTime <= s.end);
    if (!activeScene && this.scenes.length > 0) {
      if (this.currentTime < this.scenes[0].start) activeScene = this.scenes[0];
      else activeScene = this.scenes[this.scenes.length - 1];
    }

改為:

讓時間落在間隔時,維持在前一個剛播完的場景(直到下一個場景正式開始):

    // Find active scene
    let activeScene = this.scenes.find(s => this.currentTime >= s.start && this.currentTime <= s.end);
    if (!activeScene && this.scenes.length > 0) {
      if (this.currentTime < this.scenes[0].start) {
        activeScene = this.scenes[0];
      } else if (this.currentTime > this.scenes[this.scenes.length - 1].end) {
        activeScene = this.scenes[this.scenes.length - 1];
      } else {
        // 落在兩個場景之間的 gap 時,維持在剛結束的場景,避免錯誤回退到最後一個場景 (scene-7)
        for (let i = this.scenes.length - 1; i >= 0; i--) {
          if (this.currentTime >= this.scenes[i].end) {
            activeScene = this.scenes[i];
            break;
          }
        }
      }
    }

這樣在 141.3s ~ 142.1s(02:21)時,畫面會平穩維持在 scene-4 結尾,直到 142.1s 才平順切換到 scene-5,徹底消除閃現 scene-7 的問題。

應該不會再遇到這個bug, 已加入提示詞到 SKILL.md


渲染(Render) 太慢的解法, 加速選項

🔴 瓶頸根因分析

目前的慢點在於:

  1. PNG 截圖:每幀都是無損 PNG,encode 成本高
  2. 串列處理:seek → screenshot → write 完全同步,沒有 pipeline 重疊
  3. FFmpeg 用 libx264 preset medium:encode 較慢
  4. 沒有任何並行
    ──────

選項 A:改用 JPEG 截圖(最快、最簡單)

• CDP 改 format: ‘jpeg’, quality: 93
• FFmpeg input 改 -vcodec mjpeg
• 預估加速:2~3x,每幀 encode 時間大幅下降
• ⚠️ 輕微畫質損失(93 品質下幾乎無感)
──────

選項 B:Pipeline 異步化(seek / screenshot / write 三階段重疊)

• 目前是 seek → screenshot → write,完全串列
• 改成「seek n+1 時,同時 write n 的資料」雙 buffer 交錯
• 預估加速:20~30%,純 CPU/IO 重疊收益
──────

選項 C:FFmpeg encode 加速(調 preset / 硬體加速)

• preset medium → preset veryfast 或 ultrafast(檔案略大但快很多)
• 若有 NVIDIA GPU:改 -c:v h264_nvenc
• 若有 Intel GPU:改 -c:v h264_qsv
• 預估加速:1.5~4x(視 GPU 而定)
• ⚠️ 硬體編碼需要確認 GPU 是否支援
──────

選項 D:多 Worker 分段平行渲染(SKILL.md 中提到的 chunk 方案)

• 開 N 個 Playwright Browser,各自負責一段時間範圍
• 每段輸出 chunk_0.mp4、chunk_1.mp4…
• 最後 ffmpeg concat 合併
• 預估加速:24x(N Worker 數)
• ⚠️ chunk 不連續風險:
• 每個 Worker 需獨立開一個 browser + 等 networkidle → 冷啟動浪費
• chunk 邊界時間點的 seek 精確度若有誤差 → 畫面跳幀
• concat 時若 chunk encode 參數不完全一致 → 可能有 1
2 幀閃爍
• 建議:chunk 之間加 2~4 幀 overlap,concat 時用 -c copy,風險可控但複雜度高

──────

選項 E:降低截圖解析度再 upscale(不推薦)

• 截 960×540,FFmpeg 用 scale=1920:1080 放大
• 有畫質損失,文字會模糊,不適合教學影片
──────

選項 F:組合拳(推薦組合)

組合方式預估加速複雜度
F1(最穩)A(JPEG)+ C(preset veryfast)3~4x低
F2(進階)A + C + B(pipeline)4~5x中
F3(最快)A + D(4 Worker)+ C6~10x高,有 chunk 風險

關於 chunk 連續性的補充

│ 不建議純靠 chunk 分割作為唯一加速手段,原因:

• 每個 Worker 都要重新啟動 Chromium + load page(24s 開銷)
• CSS 動畫在 seek 到中間點時,animation-play-state: paused 的狀態可能與從頭跑不完全相同
• 最安全的分割策略:只在「場景邊界」切割,避免在動畫進行中切斷


結語

讓 AI 成為我們的高效生產力工具,關鍵不只在於 Prompt 寫得多詳細,更在於「我們為 AI 搭建了什麼樣的工程架構」。

從「單檔命令式 Canvas」轉向「宣告式 DOM/CSS + 資料抽離」,不僅讓影片畫面在不同解析度下保持向量銳利,更讓後續的每次微調從耗資數千 Token 的大工程,收斂為幾百 Token 的精準修訂。希望這個架構思維與提示詞,能幫助你在製作自己的程式動畫影片時少走彎路!

Facebook網友回應

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *