Remotion:用 React 把文章渲染成视频
项目里有一条停了三个月的流程。AI 科普那条视频,7 个场景、7 张 PNG、7 段配音,产出 507 KB 的 MP4,跑完是 6 月 23 日晚上。素材还在,脚本也在,之后没再出过第二条。不是内容不够,是这套结构不支持复用:画面文案写死在 Python 列表里,改一句旁白就是改代码。
这几天把 Remotion 拉进来实测了一遍,从取稿到成片跑通,结论和数据都写在下面。
视频是一段可以写出来的代码
Remotion 建仓在 2020 年 6 月,61.3k stars、4.7k forks,TypeScript 写的,最新一次提交就在今天。它把一段视频定义成 React 组件:Composition 声明宽高、帧率、总帧数,画面里用 useCurrentFrame() 取当前帧号,再用 interpolate() 或 spring() 算出这一帧的样式和位置。
视频的本质是一串连续画面,帧号就是时间轴。动画不是拖出来的,是算出来的。
这跟提示词生成视频那类工具的区别,在于结果的掌控方式。提示词给你一段没法精确修改的成片;Remotion 给你一个可编辑的代码工程,字幕、时序、配色、音轨全在一套目录里,改完重新渲染。官方给的默认值也贴着移动端走:竖版 1080×1920、30fps、MP4。
生态规模从仓库结构能直接看出来:packages/ 目录下 137 个包,从 player(网页内嵌播放器)到 captions(字幕)到 lambda(分布式渲染)到 three、skia(图形)。还有一套给编码智能体用的 skills 和一个 MCP 服务。
三个工程上的关键决定
渲染交给真实浏览器内核。 出片时起的是 Chrome Headless Shell,写的 CSS、字体、布局就是最终像素,不存在「预览和成片不一致」。代价在第一次:要下载 93.5 MB 的内核文件。我这边首次渲染就卡在这一步,前后等了近十分钟,缓存之后同样一条 180 帧的片子只要 7 秒。
CLI 到 Node API 是一条完整的链。 命令行有 remotion studio 预览、remotion still 导出单帧校验、remotion render 出片;服务端走 @remotion/bundler 打包加 @remotion/renderer 的 selectComposition 和 renderMedia,全程在 Node 进程里完成,不需要去驱动一个浏览器点按钮。再往上还有 Lambda 和云函数两种远端渲染。
内容是 props,不是文档。 组件和数据分离,同一套组件换一组 props 就是一条新片。这一点决定它能不能接进自动化管线——也是我们那条停掉的流程最缺的东西。
我们那批视频卡在哪
6 月那套流程跑起来是这样:一个 Python 脚本,SCENES 是写死的场景列表,PIL 逐场景画一张 1080×1920 的 PNG,edge-tts 用 zh-CN-XiaoxiaoNeural、语速 +10% 逐段配音;第二个脚本用 imageio 把图变成帧序列;最后一条 ffmpeg 命令把几段 mp3 拼成音轨。
四个具体问题:
- 文案即代码。 改一句旁白要动 produce 脚本,一个视频对应一个脚本,
scripts/里躺着 6 个画帧脚本、3 个配音脚本和 1 个合成脚本。 - 画面是静止的。 合成脚本里那行
for _ in range(n_frames): frames.append(arr),一张图重复 N 帧,成片只有切镜,没有运动。 - 时长靠手填。 场景时长是人工量完写进数组的,配音语速一变,整段对不上。
- 三步不闭环。 画帧、合成、合音轨分散在三处,一处出错就得从头再跑一遍。
流程本身能跑通,跑完就没再跑第二次。
更现实的一条理由:视频分发在我们这边一直是缺口。每天上午的推送任务把最新文章改写成短视频平台的长文,到这个月已经执行了 95 次,产出全是文字。文字那条路已经稳定,视频那条路每次都要重新起一个脚本。
实测:从取稿到出片 11.2 秒
测试用的是一条真文章,从内容接口取最新一篇 AI 稿,把标题、摘要、日期作为 props 传进组件,渲染成 6 秒竖版卡片视频。组件部分只声明一次:
<Composition
id="BaiwujiCard"
component={ArticleCard}
durationInFrames={180}
fps={30}
width={1080}
height={1920}
defaultProps={defaultProps}
/>
画面里所有动效都从帧号推出来,没有计时器、没有 CSS 动画:
const frame = useCurrentFrame();
const rise = spring({ frame, fps, config: { damping: 200 }, durationInFrames: 45 });
const barWidth = interpolate(frame, [15, 70], [0, 96], { extrapolateLeft: "clamp" });
服务端渲染是一条四步的链,我们的定时任务本来就是 Node/Python 脚本,接得上:
const serveUrl = await bundle({ entryPoint: path.resolve("./src/index.ts") });
const composition = await selectComposition({ serveUrl, id: "BaiwujiCard", inputProps: props });
await renderMedia({ composition, serveUrl, codec: "h264", outputLocation: "out/card-api.mp4", inputProps: props });
本机实测分阶段耗时:
| 阶段 | 耗时 |
|---|---|
| 取最新文章(API) | 1,428 ms |
| bundle 打包 | 2,524 ms |
| selectComposition | 772 ms |
| renderMedia 渲染编码 | 6,463 ms |
| 合计 | 11,187 ms |
产出:625.8 kB,6.0 秒,1080×1920,30fps,180 帧。命令行那条链同时验证了单帧导出(remotion still 出 120 kB 的 PNG)和整片渲染,CLI 报告的并发是 5x。
换成一篇文章一条片,11 秒的量级意味着一天的文章量跑一遍不构成负担;内容换掉只改 props,脚本不动。
适合什么,不适合什么
适合的场景有个共同点:画面由数据和规则决定,需要批量、需要每天重跑。文章卡片、数据播报、字幕视频、播客波形、产品演示,都是这套模型的强项。已经有 React 技能栈的团队上手几乎没有新概念。
不适合的情况也要说清楚。实拍素材的剪辑不行——那是时间线编辑软件的地盘,Remotion 处理的是「每一帧长什么样」,不是「把这段素材裁掉两秒」。一次性精修的片子也不划算,写组件的时间超过手动拖一遍的时间。
环境上有硬约束。渲染依赖真实浏览器内核,文档写明 macOS 需要 15 及以上,Linux 需要 libc 2.35 以上并额外装系统依赖,Alpine 和 nixOS 明确不支持。无头浏览器缺席,整条链跑不起来。
许可证是另一件必须先看的事。Remotion 不是标准开源许可:个人、员工不超过 3 人的营利组织、非营利组织,以及还在评估阶段的使用者,都可以免费使用,包括商用。规模超过这个范围需要购买公司授权,而且不允许把 Remotion 改造成衍生品再出售。做产品集成前先确认自己落在哪一档。
五步上手
# 1. 脚手架(空模板,不带 Tailwind)
npx create-video@latest --yes --blank --no-tailwind my-video
cd my-video && npm i
# 2. 起 Studio,改一行看一帧
npm run dev
# 3. 改 src/Root.tsx 里的 Composition 与 defaultProps
# 4. 先导单帧校验排版,再出整片
npx remotion still BaiwujiCard out/frame90.png --frame=90
npx remotion render BaiwujiCard out/card.mp4
第 5 步是接管线:把 @remotion/bundler 和 @remotion/renderer 装进已有的 Node 服务,按上面的四步链调用。我们的 Hermes 技能库里已经有一份 Remotion 技能(v1.0.0),脚手架、Studio 预览、单帧校验、整片渲染这几条固定动作可以直接复用。
那 7 个场景的 AI 科普视频,接下来会用这套组件重做一遍——同一份文案进 props,画面里的进度条、标题条、转场从帧号算出来,配音和画面节奏不再靠手填时长对齐。
最后一个问题
组件化之后维护成本降下来了,另一个问题浮上来:片子里每个数字由谁签字。现在的卡片视频直接从文章摘要渲染,摘要错了,视频不会比它更对,而视频比文字难改——文字能改接口,视频要重渲染再重发。真接进管线之前,「哪一层内容必须先人工确认」这条规则得先写在组件外面。