motionkit 是做什么的?
一部电影就是一个页面加一个脚本,其 renderFrame(f) 函数负责绘制第 f 帧(每秒30帧,分辨率1920x1080),除此之外无需其他内容。隐藏的Chrome浏览器逐帧渲染,ffmpeg进行编码(MP4视频以高质量输出,每秒60帧,大约每秒4到5 MB),配乐由合成器生成。
文件位置
- 引擎:本文件旁边的
engine/文件夹(包含动作、渲染器、音效、检查、字体)。将其称为$E并使用其完整路径。只读取该文件夹;切勿将影片放在其中(安装时引擎会自行添加node_modules,这是正常现象)。 - 工作文件夹:用户当前工作的文件夹。影片文件(
<名称>.html、<名称>.js)、assets/、ref/和out-<名称>/都将放在这里,所有命令也都从此目录运行。页面会自动在引擎中找到lib.js、moves.js和fonts/。如果工作文件夹是一个Git仓库,请将out-*/和ref/添加到其.gitignore文件中。
第一步:设置(每台机器只需一次)
- 运行
sh $E/setup.sh。它只会检查:bun、带有ffprobe的ffmpeg、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: "复制这个动效。", 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) 在应该可见时为真
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 <名称> 开始制作影片:它会在工作文件夹中写入 <名称>.html 和 <名称>.js,内容复制自 hello 示例。然后重写剪辑片段、场景和文案。然后 bun $E/build.ts <名称> 会依次执行:时间线、配乐(sound.py)、音效检查、阅读时间检查(lint.py)、渲染、合并、同步检查(sync.py),并写入 out-<名称>/<名称>.mp4。没有 bpm 的时间线会从 audio.py 获得更简单的配乐(仅剪辑)。时间线参数:sound: "fx"(仅移动音效,无音乐)、sound: "none"、alpha: true(透明背景,输出 .mov 文件;请参阅 EditMax 部分)。
影片脚本的结构
$E/hello.js 是模板;请阅读一遍。每个部分的含义如下:
- 来自
lib.js的全局变量:W、H(1920、1080)、X(画布上下文)、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 (选项) }。动作播放完毕后,剪辑片段会保持其完成状态:不会自动隐藏。通过结束场景来隐藏它,或者使用if (K.x.on(f)),其中on从start开始为真,直到until为止(没有until则永远为真)。要在过渡效果的from内部显示已完成文本动作,请在t = 1时绘制它:M.words(ctx, 1, K.line.o)。一次性效果(burst、glitchCut)还需要检查K.x.t(f) < 1。- 在剪辑片段内部调用形状:过渡效果使用选项中的
from和to;包装器(punch、shake、sweep、smear)使用draw。其他所有元素(pathDraw、burst、bars、chart、callout、logoDraw、bounce、ticker)仅使用选项。mesh和parallax是背景:直接调用它们,如M.mesh(ctx, f / 240, { colors }),其中t只是缓慢递增的数字。 - 文本选项:
x是行的中心(默认对齐方式为align: "center"),y是基线。stretch: B.wide启用宽体样式,stretch: null禁用。不会自动测量或换行文本:在宽度为150时,13个字符大约跨越1,340像素,因此渲染一个静态帧,检查每一行都位于帧内并留有边距。 - 场景表:
[起始帧, draw(f), drawAfterFinish(f)]。renderFrame重置上下文状态(保留该行),绘制当前场景,然后添加颗粒和晕影效果,最后第三个插槽用于必须保持干净的元素(如徽标)。 window.ready:渲染器等待的一个 Promise:Promise.all([M.fonts(), M.levels("out-<名称>/levels.json"), M.load({ ... })])。M.levels仅在M.eq需要时才使用,并且在文件存在之前是安全的。- 时间线:
sections是[节拍, 名称];一部短片可以只使用[[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-<名称>/fNNNN.jpg;请阅读它们。bun $E/pure.ts <名称>确认帧在单独渲染和在其他帧之后渲染时保持一致。 sound.py需要numpy,并且始终通过uv run运行;其他Python脚本仅使用标准库。
宣传片和产品影片:展示产品
一部只有大字幕和背景的影片显得很基础。成功的宣传片会通过深度和缓慢移动的摄像机将产品置于一个视觉世界中,使用简短精炼的文字,并保持每个镜头2到6秒(几个小节),而不是每拍一个剪辑。对于宣传片或产品影片,不要从 hello 开始:
- 首先确定视觉风格:一个背景世界、一个强调色、一种字体家族。用两行字向用户说明。
- 为6到8个镜头制作故事板,展示真实内容:产品的真实屏幕或输出、真实命令、真实代码、真实结果。切勿绘制产品不具备的界面,切勿捏造数字,切勿使用其他公司的徽标。字幕应是小型标签(
txt,等宽字体,左下角),而不是标题。 - 首先构建一个主镜头,渲染静态帧,并将其与真实宣传片进行比较,然后再构建其余部分。
页面必须在 moves.js 之后加载 stage.js(由 new.ts 创建的页面会自动加载)。在 renderFrame 开头加上 M.frame(); M.reset(X);。舞台元素(全部位于 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 }) | 一个小屏幕网格,每个屏幕实时播放一个动作(切勿在瓷砖内部放置过渡效果) |
term(ctx, f, { x, y, size, lines: [{ text, at, speed, prompt, mark: "ok" 或 "run", color }] }) | 终端行,在指定帧上逐字显示或出现 |
code(ctx, { lines[], x, y, size, lead, hot, shown }) | JavaScript代码,带有柔和的语法高亮和一行高亮显示 |
wave(ctx, { peaks, x, y, w, h, played, beats[], blocks[] }) | 真实配乐波形,带有播放头、节拍刻度和轨道上的剪辑片段块 |
舞台元素本身不发出声音:在时间线上手动添加提示,如 { f, kind, gain },kind 可以是 type、tick、pop、ding。
配合真实歌曲剪辑
- 用户提供音乐文件,并且必须拥有使用它的权利;请说明这一点,并将许可证要求的署名信息放在结束卡片上。切勿从流媒体网站下载歌曲。
uv run $E/beats.py <文件>会打印速度和每个小节的响度。选择一个从某个小节开始、几小节后有一段变化的节选,然后运行uv run $E/beats.py <文件> --from <秒数> --len <秒数> --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 <名称> --overlay,以$E/title.js为模板。时间线包含alpha: true和sound: "fx";构建过程会写入out-<名称>/<名称>.mov(ProRes 4444格式,带有透明背景)。规则:清除帧(X.clearRect),切勿填充帧;不使用grain、vignette、mesh、parallax或全帧过渡效果;在文字下方添加柔和阴影或底板,使其在任何素材上都清晰可读;将文字限制在帧中间90%的区域;在结尾淡出。 - 全帧(开场、结束卡片、镜头之间的卡片):
bun $E/new.ts <名称>,以$E/hello.js为模板;构建过程会写入out-<名称>/<名称>.mp4。 - 音效(在时间线中设置
sound:):对于独立使用的开场或结束卡片,可以省略以获得完整配乐;如果剪辑片段播放覆盖素材、语音或用户音乐,则使用"fx"只保留动作本身的声音;如果是静音,则使用"none"。如果编辑中已有音乐,请询问用户想要哪种方式。 - 构建,修复每个
WARNING,并在继续之前阅读静态帧。
4. 将文件放到持久位置。 EditMax引用导入的文件,而不是复制它们。将完成的文件从 out-<名称>/ 复制到用户保留的文件夹中(询问一次;建议使用 ~/Movies/Motions/),然后从该位置导入。
5. 放置而不移动用户的剪辑。 使用时间参数调用 insert_clip 会
……[翻译截断]