用 AI 制作专业科普视频:remotion-video skill 完整实战教程

从一篇文章到 15 分钟高质量纪录片,我用 Claude Code + Remotion 完成了一次完整的视频制作——全程无需剪辑软件,纯代码驱动。本文记录这套工作流的每一个细节,以及我踩过的坑。

题图:在暗室中用代码制作视频
图:纯代码驱动的视频制作——开发者的新「剪辑台」


这次做了什么

马斯克在 2026 年 2 月完成了一次史无前例的资本重组——SpaceX 全资收购 xAI,合并实体估值冲至 1.25 万亿美元。我把一篇深度分析文章,用 remotion-video skill 制作成了一个 12 场景、15 分钟的科普纪录片。

最终视频:12 个场景、独立配音、动态字幕、Ken Burns 图片动效、数据可视化动画,渲染输出 162.6MB 的 MP4

视频链接:https://weixin.qq.com/sph/AlO70HMPx


什么是 remotion-video skill

remotion-video skill 是一套跑在 Claude Code 上的 AI 视频制作工作流。核心理念是:

  • 音频优先:先用 MiniMax TTS 生成配音,视频时长由音频驱动
  • 代码即视频:每个场景是一个 React 组件,动画全部可编程
  • 一键渲染npx remotion render 输出 MP4,全程不碰剪辑软件

触发方式是在 Claude Code 中直接说"帮我把这篇文章做成视频",或者输入 /remotion-video

7步工作流:从文章到MP4
图:remotion-video skill 的 7 步完整工作流,文章进去,MP4 出来


完整工作流:7 步从文章到视频

Step 0:环境准备

在开始前需要两个东西:

# 1. MiniMax API Key(生成配音)
export MINIMAX_API_KEY="your-api-key"
export MINIMAX_VOICE_ID="your-clone-voice-id"   # 你的克隆声音 ID

# 2. ffprobe(检测音频时长)
brew install ffmpeg

克隆声音是关键。去 minimax.io 上传一段 20 秒左右的清晰音频,等待 1-3 分钟就能得到专属的 voice_id。克隆声音比预设音色自然很多,非常值得这一步。

创建项目目录:

mkdir -p ~/remotion-projects/my-video/{src/scenes,public/audio,public/images,scripts,out}
cd ~/remotion-projects/my-video
npx create-video@latest  # 初始化 Remotion 项目

Step 1:脚本规划

告诉 Claude 你的文章和期望时长,它会帮你把文章拆解成场景化脚本。

这次 15 分钟视频被拆成了 12 个场景,每个场景对应一个叙事节点:

场景 主题 时长
01 开场钩子:水槽与1.25万亿 ~70秒
02 往前推5天:特斯拉停产高端车 ~65秒
03 算力撞上物理天花板 ~90秒
04 热力学的诅咒:散热悖论 ~75秒
05 太空数据中心假说 ~80秒
06 马斯克为什么有资格做这件事 ~85秒
07 三块拼图:Starlink / Optimus / xAI ~90秒
08 三层楼的帝国架构 ~80秒
09 货币失效:成本黑洞 ~75秒
10 指数加速飞轮 ~80秒
11 四面悬崖:清醒时刻 ~85秒
12 尾声:燃烧时代的终结 ~90秒

脚本原则

  • 每场景配音不超过 320 字(语速约 213 字/分钟)
  • 每句不超过 25 字,适合 TTS 断句
  • 旁白与画面描述分开写

Step 2:音频生成

scripts/generate_audio_minimax.py 批量生成 12 个音频文件:

# scenes.json 格式
{
  "scenes": [
    {
      "id": "scene_01",
      "narration": "三年前,当马斯克抱着那个水槽走进推特总部时..."
    },
    ...
  ]
}
python3 scripts/generate_audio_minimax.py
# 输出:public/audio/scene_01.mp3, scene_02.mp3 ... scene_12.mp3

脚本支持断点续作——已生成的文件自动跳过,中途失败不会从头再来。


Step 3:时长检测,生成 audioConfig.ts

音频生成后,用 detect_durations.py 自动检测每个文件的精确时长,生成时间配置:

python3 scripts/detect_durations.py

输出 src/audioConfig.ts

export const audioDurations: Record<string, number> = {
  "scene_01": 72.4,
  "scene_02": 67.8,
  "scene_03": 91.2,
  // ...
};

export const FPS = 30;

// 自动计算每个场景的起始帧
export const getSceneStartFrame = (sceneIndex: number): number => {
  const keys = Object.keys(audioDurations);
  return keys.slice(0, sceneIndex)
    .reduce((sum, key) => sum + Math.ceil(audioDurations[key] * FPS), 0);
};

这是音画同步的核心:视频时长完全由真实音频驱动,不会出现声音提前结束或画面空转的问题。

音画同步架构:音频文件驱动整个视频时长链路
图:音频驱动架构——从 MP3 文件到每帧时间戳,一条完整的依赖链


Step 4:React 场景组件

每个场景是一个独立的 React 组件。以数据可视化场景为例:

// Scene03.tsx:算力撞上物理天花板
export const Scene03: React.FC<{ durationInFrames: number }> = ({ durationInFrames }) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  // 数字增长动画:10万 → 55万 GPU
  const gpuCount = interpolate(
    frame,
    [30, 90],
    [100000, 550000],
    { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
  );

  // Spring 入场动画
  const cardProgress = spring({ frame: frame - 20, fps, config: { damping: 18 } });

  return (
    <AbsoluteFill style={{ background: COLORS.gradientBg }}>
      <Audio src={staticFile("audio/scene_03.mp3")} />

      {/* 背景图片(氛围背景模式,opacity 0.55)*/}
      <BackgroundPhoto src={staticFile("images/img_energy.jpg")} opacity={0.55} />
      <AbsoluteFill style={{ background: "rgba(5,5,16,0.40)" }} />

      {/* GPU 数量动态展示 */}
      <div style={{ fontSize: 120, color: COLORS.gold, opacity: Math.max(0, cardProgress) }}>
        {Math.floor(gpuCount).toLocaleString()}
        <span style={{ fontSize: 36 }}>块 GPU</span>
      </div>

      <Subtitles narration={NARRATION} totalFrames={durationInFrames} />
    </AbsoluteFill>
  );
};

Remotion 的核心是 useCurrentFrame():返回当前渲染帧(0、1、2…),所有动画都基于它计算。帧号 × 时间 = 动画进度,纯粹的函数式思维。


Step 5:图片的正确使用方式(重要!踩坑记录)

这是本次制作最大的教训。图片不只是背景,它是视觉叙事的重要组成部分。

图片使用对比:三重压制(左)导致画面漆黑,分屏面板(右)图片清晰有力
图:左侧三重压制导致图片消失(104MB),右侧正确用法图片清晰呈现(162MB)

踩坑:三重压制 = 图片消失

初版代码是这样写的:

// ❌ 错误:三重叠加导致图片几乎不可见
<BackgroundPhoto src={src} opacity={0.25} blur={3} />
<AbsoluteFill style={{ background: "rgba(5,5,16,0.72)" }} />

低透明度(0.25) + 深色遮罩(0.72)+ 模糊(3px),三重叠加后图片几乎完全消失,视频文件从应有的 162MB 萎缩到 104MB——机器用文件大小告诉你图片没了。

还有一个更隐蔽的 bug:

// ❌ 错误:zIndex: -1 导致图片被父元素背景色遮挡
<AbsoluteFill style={{ zIndex: -1 }}>
  <Img src={src} />
</AbsoluteFill>

zIndex: -1 会把元素推到父元素的背景绘制层之后,渲染结果中图片直接消失。正确做法是利用 DOM 顺序,把 BackgroundPhoto 放在其他子元素之前即可。

正确用法一:氛围背景(opacity 0.55)

// ✅ 普通叙述场景:图片清晰可见,不遮挡文字
<BackgroundPhoto
  src={staticFile("images/scene.jpg")}
  opacity={0.55}     // 关键:0.55 而不是 0.25
  kenBurns           // Ken Burns 缩放平移效果
  blur={0}           // 不加模糊
