HeyGen + Remotion 集成指南:在 OpenMontage 中生成虚拟人视频并完成帧级精确合成
【免费下载链接】OpenMontageWorld's first open-source, agentic video production system. 12 production pipelines, 100+ tools, 700+ agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage
导读
本文以 OpenMontage 仓库内 HeyGen 技能参考文档 remotion-integration.md 为核心,系统讲解如何把 HeyGen 云端 AI 虚拟人(Avatar)视频接入 Remotion 代码化视频合成流水线:从输出格式选型(MP4 带背景 / WebM 透明通道)、并行开发工作流、画幅对齐,到 Remotion 中基于OffthreadVideo的帧级精确渲染、Loom 风格圆形头像遮罩、分层合成与动态时长计算。读完本文,你将掌握一条「HeyGen 生成 → 轮询取 URL → Remotion 合成 → 命令行渲染」的完整可落地链路,并能结合仓库中的TalkingHead合成组件与 HeyGen 工具封装将其嵌入自己的生产管线。
Overview:一条典型的合成工作流
HeyGen 负责生成虚拟人口播视频,Remotion 负责把它与其他素材(背景、Logo、图表、动画、字幕)在代码层面合成最终成片。典型流程如下:
- 用 HeyGen 生成虚拟人视频(MP4 或 WebM);
- 等待生成完成,获取视频 URL;
- 在 Remotion 中直接使用 URL,或先下载到本地再引用;
- 与背景、叠加层、动画等其他元素合成。
仓库中 SKILL.md 对 HeyGen 技能的整体定位是:AI avatar video creation API for generating talking-head videos, explainers, and presentations,并在 Quick Reference 中明确将「与 Remotion 搭配使用」指向本文对应的 remotion-integration.md,说明该集成方案是仓库内 HeyGen 技能的官方推荐用法之一。
在 OpenMontage 的 remotion-composer 子项目中,已经存在与本文档同构的实战实现:TalkingHead.tsx 以videoSrc为输入,用OffthreadVideo承载口播视频层,用Sequence按秒级时间轴驱动图表、数据卡、标注框等 overlay 层,最上层叠加CaptionOverlay字幕——这正是本文所述分层合成思路在真实仓库中的落地形态,下文会多次回指该实现。
Quick Start:三步跑通最小闭环
// 1. Get avatar with default voice const avatar = await getAvatarDetails(avatarId); // 2. Generate video (MP4 with background - most common) const videoId = await generateVideo({ video_inputs: [{ character: { type: "avatar", avatar_id: avatar.id, avatar_style: "normal" }, voice: { type: "text", input_text: script, voice_id: avatar.default_voice_id }, background: { type: "color", value: "#1a1a2e" }, }], dimension: { width: 1920, height: 1080 }, }); // 3. Poll for completion (10-15+ min) // 4. Use in Remotion with motion graphics overlaid on top关键点在于使用头像自带的default_voice_id:多数 HeyGen 头像自带预匹配的默认音色,语音与形象的自然度最好,也省去手动挑选音色的步骤(详见 avatars.md 与 voices.md)。
选择正确的输出格式
这是整个集成方案中第一个、也是最重要的决策点,因为它直接决定后续在 Remotion 中如何合成:
| 你的合成方案 | 推荐格式 | 原因 |
|---|---|---|
| 虚拟人作为主持人,上层叠加动效 | MP4 + 背景 | 更简单,叠加层直接盖在视频上 |
| Loom 风格(虚拟人叠加在录屏上) | WebM +closeUp,在 Remotion 中做遮罩 | 需要透明通道,用 CSS 圆形遮罩 |
| 虚拟人叠加在其他视频/内容之上 | WebM(透明) | 需要透过虚拟人看到背后内容 |
| 全屏虚拟人 | MP4 + 背景 | 标准做法 |
绝大多数场景使用 MP4 带背景。只有当需要看到虚拟人背后的内容时才使用 WebM。
注意:WebM 只支持normal与closeUp两种风格。需要圆形构图时,请在 Remotion 中用 CSSborder-radius: 50%实现,而不是依赖 HeyGen 的circle风格。
这一点在 video-generation.md 的 WebM 一节中有同样结论:WebM 只用于「虚拟人叠加在录屏上」「虚拟人漂浮在视频背景上」「真正的 alpha 通道合成」三种场景。
推荐:并行开发工作流
HeyGen 视频生成耗时10-15 分钟以上,绝不应该干等。推荐的并行策略:
- 先启动 HeyGen 生成——把
video_id存到文件里,立即退出; - 并行构建 Remotion 合成——先用占位素材或头像的
preview_video_url(一段短视频循环)顶着; - 构建过程中周期性检查 HeyGen 状态,或者构建完成后再查;
- 就绪后把占位素材替换为真实视频 URL。
根据脚本估算时长:语速约 150 词/分钟,因此wordCount / 150 * 60 * fps即可估算出大致帧数。
合成设计建议:把组件设计成「有无虚拟人视频都能工作」,这样动效可以独立于虚拟人进行测试。仓库中的 TalkingHead.tsx 正是这种设计——videoSrc只是众多 props 之一,overlay 与字幕层完全不依赖视频是否就位,可以在虚拟人视频渲染完成前独立预览全部动效。
画幅对齐:HeyGen 输出尺寸必须与 Remotion 合成尺寸一致
关键约束:HeyGen 的输出画幅必须与 Remotion 合成画幅完全一致,否则会出现黑边、裁切或缩放失真。
通用画幅预设常量
// Shared dimension constants for both HeyGen and Remotion const DIMENSIONS = { landscape_1080p: { width: 1920, height: 1080 }, landscape_720p: { width: 1280, height: 720 }, portrait_1080p: { width: 1080, height: 1920 }, portrait_720p: { width: 720, height: 1280 }, square_1080p: { width: 1080, height: 1080 }, square_720p: { width: 720, height: 720 }, } as const; type DimensionPreset = keyof typeof DIMENSIONS;HeyGen 侧:按预设生成视频
// Generate HeyGen video with specific dimensions async function generateHeyGenVideo( script: string, avatarId: string, voiceId: string, preset: DimensionPreset ): Promise<string> { const dimension = DIMENSIONS[preset]; const response = await fetch("https://api.heygen.com/v2/video/generate", { method: "POST", headers: { "X-Api-Key": process.env.HEYGEN_API_KEY!, "Content-Type": "application/json", }, body: JSON.stringify({ video_inputs: [ { character: { type: "avatar", avatar_id: avatarId, avatar_style: "normal", }, voice: { type: "text", input_text: script, voice_id: voiceId, }, background: { type: "color", value: "#00FF00", // Green screen for compositing }, }, ], dimension, }), }); const { data } = await response.json(); return data.video_id; }Remotion 侧:在 Root.tsx 中注册同尺寸合成
// remotion/src/Root.tsx import { Composition } from "remotion"; import { AvatarComposition } from "./AvatarComposition"; const DIMENSIONS = { landscape_1080p: { width: 1920, height: 1080 }, // ... same as above }; export const RemotionRoot: React.FC = () => { return ( <> <Composition id="AvatarVideo" component={AvatarComposition} durationInFrames={300} // Will be set dynamically fps={30} width={DIMENSIONS.landscape_1080p.width} height={DIMENSIONS.landscape_1080p.height} defaultProps={{ avatarVideoUrl: "", }} /> </> ); };仓库中的 Root.tsx 注册了同构的TalkingHead合成,其默认画幅为 9:16(1080x1920),与TalkingHead.tsx中POSITION_STYLES按竖屏画幅设计的 overlay 定位(如lower_third底部留 320px 给字幕区)相互呼应——再次印证「先定画幅,再写合成」的工程顺序。
为 Remotion 生成虚拟人视频
标准做法:MP4 带背景
绝大多数 Remotion 合成采用 MP4 + 背景,叠加层与动效直接盖在视频上层:
async function generateAvatarForRemotion( script: string, avatarId: string, voiceId: string, options: { style?: "normal" | "closeUp" | "circle"; backgroundColor?: string; } = {} ): Promise<string> { const { style = "normal", backgroundColor = "#1a1a2e" } = options; const response = await fetch("https://api.heygen.com/v2/video/generate", { method: "POST", headers: { "X-Api-Key": process.env.HEYGEN_API_KEY!, "Content-Type": "application/json", }, body: JSON.stringify({ video_inputs: [{ character: { type: "avatar", avatar_id: avatarId, avatar_style: style, }, voice: { type: "text", input_text: script, voice_id: voiceId, }, background: { type: "color", value: backgroundColor, }, }], dimension: { width: 1920, height: 1080 }, }), }); const { data } = await response.json(); return data.video_id; }透明背景:WebM 接口
仅当需要看到虚拟人背后的内容时使用(例如虚拟人叠加在录屏之上):
// Use /v1/video.webm endpoint for transparent background // Note: Different structure than /v2/video/generate const response = await fetch("https://api.heygen.com/v1/video.webm", { method: "POST", headers: { "X-Api-Key": process.env.HEYGEN_API_KEY!, "Content-Type": "application/json", }, body: JSON.stringify({ avatar_pose_id: avatarPoseId, // Required: avatar pose ID avatar_style: "normal", // Required: "normal" or "closeUp" only input_text: script, // Required (with voice_id) voice_id: voiceId, // Required (with input_text) dimension: { width: 1920, height: 1080 }, }), });注意 WebM 端点(/v1/video.webm)的请求结构与/v2/video/generate不同:使用扁平字段avatar_pose_id/avatar_style/input_text/voice_id,且input_text+voice_id与input_audio二选一,不能同时提供。生成后与 MP4 使用相同的状态轮询接口,返回的video_url为.webm文件(详见 video-generation.md 与 video-status.md)。
在 Remotion 中使用 HeyGen 视频
关键:渲染必须用OffthreadVideo而非Video
虚拟人视频一律使用OffthreadVideo。基础Video组件走浏览器视频解码器,无法做到逐帧精确,渲染时会产生抖动(jitter);OffthreadVideo通过 FFmpeg 逐帧抽取画面,保证平滑且帧精确。
OffthreadVideo属于remotion核心包,无需额外安装。
基础用法
// remotion/src/AvatarComposition.tsx import { OffthreadVideo, useVideoConfig } from "remotion"; interface AvatarCompositionProps { avatarVideoUrl: string; } export const AvatarComposition: React.FC<AvatarCompositionProps> = ({ avatarVideoUrl, }) => { return ( <div style={{ flex: 1, backgroundColor: "#1a1a2e" }}> <OffthreadVideo src={avatarVideoUrl} style={{ width: "100%", height: "100%", objectFit: "contain", }} /> </div> ); };仓库实现与此一致:TalkingHead.tsx 中的口播视频层即用OffthreadVideo+objectFit: "cover"承载,且通过resolveAsset(videoSrc)统一解析本地静态资源与远程 URL。
WebM 透明通道合成(推荐)
使用/v1/video.webm生成的 WebM 自带 alpha 通道,无需任何色键处理:
import { OffthreadVideo, AbsoluteFill, Sequence } from "remotion"; export const AvatarWithMotionGraphics: React.FC<{ avatarWebmUrl: string }> = ({ avatarWebmUrl }) => { return ( <AbsoluteFill> {/* Layer 1: Your background/content */} <AbsoluteFill style={{ backgroundColor: "#1a1a2e" }}> <YourMotionGraphics /> </AbsoluteFill> {/* Layer 2: Avatar with transparent background - use OffthreadVideo for frame-accurate rendering */} <OffthreadVideo src={avatarWebmUrl} transparent style={{ position: "absolute", bottom: 0, right: 0, width: "50%", height: "auto", }} /> {/* Layer 3: Overlays on top of avatar */} <Sequence from={30}> <AnimatedTitle text="Welcome!" /> </Sequence> </AbsoluteFill> ); };要点:WebM 加transparent属性;用Sequence控制叠加层出现时机(此处from={30}表示第 30 帧后出现,即第 1 秒)。
Loom 风格:圆形头像叠加录屏
使用closeUp风格 + WebM,再在 Remotion 中做圆形遮罩:
import { OffthreadVideo, AbsoluteFill } from "remotion"; export const LoomStyleComposition: React.FC<{ screenRecordingUrl: string; avatarWebmUrl: string; // Generated with avatar_style: "closeUp" via /v1/video.webm }> = ({ screenRecordingUrl, avatarWebmUrl }) => { return ( <AbsoluteFill> {/* Screen recording fills the frame */} <OffthreadVideo src={screenRecordingUrl} style={{ width: "100%", height: "100%" }} /> {/* Avatar with circular mask - transparent bg shows screen behind */} <OffthreadVideo src={avatarWebmUrl} transparent style={{ position: "absolute", bottom: 40, left: 40, width: 180, height: 180, borderRadius: "50%", // Circular mask applied in CSS overflow: "hidden", objectFit: "cover", }} /> </AbsoluteFill> ); };注意:WebM 不支持circle风格——请用normal或closeUp生成,再用 CSS 圆形遮罩(border-radius: 50%+overflow: hidden+objectFit: cover)实现圆形头像效果。
遗留方案:绿幕 + 色键
如果手里只有绿幕 MP4(不推荐,应改用 WebM):
// Note: True chroma key requires WebGL or post-processing // WebM transparent background is much simpler <OffthreadVideo src={avatarVideoUrl} style={{ mixBlendMode: "multiply", // Basic compositing only }} />真实色键需要 WebGL 或后期处理,mixBlendMode只能做基础合成,因此 WebM 透明通道是远为简单的方案。
分层合成示例
import { OffthreadVideo, Sequence, useVideoConfig, Img } from "remotion"; interface LayeredAvatarProps { avatarVideoUrl: string; backgroundUrl: string; logoUrl: string; title: string; } export const LayeredAvatarComposition: React.FC<LayeredAvatarProps> = ({ avatarVideoUrl, backgroundUrl, logoUrl, title, }) => { const { fps } = useVideoConfig(); return ( <div style={{ position: "relative", width: "100%", height: "100%" }}> {/* Layer 1: Background */} <Img src={backgroundUrl} style={{ position: "absolute", width: "100%", height: "100%", objectFit: "cover", }} /> {/* Layer 2: Avatar video - use OffthreadVideo to prevent jitter */} <OffthreadVideo src={avatarVideoUrl} style={{ position: "absolute", bottom: 0, right: 0, width: "40%", height: "auto", }} /> {/* Layer 3: Title (appears after 1 second) */} <Sequence from={fps}> <div style={{ position: "absolute", top: 50, left: 50, color: "white", fontSize: 48, fontWeight: "bold", }} > {title} </div> </Sequence> {/* Layer 4: Logo */} <Img src={logoUrl} style={{ position: "absolute", top: 20, right: 20, width: 100, height: "auto", }} /> </div> ); };分层顺序(从底到顶):背景图 → 虚拟人视频 → 标题(延迟 1 秒出现)→ Logo。仓库 TalkingHead.tsx 采用了相同的分层哲学并加以泛化:视频层 → overlay 层(PositionedOverlay封装了淡入淡出与位置预设)→ 最顶层字幕,overlay 类型涵盖文本卡、数据卡、标注框、对比卡与柱状/折线/饼图等图表组件(见 components)。
完整工作流
生成并合成:从脚本到成片
import { bundle } from "@remotion/bundler"; import { renderMedia, selectComposition } from "@remotion/renderer"; async function generateAvatarVideoForRemotion( script: string, outputPath: string ) { // 1. Generate HeyGen video console.log("Generating HeyGen avatar video..."); const videoId = await generateHeyGenVideo( script, "josh_lite3_20230714", "1bd001e7e50f421d891986aad5158bc8", "landscape_1080p" ); // 2. Wait for completion console.log("Waiting for HeyGen video..."); const avatarVideoUrl = await waitForVideo(videoId); console.log(`HeyGen video ready: ${avatarVideoUrl}`); // 3. Get video duration for Remotion const avatarDuration = await getVideoDuration(avatarVideoUrl); const durationInFrames = Math.ceil(avatarDuration * 30); // 30 fps // 4. Bundle Remotion project console.log("Bundling Remotion project..."); const bundleLocation = await bundle({ entryPoint: "./remotion/src/index.ts", }); // 5. Select composition const composition = await selectComposition({ serveUrl: bundleLocation, id: "AvatarVideo", inputProps: { avatarVideoUrl, }, }); // 6. Render final video console.log("Rendering final composition..."); await renderMedia({ composition: { ...composition, durationInFrames, }, serveUrl: bundleLocation, codec: "h264", outputLocation: outputPath, inputProps: { avatarVideoUrl, }, }); console.log(`Final video rendered: ${outputPath}`); return outputPath; }这条链路与 OpenMontage 的工程实现一一对应:仓库中 heygen_video.py 是 HeyGen 视频生成的工具封装(要求环境变量HEYGEN_API_KEY,走POST /v1/workflows/executions的 GenerateVideoNode 工作流),其底层轮询逻辑 poll_heygen 采用 5 秒起始间隔、1.2 倍指数退避、最长 600 秒超时的策略,与本文推荐「10-15 分钟生成、周期轮询」完全一致;Remotion 渲染器 则对应bundle/selectComposition/renderMedia的可编程渲染路径。
动态时长:用 calculateMetadata 自动对齐视频长度
// remotion/src/AvatarComposition.tsx import { CalculateMetadataFunction } from "remotion"; export const calculateAvatarMetadata: CalculateMetadataFunction< AvatarCompositionProps > = async ({ props }) => { // Fetch video duration from HeyGen video const duration = await getVideoDurationInSeconds(props.avatarVideoUrl); return { durationInFrames: Math.ceil(duration * 30), fps: 30, width: 1920, height: 1080, }; }; // In Root.tsx <Composition id="AvatarVideo" component={AvatarComposition} calculateMetadata={calculateAvatarMetadata} defaultProps={{ avatarVideoUrl: "", }} />calculateMetadata让合成时长、帧率、画幅在渲染前按真实视频动态确定,避免写死durationInFrames={300}造成的截断或空帧。
最佳实践
1. 用绿幕换取合成灵活性
需要合成时,让 HeyGen 以纯绿背景生成:
background: { type: "color", value: "#00FF00", // Pure green for chroma key }2. 匹配帧率
HeyGen 默认输出 25 fps,设置 Remotion 帧率时要考虑这一点:
// Option 1: Match HeyGen's 25 fps fps: 25 // Option 2: Use 30 fps with playback rate adjustment <OffthreadVideo src={avatarVideoUrl} playbackRate={25/30} // Slow down slightly to match />3. URL 直用 vs 本地下载
直接使用 URL,当:
- 在 Remotion Studio 中预览(
npm run dev); - URL 在渲染完成前不会过期;
- 开发期追求快速迭代。
// Direct URL usage - simpler, faster for dev <OffthreadVideo src={avatarVideoUrl} />先下载到本地,当:
- URL 会过期(HeyGen URL 约 24 小时后失效);
- 渲染发生在之后或需要重复渲染;
- 网络可靠性堪忧;
- 需要离线渲染。
// Download with retry for reliability async function downloadVideoWithRetry( url: string, outputPath: string, maxRetries = 5 ): Promise<string> { for (let attempt = 0; attempt < maxRetries; attempt++) { try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); const buffer = await response.arrayBuffer(); await fs.promises.writeFile(outputPath, Buffer.from(buffer)); return outputPath; } catch (error) { const delay = 2000 * Math.pow(2, attempt); console.log(`Retry ${attempt + 1}/${maxRetries} in ${delay}ms...`); await new Promise((r) => setTimeout(r, delay)); } } throw new Error("Download failed after retries"); } // Use local file in Remotion const localPath = await downloadVideoWithRetry(avatarVideoUrl, "./public/avatar.mp4");混合方案(生产环境推荐):
// Save both URL and local path in metadata const metadata = { videoUrl: result.video_url, // For quick preview localPath: "./public/avatar.mp4", // For reliable rendering expiresAt: Date.now() + 24 * 60 * 60 * 1000, // URL expiration }; // In Remotion component, prefer local if available const videoSrc = fs.existsSync(localPath) ? staticFile("avatar.mp4") : avatarVideoUrl;4. 控制虚拟人位置
常见构图位置预设:
const AVATAR_POSITIONS = { fullscreen: { width: "100%", height: "100%", position: "center" }, bottomRight: { width: "40%", bottom: 0, right: 0 }, bottomLeft: { width: "40%", bottom: 0, left: 0 }, pictureInPicture: { width: "25%", bottom: 20, right: 20 }, leftThird: { width: "33%", left: 0, height: "100%" }, };仓库 TalkingHead.tsx 以同样的思路为 9:16 画幅定义了lower_third/upper_third/left_panel/right_panel/full_overlay五档 overlay 位置预设,可参考其坐标数值设计自己的竖屏布局。
输出格式速查
HeyGen 输出
- 格式:MP4(H.264)
- 音频:AAC
- 分辨率:按请求指定(如 1920x1080)
Remotion 输出
- 编码器:H.264(默认)、VP8、VP9、ProRes
- 质量应与 HeyGen 输出匹配或更高
await renderMedia({ codec: "h264", crf: 18, // High quality // ... });故障排查
视频在 Remotion 中无法播放
- 检查 URL 可访问性(CORS 问题);
- 确认视频格式兼容性;
- 先尝试下载到本地再引用。
画幅不匹配
确保 HeyGen 与 Remotion 使用完全一致的尺寸:
// Shared config const VIDEO_CONFIG = { width: 1920, height: 1080, fps: 30, }; // HeyGen dimension: { width: VIDEO_CONFIG.width, height: VIDEO_CONFIG.height } // Remotion <Composition width={VIDEO_CONFIG.width} height={VIDEO_CONFIG.height} />渲染时视频抖动
如果渲染结果中虚拟人画面抖动或卡顿:
- 用
OffthreadVideo替换Video——基础Video组件走浏览器视频解码器,无法逐帧精确; - 更新导入(无需额外安装,
OffthreadVideo就在核心remotion包里):// Before (causes jitter) import { Video } from "remotion"; // After (frame-accurate) import { OffthreadVideo } from "remotion"; - 使用带透明通道的 WebM 时,加
transparent属性:<OffthreadVideo src={avatarWebmUrl} transparent />
音画不同步
若虚拟人音频漂移:
- 核对源视频帧率;
- 检查编码问题;
- 考虑用一致的参数重新编码。
总结
HeyGen + Remotion 的组合,本质上是「云端虚拟人口播能力」与「代码化合成渲染能力」的分工:HeyGen 负责把脚本变成可信的虚拟人视频,Remotion 负责在帧级精确的合成器中把视频、图表、字幕、品牌元素编排成最终成片。掌握本文的四条主线——输出格式选型(MP4 vs WebM)、并行开发工作流、画幅/帧率对齐、OffthreadVideo帧精确渲染——即可把这条链路稳定嵌入 OpenMontage 式的自动化视频生产线。进一步可阅读 video-generation.md(多场景视频、WebM 字段详解)、video-status.md(轮询与下载重试)、webhooks.md(生产环境替代轮询的方案),并对照仓库实现 TalkingHead.tsx 与 heygen_video.py 深化理解。
【免费下载链接】OpenMontageWorld's first open-source, agentic video production system. 12 production pipelines, 100+ tools, 700+ agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考