Communitygithub.com

HusseinRagab/motion-reels

製作 9:16 垂直動態圖像 Reels(1080x1920),用於室內設計頁面,包含程式繪製的動畫——標題、逐步繪製的平面圖、前後擦除效果、照片推進、材質樣本,並透過 Playwright 和 ffmpeg 逐幀渲染。當被要求製作、分鏡、渲染或修復 Reel、Shorts 或垂直動態影片時使用。

motion-reels 是什麼?

這個技能建立一個結構化的、基於程式碼的工作流程,用於產生 9:16 的垂直短影音(Reels),特別針對室內設計內容。它將劇本或逐字稿轉換為精確的時間軸,然後驅動 Playwright 在 Canvas 上繪製動態圖形,包括文字、照片、平面圖和材質樣本,最後由 ffmpeg 合成最終影片。其設計強調一種平靜、高級的美感,有嚴格的規則禁止文字移動、鏡頭抖動,並強制使用聯合國藝妓風格的緩動函數。工作流程包含多個檢查點(閘門),在關鍵階段(如文字定稿、佈局、最終影片)需要業主明確批准,確保創意控制權掌握在業主手中。Claude 的角色是技術執行者,基於業主定義的視覺風格和內容來渲染影片,絕不會代為發布。它支援完整的從頭到尾的流程:從簡報、節拍表、時間軸生成、佈局檢查、逐幀渲染、故障掃描到最終合成。也支援阿拉伯語從右到左的文字。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/HusseinRagab/reel-motion-kit/tree/HEAD/motion-reels

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

motion-reels 是做什麼的?

以程式碼繪製的垂直 Reels 動態圖形。每個 Reel 由一個或多個區塊(block)組成。區塊是一個小型的 JavaScript 檔案,負責在一個「世界」中放置標題、圖畫、照片和材質樣本,並指定每個元素出現的時間。每個時間點來自文字時間軸:可以是節拍表(無語音)或語音錄製的文字時間。引擎在全新的瀏覽器頁面上逐幀渲染,檢查佈局,並與語音和/或音樂合成最終結果。

擁有頁面的人決定外觀、文字內容以及發布內容。Claude 絕不發布任何內容。

設定(一次性)

  • Python 3.10+ 以及 ffmpeg/ffprobe 需在 PATH 環境變數中。然後執行儲存庫中的 setup.ps1(Windows)或 setup.sh(macOS/Linux),或執行 pip install -r requirements.txt + python -m playwright install chromium(或設定環境變數 MG_CHROME 指向現有 Chrome 瀏覽器)。
  • 僅語音:pip install -r requirements-voice.txt(faster-whisper;首次執行會下載語音模型)。
  • 測試:執行 python engine/selftest.py 必須顯示 SELFTEST OK(檢查通過範例,負面測試如預期失敗,阿拉伯語字幕正確分割,且一次簡短渲染加上故障掃描與合成結果達到 -14 LUFS)。使用 --quick 可跳過渲染步驟。

引擎檔案(全部在 engine/ 目錄下):render.py(檢查/分鏡/靜態圖片/影片)、beats.py、words.py、frames.py、faultscan.py、assemble.py、storyboard_sheet.py、make_grain.py、templates/(殼層、reel.js、reel.css、字型)。

每個 Reel 一個資料夾

<reel 名稱>/
  brief.md          該 Reel 的目的、一個核心想法、行動呼籲、可用的照片
  beats.txt         螢幕上顯示的文字,一行一個節拍(或者,當有語音時,使用 script.txt + take.wav)
  words.json        時間軸(由 beats.py 或 words.py 產生)
  photos/           業主的照片(僅限他們擁有使用權限的照片)
  A.js, B.js ...   區塊
  build/            檢查結果、分鏡靜態圖片、區塊影片、render.json 檔案、連絡表
  <Reel>_REV01_<日期>.mp4   最終交付檔案;後續修正檔命名為 _REV02,不會覆蓋

閘門(不得跳過或重新排序;每個 ⛔ 標記處暫停,等待業主確認)

  1. 簡報。 一個 Reel 只有一個核心想法(房間改造、材質故事、格局變更、小技巧)。索取照片和帳號資訊。說明每張照片的內容。在前 2 秒內抓住觀眾注意力。
  2. 節拍表(beats.txt):螢幕上顯示的確切文字,依序排列,一行一個節拍。每個節拍保持簡短(2–7 個字)。將清單中的每個項目放在獨立的一行,讓每個項目都有足夠時間呈現。 ⛔ 業主確認文字。 確認後若需修改文字,意味著需要重新調整區塊的時間軸(以及重新錄製語音,如果有語音的話):在進行任何修改前必須先告知業主。
  3. 時間軸。
    • 無語音:執行 python engine/beats.py beats.txt words.json(預設節奏為每秒 2.6 個字 + 每個節拍後停留 0.7 秒;若在行尾加上 [+1.5] 則表示更長的停留;[pause 2] 表示停留且不顯示新內容)。
    • 語音(任何 TTS 或錄音,每個區塊一個完整錄音檔,不得編輯):執行 python engine/words.py take.wav words.json,然後將轉錄文字與腳本逐字比對。畫面配合錄音檔,無論如何不能反過來。
  4. ⛔ 分鏡靜態圖片。 建立區塊佈局(僅需位置即可),執行 check,然後執行 render.py A.js words.json storyboard build/sb 和 storyboard_sheet.py build/storyboard.png build/sb。自行審查連絡表(檢查清單如下),然後發送給業主。在業主確認佈局之前,不得製作任何動畫。
  5. 製作動畫。 根據時間軸中命名該元素的詞彙來安排每個元素的出現時機(規則如下)。每次修改後都執行 check。
  6. 渲染。 每個區塊執行 render.py A.js words.json video build/A.mp4 --workers 4(發送給業主的任何東西絕不使用 --draft)。每個區塊的 build/A.mp4.render.json 必須顯示 "capture": "fresh" 且與其他區塊使用相同瀏覽器;如果瀏覽器變更,則所有區塊都必須重新渲染。
  7. 故障掃描。 對每個區塊以及最終的 Reel 執行 faultscan.py build/A.mp4 --sheet build/A_contact.png。檢查所有被標記的時間點。黑色條帶或單幀閃爍都是故障:必須停止並修復。照片內容較暗是正常的。
  8. 合成。 執行 assemble.py blocks.json <Reel>_REV01_<日期>.mp4 [--music bed.mp3]。它會顯示測量到的響度;必須達到目標值(預設 -14 LUFS),且真實峰值必須低於或等於 -1.5 dBTP。
  9. ⛔ 審查。 發送 Reel 及其連絡表。僅修正圖片的問題只需重新渲染修改過的區塊,並以 _REV 下一版的名稱交付。業主負責發布。

動畫規則(原因:營造平靜、高級的感受;沒有抖動,易於閱讀)

  • 文字永不移動。 文字淡入(0.28 秒)後便保持靜止。文字沒有滑入、縮放、彈跳或漂移效果。
  • 標題: hl(el, text, cue(...)) 會根據時間軸中對應的詞彙,讓每個詞逐個淡入。當標題是語音的 paraphrasing 時,使用 {stream:true}:文字從提示詞開始,從左到右間隔 0.1 秒出現。
  • 鏡頭: 當畫面停留時保持完全靜止;僅在切換畫面之間滑動(0.8 秒,ease-in-out)。在 9:16 的比例下,將畫面在「世界」中垂直堆疊(畫面 2 在 y 1920,畫面 3 在 y 3840),然後向下滑動:SHOTS=[[t,540,960,1],[t2,540,2880,1],...]。
  • 照片可以緩慢推近: push:[1,1.04] = 恆定速度,不得超過 +6%(MOTION.PUSH_MAX;引擎會拒絕)。承載文字的照片不得推近。
  • 圖畫在被命名時繪製,先繪製參考線:牆壁在窗戶和門之前,地板線在從它開始測量的高度之前。at('#walls', cue('Walls'), null, 'draw', 0.9);每個元素需 0.6–1.0 秒。
  • 尺寸使用同一比例。 如果平面圖顯示兩個尺寸,兩者使用相同比例。
  • 標示文字中提及的項目;每個數字都需帶有單位(如 "3.2 m",而不是裸露的 "3.2")。
  • 任何地方都不使用彈跳、超調、彈性或彈簧緩動函數。 所有時間設定來自 reel.js 中的 MOTION;在那裡修改,絕不能作為區塊中的字面值。
  • 提供給 at() 的裸虛線 SVG 元素會在提示詞之前顯示:請將其包裹在一個 <g> 標籤中。

