Spatial Film
已在一個 60 秒的產品影片(7 個場景,5 個工作者在一波中完成,每個接縫畫面精確,檢查乾淨,
1080p60 在 4 分 19 秒內)上驗證了端到端流程。範本的 film.json 中保存了該影片的真實場景時間條與提示表,作為工作範例。
<skill> = 此技能所在的資料夾(此 SKILL.md 所在的目錄)。每個腳本都從影片專案根目錄執行。
下面的 python 是指 explainer-kit 的設置檢查保存在 ~/.talking-explainer/config.json 中的 Python 指令
(Windows 上為 py -3.12,macOS 上為 python3)。
你需要什麼
- Node 20+、
ffmpeg+ffprobe在 PATH 中、Python 3.10+ 並安裝numpy和Pillow。 - HyperFrames 技能:
npx hyperframes skills update general-video hyperframes-animation media-use(封包 建構器、藍圖 + 動畫對應表、音樂輔助工具)。CLI 本身是根據專案固定版本,並透過npx執行。 - 用於預渲染介面的 Playwright:在影片資料夾中執行
npm i -D playwright(或設定PLAYWRIGHT_PATH)。 - 可選,用於來源音樂:一個 HeyGen 帳號 - 在自己的終端機中執行一次
npx hyperframes auth login。 如果沒有,請自備音軌並從第 4 階段music-analyze.py開始。 - 一台具有 GPU 能力的機器用於最終渲染(在軟體路徑上仍然可以渲染,但速度約慢 15 倍)。
如何在不浪費上下文的情況下使用此技能
- 一次只載入一個階段的參考文件,在該階段開始時載入。切勿一次預載所有文件。
- 腳本是經過驗證的程式碼。執行它們;切勿內聯重新產生它們的邏輯。
- 主工作階段從不讀取整個場景檔案(20-60KB)。透過縮圖頁判斷場景;使用
grep -n加上精確修補來修正。 - 工作者會收到檔案路徑,而不是貼上的內容。
storyboard.html(60KB+)是透過儲存格 ID 進行 grep,從不讀取整個檔案。 - 先讀取縮圖頁;僅在檢查細節時才開啟單張快照 PNG。
階段(每個階段以一個閘門結束 - 在通過之前不要繼續)
| # | 階段 | 載入 | 閘門 |
|---|---|---|---|
| 0 | 搭建:node <skill>/scripts/new-film.mjs <dir> --name <slug> | - | 資料夾包含 film.json、frame.md、kit、範本文件 |
| 1 | 接收、BRIEF、STORYBOARD、草稿頁 | references/storyboard.md | 使用者在 ## Locked 下記錄的明確「go」 |
| 2 | frame.md、kit、預渲染介面 | references/surfaces.md(+ 僅當出現 GHL/n8n/Zapier 時才載入 platform-canvases.md) | frame.md 中沒有 {{;每個捕捉 PNG 都已檢視過 |
| 3 | 測試框架冒煙測試、封包、平行工作者 | references/workers.md | 每個場景:lint 0 錯誤、縮圖頁與其區塊相符 |
| 4 | 音樂 + 音效(在工作人員建置時執行) | references/audio.md | 音樂基底以原生 BPM 呈現,第 0 小節在強拍上;提示在 film.json 中 |
| 5 | 組裝、檢查、接縫、渲染、交付 | references/verify-ship.md | 檢查通過、接縫檢查全部 PASS、硬體 GPU 渲染已驗證 |
路線經過 /hyperframes -> /general-video 以進行意圖和分鏡審查步驟(此技能提供
專案形狀、合約、腳本和閘門;general-video 提供封包建構器和工作者角色)。
檔案
| 路徑 | 說明 |
|---|---|
template/ | 專案骨架:film.json(場景時間條、BPM、背景音樂、音效、提示 - 唯一的時間來源)、frame.md(帶有填空的設計真實合約)、BRIEF.md、STORYBOARD.md、assets/kit.css(基礎影片 kit)、assets/fonts/(Poppins 500/700 + OFL)、references/worker-brief.md、references/scene-starter.html(lint 乾淨的場景骨架)、references/snippets.html、package.json(固定 hyperframes 0.8.117)。 |
surfaces/ | 準備好的 GHL、n8n 和 Zapier 畫布 HTML 重建 + 一個長行銷頁面(虛構的 Northwind Dental 資料)、platform.css、sprite.svg、jobs.json。 |
scripts/new-film.mjs | 從範本搭建一個專案。 |
scripts/capture-surfaces.cjs | 將 src/captures/*.html 渲染為 assets/captures/*.png,2 倍大小(jobs.json)。 |
scripts/measure-surface.cjs | 1 倍大小的表面元素邊界框,用於精確註釋/裁切目標。 |
scripts/make-harness.py、scripts/test-scene.mjs | 每個場景隔離的測試專案;對一個場景進行 lint + 快照。 |
scripts/music-search.mjs、sfx-search.mjs | 透過 media-use 的 REST 輔助工具查詢 HeyGen 目錄(Windows 安全)。 |
scripts/music-analyze.py、music-bars.py | 僅需 numpy 的節奏 + 每小節能量分析(不需要 librosa)。 |
scripts/bgm-cut.py | 在小節線上剪輯背景音樂基底,拼接音軌自己的結尾、淡入淡出;--b 重複基底以在較長的影片下循環。 |
scripts/assemble.py | film.json -> index.html(場景時段在小節網格上、背景音樂、帶有持續時間 + 音軌的音效)。 |
scripts/seam-check.py | 像素級驗證每個接縫(剪輯前/時/後)和循環(第 0 幀 vs 最後一幀)。 |
scripts/render-final.mjs | 使用 --browser-gpu 的 CLI 渲染,記錄到檔案,驗證 gpu 模式 + ffprobe,接著執行 flash-scan.py。 |
scripts/flash-scan.py | 渲染的每一幀:一個與兩個相鄰幀都不同的幀(一個元素顯示了 1/60 秒)= 一個錯誤。 |
scripts/encode-web.mjs | 網路編碼(H.264 CRF 21、faststart、AAC 或 --silent)+ 海報幀。 |
scripts/blink-fit.py、scripts/build-presenter.py | 含旁白影片:眨眼疊加層用於姿勢頁面(--band = 眼睛區域);從其音軌的演示者疊加層,當吉祥物有 mouth-fit.json 時包含嘴型同步。 |
scripts/el-tts.py、el-words.py、el-music.py | 透過 ElevenLabs 的旁白影片(金鑰在環境變數 EL 中,絕不放在磁碟上):從 SCRIPT.md 取得語音候選、Scribe 文字時間、產生的背景音樂。 |
scripts/seam-diff.py | 接縫差異,遮蔽演示者方塊(含旁白影片)。 |
scripts/fromto-scan.py | 渲染前:標記 fromTo 中僅有的 FROM 屬性,這些屬性在渲染工作者(場景中段搜尋)時永遠不會被套用。每次渲染前必須為 0 個命中。 |
scripts/patch_exact.py | 精確、CRLF 安全的尋找/取代,用於腳本化的場景修復(斷言每個模式只有一個匹配)。 |
scripts/selftest.mjs | 端到端證明此技能在此機器上可正常運作。必須輸出 PASS。 |
含旁白的影片(一個語音旁白 + 一個螢幕上的演示者)
已在一個含旁白的「API vs Webhook」解說片(83 秒、垂直和橫向、克隆語音、吉祥物演示者、 10 個場景由 5 個工作者製作)上驗證。上述的音樂-影片規則仍然適用,除非被以下規則取代:
- 先處理語音候選。
EL=<key> python <skill>/scripts/el-tts.py . eric=<voice_id> michelle=<voice_id>讀取SCRIPT.md的縮排行,並為每個語音將一個 MP3 寫入src/audio/voice-candidates/(金鑰僅從 環境變數取得,絕不放在磁碟上)。讓使用者透過聽覺挑選,然後僅轉錄挑選的語音。 - 旁白就是時鐘。 文字時間:
EL=<key> python <skill>/scripts/el-words.py <voice.mp3> src/timing/voice-words.json(ElevenLabs Scribe - 無本機模型,需要金鑰),或 使用帶有word_timestamps的 faster-whisper(設定語言,開啟 VAD;explainer-kit 的transcribe.py,免費且本機)。將文字 JSON 保留在src/timing/中。在film.json中設定"bpm": 60, "beats_per_bar": 1,這樣一個「小節」就是 1 秒,場景 長度就是句子停頓處的普通秒數。分鏡區塊提供場景內部的文字時間;開始時間約早 0.1 秒,並在該字詞上著陸。在下一剪之前,給標題至少約 1 秒的螢幕時間(針對顯示台詞的最後一個字檢查剪輯點)。 film.json額外項目(assemble.py + make-harness.py 會讀取它們):"audio": [{id, src, dur, volume}]用於 語音,"overlays": [{id, track, z}]用於一個全影片圖層(演示者),它位於每個場景之上,因此 它永遠不會在接縫處彈出,且場景保持無演示者狀態。- 演示者來自姿勢 PNG: 每個姿勢一個透明 PNG(explainer-kit 的
prep-mascot.py修剪、 縮放並寫入清單),然後執行scripts/blink-fit.py <mascot_dir>(可選,需要 torch;透過 NCC 將 BLINK 眼睛區域擬合到每個姿勢上;評分低於 0.70 的姿勢 不進行眨眼)和scripts/build-presenter.py(從src/presenter-track.json寫入疊加層:姿勢在提示字詞上、 以頭部為錨點使切換永不跳動、語音包絡晃動、呼吸、眨眼、特效)。絕不手動編輯其輸出。 - 音訊: 將語音 loudnorm 標準化至 -16 LUFS / -1.5 dBTP(一個在 +4.6 dBTP 限幅的普通增益),對其下方的背景音樂進行側鏈壓縮(離線,約低於語音 17 dB),音效輕柔(0.1-0.45)。填充語音金鑰,否則壓縮會將背景音樂截斷至語音長度(檢查是否警告「bgm 110.25s vs slot 113s」):
[1:a]apad=whole_dur=<TOTAL>[k];[0:a][k]sidechaincompress=threshold=0.03:ratio=6:attack=25:release=450,atrim=end=<TOTAL>。 無目錄背景音樂(HeyGen 搜尋 500 秒或未登入):EL=<key> python <skill>/scripts/el-music.py .media/candidates/bed.mp3 <ms> "<提示:器樂、BPM、適合旁白下播放、無突兀音效、以解決和弦結束>"- 一條在影片長度時自行結束的音軌不需要拼接。 - 接縫檢查噪音: 演示者在每個接縫處移動,因此
seam-check.py僅因它而 FAIL。使用scripts/seam-diff.py a.png b.png out.png x0,y0,x1,y1(遮蔽演示者方塊)判斷每個被標記的對 - 方塊外為 0.00% 即為通過。 - 垂直(1080x1920)尺寸法則: 每個場景的頂部區域必須包含一個真實標題(60-84px),內文 >= 40px, 物件使用整個寬度和演示者上方的高度。第一批交付的影片在畫面上方 45% 的區域中使用了約 33px 的文字 - 在分派前將這些數字放入 frame.md 中,而不是之後。
- 不是循環: 一個含旁白的影片以一個固定的鎖定畫面結束;忽略 seam-check 的循環行。
硬性規則(這些規則使它變得高級且流暢)
- 來源為真。 產品 UI 是從產品自己的 HTML/CSS 移植的;第三方畫布是從使用者的真實截圖重建的,絕不依靠記憶。僅使用虛構資料。僅使用產品真正擁有的功能。
- 單一相機。 每個場景中透視為 1866px(35mm);在相機圖層下有一個漂移圖層,使得沒有任何東西是靜止的;遠處葉片有視差 + 景深模糊。不要在 3D 父元素上使用 opacity/filter/overflow/clip-path(它會使其平面化)。
- 1080p+ 清晰。 使用 CSS
zoom(.kit-zoom)縮放 UI,絕不使用transform: scale(>1);每個元素以其在螢幕上最接近的大小創作;截圖是 2 倍 PNG,以原生大小或更小顯示。 - 60fps 流暢法則。 僅使用 transform/opacity;著陸使用彈性曲線,進場使用 expo/power3,相機移動使用 inOut;無 linear、steps、snap 或四捨五入;相機移動重疊 0.15-0.3 秒且持續 0.6 秒以上。
- 確定性。 每個場景一個暫停的時間線,最後註冊在
window.__timelines[id]上,fromTo使用明確的開始時間,無 random/clock/CSS 轉場。 - 接縫是共享的幀。 每個接縫是一個精確的畫面,由兩個相鄰場景各自持有約 0.1 秒,由一個在攝影機外的 2D 圖板鎖定。無硬切、無空幀、無淡出至無。最後一幀 = 第一幀。
- 在網格上。 場景是整/半小節;點擊、滴答聲、圖章和切換落在拍點上;音樂以其原生節奏在小節線上剪輯。
- 修復合約,而非修復場景。 根相對路徑
assets/...、id 永遠不以數字開頭、id 和 class 以場景 id 為前綴。每個場景都會違反的規則,在 frame.md / worker-brief 中修復一次。 - 禁止事項。 無表情符號、霓虹光暈、紫色到藍色漸層、彩色左邊框卡片、圓形載入動畫(shimmer)、庫存人物、「John Doe」、幻燈片節奏或毫無意義的動作。
- 從 CLI 使用硬體 GPU 進行最終渲染。 絕不使用 Studio 的匯出(軟體路徑,約慢 15 倍)。
問題排除
| 症狀 | 原因 | 修復 |
|---|---|---|
| Lint:資產路徑錯誤 | 子合成中的 ../assets/ | 使用根相對路徑 assets/...(在合約中修復) |
| Lint:無效的合成 id | id 以數字開頭 | 使用 s01-click,而不是 01-click(film.py 拒絕它) |
| 封包建構器拋出錯誤 / 封包 > 48KB | 兩個藍圖 id,或太多 rules: | 一個 id (Adapt);每個區塊 2-3 條規則 |
| UI 看起來模糊 | 被 transform 縮放或推向相機超過了創作大小 | 使用 .kit-zoom,以最接近的大小創作 |
| 3D 突然變平 | 在 3D 祖先上使用了 opacity/filter/overflow | 將其移動到葉子節點 |
| 接縫跳動 | 其中一方未持有共享幀 | 使用 seam-check.py,修復漂移方的圖板/姿勢 |
| 接縫 FAIL 幾個百分比但幀看起來相同 | 圖板下方的 rig 複製體使陰影加倍 | 僅在持有期間顯示圖板(frame.md 接縫) |
| 產品 UI 偏離真實應用程式 | 從記憶或不完整的 CSS 重建 | 以 2 倍大小截取真實應用程式(surfaces.md 第一部分) |
800+ 個 duplicate_audio_track 警告 | 音效沒有 data-duration | 重新執行 assemble.py |
music-search 說沒有憑證 | 未登入 | 使用者在自己的終端機中執行 npx hyperframes auth login |
| 渲染很慢,約 1 幀/秒 | 軟體 GPU(Studio 匯出) | 使用 render-final.mjs(CLI,--browser-gpu --workers 6) |
| 渲染在停止後仍在執行 | 停止 shell 並不會停止 node | 殺死行程樹(verify-ship.md) |
| 沒有渲染進度可見 | 輸出透過 ` | tail` 管道 |
自我測試
node <skill>/scripts/selftest.mjs
在一個暫存資料夾中搭建一個 2 場景的影片,然後執行測試框架、組裝、check、接縫檢查、一個硬體 GPU 渲染(1920x1080 @ 60 fps)以及網路編碼。它必須輸出 PASS - 在新機器上或任何編輯後執行它。