remotion-video

安装量: 360
排名: #2597

安装

npx skills add https://github.com/wshuyi/remotion-video-skill --skill remotion-video

Remotion Video

用 React 以编程方式创建 MP4 视频的框架。

核心概念 Composition - 视频的定义(尺寸、帧率、时长) useCurrentFrame() - 获取当前帧号,驱动动画 interpolate() - 将帧号映射到任意值(位置、透明度等) spring() - 物理动画效果 - 时间轴上排列组件 快速开始 创建新项目 npx create-video@latest

选择模板后:

cd npm run dev # 启动 Remotion Studio 预览

项目结构 my-video/ ├── src/ │ ├── Root.tsx # 注册所有 Composition │ ├── HelloWorld.tsx # 视频组件 │ └── index.ts # 入口 ├── public/ # 静态资源(音频、图片) ├── remotion.config.ts # 配置文件 └── package.json

基础组件示例 最小视频组件 import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion";

export const MyVideo = () => { const frame = useCurrentFrame(); const { fps, durationInFrames } = useVideoConfig();

return (

Frame {frame}

); };

注册 Composition // Root.tsx import { Composition } from "remotion"; import { MyVideo } from "./MyVideo";

export const RemotionRoot = () => { return ( ); };

动画技巧 interpolate - 值映射 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 - 物理动画 import { spring, useCurrentFrame, useVideoConfig } from "remotion";

const frame = useCurrentFrame(); const { fps } = useVideoConfig();

const scale = spring({ frame, fps, config: { damping: 10, stiffness: 100 }, });

Sequence - 时间编排 import { Sequence } from "remotion";

<> </>

AI 语音解说集成

为视频添加 AI 语音解说,实现音视频同步。支持两种方案:

方案 优点 缺点 硬件要求 推荐度 MiniMax TTS 云端克隆、速度极快(<3秒)、音质优秀 按字符计费 无 ⭐⭐⭐ 首选 Edge TTS 零配置、免费 固定音色、无法自定义 无 ⭐⭐ 方案选择流程 1. 首选 MiniMax TTS - 检测 API Key 是否配置 - 测试调用是否正常(余额充足) - 如果成功 → 使用 MiniMax

  1. MiniMax 不可用时 → 退回 Edge TTS(使用预设音色 zh-CN-YunyangNeural)

方案一:MiniMax TTS(推荐)

云端 API 方案,无需本地 GPU,生成速度极快,音色克隆效果优秀。

配置 注册 https://www.minimax.io (国际版)或 https://platform.minimaxi.com (国内版) 获取 API Key 在 MiniMax Audio 上传音频克隆音色,获取 voice_id API 差异 版本 API 域名 说明 国际版 api.minimax.io 推荐,稳定 国内版 api.minimaxi.com 需国内账号

⚠️ 常见错误:api.minimax.chat 是错误的域名,会返回 "invalid api key"。请确认使用上表中的正确域名。

生成脚本

使用 scripts/generate_audio_minimax.py 生成音频,支持:

断点续作:已存在的音频文件自动跳过 实时进度:显示生成进度,避免茫然等待 自动更新配置:生成完成后自动更新 Remotion 的场景配置

设置环境变量

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 会产生字面量字符

这会在生成的文件中写入字面的 \n 字符串,而非换行!

content = f'export const SCENES = [{",\n".join(items)}];'

✅ 正确写法:分开处理字符串拼接

先用真正的换行符拼接

scenes_content = ",\n".join(items) # 在 f-string 外部拼接

再放入模板

content = f'''export const SCENES = [ {scenes_content} ];'''

方案二:Edge TTS

无需特殊硬件,完全免费,适合不需要克隆音色的场景。

安装 pip install edge-tts

推荐语音 语音 ID 名称 风格 zh-CN-YunyangNeural 云扬 专业播音腔(推荐) zh-CN-XiaoxiaoNeural 晓晓 温暖自然 zh-CN-YunxiNeural 云希 阳光少年 生成脚本

使用 scripts/generate_audio_edge.py 生成音频:

python scripts/generate_audio_edge.py

Remotion 音频同步 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) => ( ))}

教程类视频架构(场景驱动)

教程、讲解类视频的核心架构:音频驱动场景切换。

架构概览 音频脚本 → TTS 生成 → audioConfig.ts → 场景组件 → 视频渲染

关键思想:

音频决定时长:每个场景的持续时间由音频长度决定 场景即章节:一个概念 = 一个场景 = 一段音频 配置即真理:audioConfig.ts 是音画同步的单一数据源 audioConfig.ts 模板

参见 templates/audioConfig.ts,包含:

SceneConfig 接口定义 SCENES 数组 getSceneStart() 计算函数 TOTAL_FRAMES 和 FPS 常量 场景切换 Hook 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];

主场景组件模式 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) => (
    <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 使用动态帧数 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 场景 视频太简略 只有一个大场景 一个概念 = 一个场景组件,分层讲解 场景组件设计原则 单一职责:每个场景组件只负责一个概念 独立动画:每个场景有自己的 useCurrentFrame(),动画从 0 开始 延迟出现:用 delay 参数控制元素依次出现 相机适配:不同场景可能需要不同相机位置 // 场景组件示例 const Scene02Input: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig();

// 入场动画 const gridScale = spring({ frame, fps, config: { damping: 15 } });

return ( ); };

相机控制器模式 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」。

常用功能 添加视频/音频 import { Video, Audio, staticFile } from "remotion";

// 使用 public/ 目录下的文件

返回排行榜