Skip to main content

remotion-video

使用 Remotion 框架以编程方式创建视频。Remotion 让你用 React 组件定义视频内容,支持动画、字幕、音乐可视化、3D 视频、教程讲解视频等。适用于程序化视频、批量生成、数据驱动视频、音乐可视化、自动字幕等场景。

Source facts

Repository
SpaceZephyr/myskill
Last source activity
March 20, 2026 at 10:38
Detected SKILL.md language
Chinese
Stars
341
Forks
84

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
remotion-video
description
使用 Remotion 框架以编程方式创建视频。Remotion 让你用 React 组件定义视频内容,支持动画、字幕、音乐可视化、3D 视频、教程讲解视频等。适用于程序化视频、批量生成、数据驱动视频、音乐可视化、自动字幕等场景。
# Remotion Video 用 React 以编程方式创建 MP4 视频的框架。 ## 核心概念 1. **Composition** - 视频的定义(尺寸、帧率、时长) 2. **useCurrentFrame()** - 获取当前帧号,驱动动画 3. **interpolate()** - 将帧号映射到任意值(位置、透明度等) 4. **spring()** - 物理动画效果 5. **<Sequence>** - 时间轴上排列组件 ## 快速开始 ### 创建新项目 ```bash npx create-video@latest ``` 选择模板后: ```bash cd <project-name> 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 ( <AbsoluteFill style={{ backgroundColor: "white", justifyContent: "center", alignItems: "center" }}> <h1 style={{ fontSize: 100 }}>Frame {frame}</h1> </AbsoluteFill> ); }; ``` ### 注册 Composition ```tsx // Root.tsx import { Composition } from "remotion"; import { MyVideo } from "./MyVideo"; export const RemotionRoot = () => { return ( <Composition id="MyVideo" component={MyVideo} durationInFrames={150} // 5秒 @ 30fps fps={30} width={1920} height={1080} /> ); }; ``` ## 动画技巧 ### 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"; <> <Sequence from={0} durationInFrames={60}> <Intro /> </Sequence> <Sequence from={60} durationInFrames={90}> <MainContent /> </Sequence> <Sequence from={150}> <Outro /> </Sequence> </> ``` ## AI 语音解说集成 为视频添加 AI 语音解说,实现音视频同步。支持两种方案: | 方案 | 优点 | 缺点 | 硬件要求 | 推荐度 | |------|------|------|----------|--------| | **MiniMax TTS** | 云端克隆、速度极快(<3秒)、音质优秀 | 按字符计费 | 无 | ⭐⭐⭐ 首选 | | **Edge TTS** | 零配置、免费 | 固定音色、无法自定义 | 无 | ⭐⭐ | ### 方案选择流程 ``` 1. 首选 MiniMax TTS - 检测 API Key 是否配置 - 测试调用是否正常(余额充足) - 如果成功 → 使用 MiniMax 2. MiniMax 不可用时 → 退回 Edge TTS(使用预设音色 zh-CN-YunyangNeural) ``` --- ## 方案一: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) => ( <Sequence key={scene.id} from={sceneStarts[i]} durationInFrames={scene.frames}> <SceneComponent /> <Audio src={staticFile(scene.file)} /> </Sequence> ))} ``` --- ## 教程类视频架构(场景驱动) 教程、讲解类视频的核心架构:**音频驱动场景切换**。 ### 架构概览 ``` 音频脚本 → 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 ( <AbsoluteFill style={{ backgroundColor: "#1a1a2e" }}> {/* 3D 内容 */} <ThreeCanvas width={width} height={height} camera={{ position: [0, 0, 4], fov: 50 }}> {/* 根据 sceneIndex 渲染不同场景 */} {sceneIndex === 0 && <Scene01Intro />} {sceneIndex === 1 && <Scene02Concept />} {sceneIndex === 2 && <Scene03Demo />} </ThreeCanvas> {/* 音频同步 - 每个场景一个 Sequence */} {SCENES.map((scene, idx) => ( <Sequence key={scene.id} from={getSceneStart(idx)} durationInFrames={scene.durationInFrames}> <Audio src={staticFile(`audio/${scene.audioFile}`)} /> </Sequence> ))} {/* UI 层:标题 + 进度 */} <div style={{ position: "absolute", top: 40, left: 0, right: 0, textAlign: "center" }}> <h1 style={{ color: "white", fontSize: 42 }}>教程标题</h1> </div> <div style={{ position: "absolute", bottom: 60, left: 60 }}> <span style={{ color: "white" }}>{currentScene?.title}</span> </div> {/* 进度条 */} <div style={{ position: "absolute", bottom: 30, left: 60, right: 60, height: 4, backgroundColor: "rgba(255,255,255,0.2)" }}> <div style={{ width: `${((sceneIndex + 1) / SCENES.length) * 100}%`, height: "100%", backgroundColor: "#3498DB" }} /> </div> </AbsoluteFill> ); }; ``` ### Root.tsx 使用动态帧数 ```tsx import { Composition } from "remotion"; import { TutorialVideo } from "./TutorialVideo"; import { TOTAL_FRAMES } from "./audioConfig"; export const RemotionRoot: React.FC = () => { return ( <Composition id="Tutorial" component={TutorialVideo} fps={30} durationInFrames={TOTAL_FRAMES} // 从 audioConfig 动态获取 width={1920} height={1080} /> ); }; ``` ### ⚠️ 教程视频踩坑经验 | 问题 | 原因 | 解决方案 | |------|------|----------| | 场景切换生硬 | 直接切换无过渡 | 用 spring/interpolate 添加入场动画 | | 3D 内容与音频不同步 | 硬编码帧数 | 所有时长从 audioConfig 读取 | | 渲染时 WebGL 崩溃 | 多个 ThreeCanvas 同时存在 | 用 sceneIndex 条件渲染,同时只有一个 3D 场景 | | 视频太简略 | 只有一个大场景 | **一个概念 = 一个场景组件**,分层讲解 |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub