--- name: remotion-video description: | 使用 Remotion 框架以编程方式创建视频。Remotion 让你用 React 组件定义视频内容,支持动画、字幕、音乐可视化等。 触发词: - "用代码做视频"、"编程视频"、"React 视频" - "Remotion"、"remotion" - "/remotion-video" 适用场景: - 程序化视频:(1) 批量生成 (2) 数据驱动(如年度总结)(3) 音乐可视化 (4) 自动字幕 - 教程讲解视频:(5) 技术概念可视化(如 CNN、算法)(6) 分层递进讲解 (7) AI 配音教程 - 3D 视频:(8) 产品展示/模型动画 (9) 卡通角色讲解 (10) 3D 数据可视化 (11) Logo 动画 --- # Remotion Video 用 React 以编程方式创建 MP4 视频的框架。 ## 核心概念 1. **Composition** - 视频的定义(尺寸、帧率、时长) 2. **useCurrentFrame()** - 获取当前帧号,驱动动画 3. **interpolate()** - 将帧号映射到任意值(位置、透明度等) 4. **spring()** - 物理动画效果 5. **** - 时间轴上排列组件 ## 快速开始 ### 创建新项目 ```bash npx create-video@latest ``` 选择模板后: ```bash cd npm run dev # 启动 Remotion Studio 预览 ``` ### 项目结构 ``` my-video/ ├── src/ │ ├── Root.tsx # 注册所有 Composition │ ├── HelloWorld.tsx # 视频组件 │ └── index.ts # 入口 ├── public/ # 静态资源(音频、图片) ├── remotion.config.ts # 配置文件 └── package.json ``` ## 基础组件示例 ### 最小视频组件 ```tsx import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion"; export const MyVideo = () => { const frame = useCurrentFrame(); const { fps, durationInFrames } = useVideoConfig(); return (

Frame {frame}

); }; ``` ### 注册 Composition ```tsx // Root.tsx import { Composition } from "remotion"; import { MyVideo } from "./MyVideo"; export const RemotionRoot = () => { return ( ); }; ``` ## 动画技巧 ### interpolate - 值映射 ```tsx import { interpolate, useCurrentFrame } from "remotion"; const frame = useCurrentFrame(); // 0-30帧:透明度 0→1 const opacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: "clamp", // 超出范围时钳制 }); // 位移动画 const translateY = interpolate(frame, [0, 30], [50, 0]); ``` ### spring - 物理动画 ```tsx import { spring, useCurrentFrame, useVideoConfig } from "remotion"; const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const scale = spring({ frame, fps, config: { damping: 10, stiffness: 100 }, }); ``` ### Sequence - 时间编排 ```tsx import { Sequence } from "remotion"; <> ``` ## AI 语音解说集成 为视频添加 AI 语音解说,实现音视频同步。支持三种方案: | 方案 | 优点 | 缺点 | 硬件要求 | 推荐度 | |------|------|------|----------|--------| | **MiniMax TTS** | 云端克隆、速度极快(<3秒)、音质优秀 | 按字符计费 | 无 | ⭐⭐⭐ 首选 | | **Edge TTS** | 零配置、免费 | 固定音色、无法自定义 | 无 | ⭐⭐ | | **vLLM-Omni Qwen3-TTS** | 本地部署、支持克隆音色、完全免费 | 需要 GPU、部署复杂 | GPU 8GB+ | ⭐⭐⭐ 自托管首选 | ### 方案选择流程 ``` 1. 首选 MiniMax TTS - 检测 API Key 是否配置 - 测试调用是否正常(余额充足) - 如果成功 → 使用 MiniMax 2. MiniMax 不可用时 → 退回 Edge TTS(使用预设音色 zh-CN-YunyangNeural) 3. 需要本地部署/完全免费 → 使用 vLLM-Omni Qwen3-TTS(需要 GPU) ``` --- ## 方案一:MiniMax TTS(推荐) 云端 API 方案,无需本地 GPU,生成速度极快,音色克隆效果优秀。 ### 配置 1. 注册 https://www.minimax.io (国际版)或 https://platform.minimaxi.com (国内版) 2. 获取 API Key 3. 在 MiniMax Audio 上传音频克隆音色,获取 voice_id ### API 差异 | 版本 | API 域名 | 说明 | |------|----------|------| | 国际版 | `api.minimax.io` | 推荐,稳定 | | 国内版 | `api.minimaxi.com` | 需国内账号 | **⚠️ 常见错误**:`api.minimax.chat` 是**错误的域名**,会返回 "invalid api key"。请确认使用上表中的正确域名。 ### 生成脚本 使用 `scripts/generate_audio_minimax.py` 生成音频,支持: - **断点续作**:已存在的音频文件自动跳过 - **实时进度**:显示生成进度,避免茫然等待 - **自动更新配置**:生成完成后自动更新 Remotion 的场景配置 ```bash # 设置环境变量 export MINIMAX_API_KEY="your_api_key" export MINIMAX_VOICE_ID="your_voice_id" # 运行脚本 python scripts/generate_audio_minimax.py ``` ### 价格参考(2025年) | 模型 | 价格 | |------|------| | speech-02-hd | ¥0.1/千字符 | | speech-02-turbo | ¥0.05/千字符 | ### ⚠️ MiniMax TTS 踩坑经验 | 问题 | 原因 | 解决方案 | |------|------|----------| | `invalid api key` | 使用了错误的 API 域名 | 国际版用 `api.minimax.io`,国内版用 `api.minimaxi.com` | | config.ts 语法错误 `Syntax error "n"` | Python 脚本在 f-string 中用 `",\\n".join()` 产生了字面量 `\n` 而非真正换行 | 见下方「Python 生成 TypeScript 注意事项」 | | 长时间无进度显示 | 后台执行命令看不到输出 | 前台执行脚本,或用 `tail -f` 实时查看日志 | ### Python 生成 TypeScript 注意事项 **❌ 错误写法**:在 f-string 中使用 `\n` 会产生字面量字符 ```python # 这会在生成的文件中写入字面的 \n 字符串,而非换行! content = f'export const SCENES = [{",\\n".join(items)}];' ``` **✅ 正确写法**:分开处理字符串拼接 ```python # 先用真正的换行符拼接 scenes_content = ",\n".join(items) # 在 f-string 外部拼接 # 再放入模板 content = f'''export const SCENES = [ {scenes_content} ];''' ``` --- ## 方案二:Edge TTS 无需特殊硬件,完全免费,适合不需要克隆音色的场景。 ### 安装 ```bash pip install edge-tts ``` ### 推荐语音 | 语音 ID | 名称 | 风格 | |---------|------|------| | zh-CN-YunyangNeural | 云扬 | 专业播音腔(推荐) | | zh-CN-XiaoxiaoNeural | 晓晓 | 温暖自然 | | zh-CN-YunxiNeural | 云希 | 阳光少年 | ### 生成脚本 使用 `scripts/generate_audio_edge.py` 生成音频: ```bash python scripts/generate_audio_edge.py ``` ### Remotion 音频同步 ```tsx import { Audio, Sequence, staticFile } from "remotion"; // 音频配置(根据生成的时长) const audioConfig = [ { id: "01-intro", file: "01-intro.mp3", frames: 450 }, { id: "02-main", file: "02-main.mp3", frames: 600 }, ]; // 计算起始帧 const sceneStarts = audioConfig.reduce((acc, _, i) => { if (i === 0) return [0]; return [...acc, acc[i - 1] + audioConfig[i - 1].frames]; }, [] as number[]); // 场景渲染 {audioConfig.map((scene, i) => ( ))} ``` --- ## 方案三:vLLM-Omni Qwen3-TTS(本地部署) 本地部署方案,使用 vLLM-Omni 部署 Qwen3-TTS 模型,完全免费,支持自定义音色。 ### 硬件要求 | 组件 | 最低配置 | 推荐配置 | |------|----------|----------| | GPU | 8GB VRAM | 16GB+ VRAM | | 内存 | 16GB | 32GB | | 存储 | 20GB | 50GB SSD | ### 部署步骤 #### 1. 安装 vLLM-Omni ```bash # 克隆 vLLM-Ommi 仓库 git clone https://github.com/thudm/vllm-omni cd vllm-omni # 安装依赖 pip install -r requirements.txt ``` #### 2. 启动服务器 ```bash # 启动 vLLM-Omni 服务器(使用 Qwen3-TTS 模型) python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice \ --port 8000 ``` #### 3. 测试服务器 ```bash curl http://localhost:8000/v1/models ``` ### 配置 设置环境变量: ```bash # 服务器地址(默认) export VLLM_BASE_URL="http://localhost:8000/v1" # 模型名称(默认) export VLLM_MODEL_NAME="Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice" # 预设语音(可选) export VLLM_VOICE="Vivian" ``` ### 可用语音 Qwen3-TTS 支持多种预设语音: | 语音 ID | 名称 | 风格 | |---------|------|------| | Vivian | 薇薇安 | 温柔女声 | | Adam | 亚当 | 成熟男声 | | echo | 艾科 | 活泼女声 | | ... | ... | ... | 完整语音列表请参考 [Qwen3-TTS 文档](https://github.com/Qwen/Qwen3-TTS)。 ### 生成脚本 使用 `generate_audio_qwen.py` 生成音频: ```bash # 安装依赖 pip install openai>=1.0.0 # 运行脚本 python generate_audio_qwen.py ``` 脚本特性: - **断点续作**:已存在的音频文件自动跳过 - **实时进度**:显示生成进度 - **自动更新配置**:生成完成后自动更新 audioConfig.ts - **格式转换**:自动处理 WAV → MP3 转换 ### ⚠️ vLLM-Ommi 踩坑经验 | 问题 | 原因 | 解决方案 | |------|------|----------| | 连接拒绝 | 服务器未启动 | 确认 vLLM-Omni 服务正在运行 | | CUDA OOM | GPU 显存不足 | 降低 batch_size 或使用更小的模型 | | 音频格式错误 | 返回 WAV 而非 MP3 | 脚本会自动用 ffmpeg 转换 | ### 性能参考 | 模型 | 生成速度 | 音质 | |------|----------|------| | Qwen3-TTS-12Hz-0.6B-CustomVoice | ~5-10s/句 | 优秀 | --- ## 教程类视频架构(场景驱动) 教程、讲解类视频的核心架构:**音频驱动场景切换**。 ### 架构概览 ``` 音频脚本 → TTS 生成 → audioConfig.ts → 场景组件 → 视频渲染 ``` 关键思想: 1. **音频决定时长**:每个场景的持续时间由音频长度决定 2. **场景即章节**:一个概念 = 一个场景 = 一段音频 3. **配置即真理**:`audioConfig.ts` 是音画同步的单一数据源 ### audioConfig.ts 模板 参见 `templates/audioConfig.ts`,包含: - SceneConfig 接口定义 - SCENES 数组 - getSceneStart() 计算函数 - TOTAL_FRAMES 和 FPS 常量 ### 场景切换 Hook ```tsx import { useCurrentFrame } from "remotion"; import { SCENES } from "./audioConfig"; // 根据当前帧号返回场景索引 const useCurrentSceneIndex = () => { const frame = useCurrentFrame(); let accumulated = 0; for (let i = 0; i < SCENES.length; i++) { accumulated += SCENES[i].durationInFrames; if (frame < accumulated) return i; } return SCENES.length - 1; }; // 使用 const sceneIndex = useCurrentSceneIndex(); const currentScene = SCENES[sceneIndex]; ``` ### 主场景组件模式 ```tsx import { AbsoluteFill, Audio, Sequence, staticFile, useVideoConfig } from "remotion"; import { ThreeCanvas } from "@remotion/three"; import { SCENES, getSceneStart, TOTAL_FRAMES } from "./audioConfig"; export const TutorialVideo: React.FC = () => { const { width, height } = useVideoConfig(); const sceneIndex = useCurrentSceneIndex(); const currentScene = SCENES[sceneIndex]; return ( {/* 3D 内容 */} {/* 根据 sceneIndex 渲染不同场景 */} {sceneIndex === 0 && } {sceneIndex === 1 && } {sceneIndex === 2 && } {/* 音频同步 - 每个场景一个 Sequence */} {SCENES.map((scene, idx) => ( ))} {/* UI 层:标题 + 进度 */}

教程标题

{currentScene?.title}
{/* 进度条 */}
); }; ``` ### Root.tsx 使用动态帧数 ```tsx import { Composition } from "remotion"; import { TutorialVideo } from "./TutorialVideo"; import { TOTAL_FRAMES } from "./audioConfig"; export const RemotionRoot: React.FC = () => { return ( ); }; ``` ### ⚠️ 教程视频踩坑经验 | 问题 | 原因 | 解决方案 | |------|------|----------| | 场景切换生硬 | 直接切换无过渡 | 用 spring/interpolate 添加入场动画 | | 3D 内容与音频不同步 | 硬编码帧数 | 所有时长从 audioConfig 读取 | | 渲染时 WebGL 崩溃 | 多个 ThreeCanvas 同时存在 | 用 sceneIndex 条件渲染,同时只有一个 3D 场景 | | 视频太简略 | 只有一个大场景 | **一个概念 = 一个场景组件**,分层讲解 | ### 场景组件设计原则 1. **单一职责**:每个场景组件只负责一个概念 2. **独立动画**:每个场景有自己的 useCurrentFrame(),动画从 0 开始 3. **延迟出现**:用 delay 参数控制元素依次出现 4. **相机适配**:不同场景可能需要不同相机位置 ```tsx // 场景组件示例 const Scene02Input: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); // 入场动画 const gridScale = spring({ frame, fps, config: { damping: 15 } }); return ( ); }; ``` ### 相机控制器模式 ```tsx import { useThree } from "@react-three/fiber"; // ✅ 推荐写法:直接设置相机位置,避免插值导致的持续抖动 const CameraController: React.FC<{ sceneIndex: number }> = ({ sceneIndex }) => { const { camera } = useThree(); const cameraSettings: Record = { 0: [0, 0, 4], // 开场:正面 1: [0, 0, 3], // 输入层:靠近 2: [-0.5, 0, 3.5], // 卷积:偏左 3: [0, 0, 5], // 总结:拉远全景 }; const target = cameraSettings[sceneIndex] || [0, 0, 4]; // 直接设置位置,不用插值 camera.position.set(target[0], target[1], target[2]); camera.lookAt(0, 0, 0); return null; }; ``` ⚠️ **不要用 `position += (target - position) * factor` 这种写法**,永远无法精确收敛,会导致画面持续抖动。详见「🚨 3D 场景常见陷阱 - 陷阱1」。 --- ## 常用功能 ### 添加视频/音频 ```tsx import { Video, Audio, staticFile } from "remotion"; // 使用 public/ 目录下的文件