/>
{/* 轻量遮罩,不超过 0.42 */}
<AbsoluteFill style={{ background: "rgba(5,5,16,0.40)" }} />

正确用法二:分屏面板(视觉主角)

关键叙事场景(开篇、高潮、转折),图片占画面 40-45%,以 90% 不透明度呈现:

// SidePanelImage.tsx:图片作为视觉主角
export const SidePanelImage: React.FC<{
  src: string;
  side: "left" | "right";
  widthPercent?: number;  // 默认 42
  opacity?: number;       // 默认 0.9
}> = ({ src, side, widthPercent = 42, opacity = 0.9 }) => {
  const frame = useCurrentFrame();

  // Ken Burns:微妙的缩放 + 平移
  const scale = interpolate(frame, [0, 600], [1.05, 1.18], {
    easing: Easing.bezier(0.4, 0.0, 0.2, 1),
    extrapolateRight: "clamp",
  });

  return (
    <div style={{
      position: "absolute",
      [side]: 0, top: 0,
      width: `${widthPercent}%`, height: "100%",
      overflow: "hidden",
    }}>
      <Img src={src} style={{
        width: "100%", height: "100%",
        objectFit: "cover", opacity,
        transform: `scale(${scale})`,
      }} />
      {/* 边缘渐变,与内容区自然融合 */}
      <div style={{
        position: "absolute", top: 0,
        [side === "left" ? "right" : "left"]: 0,
        width: "50%", height: "100%",
        background: `linear-gradient(to ${side === "left" ? "right" : "left"},
          rgba(5,5,16,0) 0%, rgba(5,5,16,0.95) 100%)`,
      }} />
    </div>
  );
};

使用方式:

// Scene01:开篇场景,英雄图左侧 + 标题右侧
<AbsoluteFill style={{ background: "#05050F" }}>
  {/* 内容区(右侧 58%)*/}
  <AbsoluteFill style={{ paddingLeft: "45%" }}>
    <h1>硅基帝国</h1>
    <p>$1.25 万亿</p>
  </AbsoluteFill>

  {/* 图片面板(左侧 42%,在 DOM 顺序最后,层叠在内容上方)*/}
  <SidePanelImage
    src={staticFile("images/img_hero.jpg")}
    side="left"
    widthPercent={42}
    opacity={0.9}
  />
</AbsoluteFill>

效果对比:图片 opacity 0.25 → 0.55(背景模式),视频从 104MB → 162MB,清晰可见。

图片使用决策树

场景照片
├─ 关键叙事场景(开篇/高潮/转折)
│     └─ 分屏面板:SidePanelImage,40-45%屏宽,opacity 0.9
└─ 普通叙述场景(氛围营造)
      └─ 背景层:BackgroundPhoto,全屏,opacity 0.55,遮罩 ≤ 0.42

Step 6:字幕系统

字幕是提升信息留存率的关键(研究数据:良好字幕设计可提升 40%)。

这个项目用了一个基于旁白文本自动切分的字幕组件,无需手写时间轴:

// Subtitles.tsx:按标点断句,自动分配时间
export const Subtitles: React.FC<{
  narration: string;
  totalFrames: number;
}> = ({ narration, totalFrames }) => {
  const frame = useCurrentFrame();

  // 按标点切分成字幕段
  const segments = splitIntoSegments(narration);

  // 均匀分配时间
  const currentSegment = segments.find((seg, i) => {
    const start = (i / segments.length) * totalFrames;
    const end = ((i + 1) / segments.length) * totalFrames;
    return frame >= start && frame < end;
  });

  return currentSegment ? (
    <div style={{
      position: "absolute", bottom: 80,
      left: 60, right: 60,
      textAlign: "center",
    }}>
      <div style={{
        display: "inline-block",
        background: "rgba(0,0,0,0.20)",
        backdropFilter: "blur(6px)",
        padding: "12px 28px", borderRadius: 8,
      }}>
        <span style={{
          fontSize: 28, color: "#fff",
          textShadow: "0 2px 10px rgba(0,0,0,1)",
        }}>
          {currentSegment.text}
        </span>
      </div>
    </div>
  ) : null;
};

