motionkit 是做什麼的?
一部電影就是一個網頁加上一個腳本,腳本中的 renderFrame(f) 繪製第 f 幀(每秒 30 幀,1920x1080),除此之外不做任何事。隱藏的 Chrome 逐步執行各幀,ffmpeg 負責編碼(MP4 會加倍至每秒 60 幀,高畫質,大約每秒 4 到 5 MB),配樂則是合成產生的。
檔案位置
- 引擎:此檔案旁邊的
engine/資料夾(包含動作、渲染器、音效、檢查、字型)。將其路徑設為$E並使用完整路徑。從中讀取內容,但切勿將電影放在裡面(設定時會在那裡加入它自己的node_modules,這是預期行為)。 - 工作資料夾:使用者正在工作的資料夾。電影(
<name>.html、<name>.js)、assets/、ref/和out-<name>/都會放在這裡,且所有指令都從此目錄執行。一個網頁會自行在引擎中找到lib.js、moves.js和fonts/。如果工作資料夾是一個 git 儲存庫,請將out-*/和ref/加入其.gitignore。
首先:設定(每台機器一次)
- 執行
sh $E/setup.sh。它只會檢查:bun、ffmpeg(含 ffprobe)、python3、uv、Chromium 瀏覽器(Chrome、Chromium 或 Edge;可用CHROME_PATH覆蓋),以及引擎的一個套件。 - 如果顯示
Ready,請繼續。如果只有engine package遺失(這是全新安裝的正常情況),請直接執行sh $E/setup.sh --install,無需詢問:它只會將一個套件放入技能自己的資料夾中。如果某個工具或瀏覽器顯示為MISSING,則向使用者顯示該清單以及它所列印的安裝指令,並在安裝軟體到他們的機器前詢問一次。 - 如果同意,執行
sh $E/setup.sh --install(macOS 上為 Homebrew,bun 和 uv 使用官方安裝程式),然後再次執行檢查。標示為run this yourself的行需要使用者的密碼(Linux 上的sudo)或手動下載:切勿執行這些行;提供使用者確切的指令並等待。如果安裝失敗,提供使用者一個他們可以自行執行的指令。 - 證明它可以運作:從工作資料夾執行
bun $E/build.ts hello並確認out-hello/hello.mp4存在。
僅在 macOS 上測試過;如果使用者使用 Linux 或 Windows,請告知。
可複製的範例:$E/hello.js(一部在節拍網格上的電影,所有聲音來自片段,場景由程式碼繪製)、$E/title.js(一個可疊加在素材上的透明標題)、$E/moves-demo.js(每種動作各一次)。
建構電影:先有片段
將每個動作的一次使用宣告為一個片段。圖像、聲音和閱讀時間檢查都來自那一行。
const M = window.MOVES, B = M.brand, G = M.grid(120); // 120 bpm:一拍是 15 幀,一個小節是 60 幀
const K = { line: M.clip("words", G.bar(1) + 2, 10, { text: "Copy the motion.", x: 960, y: 590, size: 110, fam: B.display, stretch: B.wide, weight: 600, color: B.paper, until: G.bar(2) }) };
// 在一個場景中: K.line.draw(X, f) K.line.on(f) 在其應顯示時為 true
window.timeline = () => ({ fps: 30, total: TOTAL, bpm: G.bpm, sections: [[0, "intro"], [4, "build"], [8, "drop"], [24, "outro"]], cues: M.cues(), captions: M.captions() });
對於發表或產品電影,請先閱讀下方的「發表與產品電影」。使用 bun $E/new.ts <name> 開始一部電影:它會在工作資料夾中寫入 <name>.html 和 <name>.js,內容從 hello 範例複製而來。改寫片段、場景和文案。然後 bun $E/build.ts <name> 會依序執行:時間軸、配樂(sound.py)、聲音檢查、閱讀時間檢查(lint.py)、渲染、合併、同步檢查(sync.py),並寫出 out-<name>/<name>.mp4。沒有 bpm 的時間軸會從 audio.py 獲得一個更簡單的配樂(僅有剪輯)。時間軸開關:sound: "fx"(僅有動作音效,無音樂)、sound: "none"、alpha: true(透明背景,輸出 .mov;請參閱 EditMax 章節)。
電影腳本的結構
$E/hello.js 是範本;請閱讀一次。每個部分的意義:
- 來自
lib.js的全域變數:W、H(1920, 1080)、X(畫布 context)、clamp、lerp、TAU、hash、noise、txt(ctx, text, x, y, size, weight, color, fam, italic, align)、rr(圓角矩形)、vignette(strength, innerRadius)。 M.clip(move, start, length, options):start和length是以幀為單位;length是動作播放所需的時間,而不是它停留的時間。它回傳{ draw(ctx, f), t(f) (0..1 貫穿整個動作), on(f), o (即 options) }。在其長度之後,一個片段會繼續繪製其完成狀態:它不會自己隱藏。透過結束場景來隱藏它,或使用if (K.x.on(f)),其中on從start開始為 true,直到until(沒有until則永遠為 true)。要在轉場的from內部顯示一個已完成的文字動作,請在t = 1時繪製它:M.words(ctx, 1, K.line.o)。一次性效果(burst、glitchCut)也需要K.x.t(f) < 1。- 在片段內部呼叫形狀:轉場接受 options 中的
from和to;包裝器(punch、shake、sweep、smear)接受draw。其他所有項目(pathDraw、burst、bars、chart、callout、logoDraw、bounce、ticker)只接受 options。mesh和parallax是背景:直接呼叫它們,M.mesh(ctx, f / 240, { colors }),其中t只是一個緩慢增長的數字。 - 文字選項:
x是行的中心(align: "center"是預設值),y是基線。stretch: B.wide提供寬版裁切,stretch: null將其關閉。沒有東西會為你測量或自動換行文字:在 150 寬度的字體大小下,13 個字元約佔 1340 像素,因此請渲染一個靜止畫面並檢查每一行是否都位於框架內且有邊距。 - 場景表:
[first frame, draw(f), drawAfterFinish(f)]。renderFrame會重置 context 狀態(保留那行),繪製當前場景,然後是顆粒效果和暗角,接著是第三個槽位,用於任何必須保持清晰的內容(如標誌)。 window.ready:渲染器會等待的一個 promise:Promise.all([M.fonts(), M.levels("out-<name>/levels.json"), M.load({ ... })])。M.levels僅在需要M.eq時才需要,且在檔案存在之前是安全的。- 時間軸:
sections是[beat, name];一個短片可以只使用[[0, "intro"], [8, "drop"], [16, "outro"]],並且每個drop的開始會獲得半拍靜音(在那裡保持或mute:片段)。sync列出剪輯點和低音重擊必須同時發生的幀:從 drop 開始使用每個場景的第一幀。「變化」是根據影片幀與幀之間的通常差異來判斷的,因此一個恰好完成在同步幀上的轉場在那裡不會留下任何變化,並且檢查會印出NO CHANGE:添加一個短暫閃爍或在那個幀上開始下一個動作。cuts是每個場景的開始。 - 顏色:從
M.brand(B)讀取所有內容。M.rgba(B.ink, 0.7)以指定不透明度給出一個顏色,M.mix(a, b, t)給出兩個顏色之間的一個顏色。M.setBrand會就地更改M.brand;在任何片段被宣告之前呼叫它,並同時設定accentOnDark(用於深色背景的強調色;它不跟隨accent)。對於沒有寬版裁切的字型,設定wide: null。 - 長度:閱讀時間設定了最小值:一個 3 個字的行在出現後大約需要 1.3 秒,因此在 120 bpm 下,每個小節一行。配樂大約在最後一幀之後半秒結束並在那裡被截斷;以一個
outro部分結束,使剪輯落在其尾部。 - 建構結果:即使檢查失敗,
build.ts也會以代碼 0 退出;失敗會在其上方細節之後列印為WARNING:行。將每個警告視為失敗:讀取輸出(過濾掉frame n/total進度行),修正,重新建構直到沒有任何警告。靜止畫面會寫入為out-<name>/fNNNN.jpg;請閱讀它們。bun $E/pure.ts <name>確認幀在單獨渲染和在其他幀之後渲染時結果相同。 sound.py需要 numpy,並且總是透過uv run執行;其他 Python 腳本僅使用標準函式庫。
發表與產品電影:展示產品
一部由大字和背景組成的電影會顯得很基本。有效的發表電影會將產品保持在螢幕上,處於一個具有深度和緩慢移動鏡頭的統一視覺世界中,使用簡短精簡的文字,並將鏡頭保持 2 到 6 秒(幾個小節),而不是每拍一個剪輯。對於發表或產品電影,請勿從 hello 開始:
- 首先決定外觀:一個背景世界,一個強調色,一種字體家族。用兩行文字告知使用者。
- 對 6 到 8 個真實事物的鏡頭進行分鏡:產品真實的螢幕或輸出、真實的指令、真實的程式碼、真實的結果。切勿繪製產品沒有的介面,切勿發明數字,切勿使用其他公司的標誌。說明文字是小的標籤(
txt,等寬字體,左下角),而不是標題。 - 首先建構一個主要鏡頭,渲染靜止畫面,並在建構其餘部分之前將其與真實的發表影片進行比較。
網頁必須在 moves.js 之後載入 stage.js(由 new.ts 建立的頁面已經這樣做)。以 M.frame(); M.reset(X); 開始 renderFrame。舞台元件(全部在 MOVES 上):
| 元件 | 說明 |
|---|---|
backdrop(ctx, f, { base, lights[], streak }) | 近乎黑色,帶有緩慢的光池:所有事物所在的背景世界 |
glass(ctx, x, y, w, h, { r, top, bottom, rim, shadow }) | 一個深色圓角面板,帶有光暈邊緣和柔和陰影 |
camera(ctx, cam, draw) 搭配 cam(a, b, t, ease) | 觀察更大場景中的一個點:{ x, y, zoom, rot };從極近距離開始並拉遠,或緩慢漂移 |
tilt(ctx, draw, { depth, focus, blur, fog, top, height, width, cx, roll }) | 將全幀場景繪製為一個傾斜的平面,遠端邊緣較小、較暗且失焦 |
tiles(ctx, f, { cells: [{ draw(ctx, t), label, bg, len, hold }], cols, x, y, w, gap }) | 一個小螢幕網格,每個都在即時播放一個動作(切勿在 tile 內部放置轉場) |
code(ctx, { lines[], x, y, size, lead, hot, shown }) | JavaScript,帶有低調的語法顏色和一行高亮 |
舞台元件本身不產生任何聲音:在時間軸上手動添加提示,{ f, kind, gain },種類如 type、tick、pop、ding。
配合真實歌曲剪輯
- 使用者提供音樂檔案,且必須有使用它的權利;請告知使用者,並將授權要求的版權資訊放在結尾卡片上。切勿從串流網站下載歌曲。
uv run $E/beats.py <file>會列印速度和每個小節的音量。選擇一個從一個小節開始的片段,並在幾小節後有一個段落變化,然後執行uv run $E/beats.py <file> --from <seconds> --len <seconds> --out beats.json,它會寫入beats.json和beats.js。- 在網頁中於電影腳本之前載入
beats.js。將每個場景的開始寫為一個節拍編號:const b = (n) => window.BEATS.frames[n]。在音軌變化的位置,在小節(每 4 拍)上進行剪輯。 - 在時間軸中設定
music: { file, from }、sound: "fx"(動作本身的聲音會保持,但音量較低,在音樂之下)或"none"、一個bpm,以及sync: [真實剪輯的幀]。建構過程會將音軌混音至 -16 LUFS,淡化其結尾,並檢查每個剪輯是否在一幀內有一個低音重擊。不要僅僅為了通過檢查而添加閃爍。
供 EditMax 使用的動作(影片編輯器時間軸上的片段)
當使用者在 EditMax 中編輯並希望在剪輯中加入一個動作時使用此功能:覆蓋在素材上的動畫標題、開頭或結尾卡片、鏡頭之間的動態文字節拍。它需要在同一個代理工作階段中有兩樣東西:一個 Shell(用於渲染)和 EditMax 的 MCP 工具(inspect_timeline、import_media、...;從 EditMax 的代理標籤頁設定)。EditMax 自己的應用程式內聊天拒絕 shell 指令,因此無法執行此技能:請告知使用者並告訴他們連接一個代理,例如 Claude Code。如果 EditMax 工具遺失或沒有回應,無論如何都渲染片段並提供使用者檔案路徑以供拖入。
1. 選擇方法。 當 EditMax 自己的工具能涵蓋該動作時,優先使用它們,因為文字在應用程式中仍可編輯:
| 想要的 | 使用 |
|---|---|
| 一個淡入、滑入、彈出、打字或擦入的標題或下方第三行 | add_text / add_lower_third + set_text_animation |
| 兩個片段之間的溶解、推入、滑動、擦除或光圈轉場 | set_transition |
| 一個移動、縮放或淡化的片段 | set_keyframe、set_clip_transform、set_clip_fades |
| 其他任何東西:重擊、擾亂、翻牌、文字輪盤、劃掉、高亮、路徑或標誌繪製、圖表、計數器、爆發、多動作序列、完整的開頭或結尾 | 使用此技能渲染一個片段 |
2. 讀取專案。 inspect_timeline:注意幀大小、幀率、軌道以及動作發生的時間。此引擎渲染 1920x1080,每秒 30 幀。其他尺寸的 16:9 專案可以乾淨地縮放它。對於垂直或方形專案,直接說明片段將被加上信箱黑邊,或者必須使用 set_clip_transform 放大並裁切,並且原生標題可能更適合。
3. 渲染。
- 疊加層(位於素材之上):
bun $E/new.ts <name> --overlay,以$E/title.js為模型。時間軸有alpha: true和sound: "fx";建構過程會寫出out-<name>/<name>.mov(帶有透明背景的 ProRes 4444)。規則:清除幀(X.clearRect),切勿填充它;不使用grain、vignette、mesh、parallax或全幀轉場;在文字下方放置一個柔和陰影或底板,使其在任何素材上都可讀;將文字保持在幀中間的 90% 內;在結尾淡出。 - 全幀(開頭、結尾、鏡頭之間的卡片):
bun $E/new.ts <name>,以$E/hello.js為模型;建構過程會寫出out-<name>/<name>.mp4。 - 音效(在時間軸中設定
sound:):對完整的配樂(單獨存在的開頭或結尾)省略它;對於僅包含動作聲音(任何覆蓋在素材、語音或使用者音樂上的內容)使用"fx";對靜音使用"none"。如果編輯中已有音樂,請詢問使用者想要哪一種。 - 建構,修正每個
WARNING,並在繼續之前閱讀靜止畫面。
4. 將檔案放在持久的位置。 EditMax 會引用已匯入的檔案,不會複製它們。將完成的檔案從 out-<name>/ 複製到使用者保留的資料夾(詢問一次;建議 ~/Movies/Motions/),並從那裡匯入。
5. 放置它而不移動使用者的剪輯。 使用時間的 insert_clip 是一個撕裂...