remotion-demo-video
一个精简的工作流,用于构建Remotion 4.x的宣传/演示/预告片视频。它编码了项目布局、动画原语、渲染命令以及实际中会浪费大量时间的特定陷阱。
何时使用此技能
当用户想要创建、编辑、渲染或调试Remotion视频时触发。特别是:
- 为产品/论文/仓库演示搭建一个新的Remotion项目
- 向现有Remotion项目添加新场景
- 将预先录制的屏幕录制片段拼接成一个包含叠加层、标题和转场的精美视频
- 渲染——单个场景、完整时间线、透明(alpha)叠加层、PNG序列
- 调试Remotion特有的怪异问题(composition-id不匹配、默认值不同步、
useCurrentFrame在<Sequence>内返回意外值、Windows上ffmpeg清理时的EPERM错误等)
如何理解工作流
一个精美的Remotion视频不是“一个你可以渲染成单个MP4的巨大时间线”。它是一组小型、可单独渲染的场景——每个场景渲染为自己的片段,并在下游编辑器(DaVinci Resolve、Premiere、Final Cut)中组装,在其中添加音乐、旁白和场景间转场。
这是从SimWorld宣传片发布中得出的最大见解,此技能应默认引导用户采用此工作流。试图在Remotion中完成所有事情——最终的时序调整、音频混合、淡入淡出——既痛苦又缓慢;而在编辑器中做这些则轻而易举。
让每个工具发挥其优势
- Remotion擅长的: 可编程的、可重复的、参数化的场景——动画标题、数据驱动图表、UI模型、任何你会在After Effects中手工设置关键帧的内容。场景逻辑存在于代码中;“用30个不同的产品名称重建同一个标题”只是一个属性更改。
- 下游编辑器擅长的: 时序、音频、淡入淡出、调色、“让这个场景延长1.2秒”。所有这些在编辑器中是立竿见影的,而在Remotion中则需要编辑代码并重新渲染。
工作循环
- 确定脚本。 逐节提纲,每节有目标秒数。总时长通常为30-90秒。
- 设置一次项目外壳。
theme.ts(调色板、字体、fps、尺寸)、Root.tsx(合成注册表)、共享组件(Reveal、ProgressBar、缓动函数)。 - 一次构建一个场景。 每个场景是一个自包含的
<AbsoluteFill>React组件,针对frame 0设计,并有自己独立的<Composition>,以便你可以单独渲染它。在Studio中迭代(npm run dev),使用属性面板;喜欢后渲染为MP4(npm run render:<scene>)。 - 将每个完成的场景渲染为自己的片段——这是实际的交付物,而不是完整时间线的MP4。每个场景的片段可以拖放到任何编辑器的时间线中。对于需要alpha通道的片段(下三分之一、要合成到屏幕录制上的标志),使用
--codec=prores --pixel-format=yuva444p10le --prores-profile=4444。 - (可选)将场景连接到
Main.tsx中,使用从theme.ts中的TIMELINE常量读取的<Sequence>块。这为你提供了一个完整时间线预览渲染(npm run render),用于进行完整性检查——但将其视为预览,而非主版本。 - 在DaVinci / Premiere / Final Cut中组装。 将每个场景的片段按顺序拖到轨道上,调整修剪手柄以获得精确时序,添加音乐/旁白/音效设计,在场景之间应用转场(对于这种审美,1帧的淡入淡出通常感觉合适),调色,导出。
何时建议完整的Remotion渲染
只有在以下情况下,完整时间线渲染才是正确的输出:
- 用户没有下游编辑器,今天需要一个可交付文件。
- 视频完全静音(完全没有音乐或旁白)。
- 没有比硬切更复杂的场景间转场(即使如此,使用逐个场景的方法在代码中调整也更容易)。
否则,默认建议逐个场景渲染 + 编辑器组装。
此技能在scaffold/中附带了一个工作脚手架——将其复制为新项目的起点,然后根据需要阅读下面的参考资料。
实际的端到端循环(当输入是论文/规范/仓库时)
这是此技能真正设计用于的工作流。用户通常不会过来说“我想要一个显示X的动画标题”。他们会带着一个论文PDF、项目规范或现有仓库,问题是“把这个变成一段60秒的视频”。此技能的存在就是为了使这个循环快速且易于处理。
1. 源材料 → 论文 PDF / 规范 / 现有仓库 / README
↓
2. Claude分析 → 提取叙事节拍;确定每个节拍需要展示什么
(UI界面、实验录制、图表、智能体视角等)
↓
3. Claude搭脚手 → 场景代码 + 每个视觉区域中的<VideoPlaceholder slot="..." />
架,带占位符 再加上一个ASSETS.md,列出用户需要录制的所有片段
(插槽名称、时长、应显示的内容)
↓
4. 用户录制素材 → 将mp4文件放入public/clips/<slot>.mp4
匹配脚手架中的插槽名称
↓
5. Claude替换 → 将<VideoPlaceholder>替换为
占位符为片段 <OffthreadVideo src={staticFile("clips/<slot>.mp4")}>
↓
6. 逐个场景迭代 → npm run render:<scene>,观看,调整文本/
时序/动画,重新渲染。代码端的更改很快;
如果需要重新录制素材,那是循环中最慢的部分。
↓
7. 在编辑器中组装 → 将每个场景的MP4放入DaVinci / Premiere / FCP,
添加音乐 + 旁白 + 场景间转场,导出。
第2步详解——分析源材料
当用户提供论文/规范时,在编写任何代码之前执行此操作:
- 提取叙事。 3-5个节拍的弧线是什么?(“问题 → 我们的方法 → 关键结果 → 影响”是一个起始模板;根据实际论文进行调整。)叙事驱动场景列表。
- 对于每个节拍,决定展示什么。 仅动画文字很无聊。每个节拍都应该有一个具体的视觉内容——一个UI界面、一个实验录制、一个图表、一个比较、一个系统图。将视觉效果与声称相匹配:如果论文声称“方法X快3倍”,显示并排的计时。
- 区分Remotion生成的内容和用户必须录制的内容。 Remotion擅长:标题、转场、图表、UI模型、布局、动画图表。Remotion无法生成:用户产品的真实屏幕录制、实验素材、智能体视角、项目外部的任何内容。将这些列为用户提供的素材。
- 在搭建脚手架之前草拟时间线。 逐节节拍,目标时长总和为30-90秒。相应地更新
theme.ts中的TIMELINE。总长度应在构建场景之前确定,以便每个场景都知道自己的预算。
第3步详解——使用占位符搭建脚手架
此技能的关键交接约定:插槽名称。用户需要录制的每个片段都有一个稳定的slot字符串。占位符和真实片段共享该插槽名称。
// 搭建脚手架期间:
<VideoPlaceholder
slot="agent-loop"
description="连续智能体录制:行走、失败、使用更新策略重试。"
accent={COLORS.blue}
/>
// 用户录制public/clips/agent-loop.mp4后,替换为:
<OffthreadVideo
src={staticFile("clips/agent-loop.mp4")}
muted
style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" }}
/>
始终在脚手架根目录中编写一个ASSETS.md(脚手架附带一个模板)。它列出用户需要填写的每个插槽,包括:插槽名称(=文件名)、应显示的内容、目标时长、宽高比建议以及任何时序说明(例如“失败事件大约在7秒标记处”)。这是用户录制任务的单一事实来源。
第4-5步详解——资产交接
用户带着放入public/clips/的文件回来。需要检查三件事:
- 文件名与插槽名称完全匹配。
agent-loop.mp4,而不是agent_loop.mp4或AgentLoop.mp4。 - 时长至少为场景长度(如果片段较短且需要重复,则使用
<Loop>)。 - 宽高比合理——对于全屏场景,匹配合成的1920×1080。对于2×2网格中的卡片,4:3或16:9都适用;只需保持一致。
然后机械地将每个<VideoPlaceholder>替换为<OffthreadVideo>。每个插槽只需一行替换;无需更改动画逻辑。如果用户的录制是时序敏感的(例如,“失败事件在7秒处”),则将其作为场景属性暴露出来,以便依赖时序的叠加层(FAIL印章、奖励数字)可以在无需重新录制的情况下进行调整。
为什么这个循环对技能很重要
此技能的工作不是“按需编写Remotion代码”——而是使从源材料到发布视频的循环易于处理。占位符模式正是让Claude能够一次性搭建整个视频的结构,然后返回一个ASSETS.md,以便用户确切知道要录制什么的关键。没有它,循环就会停滞——用户录制错误的内容,或者脚手架等待素材,或者代码结构必须在事后进行改造。
当用户带着源材料出现时,正确的开场白通常是:
“让我先阅读论文/规范。然后我将提出一个包含目标时长的逐节大纲、一个你需要录制的片段列表,并使用占位符搭建代码脚手架,这样你可以在我继续构建场景的同时开始录制。”
新项目快速入门
# 1. 复制脚手架
cp -r <skill-dir>/scaffold ./my-demo
cd my-demo
npm install
# 2. 打开Remotion Studio进行实时编辑
npm run dev # 打开 http://localhost:3000
# 3. 迭代时渲染单个场景
npm run render:opening
# 4. 准备就绪后渲染完整时间线
npm run render
脚手架包含一个工作场景(OpeningTitle)、奶油色温暖主题、Reveal + 缓动函数、ProgressBar、一个占位符Main时间线以及一个预配置了渲染脚本的package.json。
项目布局(行之有效的结构)
my-demo/
├── package.json # 渲染脚本、预渲染钩子
├── remotion.config.ts # 编解码器/像素格式默认值
├── tsconfig.json
├── public/
│ ├── assets/ # 标志、静态图像(staticFile("assets/..."))
│ └── clips/ # 屏幕录制/视频源
├── scripts/
│ └── sync-clips.mjs # 每次渲染前标准化片段
└── src/
├── index.ts # registerRoot(Root)
├── Root.tsx # <Composition>注册表——id、schema、defaultProps
├── theme.ts # COLORS、FONT_STACK、RADIUS、SHADOWS、TIMELINE
├── fonts.ts # @remotion/google-fonts + 本地@font-face
├── Main.tsx # 完整时间线合成器(<Sequence>块)
├── components/
│ ├── Reveal.tsx # 淡入+上移包装器(delay/duration/rise)
│ ├── Easing.ts # EASE_OUT、EASE_IN_OUT(贝塞尔)
│ ├── ProgressBar.tsx # 固定在底部的4阶段指示器
│ └── VideoPlaceholder.tsx # 带有插槽标签的灰色卡片,稍后替换为OffthreadVideo
└── scenes/
├── OpeningTitle.tsx # 每个文件一个场景,每个都是<AbsoluteFill>
├── ProductReveal.tsx
├── AgentLoop.tsx
└── ...
为什么这样布局:场景是独立的(易于单独渲染和迭代),共享的视觉语言存在于theme.ts + components/中,因此外观保持一致,而Root.tsx是哪些内容可渲染的单一事实来源。
合成模式(仔细阅读)
每个场景导出三样东西:
- 一个zod schema——定义Remotion Studio中的属性面板UI(滑块、下拉菜单)。
- 一个
*_DEFAULTS常量——当此场景嵌入到完整时间线中时,由Main.tsx使用的值。 - React组件本身,类型为
React.FC<Props>。
// src/scenes/ProductReveal.tsx
import { z } from "zod";
export const productRevealSchema = z.object({
titleSize: z.number().min(40).max(280),
subtitleSize: z.number().min(14).max(80),
outerPaddingV: z.number().min(0).max(200),
});
export type ProductRevealProps = z.infer<typeof productRevealSchema>;
export const PRODUCT_REVEAL_DEFAULTS: ProductRevealProps = {
titleSize: 168,
subtitleSize: 64,
outerPaddingV: 80,
};
export const ProductReveal: React.FC<ProductRevealProps> = (p) => { ... };
然后在Root.tsx中,使用内联副本的默认值注册它:
<Composition
id="ProductReveal"
component={ProductReveal}
durationInFrames={120}
fps={30}
width={1920}
height={1080}
schema={productRevealSchema}
defaultProps={{
// ⚠ PRODUCT_REVEAL_DEFAULTS的内联副本——Remotion Studio的
// “Save default props”通过AST操作写回此对象。请保持
// 与上面导出的常量同步。
titleSize: 168,
subtitleSize: 64,
outerPaddingV: 80,
}}
/>
为什么重复: Remotion Studio的“Save default props”按钮通过AST操作编辑Root.tsx中的defaultProps={…}字面量。它无法重写导入的常量。因此,内联副本是Studio会修改的那个,而导出的*_DEFAULTS是Main.tsx读取的那个。当Studio写入新值时,将它们复制到导出的常量中,以便完整时间线渲染也使用它们。这是references/pitfalls.md中的陷阱#4。
动画原语(你实际需要的小集合)
Remotion中的所有动画都是useCurrentFrame() + interpolate()。没有内部时钟,没有“播放”——每一帧都是frame的纯函数。