字幕背景透明度设为 0.20(而非常见的 0.7),既保证可读性,又不遮挡视频内容。


Step 7:渲染输出

# 启动预览(热重载,实时看效果)
npm run dev

# 渲染最终视频(前台执行,可看进度)
npx remotion render MuskSiliconEmpire out/video.mp4

渲染时间约 8-12 分钟(M1 MacBook Pro),进度实时显示。

注意:如果场景有 3D 内容(@remotion/three),需要加 --gl=angle 参数:

npx remotion render MyVideo out/video.mp4 --gl=angle

项目完整目录结构

musk-silicon-empire/
├── src/
│   ├── Root.tsx                  # 注册所有场景,计算总时长
│   ├── audioConfig.ts            # 音频时长配置(自动生成)
│   ├── colors.ts                 # 全局配色常量
│   ├── components/
│   │   ├── BackgroundPhoto.tsx   # 背景图片(Ken Burns + 色彩分级)
│   │   ├── SidePanelImage.tsx    # 分屏图片面板(视觉主角模式)
│   │   ├── Particles.tsx         # 粒子背景
│   │   ├── TypeWriter.tsx        # 打字机 + 淡入文字
│   │   └── Subtitles.tsx         # 自动字幕
│   └── scenes/
│       ├── Scene01.tsx ~ Scene12.tsx
├── public/
│   ├── audio/                    # TTS 生成的 12 个 MP3
│   └── images/                   # 6 张场景配图
├── scripts/
│   ├── generate_audio_minimax.py
│   ├── detect_durations.py
│   └── generate_subtitles.py
├── scenes.json                   # 场景数据(旁白文本)
└── out/video.mp4                 # 最终输出(162.6MB,15分钟)

核心经验总结

五条核心经验:音频驱动、图片叙事、组件自治、前台渲染、先预览
图:制作过程中提炼的五颗「经验宝石」

1. 音频驱动,时长自动同步

手动填写 durationInFrames: 300 这种写法迟早出问题。正确姿势:用 ffprobe 检测真实音频时长,全部存入 audioConfig.ts,代码自动计算帧数。

2. 图片是叙事工具,不是背景装饰

最重要的教训:图片 opacity 0.25 + 遮罩 0.72 + blur,三重叠加 = 图片消失,视觉叙事崩塌。

  • 普通场景:opacity 0.55,遮罩 0.40,无模糊
  • 关键场景:用 SidePanelImage 占 40-45% 屏宽,opacity 0.9

3. 场景组件独立自治

每个 Scene 组件内的 useCurrentFrame() 从 0 开始计数(<Sequence> 隔离),内部动画不需要关心全局帧号。

4. 渲染前台执行,看得到进度

不要后台渲染 npm run render &,8-12 分钟的渲染看不到进度会很焦虑。前台执行,Remotion 会实时显示百分比。

5. 先预览再渲染

npm run dev 打开 Remotion Studio,可以拖拽时间轴逐帧检查,发现问题及时改,避免浪费渲染时间。


工具链一览

工具 用途
Claude Code + remotion-video skill 整体工作流编排
Remotion React 视频框架,useCurrentFrame() 驱动动画
MiniMax TTS 克隆声音配音
Pexels API 高质量场景配图
豆包 Seedream AI 生成概念配图
ffprobe 检测音频时长
Python 脚本 批量生成音频、检测时长、生成字幕

获取 remotion-video skill

这套 skill 已经打包好,包含:

  • SKILL.md:完整工作流文档(5600+ 行)
  • scripts/:音频生成、时长检测、字幕生成脚本
  • templates/:audioConfig 模板

remotion-video/ 文件夹放到 ~/.claude/skills/ 下,在 Claude Code 中即可调用 /remotion-video 开始你的第一个 AI 视频项目。


本文记录于 2026 年 2 月,制作工具:Claude Code + Remotion 4.x + MiniMax TTS

已有 0 条评论
滚动至顶部