時間設定代幣(reel.js 中的 MOTION)

代幣值用途
WORD_FADE / WORD_LEAD0.28 秒 / 0.08 秒文字淡入;在提示詞之前稍微開始
STREAM0.10 秒串流模式標題文字間距
REVEAL / EXIT0.5 秒 / 0.4 秒區塊、標籤、材質樣本、照片的進入/離開
draw (at(...,'draw',dur))0.6–1.0 秒每個繪製的元素
CAM_SLIDE / CAM_LEAD0.8 秒 / 0.35 秒鏡頭滑動;在畫面的提示詞之前開始
PUSH_MAX0.06照片推進限制
WIPE1.2 秒前後擦除效果
緩動函數:揭示效果使用 ease-out cubic,滑動、繪製和擦拭效果使用 ease-in-out cubic。

佈局與檢查

  • 安全區域(reel.js 中的 SAFE):文字、標籤和字幕必須保持在 1080x1920 幀的 x 65–1015,y 269–1248 範圍內(頂部 14%,底部 35%,側邊 6%)。這些數字是 Reels 廣泛使用的第三方指導原則,並非 Meta 針對原生貼文發布的規則:請根據已發布 Reel 的手機截圖重新測量,如果應用程式有變,則設定 safeZone({...})。照片和圖畫可以超出此範圍,但文字不行。
  • render.py <block> words.json check build/<block>.check.json 在每次渲染前必須執行。它會每隔 0.25 秒取樣一次,並檢測不同元素(文字、圖畫、標籤、材質樣本、照片、字幕)之間是否發生持續 0.5 秒或以上的重疊,以及當鏡頭靜止時文字是否超出安全區域。如果檢查失敗,必須修正佈局,不得渲染。需透過負面測試證明新規則(重新引入一個已知故障;檢查必須失敗)。
  • 屬於照片的文字需放在照片元素內部(parent: photoDiv),並在其後加上 scrim(),或者將照片設為背景(bg:true)。否則檢查會報告文字疊在照片上,這正是你想要的。
  • 檢查無法發現被幀邊緣裁切的內容:這需要你的自行審查。

建立區塊(reel.js)

boot(()=>{                                   // 每個區塊以 boot(...) 開始
  handle('@yourstudio');                     // 安全區域頂部的小型帳號識別
  $('world').insertAdjacentHTML('beforeend', `<div id="h1" class="abs serif" style="left:90px;top:360px;width:900px;font-size:104px"></div>`);
  hl('h1', 'A small room, *re-planned.*', cue('A small'));          // *...* = 強調斜體;~word = 不在時間軸中
  at('#walls', cue('Walls'), null, 'draw', 0.9);                   // fx: 'fade' | 'pop' | 'wipe' | 'draw'
  photo({src:rel('photos/living.jpg'), x:0, y:1920, w:1080, h:1350, a:cue('Light'), push:[1,1.04]});
  beforeAfter({before:rel('photos/before.jpg'), after:rel('photos/after.jpg'), x:90, y:2440, w:900, h:900, a:cue('Before'), t:cue('after')});
  swatch({name:'Oak', note:'floor', color:'#B08A62', x:90, y:4480, size:270, a:cue('Oak')});   // 或 src: rel('photos/oak.jpg')
  clip({dir:rel('clips/walk'), n:240, x:0, y:5760, w:1080, h:1920, a:cue('Walk'), t0:cue('Walk')}); // 來自 frames.py 的幀
  captions(1120, 3);                         // 從時間軸嵌入字幕(有語音的 Reel)
  SHOTS=[[0,540,960,1],[cue('Light'),540,2880,1]];
});

