用代码生成宣传片(一):技术栈总览与 Remotion 画面生成
本文基于 MiBee NVR 开源项目 45 秒宣传片的实战经验整理。你将学到用 代码生成视频画面(Remotion)、用 AI 语音生成配音(edge-tts)、离线合成 BGM(numpy),以及用 ffmpeg 把三者合成成品。所有步骤均可直接上手。
这套技术能做什么
一个多语言宣传片项目包含三个独立的「生产环节」,最后由 ffmpeg 拼合成最终视频:
| 环节 | 工具 | 产物 |
|---|---|---|
| 画面 | Remotion(React 写视频) | 一段无声 MP4 |
| 配音 | edge-tts(微软免费 TTS) | 每段旁白的 MP3 |
| 配乐 | numpy 离线合成 | 一段 BGM 的 WAV |
| 合成 | ffmpeg filter_complex | 最终成片 MP4(画面+人声+BGM) |
整体管线如下图所示:
flowchart TD
A[画面生成<br/>Remotion] --> D[ffmpeg 合成]
B[配音生成<br/>edge-tts] --> D
C[配乐生成<br/>numpy] --> D
D --> E[成片 MP4]
classDef blue fill:#2196F3,stroke:#1565C0,color:#fff;
classDef green fill:#4CAF50,stroke:#2E7D32,color:#fff;
classDef orange fill:#FF9800,stroke:#E65100,color:#fff;
classDef purple fill:#9C27B0,stroke:#6A1B9A,color:#fff;
class A blue;
class B green;
class C orange;
class D purple;
class E green;流程说明:三个独立环节分别产出无声画面、配音 MP3 和配乐 WAV,最后通过 ffmpeg 合成输出最终 MP4 成片。每个环节可独立迭代修改,不影响其他环节。
为什么用代码而不是剪映/PR
传统视频剪辑软件(剪映、PR、DaVinci)制作单个视频效率很高,但需要批量产出时——比如将同一段宣传片翻译成 6 种语言、每种语言还要男声和女声两个版本——手动剪辑的工作量会线性增长。代码方案的核心优势:
- 可版本化:文案是字符串,画面是 React 组件,配音是 voice ID,BGM 是合成参数,合成是 ffmpeg filter 图。全是文本,能进 Git、能 diff、能回滚。
- 可复现:同一组参数永远得到同一结果。Remotion 的纯函数帧模型保证了画面输出的确定性。
- 完全免费:edge-tts 使用微软公开接口,BGM 用 numpy 现场计算,不花一分钱。
- 多语言零边际成本:旁白换一个 voice 参数就能出英文版/粤语版,画面完全复用——画面只渲染一次,6 种语言只是 6 种不同的音频叠加方案。
- 可调试:每个环节独立(画面/配音/BGM/合成),出问题能分层定位。
技术栈与目录结构
推荐的项目目录结构如下(请将项目根目录设为 BASE,所有相对路径均基于此):
| |
运行环境只需要 Node.js(推荐 18+)和 Python 3.8+。本系列所有代码均以此目录结构为基准。
Remotion 画面生成
安装与 Chrome 配置
| |
Remotion 渲染必须要有浏览器内核支持。在沙箱或离线环境中,Remotion 默认下载 Chromium 常常失败。最稳妥的方案是手动下载 chrome-headless-shell 并显式指定路径。
从 Chrome for Testing 下载对应操作系统的最新版 chrome-headless-shell,解压到 remotion-videos/chrome-dl/ 目录。然后在 remotion.config.ts 中配置:
| |
chrome-headless-shell 是去掉了 UI、扩展、打印等组件,只保留渲染引擎的精简版 Chrome,体积更小、更适合服务端和沙箱环境。
注册 Composition
一个 Composition 就是一段完整的视频。在 src/Root.tsx 中注册:
| |
关键参数:
durationInFrames:总帧数 = 时长(秒) × fpsfps:帧率,通常 30width/height:输出分辨率,1920×1080 是标配
用 Sequence 拼接多个场景
每个场景是一个独立组件,通过 Remotion 的 <Sequence> 组件拼接。Sequence 接受 from(起始帧)和 durationInFrames(持续帧数)两个参数:
| |
useCurrentFrame()帧模型:<Sequence from={150}>会给子组件施加一个帧偏移。渲染全局第 150 帧时,子组件中useCurrentFrame()返回的是0(局部帧),而非全局的 150。因此每个 Scene 内的interpolate()参数都基于该 Scene 的局部帧区间编写。以Overlay为例:frame < 15判断的是「场景的前 15 帧」做淡入——这正是局部帧偏移带来的便利。
场景间的交叉淡入淡出通过覆盖层组件实现:场景开头从背景色变透明(淡入),结尾从透明变背景色(淡出)。
核心动画 API
Remotion 提供了几个动画函数,掌握它们就能实现绝大部分动效:
| |
三个核心工具:
interpolate(input, [inMin,inMax], [outMin,outMax], options):把帧号线性映射到任意数值范围的万能函数。extrapolateLeft/extrapolateRight控制超出区间时的行为(clamp夹住、extend继续延伸)。spring({frame, fps, config}):基于物理模拟的弹性动画,比线性过渡更生动。damping(阻尼)、stiffness(刚度)、mass(质量)三个参数控制弹性表现。- 三角函数:
Math.sin(frame * frequency) * amplitude可做出浮动、呼吸、脉冲等周期性效果,轻量且无需额外依赖。
背景与 SVG 图标
⚠️ headless Chrome 没有彩色 emoji 字体:代码中出现的 🐝🫐📷⚡ 等 emoji 会渲染成空白方框——看起来就像「图标不完整」。所有图标必须用内联 SVG 绘制,不能用 emoji。
每个图标封装为一个返回 <svg> 的 React 组件:
| |
建议也将背景封装为可复用组件。常见类型:
- 渐变底色:消灭画面「悬浮在黑色空洞中」的感觉。
- 浮动六边形 / 粒子流:增加科技感与空间层次。
- 辉光效果:利用 CSS
drop-shadow多层叠加制造发光感。
字号经验:1920×1080 在手机上缩放至约 0.21 倍,16–28px 的字会缩成 3–6px 糊成一团。如果希望视频在手机上也能看清,正文字号建议 ≥ 40px,或设计时做全局字号映射。
渲染命令
画面就绪后,渲染输出无声 MP4:
| |
promo是Root.tsx中注册的id- 输出是无声 MP4(音频由后续 ffmpeg 合成阶段处理)
--log=error只显示错误信息,减少干扰- 渲染速度参考:45 秒视频约 2 分钟(取决于 CPU/内存和并发数)
Config.setConcurrency(4)控制同时进行的截图进程数
Remotion 原理:React 如何变成视频
理解原理,才能改得动、调得对、出问题知道往哪查。
帧模型:视频 = 纯函数
视频的本质是一连串「按帧确定」的画面。 普通网页是交互驱动的——用户不操作就不变。视频不是,第 100 帧永远应该长那个样子。Remotion 把「第 N 帧长什么样」建模成一个纯函数:输入帧号 frame,输出确定的 DOM / SVG。
useCurrentFrame() 的值从哪来? 它虽然是 React Hook,但值不由用户交互决定——渲染器在生成第 N 帧时把 frame=N 注入进去。同理 useVideoConfig() 注入 fps、width、height、durationInFrames。所以 interpolate(frame, [0,30], [0,1]) 在第 15 帧时必然得到 0.5——确定性、可复现、可测试。
逐帧截图管道
flowchart TD
A[React 组件] --> B[headless Chrome<br/>渲染每帧 DOM]
B --> C[逐帧截图<br/>JPEG/PNG]
C --> D[ffmpeg 编码]
D --> E[无声 MP4]
classDef blue fill:#2196F3,stroke:#1565C0,color:#fff;
classDef green fill:#4CAF50,stroke:#2E7D32,color:#fff;
classDef orange fill:#FF9800,stroke:#E65100,color:#fff;
classDef purple fill:#9C27B0,stroke:#6A1B9A,color:#fff;
class A orange;
class B blue;
class C blue;
class D purple;
class E green;渲染流程:React 组件在 headless Chrome 中逐帧渲染为真实 DOM,截图引擎导出每帧为 JPEG/PNG 图片,最后 ffmpeg 编码为 H.264 视频流。
具体步骤如下:
- Remotion 启动 headless 浏览器(Chromium 或
chrome-headless-shell)。 - 浏览器中 React 渲染每一帧为真实 DOM——完整支持 CSS 渐变、Web 字体、SVG 滤镜、
drop-shadow等现代 Web 特性。 - 调用浏览器的截图能力,将该帧导出为 JPEG / PNG 图片。
- 重复 1350 次(45s × 30fps)→ 得到 1350 张图片。
- 内部 ffmpeg 将图片序列编码为 H.264 视频流,输出无声 MP4。
为什么需要 Chrome
现代 GPU 加速渲染器才能正确处理 SVG 滤镜、CSS drop-shadow、mix-blend-mode、Web 字体等特性。Chrome 的 chrome-headless-shell 是砍掉界面(UI、扩展、打印等)只保留渲染引擎的精简版本——体积更小、资源消耗更低,适合服务端和沙箱环境。
为什么可以分布式 / Serverless
「第 N 帧」的渲染不依赖其他任何帧。 这是纯函数模型带来的关键特性——1350 帧可以任意切分,交给多台机器并行截图,最后再拼合成完整视频。Remotion Lambda(官方 Serverless 方案)正是基于此原理:并发数取决于预算,而非单机性能。本地 Config.setConcurrency(4) 做的就是同一台机器上 4 个截图进程并行。
本系列:① 总览与 Remotion ② edge-tts 配音 ③ numpy BGM ④ ffmpeg 合成