波波技术栈
article

用 OpenMontage 从零生成一个中文科普动画视频

本文以《天空为什么是蓝色的?》为例,完整演示了使用 OpenMontage 从零生成中文科普动画视频的全流程。教程覆盖研究、提案、剧本、分镜到渲染发布的八个管线阶段,重点展示了本地 Piper TTS 配音与 Remotion 程序化动画的零成本组合方案,并提供了场景规划、脚本编写及技术参数等实操细节。

实战案例:30 秒中文科普短片《天空为什么是蓝色的?》(为什么天空是蓝色的) 全流程零成本:本地 Piper TTS 配音 + Remotion 程序化动画 + 无任何外部素材。 你将得到一个 1920×1080 / 30fps / 约 30 秒的 MP4 成片。


1. 总体流程

OpenMontage 是**指令驱动(Agent-First)**的视频生产系统:AI 助手(Agent)是导演,负责读管线、做创意决策、调工具;Python 工具只做执行与持久化。所有制作必须走管线,不允许跳过。

idea → research → proposal → script → scene_plan → assets → edit → compose → publish
  概念   调研     提案       剧本     分镜       素材     剪辑    合成渲染   发布

本教程对应的管线:animated-explainer(动画解说),入口规则见 AGENT_GUIDE.md。


2. 从下载开始:环境准备

2.1 下载并安装 Python(3.10+)

  1. 打开 https://www.python.org/downloads/ 下载最新 3.10+ 安装包;
  2. 安装时务必勾选 Add python.exe to PATH;
  3. 验证:python --version 能输出版本号。

2.2 创建虚拟环境并安装项目依赖

# 进入项目根目录
cd e:/gitdown/OpenMontage

# 创建虚拟环境
python -m venv .venv

# 安装依赖(含 Piper 封装、ffmpeg 校验等工具)
.venv/bin/python.exe -m pip install -r requirements.txt

2.3 下载并安装 Piper TTS

  1. 从 piper1-gpl 的 GitHub Releases 页下载对应 Windows 版本(本教程使用 1.6.0);
  2. 解压后把 piper 所在的目录加入 PATH(或把 piper.exe 放到任一 PATH 目录);
  3. 验证:piper --version。

2.4 下载中文语音模型

模型文件:zh_CN-huayan-medium.onnx(约 60 MB)。

# 放到 Piper 默认模型目录(不存在就新建)
New-Item -ItemType Directory -Force -Path $HOME/.piper/models
Move-Item zh_CN-huayan-medium.onnx $HOME/.piper/models/

2.5 下载并安装 Node.js + Remotion

  1. 从 https://nodejs.org/ 下载 LTS 版并安装(会自动加入 PATH);
  2. 验证:node --version;
  3. 安装 Remotion 依赖(本仓库 remotion-composer/ 已预装 node_modules,若是全新环境则执行):
cd remotion-composer
npm install

2.6 下载并安装 ffmpeg

winget install Gyan.FFmpeg
# 或从 https://ffmpeg.org/download.html 下载后加入 PATH

ffmpeg -version   # 验证

2.7 浏览器(Chrome/Edge)

Windows 一般已自带 Edge;Chrome 可从 https://www.google.com/chrome/ 下载。 Remotion 渲染会用它(详见 §9.2 坑 A)。

Windows 注意:任何 Python 脚本涉及中文文件时,先执行 $env:PYTHONUTF8="1", 否则读写中文内容会乱码或报编码错误。


3. 阶段一:研究(research)

Agent 先做主题调研,产出 research_brief(研究简报)。本案例的核心事实:

  • 瑞利散射定律:散射强度 $I \propto 1/\lambda^4$,蓝光(~450nm)散射强度约为红光(~700nm)的 $(700/450)^4 \approx 5.8 \approx 6$ 倍。
  • 太阳光是包含所有颜色的白光。
  • 你看到的蓝天 = 空气分子把蓝光散射后涌进眼睛的蓝光。
  • 数据来源:NASA Space Place、Britannica 等公开资料。

4. 阶段二:提案(proposal)

从研究简报产出 2-3 个差异化概念,让用户选择。本案例的提案:

概念 标题 叙事结构
c1(选中) 一束光的旅程:天空为什么是蓝色的 journey(旅程式)
c2 为什么天空不是紫色的? comparison(对比式)
c3 天空蓝的数学:蓝光是红光的 6 倍 data_narrative(数据叙事)

用户选择 c1 后,锁定关键决策并写入 decision_log.json(决策审计轨):

  • 渲染引擎:Remotion(程序化 React 动画,零素材)
  • 风格:custom-sky(天空蓝 #38BDF8 + 落日橙 #FB923C,扁平几何风)
  • 配音:本地 Piper(免费、离线)
  • 规格:30 秒 / 1920×1080 / 30fps

5. 阶段三:剧本(script)

产出 script.json,含 5 段旁白(每段带 provider_text 供 TTS 使用)与表演提示:

段 时间 文案
s1 0–5s 你有没有想过,头顶的蓝天,到底是从哪儿来的?
s2 5–10s 太阳送来的光,看着是白色,其实藏着所有颜色。
s3 10–19s 光穿过空气时,空气分子会把蓝光弹向四面八方。蓝光的散射强度,是红光的六倍!
s4 19–25s 所以,无论你往哪儿看,蓝光都在涌进你的眼睛。这也是为什么,傍晚的太阳会变红。
s5 25–30s 蓝天,是光的一场旅行。关注我,明天继续讲下一个为什么。

6. 阶段四:分镜(scene_plan)

产出 scene_plan.json,6 个场景对应 6 个 Remotion 场景类型:

场景 时间 类型 内容
sc-01 0–3.8s hero_title 大字标题「蓝天,到底从哪儿来?」+ 副题「一束光的旅程」
sc-02 3.8–7.8s spectrum_scene 太阳 + 棱镜展开七色光谱 + 波长标注(紫400nm/蓝450nm/红700nm)
sc-03 7.8–12s scatter_scene 空气分子把蓝光弹向四面八方,红光直行
sc-04 12–16s stat_card 大字「≈6倍」+ 公式 (700/450)⁴ ≈ 5.8
sc-05 16–23s sunset_scene 蓝光涌进眼睛 → 傍晚太阳变红(两阶段动画)
sc-06 23–30s text_card 结尾「蓝天 = 光的旅行」+ 关注 CTA

自研场景组件:spectrum_scene / scatter_scene / sunset_scene 是仓库原有组件 之外的 3 个新 React 组件,放在 remotion-composer/src/components/SkyScenes/, 并在 tools/video/video_compose.py 的 _REMOTION_SCENE_TYPES 中登记 (否则渲染器不识别这些类型为 Remotion 内容)。

7. 阶段五:素材(assets)

7.1 生成中文旁白(本地 Piper)

用 tools/audio/piper_tts.py(或直接命令行)。关键坑:中文必须通过 UTF-8 文件喂给 Piper, 直接管道传中文会被终端 locale 编码搞乱码。

# 每段单独生成(便于测时长)
piper -m C:\Users\Administrator\.piper\models\zh_CN-huayan-medium.onnx `
      -i s1.txt -f assets/audio/s1.wav

# 拼接全部段落 → narration_full.wav(28.37s,22050Hz,mono)
ffmpeg -y -f concat -safe 0 -i list.txt -c copy assets/audio/narration_full.wav

实测各段时长(用于校准字幕与场景时间轴):

段 时长 区间
s1 3.66s 0.0–3.66
s2 3.96s 3.66–7.62
s3 8.14s 7.62–15.76
s4 7.19s 15.76–22.95
s5 5.42s 22.95–28.37

7.2 生成逐词字幕(captions.json)

本环境没有 faster_whisper,所以手工按短语分块:把每段文案切成短语,按字数比例 分配该段实测时长。脚本:projects/why-sky-blue/scripts/make_captions.py, 产出 18 个 chunk,覆盖 0–28370ms,供 Remotion 的 CaptionOverlay 逐词高亮使用。

7.3 素材清单(asset_manifest.json)

记录每个素材的来源与校验状态;本案例所有素材 source: "generate"(程序化生成), 零外部下载(Pixabay 等图库在本网络 403 不可用,属于预期降级而非缺陷)。

8. 阶段六:剪辑(edit)

产出 schema 校验通过的 edit_decisions.json:6 个 cut,每个带 in_seconds / out_seconds / transition_in / transition_out / transition_duration, 时间轴与旁白边界对齐,并配置:

  • audio.narration:旁白音频引用
  • subtitles.enabled: true、style: word-by-word(逐词字幕)
  • metadata:playbook / concept_id / delivery_promise 等

9. 阶段七:合成渲染(compose)—— 核心

9.1 渲染管线怎么选

tools/video/video_compose.py 的 _needs_remotion() 判断是否走 Remotion: 只要有 transition、动画、富场景类型或图片,就返回 True(本项目 6 个 cut 全带 transition)。

9.2 两个关键修复(本案例实际踩过的坑)

坑 A:Remotion 首次渲染挂起——Chrome Headless Shell 下载卡死

首次运行 npx remotion render 会尝试下载 113 MB 的 Headless Shell,慢网络下卡在 38MB 不动。

修复:remotion-composer/remotion.config.ts 强制用系统 Chrome:

import { Config } from "@remotion/cli/config";

Config.setBrowserExecutable(
  process.env.REMOTION_BROWSER_EXECUTABLE ||
    "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
);

video_compose.py 的 npx 调用会自动加载此配置,无需改 Python。 (无 Chrome 的机器可删除此文件,让 Remotion 自动下载。)

坑 B:<Audio> 无法读取 file:// 路径

渲染器报错 Can only download URLs starting with http:// or https://。 Remotion 的音频 src 必须是 staticFile() 路径(public/ 目录内的裸文件名)。

修复:把 narration_full.wav 复制到 remotion-composer/public/, props 里用裸文件名 "src": "narration_full.wav" 而不是绝对路径。

9.3 渲染脚本

projects/why-sky-blue/scripts/compose_render.py 负责:

  1. build_props():把 edit_decisions 的时间轴 + 富场景 props(type/text/stat/chart 等)
    • captions + audio + 显式 themeConfig 合并成完整 ExplainerProps;
  2. run_validator():写 composition_preflight.json 并跑 CompositionValidator, 不通过(exit 2)就阻止渲染;
  3. 调 VideoCompose().execute({operation: "render", ...}): 自动执行 Remotion 渲染 + 最终自审(ffprobe 校验容器/码流)。
cd e:/gitdown/OpenMontage
$env:PYTHONUTF8="1"
.venv/bin/python.exe projects/why-sky-blue/scripts/compose_render.py

9.4 先冒烟再全量(重要习惯)

全量 930 帧之前,先用 30 帧冒烟验证管线,秒级出结果:

cd remotion-composer
node node_modules/@remotion/cli/remotion-cli.js render src/index.tsx Explainer `
     ../projects/why-sky-blue/renders/smoke.mp4 --props=..\projects\why-sky-blue\artifacts\composition_preflight.json --frames=0-29

确认冒烟正常(画面、音频、字幕无误)再跑全量。

9.5 渲染产物验证

ffprobe -v error -show_entries stream=codec_name,width,height,r_frame_rate,channels,bit_rate -of default=noprint_wrappers=1 projects/why-sky-blue/renders/final.mp4

预期输出:

codec_name=h264      # 视频流
width=1920  height=1080  r_frame_rate=30/1
codec_name=aac       # 音频流(48kHz 立体声)

抽帧做视觉 QA(6 个场景中点各抽一帧):

ffmpeg -y -v error -ss 1.9 -i projects/why-sky-blue/renders/final.mp4 -frames:v 1 projects/why-sky-blue/review_frames/01_hero.png
# ... 5.8 / 9.9 / 14.0 / 19.5 / 26.5 同理

本案例抽帧验证结果:6 个场景全部可见、字幕覆盖正常、日落晚段帧红色分量 > 蓝色分量(暖色正确)。

9.6 已知非关键告警(可忽略)

  • Could not validate delivery promise: 'promise_type' —— 校验器期望键名 promise_type, 我们用的是 type;不影响渲染,可改键名消告警。
  • Subtitles expected but not found... —— 字幕由 Remotion 原生 CaptionOverlay 渲染, 无需 ASS 烧录,属误报。
  • transcript_comparison skipped —— 未装 faster_whisper,跳过自动转录对比, 用人工比对脚本文案与旁白时长即可。

10. 阶段八:发布(publish)

projects/why-sky-blue/scripts/publish_bundle.py 调用 ExportBundle(离线打包器)生成:

exports/why-sky-blue/
├── video/
│   ├── output.mp4            # 成片
│   └── subtitles.srt         # 标准 SRT 字幕(18 条)
├── metadata/
│   ├── metadata.json         # SEO 标题/描述/标签/话题/章节
│   ├── description.txt       # 可直接粘贴的描述(含章节时间戳 + CTA)
│   ├── tags.txt              # 每行一个标签
│   └── chapters.txt          # YouTube 章节标记
└── thumbnails/
    └── concept.json          # 缩略图概念「蓝光×6倍」

SRT 格式坑:导出脚本里的时间格式必须含小时位(00:00:01,156), 写成 00:01,156 会不符合标准,播放器可能不认。

发布阶段是人工闸门(checkpoint_publish.json status=awaiting_human), Agent 展示成果后必须结束回合,等用户审批,不得擅自进入下一步。


11. 一分钟速查表

# 1) 配音(Piper,中文走 UTF-8 文件)
piper -m <zh_CN-huayan-medium.onnx> -i text.txt -f out.wav

# 2) 字幕(无 whisper 时手工分块)
.venv/bin/python.exe projects/why-sky-blue/scripts/make_captions.py

# 3) 校验 + 渲染(含 CompositionValidator 前置检查)
.venv/bin/python.exe projects/why-sky-blue/scripts/compose_render.py

# 4) 冒烟测试(可选,全量前先跑)
node remotion-composer/node_modules/@remotion/cli/remotion-cli.js render `
     remotion-composer/src/index.tsx Explainer smoke.mp4 --frames=0-29

# 5) 验证
ffprobe -v error -show_entries stream=codec_name -of default=noprint_wrappers=1 final.mp4

# 6) 发布打包
.venv/bin/python.exe projects/why-sky-blue/scripts/publish_bundle.py

12. 制作同类视频的 Checklist

  • 主题是否适合程序化动画?(科普/数据/流程/讲解 → 合适;写实场景 → 考虑生成式管线)
  • 剧本每段是否 ≤ 目标时长/段落数?文案是否可直接喂 TTS?
  • 用实测旁白时长校准场景时间轴(别用估算值)
  • 字幕 chunk 是否覆盖旁白全程?
  • 渲染前先跑 CompositionValidator + 30 帧冒烟
  • ffprobe 确认 h264 + aac 双流
  • 抽帧目检每个场景与字幕
  • 发布前确认 SRT 时间格式、导出包完整

附:为什么"不用 AI 生成画面"也能出片?

  • 画面由 Remotion(React 代码)逐帧确定性绘制,每个像素都是计算出来的,不是模型生成的。
  • 优点:零成本、精确到帧、文字/字幕完美、科学内容可严格按数据、改一行代码即可重渲。
  • 生成式视频模型(Sora/Veo 等)适合写实画面,但不可控、按秒计费、文字常出错——本需求恰好相反。
  • 本项目实际用了两处 AI:Agent(LLM)做创意决策 + Piper 做语音合成;画面 100% 程序化。