rel(path) 會解析區塊旁邊的檔案。cue('words', after) 若時間軸中沒有這些詞彙則會報錯,因此拼寫錯誤會大聲失敗。完整範例:examples/demo/demo.js。

阿拉伯語及其他從右到左的 Reel

  • 在 boot() 中首先呼叫 rtl()。標題使用 Noto Naskh Arabic,文字使用 IBM Plex Sans Arabic(OFL);拉丁文字則回退到預設字型。阿拉伯語中不使用斜體、字母間距或大寫(字母必須連接);強調詞僅改變顏色。
  • 文字框保持 left + width;文字對齊右邊緣。從右到左排列清單(第一個命名的 = 最右邊)。
  • 提示詞忽略變音符號、塔特維爾、alef/yaa/taa-marbuta 變體,並轉換阿拉伯-印度數字;無字母的提示詞會報錯。ة 和 ه 視為一個字母,因此請提示一個更長的短語,其中兩個詞僅在此不同。數字詞僅匹配英文:在阿拉伯語中,提示數字或轉錄的詞(words.py --lang ar 選擇多語言模型)。
  • 字幕在 ، ; ؟ 和拉丁標點符號處斷行,且不顯示這些符號。
  • 範例:examples/arabic/。

外觀

主題僅存在於 engine/templates/reel.css(CSS 變數顏色、字型)和字型檔案中。預設「溫暖簡約」:紙色 #F4EFE8,墨水色 #2B2622,強調色 #9C6B4E,Cormorant Garamond 標題 + Jost 文字(均為 SIL OFL 1.1)。 若要更改外觀:編輯變數,或將 OFL 字型檔案(附帶授權檔案)添加到 engine/fonts/,並在 fonts.css 和 window.FONTS(reel.js)中列出它們。首先向業主索取 3–5 張其網格截圖並比對。

自行審查(Claude,在發送任何連絡表或渲染結果之前)

  1. 每個元素在其提示詞處開始;參考線先繪製。
  2. 文字落地後保持靜止;鏡頭靜止時保持靜止;滑動結束時沒有跳動。
  3. 沒有任何內容被幀邊緣裁切;所有文字都在安全區域內;沒有重要內容位於底部 35% 以下。
  4. 尺寸使用同一比例;每個數字都有單位;拼寫與已簽署的節拍表一致。
  5. 每個區塊的檢查 PASS;render.json 顯示 capture: fresh,所有區塊使用相同瀏覽器。
  6. 故障掃描無誤,或每個標記的時間點都經過檢查和解釋。
  7. assemble.py 列印的響度和真實峰值符合目標。

權利與隱私

  • 僅使用業主的照片,或他們擁有使用權限的照片。客戶家中:未經同意不得顯示臉部、地址或文件。
  • 音樂:僅使用業主擁有授權的曲目,或發布時在應用程式中添加的平台自有音樂庫。
  • 不宣稱業主未提出的內容(價格、時間表、獎項)。

疑難排解

  • cue not found: "...":words.json 中沒有按順序找到這些詞彙;修正節拍表或提示詞。
  • ERR reqfail:file:///...jpg:照片或剪輯路徑錯誤;使用 rel() 並檢查檔名。
  • 先前繪製的幽靈副本或黑色條帶:你使用了 --draft 渲染。刪除 --draft 重新渲染。
  • 渲染緩慢:在具有相應核心數量的機器上使用 --workers 4(最多 8);檢查和分鏡很快,影片渲染則不然。

相關技能