Motion B-roll 用于为创作者的口播视频制作动态图形片段。每个片段都是一个从不切换的单一形状:它在不同状态之间变形,改变大小、圆角和颜色,同时内容以短暂模糊进行替换。光标通过真实的点击和拖拽来驱动这些变化,并且每一次变化都落在某个说出的词上。
输出是一组片段,创作者可以把它们放入自己的剪辑软件。此外还会提供一段把片段剪入后的预览渲染,以及两个 HTML 页面:一个用于逐步查看片段,一个用于对比前后效果。
所需内容都在该技能文件夹中:
engine/:运动引擎(motion.js)、页面外壳(base.css)、build.py(片段 → 自包含 HTML)、render.js(带运动模糊的逐帧渲染)、beats.js(静帧联系表)、字体(Geist,OFL)。
scripts/:setup.sh、inspect_video.py、words.py、composite.py、make_pages.py。
reference/engine-api.md:如何编写片段。在编写第一个片段前先阅读它。
examples/opus-aoe2/:一个真实 54 秒视频的六个完成片段。把它们作为质量标准和起点。
下文中的 $SKILL 表示该技能文件夹。在用户项目的 motion/ 文件夹中工作。
- 设置(仅首次运行)
bash $SKILL/scripts/setup.sh ./motion
这会检查 Node、Python 3 和 ffmpeg,并把 Playwright + Chromium 安装到 motion/node_modules。每个引擎脚本都要用 NODE_PATH=./motion/node_modules 运行。
- 访谈
使用 AskUserQuestion 工具;只进行一轮。需要了解:
视频。一个路径。把它复制或引用为 motion/work/source.*。
字幕。带时间戳的 SRT 或 VTT 最好。如果只有纯文本,就从音频中获取词级时间戳(pip install faster-whisper),或者要求提供 SRT。
密度:
轻:每分钟 2–4 个片段,只选最强的时刻。
中(默认):每分钟 4–7 个。
重:覆盖大多数台词,只留很短的露脸间隙。
外观:
默认调色板:暖灰色画布、黑白组件、一个橙色强调色。
或者使用他们的品牌:颜色,以及可选的字体。请他们把 logo、截图或品牌色放入 motion/inputs/,然后读取这些内容。
需要避免或包含的内容。要留在露脸画面上的台词、要展示的真实数字或名称、要重现的产品截图。
跳过用户已经回答过的问题。
- 检查素材
python3 $SKILL/scripts/inspect_video.py motion/work/source.mp4 motion/work python3 $SKILL/scripts/words.py transcript.srt > motion/work/words.txt # .vtt 也可以
自己查看 motion/work/contact.png。video.json 给出分辨率、fps 和布局分区:
full:说话人占满画面。
pip:说话人在一个框里,其余区域为空。它会包含框的位置,以及框改变大小或移动时的警告。
black:整个画面为黑(淡入淡出、片头或片尾)。
片段必须匹配视频的分辨率和 fps。来自 SRT 或 VTT 的词时间是估计值(±0.2 秒);请按估计值对待。
- 规划,并在写任何代码前获得批准
展示一个表格,每个片段一行:
| # | 入点–出点 | 覆盖的台词 | 形状做什么,在哪些词上 | 处理方式 | 原因 |
创意方向:决定每个片段占据画面的多少。这是最重要的决定;要逐个片段决定并给出理由。
全画面切出。画面全是说话人,而台词描述了某个可看的东西:产品、流程、对比、数字、章节变化。
按想法长度切出(3–10 秒),然后把脸还给观众。
不要在开场钩子的第一秒切出,也不要在个人、情感或观点类台词上切出。
切出之间至少保留约 2 秒的露脸。
在空白区域做面板。剪辑已经留出了空间:PiP 框、分屏或纯色区域。
渲染一个透明片段(bg:null),居中放在空白区域,尺寸要保证在任何时刻都不碰到说话人的框。
它可以很长(15 秒以上)作为一个连续变形,因为说话人始终在屏幕上。
如果 inspect_video.py 报告框发生变化,询问这是否是有意为之,然后按最大框来定尺寸,或者在变化处拆分面板。
什么都不做。台词是关于说话人的,或者离另一个片段太近。
内容规则:
展示主题的真实对象:文件夹、终端、文件、产品、结果。从词句中提取它们。把每个想法映射到 reference/engine-api.md 词汇表中的一个交互(胶囊 + 点击、进度、勾选、状态岛、带拖拽的卡片、滑块、开关、标签页、图表 + 提示框、搜索/筛选、文件拖放、终端输入、并排对比、章节卡片)。
绝不编造数字、引语、价格或结果。使用相对条形、骨架文本行或取自字幕的标签,并列出哪些是示意性的。只有在用户提供时才放入真实数字。
每个片段一个想法。一个连续片段(整个 PiP 部分)可以是一个带多个状态的长片段。
按语音节奏,而不是音乐:每个说出的节拍一次变化,间隔约 0.4–1.2 秒。
等待用户批准或修改计划。
- 构建
按照 reference/engine-api.md 和示例,在 motion/clips/NN-name.html 中为每个片段写一个片段文件。然后构建:
python3 $SKILL/engine/build.py motion/dist motion/clips/*.html
把每个状态变化放在它的词上:片段本地时间 = 词时间 − 片段入点。
- 在关键词上检查静帧
NODE_PATH=./motion/node_modules node $SKILL/engine/beats.js motion/dist/NN-name.html motion/work/NN.png 0.4 1.2 2.1 …
选择每个状态稳定下来的时间点,以及几个变形中间时刻。自己查看每一张联系表。修复任何拥挤、被裁切、不可读或偏离词句的地方,或者光标离开画面的地方。然后再检查一次。面板片段在联系表中渲染在黑色上,以便判断。
- 渲染
NODE_PATH=./motion/node_modules node $SKILL/engine/render.js motion/dist/NN-name.html motion/out/NN-name_0m32s40.mp4 30000/1001
使用视频的 fps。
面板片段(bg:null)必须写成 .mov,以提供带 alpha 的 ProRes 4444。
按时间线入点命名每个文件(0m32s40 = 0:32.40)。
渲染每帧使用 4 个子帧,因此比实时慢。在多核机器上并行运行两个片段。
- 交付
编写 motion/plan.json(schema 见 reference/engine-api.md),然后:
python3 $SKILL/scripts/composite.py motion/plan.json motion/out/preview.mp4 python3 $SKILL/scripts/make_pages.py motion/plan.json motion/out/preview.mp4
用户会得到以下内容,全部在 motion/out/ 中:
片段
TIMING.md:文件、入点、出点、覆盖的台词、处理方式,以及哪些是示意性的
preview.mp4:他们的视频剪入片段后的版本,硬切,原始音频
viewer.html:逐步查看片段
compare.html:原始 vs 预览,同步,以并排、堆叠或擦除方式对比
明确说明预览仅供审阅:最终剪辑时,他们要把片段放入自己的剪辑软件,在那里可以微调时间、添加转场和混合音频。
样式默认值
用户的品牌会覆盖这些默认值。
颜色:画布 #E9E7E2,墨色 #0B0B0B,白色组件,一个强调色 #FF5A1F。深色素材上的面板片段使用浅色形状(#F4F2EE)。
字体和图标:UI 文本用 Geist,代码、文件名和终端用 Geist Mono。一套图标,一种描边粗细(M.icon)。
运动:弹簧,最多带极小过冲。前缘和后缘使用不同的弹簧,使指示器拉伸。相机会缩放,让每个状态填满画面。
禁止:弹跳缓动、粒子、发光、UI 外框上的渐变、混合图标描边、死时间、任何看起来像模板的东西、编造的数据。
注意事项
绝不要在相机会缩放的对象上使用 will-change;文本会渲染模糊。
变形容器内的文本替换需要自己的进入和退出时间(M.vis din/lin/lout),否则旧文本和新文本会重叠。
mix-blend-mode: difference 标签必须放在自身带有混合模式的图层中。带滤镜的父级会隔离它们。
滑到高亮下方的项目需要 M.FAST 弹簧,否则高亮行会短暂空着。
在每次相机缩放时,包括变形过程中,都要让光标保持在画面内。
来自 SRT 的词估计会在一个 cue 内漂移;尽可能把重要变化放在 cue 的第一个或最后一个词上。
片段结束时保持其最后状态;如果它的时间槽更长,合成会保持最后一帧。