跳至内容

用 Remotion + AI 语音合成,零基础搭建一个教育视频的完整实践

一、背景与动机

我一直在研究视频自动生成的低成本方案——用代码替代人工剪辑,用 AI 替代配音和字幕,目标是让"写一篇文章就能生成一段视频"成为现实。之前折腾过 FFmpeg 模板渲染、Python 脚本拼接片段,但总觉得控制力不够,直到关注到 Remotion 这个项目:它用 React 组件描述每一帧画面,TypeScript 精确控制动画、音频、字幕,等于把视频变成了一个前端工程。

刚好早上和家里小朋友聊天,说起他们班上同学玩游戏的事——有打职业的,也有沉迷被收手机的。聊着聊着就想:要不拿这个话题做个实验?用 Remotion 走一遍全流程,看看代码生成视频的可行性到底到哪一步。

这篇文章就是这次实验的完整记录:从零搭建一个"初中生玩游戏的利弊与自控力培养"教育视频,涉及 Remotion 场景编排、AI 语音合成选型(TTS)、音频集成、字幕系统等所有环节。项目代码已开源,所有步骤可复现。

二、环境准备

整个项目的技术栈主要有三层:视频渲染引擎(Remotion + React)、语音合成(TTS)和 音频处理(FFmpeg)。项目目录结构如下:

remotionai/
├── scripts/                       # TTS 语音生成脚本
│   ├── generate_audio_voxcpm.py   # VoxCPM2 生成(当前在用)
│   └── generate_audio_edge.py     # Edge-TTS 备选方案
├── public/
│   ├── audio/                     # 旁白 + 背景音乐
│   │   ├── bgm.wav
│   │   ├── scene1_intro.mp3
│   │   ├── scene2_pros.mp3
│   │   ├── scene3_cons.mp3
│   │   ├── scene4_selfcontrol.mp3
│   │   └── scene5_ending.mp3
│   ├── images/                    # Unsplash 背景图
│   └── subtitles/subtitles.srt    # SRT 字幕文件
├── src/
│   ├── Composition.tsx            # 主编排组件
│   ├── Root.tsx                   # 注册入口
│   └── scenes/                    # 5 个场景组件
│       ├── themes.ts              # 配色 + 时长配置
│       ├── subtitles.ts           # 字幕帧数据
│       ├── SubtitleOverlay.tsx    # 字幕叠加组件
│       ├── Scene1Intro.tsx
│       ├── Scene2Pros.tsx
│       ├── Scene3Cons.tsx
│       ├── Scene4SelfControl.tsx
│       └── Scene5Ending.tsx
├── package.json
└── remotion.config.ts

1. 核心依赖

包 版本 用途
remotion + @remotion/cli 4.0.484 视频渲染引擎
react 19.2.3 UI 组件
typescript 5.9.3 类型系统
voxcpm 2.0.3 中文 TTS 语音合成
FFmpeg 最新 音频格式 / 背景音乐合成

2. Remotion 的核心机制

Remotion 的工作方式很特别:它不是"播放"视频,而是逐帧渲染——每一帧都会重新调用 React 组件,渲染出该帧的画面。这意味着所有动画都必须是帧编号(frame)的函数,不能用 CSS transition 或 @keyframes。关键的 API 就两个:

  • useCurrentFrame() — 获取当前帧编号
  • interpolate() — 将帧编号映射到动画值(透明度、位移、缩放等)

三、实战步骤

整个项目的开发分为四个环节:语音生成 → 场景组件 → 音视频集成 → 字幕系统。下面逐一展开。

1. 语音旁白生成——VoxCPM2

这是我踩坑最多的环节,先后试了 4 种方案才定下来。最终胜出的是清华开源的 VoxCPM2(2B 参数,Apache 2.0 协议),支持 30 种语言,中文发音自然度远超 macOS 的 say 命令,而且能在本地 CPU 上跑(~2.5 it/s)。

安装和生成脚本(Python):

# 安装 VoxCPM2
pip3 install voxcpm

# 修复依赖兼容性(实测需要)
pip3 install "torch==2.11.0"
pip3 install "torchvision==0.26.0"
pip3 install "tokenizers>=0.22.0"

生成 5 段旁白的核心脚本(generate_audio_voxcpm.py 简化版):

from voxcpm import VoxCPM2
import soundfile as sf

model = VoxCPM2.from_pretrained("openbmb/VoxCPM2")
# CPU 推理,不需要 GPU

scenes = [
    ("scene1_intro", "初中生该不该玩游戏?答案不是该或不该,关键在于如何驾驭它。"),
    ("scene2_pros", "玩游戏可以锻炼反应力与手眼协调,培养策略思维..."),
    ("scene3_cons", "但过度玩游戏也会带来问题:沉迷成瘾挤占学习时间..."),
    ("scene4_selfcontrol", "那么如何培养自控力呢?第一,设定时间边界..."),
    ("scene5_ending", "游戏是生活的调味品,不是主菜。能驾驭游戏的人,才是真正的玩家。"),
]

for name, text in scenes:
    audio = model.generate(text, speed=1.0)
    sf.write(f"public/audio/{name}.mp3", audio, samplerate=44100)

输出的音频参数:48kHz 原始采样,192kbps MP3,经 ZipEnhancer 降噪处理后音质非常干净。

2. 场景组件与动画设计

视频共 5 个场景,总时长约 55 秒(1635 帧 @ 30fps)。每个场景是一个独立的 React 组件,用 interpolate() 驱动动画,通过 <Sequence> 组件编排时间线。

以场景①(开场)为例,展示代码风格:

import { interpolate, useCurrentFrame } from "remotion";

const TRANSITION_FRAMES = 15;

export const Scene1Intro: React.FC<{
  audioDuration: number;
}> = ({ audioDuration }) => {
  const frame = useCurrentFrame();
  const DURATION = Math.ceil(audioDuration * 30) + 10;

  // 淡入淡出(通用)
  const opacity = interpolate(
    frame,
    [0, TRANSITION_FRAMES, DURATION - TRANSITION_FRAMES, DURATION],
    [0, 1, 1, 0],
    { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
  );

  // 标题缩放入场
  const titleScale = interpolate(frame, [0, 30], [0.8, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  return (
    <div style={{ opacity, transform: `scale(${titleScale})` }}>
      <h1 style={{ color: "#ff6b6b" }}>游戏·掌控</h1>
      <p>初中生该不该玩游戏?</p>
    </div>
  );
};

5 个场景各有不同的入场动画风格——开场用缩放,利弊分析用卡片滑入,自控力部分用 spring 弹性动画,结尾用逐行淡入。配色方案也做了差异化设计:

场景 背景色 强调色 氛围
开场引入 深蓝 #0f0f23 红 #ff6b6b 沉稳
游戏之"利" 深蓝 #0a1628 青 #4ecdc4 积极
游戏之"弊" 深红 #1a0808 红 #ff6b6b 警示
自控力之道 深绿 #0a1a0a 金 #ffd93d 希望
结尾升华 深紫 #0f0a1a 紫 #c084fc 升华

3. 音频集成——背景音乐 + 旁白

音频架构分两层:全局背景音乐贯穿全程,每个场景的旁白音频在对应时间段播放。

背景音乐用 FFmpeg 合成了一小段柔和的钢琴和弦(带衰减 + 混响),然后用 Remotion 的 <Audio> 组件实现渐入渐出:

// Composition.tsx
<Audio
  src={staticFile("audio/bgm.wav")}
  volume={interpolate(
    frame,
    [0, 30, totalFrames - 60, totalFrames],
    [0, 0.15, 0.15, 0],  // BGM 音量控制在 15%,不抢旁白
    { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
  )}
/>

每个场景的旁白使用 endAt 属性,在过渡前 15 帧停止,避免与下一场景的旁白重叠:

<Sequence from={0} durationInFrames={SCENE1_DURATION}>
  <Audio src={staticFile("audio/scene1_intro.mp3")}
    endAt={SCENE1_DURATION - TRANSITION_FRAMES} />
  <Scene1Intro audioDuration={7.2} />
</Sequence>

4. 字幕系统

字幕有三个层次:TypeScript 数据层 → React 渲染层 → SRT 文件导出层。

数据层定义每段字幕的起止帧和文本内容:

// subtitles.ts
export interface SubtitleEntry {
  startFrame: number;
  endFrame: number;
  text: string;
}

export const SUBTITLES: SubtitleEntry[] = [
  { startFrame: 0,   endFrame: 115, text: "初中生该不该玩游戏?答案不是该或不该," },
  { startFrame: 115, endFrame: 215, text: "关键在于如何驾驭它。" },
  // ... 共 19 条字幕
];

渲染层用 SubtitleOverlay.tsx 在视频底部叠加当前帧对应的字幕文本。SRT 文件用于导入外部剪辑软件做进一步编辑。

四、效果展示 / 问题踩坑

1. TTS 方案试错全记录

语音合成是折腾最久的部分,前后试了 5 个方案,走了一条曲折的选型路径:

macOS say (Tingting) ──→ 声音机械感强,弃用
       ↓
Edge-TTS (微软Xiaoxiao) ──→ 有背景噪音,需联网,备选
       ↓
CosyVoice2 (阿里) ──→ grpcio/pyworld C++ 扩展编译失败 ❌
       ↓
ChatTTS ──→ torchvision::nms macOS 兼容问题 ❌
       ↓
VoxCPM2 (清华) ✅ ← 最终选择

CosyVoice2 和 ChatTTS 的音质评测都很高,但在 macOS 上的 C++ 扩展编译都遇到了兼容性问题——grpcio 编译失败、torchvision::nms 符号找不到。VoxCPM2 是唯一一个 pip install 就能用的高质量中文 TTS,而且 Apache 2.0 开源协议对商用友好。

2. 代码审查发现的两个 Bug

在写完初稿后做代码审查时,发现两个需要修复的问题:

问题 原因 修复
场景过渡期间音频重叠 旁白没有在过渡前停止 添加 endAt={DURATION - TRANSITION_FRAMES}
总时长硬编码 数组索引 [4] 硬编码 改用 getTotalDuration() 函数动态计算

3. 音频质量优化

  • 背景噪音:最初 BGM 用纯正弦波合成,听感像嗡嗡声。改为带衰减 + 混响的柔化和弦,音量降低到 15%,效果自然很多
  • 旁白音质:从最初的 48kbps/24kHz 升级到 192kbps/44.1kHz,清晰度明显提升

4. 视频渲染

渲染命令很简单:

npx remotion render MyComp out/video.mp4

最终输出 55 秒的 MP4 文件,5 个场景流畅衔接,旁白干净同步,字幕在视频底部逐句显示。

五、总结

这次用 Remotion + AI 语音合成搭建教育视频,让我有几点体会:

第一,代码生成视频这条路可行。 对于结构化的内容型视频,用 Remotion 把每一个画面写成 React 组件,精确度和可维护性远超拖拽剪辑——改文案就是改字符串,改动画就是改一个插值参数,版本控制也天然支持。

第二,TTS 选型关键是"开箱即用"。 研究了几个音质最好的方案,最后选了安装最简的那个。VoxCPM2 在中文语音合成上的表现让我很满意,而且不需要 GPU 就能跑,对个人开发者非常友好。

第三,最花时间的往往不是核心逻辑。 这个项目里场景动画写起来很快,反而是音频降噪、字幕对齐、过渡帧微调这些"边角料"占了大半时间。做视频和做软件一样——细节决定体验。

项目完整代码已开源,感兴趣的同学可以 clone 下来自己跑一遍。如果你也想用代码做视频,Remotion 值得一试,它的 Studio 工具可以实时预览每一帧,调试体验比想象中好很多。

看完本文有收获?请分享给更多人,关注「黑光技术」,关注技术分享。

qrcode_helight_tech