Li 的程式碼動畫技能
一句話定位:把數學課上一個「說不清」的地方,做成一支 90 秒、能反覆暫停看、公式永遠排得正的程式碼動畫片。
核心技術鏈固定為四步,不用 AI 生成畫面:HTML/CSS/SVG 定畫面 → GSAP 排時間線 → 無頭 Chromium 逐幀截圖 → FFmpeg 編成 H.264 MP4。HyperFrames 只做工程契約與排程,它不生產任何一幀——它是台印刷機,稿子得你自己寫。
前置條件(開工前先查,缺項不要往下走)
在專案根目錄執行 npx -y [email protected] doctor,下列四項全綠才繼續。
| 軟體 | 版本 | 用途 | 缺了會怎樣 | 怎麼補(Linux/雲電腦) |
|---|---|---|---|---|
| Node.js | ≥22 | 跑 CLI 與腳本 | 全流程起不來 | nvm install 22 |
| FFmpeg 和 ffprobe | 4.x | 編碼、抽幀、參數核對 | 只有 ffmpeg 沒有 ffprobe,匯出與核對都會失敗 | apt install ffmpeg(自帶 ffprobe),不要用第三方套件管理器裝,容易只裝半套 |
| Chromium / Chrome 無頭 | 近兩年版本 | 逐幀取景 | 出不了圖 | apt install chromium,雲電腦一般在 /usr/local/bin/chromium |
| GSAP | 3.x 本地檔案 | 時間線與補間 | 引用 CDN 會靜默黑屏,無報錯 | 從本技能 assets/gsap.min.js 拷進工程 assets/ |
自檢另需三樣(缺了只是自檢打折,不影響出片):
| 工具 | 用途 | 缺了怎麼辦 |
|---|---|---|
| playwright-core | 六鏡自動截圖 | npm i playwright-core |
| ffmpeg 的 scale 濾鏡 | 截圖亮度診斷 | 自動降級為只做 DOM 診斷 |
| zip | 打原始碼包 | 改用 tar.gz,並在交付說明裡寫明 |
五個必踩的坑
ffmpeg與ffprobe必須同時存在,很多系統只裝了前者。- 不要用 winget / 可攜包混裝,PATH 裡指向的版本會對不上。
- GSAP 一律本地檔案,禁止 CDN;CDN 斷連的表現是全片黑屏而不是報錯。
- 瀏覽器在軟體渲染下跑(日誌出現
BeginFrame did not run是正常的),不用折騰顯卡。 - 渲染預設碼率只有約 500 kbps,1080p 正常應 3–8 Mbps。不顯式指定碼率,深色細線必糊。見第六節。
路線選擇:預設走 A,需要旁白或複雜畫面才走 B
| A 軌 · 純圖形(預設) | B 軌 · 旁白/複雜畫面 | |
|---|---|---|
| 適合 | 函數圖像、幾何、公式推導、概念辨析 | 要旁白講解、要手/角色/地圖/3D、要配樂成片 |
| 畫面 | 本技能六鏡 HTML+SVG,座標寫死 | 調用 code2video 技能:film.json 劇本 + 積木庫 |
| 聲音 | 預設無旁白,字幕即畫面文字;BGM 使用者後期自配 | TTS 旁白 + 配樂 + 響度對齊 |
| 命令 | npx [email protected] render | node $C/tools/make.mjs <片目錄> film |
| 何時改道 | 使用者明確要「有講解聲/要配音/要有旁白」,或畫面需要真人動作、地圖、角色 | 使用者只要畫面和字 |
B 軌的能力繼承但不複製:不把 code2video 的 engine/kits/presets 搬進本技能(約 20MB,且會造成兩個技能各自升級後版本漂移),改為呼叫它已安裝的那份。橋接細節見 references/two-tracks.md。分鏡腳本兩軌通用,A 軌分鏡可轉成 B 軌的 film.json(scripts/story-to-filmjson.mjs)。
收到任務後執行流程
0 · 環境自檢
npx -y [email protected] doctor
ls assets/gsap.min.js
缺項時給出安裝命令並暫停等使用者裝好,不要假裝能跑。
1 · 追問補全(使用者通常只給一句話)
老師開口多是二十個字:「用程式碼做個影片,講虛數,x²=-4 到底有沒有這樣的 x。」剩下的規格由你補齊,只問四問,每問給現成選項:
| 問 | 為什麼必須問 | 建議預設 |
|---|---|---|
| 口徑停在哪 | 決定數學結論對不對,是全片唯一會「講錯」的地方 | 停在不需要新概念的那一步 |
| 時長與段落 | 決定切幾鏡 | 90 秒六鏡 |
| 分鏡誰來定 | 使用者可能已有板書順序 | 你寫,先給他看 |
| 受眾年級 | 決定深度與用詞 | 按主題所處學段 |
畫風不問:預設深夜光軌深色風(使用者已鎖定)。除非使用者指定白底。
使用者說「你看著辦」時,把預設值寫成一句話回給他(「按深色、90 秒、六鏡、只到這一步做,不對就喊停」),他只需點頭。
2 · 出六鏡分鏡,等使用者點頭
按第三節骨架寫一張分鏡表:鏡號、時長、畫面要素、字幕原文、該鏡要解決的一個問題。只給分鏡,不寫程式碼。使用者說「可以」再進第 3 步。此步返工成本最低,務必等確認。
3 · 建工程
node <技能目錄>/scripts/init-project.mjs --name <工程目錄> --title "<片名>" --scenes 6
腳本會:拷 assets/project-template/ 的六鏡骨架與 index.html、拷 GSAP、生成 verify-seeks.json 骨架。不用手工 npx hyperframes init,模板已符合契約。
模板內容就是「反比例 k 的幾何意義」那支的完整六鏡(深色版),可直接複渲出同款;換主題時保留它的座標體系、時間軸與樣式,只改文案和 K 值,比從零寫快得多。
4 · 逐鏡寫畫面
- 六鏡各一個檔案:
compositions/scene-1-hook.html…scene-6-outro.html;index.html只做掛載,不要把內容寫成一個巨型 HTML。 - 座標全部寫死,不靠流式佈局:先定原點與比例尺(例:
OX=460, OY=620, S=96,1 單位 = 96 像素),所有點由它算。 - 曲線按函數取樣生成 path,不許手畫近似。
- 公式與配色按 references/visuals.md;口徑按 references/math-rules.md。
5 · lint
npx -y [email protected] lint
必須 0 錯誤 0 警告。有報錯就按報錯改,改完重跑。
6 · 六鏡截圖(自檢 1–3)
node <技能目錄>/scripts/verify.mjs --browser=/usr/local/bin/chromium
每鏡 seek 到資訊最全的那一秒截圖到 verify-shots/,同時診斷 GSAP 是否載入、時間線是否註冊、元素是否溢出、有無 JS 錯誤。逐張自己看,不是跑完就沒事。
7 · 抽幀核對(自檢 4)
對上一版成片抽 16 幀拼板,確認每段關鍵元素真的在自己聲稱的時刻出現。做法見 references/qa.md。
8 · 渲全片(必須顯式給碼率)
npx -y [email protected] render --output <name>.mp4 --quality delivery --video-bitrate 3M
--video-bitrate 與 --crf 互斥,二選一。90 秒 1080p 成品正常 15–25MB;低於 10MB 說明壓過頭了。
9 · 成片參數核對(自檢 6)
node <技能目錄>/scripts/inspect-video.mjs <name>.mp4 --expect-duration 90
核對解析度、幀率、總幀數、時長、碼率、體積;任一項不合格就回第 8 步重渲。
10 · 交付
zip -qr <name>-project.zip <工程目錄> \
-x "<工程目錄>/node_modules/*" -x "<工程目錄>/verify-shots/*" \
-x "<工程目錄>/.verify-tmp/*" -x "<工程目錄>/*.mp4"
交付三件:成片 mp4、純原始碼工程包(排除 mp4)、三條說明(講了什麼、替使用者定了什麼、哪幾處請老師複核)。只發檔案不算交付。
三、六鏡骨架(90 秒基準)
時長可按要求縮放,但結構順序不要變:先勾住,再推一步,再推廣,再落推論,再糾錯,最後收。
| # | 鏡 id | 起點/時長 | 這一鏡的任務 | 必含 |
|---|---|---|---|---|
| 1 | s1-hook | 0 / 10 | 拋一個真問題,讓人想往下看 | 主圖形定格 + 右側三行提問 + 底部圖註 |
| 2 | s2-q1 | 10 / 20 | 在最簡單的情形裡把關鍵量算出來 | 座標軸、曲線、動點、矩形/三角形、逐行字幕 |
| 3 | s3-other | 30 / 18 | 換象限驗證同一結論 | 四支曲線依次點亮 + 結論卡 |
| 4 | s4-triangle | 48 / 15 | 把結論拆成一半,給出大前提 | 幾何塊 + 直角標記 + 三行推論 |
| 5 | s5-table | 63 / 12 | 糾最常見的錯 | 細線表 + 錯誤卡(帶刪除線)+ 正確卡 + 金句 |
| 6 | s6-outro | 75 / 15 | 換座標尺度複述結論,收住 | 同構圖縮小版 + 居中三行 + 脈動強調 |
index.html 掛載要點:body 背景寫死底色(深色 #050a14),六鏡 track-index 0–5 連續,start/duration 與上表對齊,全片 title 寫「主題|角標名」。
四、數學口徑速查(寫錯就是教學事故)
硬約束,逐條都要落到畫面。完整清單與反例見 references/math-rules.md。
- 只推符號,不代數字。講反比例只到
|k|與½|k|,全片不出現「4」這類具體值;講一般結論時同理。 - 分數一律水平分數線,不用斜槓、不用
a/b寫法。 - 三角形符號作 S 的右下角標,不是與字母平排。
- 用「公共點」,不用「交點」。
- 負數情形表述固定:「k<0 時,二四象限亦是如此」。
- 同一概念全片一個詞,不換說法;字幕用語與教材一致。
- 結論出現前,畫面必須已經把依據畫出來;不許「結論先出、依據後補」。
五、視覺速查(預設深夜光軌)
完整色值與實現見 references/visuals.md。記住三條深底鐵律,其它都可調:
- 面積類填充降到 18% 並加描邊——深底上 40% 的填充會糊成霧。
- 三角形/幾何塊用「琥珀輪廓 + 極淡青填充」——金色實心疊在深藍上會變成髒橄欖色。
- 表格不用底色,用細線分隔——白底表格直接搬過來會三行連成一片。
字號整體比白底版大 4 號;左上角角標固定 Vibe 知識大賞 | 本鏡小標題,26px、字距 5px、left 84 / top 70。
**字體鐵律:分三類角色——中文 Noto Sans CJK SC(思源黑體)、拉丁標籤與數字 Inter、數學符號 TeX Gyre Termes 襯線斜體(與 Times 同族,和教材字母形態一致)。@font-face 必須寫在 <template> 的 <style> 裡(放 head 會被取景 harness 丟掉,字體靜默不載入)。細節與四個坑見 references/visuals.md 第零節。
六、渲染與碼率
| 參數 | 取值 | 說明 |
|---|---|---|
--quality | delivery | 高畫質檔,預設檔會掉碼率 |
--video-bitrate | 3M | 必須顯式給;與 --crf 互斥 |
--resolution | 預設 landscape(1920×1080) | 4K 只在明確要求時用 |
--resume | 中斷後續渲 | 已渲幀不重來 |
判據:90 秒 → 體積 15–25MB、碼率 1.5Mbps 以上、2700 幀、時長 90.000000s。數字不對就重渲,不要交付。
清晰度不夠時(實測:--video-bitrate 不生效)
2026-11 實測:--video-bitrate 30M 渲出來仍是 1.8Mbps,--quality delivery 也一樣。 也就是內置編碼的碼率參數基本被忽略,交付級清晰度只能自己接管編碼。使用者說「看不清」時按這條路走,別再反覆調 --video-bitrate:
# 1) 只渲幀,不編碼(2700 幀 / 約 2.4GB / 16 分鐘)
npx -y [email protected] render --format png-sequence --output pngseq
# 2) 自己編碼。靜止內容多、文字細,必須用 CBR 而不是 CRF(CRF 只給 0.5Mbps,平台二次轉碼必糊)
ffmpeg -v error -framerate 30 -i pngseq/frame_%06d.png \
-c:v libx264 -b:v 4M -minrate 4M -maxrate 4M -bufsize 8M \
-preset slow -tune animation \
-x264-params "nal-hrd=cbr:aq-mode=3:chroma-qp-offset=-2" \
-pix_fmt yuv420p -movflags +faststart -y out.mp4
chroma-qp-offset=-2 是給彩色細筆畫用的:青字、琥珀字在 4:2:0 色度取樣下邊緣會掉色,這一項讓色度比亮度多給一點碼率。
約 2 分鐘編碼,得 90s / 43MB / 4Mbps。40MB 這條線是「課堂能放、平台轉碼後還清楚」的平衡點;低於 10MB 的檔案在手機端一看就軟。要投平台(影片號/抖音)再加一檔 -b:v 8M 的母版(約 85MB),平台轉碼後仍能保住文字。
不要指望 4K 超取樣:軟體 GPU 下 2700 幀要跑一小時以上(實測 20 分鐘才到 8%),單條片子的工期撐不住。
編碼後必須抽一幀 1:1 看(crop=760:300 看文字邊緣有沒有塊狀/振鈴),不能只看整體縮圖——縮圖看不出糊。
音效(點狀音效,不是 BGM)
使用者說「加點聲音」「太靜了」但沒要 BGM 時,只鋪點狀音效:畫面每發生一個動作才響一次,響完就歸零。數學講解片上了背景音樂會削弱思考感,也會蓋住老師自己的旁白。
# 1) 按分鏡動作點寫時間線(絕對秒),範例見 assets/sfx-events.example.json
python3 <技能目錄>/scripts/make-sfx.py events.json -o sfx.wav --duration 90
# 2) 拷影片流、只混音軌(畫面一個像素都不動)
ffmpeg -v error -i in.mp4 -i sfx.wav -c:v copy -c:a aac -b:a 128k -shortest \
-movflags +faststart -y out.mp4
六種音效各管一件事:brush 曲線/線段生長、slide 動點勻速滑動、tick 一行文字出現、ding 卡片/標題/問句、thump 最關鍵的那個量、chime ✓/金句/片尾。
三條規矩:
- 不給每行字都響——只挑「畫面動作」,90 秒片 30 個點左右就夠,密了就吵。
- 峰值壓在 -16dBFS 以內,
tick落在 -29dB 上下、thump/ding落在 -24dB 上下,這個量級「聽得見但不搶戲」。 - 混完用
ffmpeg -ss <t> -t 0.6 -af volumedetect抽查:有音效處應 -24~-30dB,空白處應 -91dB(真靜音)。有音效處高於 -18dB 就調小。
七、六道自檢(交付前必須全過)
| # | 查什麼 | 命令 | 通過判據 |
|---|---|---|---|
| 1 | 結構合規 | npx -y [email protected] lint | 0/0 |
| 2 | 六鏡有畫面 | scripts/verify.mjs | 六鏡全部出圖,無黑屏、無 JS 錯誤 |
| 3 | 元素不越界 | 同上,看診斷報告 | 無溢出條目;字幕距底 ≥80px |
| 4 | 時序真出現 | 抽幀拼板(見 qa.md) | 每段元素在自稱時刻已出現,無空段 |
| 5 | 口徑正確 | 對照 math-rules.md 逐鏡讀字幕 | 七條全過 |
| 6 | 成片參數 | scripts/inspect-video.mjs | 解析度/幀數/時長/碼率/體積全達標 |
第 1、2、3、4、6 查由機器判,人複核;第 5 查只能人做——它是唯一能擋住教學事故的一道。
八、修改迭代的規矩
使用者提意見要按「三件套」接:哪一鏡 / 哪一秒 / 改成什麼。含混的「這裡不太對」要反問定位,不要整段重做。
改完必須重跑:改動涉及座標或字號 → 1+2+3;涉及文案 → 5;涉及多個鏡頭 → 全部六查。
一次攢幾條一起改比反覆小修更快,但跨鏡的大改要重出分鏡確認。
真實修訂過程(十條實錄)見 references/revisions.md。
九、禁止事項
assets/gsap.min.js缺失或改用 CDN- 用
scaleX/scaleY+transformOrigin做 SVG 線段生長(只從一端長) - 用
onUpdate驅動 SVG 屬性(seek 時不觸發,截圖會錯) - 補間
letterSpacing、width等會觸發回流的屬性 d=""時先調getTotalLength()(必須先給哨兵 d)- 渲染不提碼率
- 把全部內容寫進一個 HTML
- 公式用斜槓表示分數
- 交付時只發 mp4 不說改了什麼
- SVG
<text>裡塞<sup>(HTML 標籤進 SVG 會讓整段文字跑位;上標一律用²字元) - 在滿寬(1920)的容器上做 scale 脈動(放大 4% 就撞邊界,第 3 查直接判溢出;scale 掛在容器內的 inline-block 上)
- 把絕對定位的刪除線放在目標文字的兄弟位置(會橫跨整列;要放進那個文字自己的相對定位容器裡)
- 用
repeat: -1做循環脈動(子組合沒有data-duration時 lint 必報錯;改成有限的重複次數) - 在工程根留任何暫存
.html(會被當成第二個 root,lint 報「只能有一個 index.html」) - 曲線畫兩條 path(亮線 + 光暈)時只給亮線設
dasharray/dashoffset(光暈會整條提前顯影,看起來像「還沒畫完就有一段暗影」;兩條都要設、同一時刻補間) - 用
--video-bitrate追清晰度(實測不生效,見第六節) - 編碼只給 CRF 不看體積(靜止內容 CRF 只出 0.5Mbps,平台轉碼後必糊)
- 用系統預設字體渲染字母數字(
system-ui在無頭 Chromium 裡是 DejaVu Sans,字母數字呆板;一律"Inter", sans-serif) - 在
font-family裡寫中文名(如"Noto Sans SC",沒裝會被 lint 判font_family_without_font_face) - 座標軸只畫兩條直線、不在端點畫正方向箭頭(教材規範:x 朝右、y 朝上,各一個實心箭頭,寫法見 references/visuals.md 的「座標軸」一節)
九·五、給舊工程補字體
老工程(字體還是 system-ui)不用重建,跑一條命令就能升級到三分角色字體:
node scripts/apply-fonts.mjs --project <工程目錄>
它做五件事:拷字體到 assets/fonts/、把 @font-face 併進 <template> 的 <style>(head 裡的會被取景 harness 丟掉)、給 .row/.cap/.tag 容器鎖 Inter、把正文裡的裸變數 x/y/k/S 逐個包成 <i>(純 CSS 選擇器管不到裸文字,這是最常見的「改了字體卻沒變」的原因)、把 SVG 的 font-family 換成 'Termes', serif 並給 x/y/k 加斜體。數學符號統一 calc(1em + 2px),補襯線相對無襯線的視覺收縮。冪等,重複跑不會重複插入。
改完照常跑:lint → verify → render,必須重渲,字體不會在舊成片上生效。
十、參考檔案
| 檔案 | 什麼時候讀 |
|---|---|
| references/pipeline.md | 第一次做,或要給人講清四步怎麼串起來 |
| references/math-rules.md | 寫任何公式與結論之前 |
| references/visuals.md | 定配色、字號、卡片、公式排版時 |
| references/prompts.md | 追問補全、寫分鏡、教別人怎麼說第一句 |
| references/revisions.md | 使用者提意見、或要給教研組講「改十遍是怎麼回事」 |
| references/qa.md | 每輪自檢、判斷一片能不能交 |
| references/two-tracks.md | 需要旁白/配樂/手/地圖/角色,改道 B 軌時 |
assets/project-template/ | 建工程時由 init-project.mjs 拷貝,不要手改 |
assets/gsap.min.js | 建工程時拷進專案 assets/ |