OpenMontage HeyGen 素材管理实战:自定义背景、会说话照片与音频上传完整指南
在 OpenMontage 的 create-video 技能(基于 HeyGen Video Agent 的提示词驱动视频生成)中,想让 AI 生成的视频带上品牌素材——自定义背景、会说话的照片(talking photo)、定制音频——前提是把本地文件上传为 HeyGen 资产(Asset)。本文以 .agents/skills/create-video/references/assets.md 这份参考文档为主线,完整讲解资产上传端点、请求/响应协议与多语言调用实现,并结合 OpenMontage 工具链中 tools/video/_shared.py 与 tools/video/heygen_video.py 的源码,说明这套上传机制在实际生产管线里是如何被调用的,读完即可复制可用的上传与引用资产代码。
什么是 HeyGen 资产,上传流程为什么是单步的
HeyGen 允许上传自定义素材(图片、视频、音频)供视频生成使用,典型用途包括:作为视频背景、作为 talking photo 的源图、作为自定义配音输入。
资产上传是单步流程:直接把文件原始二进制 POST 到上传端点,Content-Type 头必须与文件的真实 MIME 类型一致。不需要先申请上传凭证,也不需要 JSON 或 form 字段包裹——这与 OpenMontage 仓库工具链中实际使用的 v2 预签名上传路径(后文源码章节详述)形成对照,两条路径在官方文档体系中并存。
上传端点详解:请求与响应协议
端点: POST https://upload.heygen.com/v1/asset
请求头
| Header | 必填 | 说明 |
|---|---|---|
X-Api-Key |
✓ | HeyGen API 密钥(对应环境变量 HEYGEN_API_KEY) |
Content-Type |
✓ | 文件的 MIME 类型(如 image/jpeg) |
请求体就是文件的原始二进制数据,无 JSON、无 form 字段。
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
code |
number | 状态码(100 表示成功) |
data.id |
string | 资产的唯一 ID,后续视频生成引用用 |
data.name |
string | 资产名称 |
data.file_type |
string | image、video 或 audio |
data.url |
string | 上传后可访问的 URL |
data.image_key |
string | null | 用于创建"上传照片头像"的键(仅图片) |
data.folder_id |
string | 所属文件夹 ID(不在文件夹中则为空) |
data.meta |
string | null | 资产元数据 |
data.created_ts |
number | 创建时间的 Unix 时间戳 |
响应中有两个关键字段值得特别留意:data.url 用于在标准 v2 视频生成 API 中按 URL 引用素材(背景图、音频);data.id 则用于按资产 ID 引用(talking photo、Video Agent 的 files 数组)。data.image_key 只在上传图片时非空,它是后续创建"上传照片头像"的凭证。
curl 示例
curl -X POST "https://upload.heygen.com/v1/asset" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: image/jpeg" \
--data-binary '@./background.jpg'
注意 --data-binary:它保证二进制原样发送,且不会像 --data 那样对特殊字符做转义——这正是"请求体即原始二进制"这一协议要求的正确打开方式。
多语言上传实现
TypeScript(Buffer 直传)
import fs from "fs";
import path from "path";
interface AssetUploadResponse {
code: number;
data: {
id: string;
name: string;
file_type: string;
url: string;
image_key: string | null;
folder_id: string;
meta: string | null;
created_ts: number;
};
msg: string | null;
message: string | null;
}
async function uploadAsset(filePath: string, contentType: string): Promise<AssetUploadResponse["data"]> {
const resolvedPath = path.resolve(filePath);
const fileBuffer = fs.readFileSync(resolvedPath);
const response = await fetch("https://upload.heygen.com/v1/asset", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
"Content-Type": contentType,
},
body: fileBuffer,
});
const json: AssetUploadResponse = await response.json();
if (json.code !== 100) {
throw new Error(json.message ?? "Upload failed");
}
return json.data;
}
// Usage
const asset = await uploadAsset("./background.jpg", "image/jpeg");
console.log(`Uploaded asset: ${asset.id}`);
console.log(`Asset URL: ${asset.url}`);
两个细节值得注意:错误判断依据的是业务码 json.code !== 100 而非 HTTP 状态码,失败信息优先取 message 字段(响应体里同时有 msg 和 message 两个字段,文档示例取的是 message);函数返回的是 data 对象,调用方随后就能拿到 asset.id 和 asset.url。
TypeScript(流式上传大文件)
import fs from "fs";
import path from "path";
import { stat } from "fs/promises";
async function uploadLargeAsset(filePath: string, contentType: string): Promise<AssetUploadResponse["data"]> {
const resolvedPath = path.resolve(filePath);
const fileStats = await stat(resolvedPath);
const fileStream = fs.createReadStream(resolvedPath);
const response = await fetch("https://upload.heygen.com/v1/asset", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
"Content-Type": contentType,
"Content-Length": fileStats.size.toString(),
},
body: fileStream as any,
// @ts-ignore - duplex is needed for streaming
duplex: "half",
});
const json: AssetUploadResponse = await response.json();
if (json.code !== 100) {
throw new Error(json.message ?? "Upload failed");
}
return json.data;
}
流式版本的关键在于三点:先用 fs/promises 的 stat 拿到文件大小以填写 Content-Length;body 传入 createReadStream 的流;以及 duplex: "half" 选项——Node.js 18+ 的 fetch 底层是 undici,流式请求体必须显式声明 duplex,否则抛 "Request with duplex option must have a stream body" 之类的错误。对 10MB 上限以内的素材,Buffer 直传通常足够;流式版本的价值在于内存占用恒定,适合批量上传管线。
Python
import requests
import os
def upload_asset(file_path: str, content_type: str) -> dict:
with open(file_path, "rb") as f:
response = requests.post(
"https://upload.heygen.com/v1/asset",
headers={
"X-Api-Key": os.environ["HEYGEN_API_KEY"],
"Content-Type": content_type
},
data=f
)
data = response.json()
if data.get("code") != 100:
raise Exception(data.get("message", "Upload failed"))
return data["data"]
# Usage
asset = upload_asset("./background.jpg", "image/jpeg")
print(f"Uploaded asset: {asset['id']}")
print(f"Asset URL: {asset['url']}")
Python 版用 data=f(文件对象)而非 files= 或 json=,让 requests 直接把文件内容作为裸请求体发送,与协议要求一致。
支持的 Content-Type
| 类型 | Content-Type | 典型用途 |
|---|---|---|
| JPEG | image/jpeg |
背景、talking photo |
| PNG | image/png |
背景、叠加层(overlays) |
| MP4 | video/mp4 |
视频背景 |
| WebM | video/webm |
视频背景 |
| MP3 | audio/mpeg |
自定义音频输入 |
| WAV | audio/wav |
自定义音频输入 |
格式选择与用途基本一一对应:照片类背景用 JPEG(体积小),需要透明通道的图形用 PNG;视频背景接受 MP4/WebM;配音输入接受 MP3/WAV。
从 URL 上传
如果素材已经托管在线上(例如品牌素材站上的产品截图),可以先拉取再上传。文档给出的 TypeScript 实现强制要求 HTTPS 源地址:
async function uploadFromUrl(sourceUrl: string, contentType: string): Promise<AssetUploadResponse["data"]> {
// 1. Validate and download the file
const url = new URL(sourceUrl);
if (url.protocol !== "https:") {
throw new Error("Only HTTPS URLs are supported");
}
const sourceResponse = await fetch(sourceUrl);
const buffer = Buffer.from(await sourceResponse.arrayBuffer());
// 2. Upload directly to HeyGen
const response = await fetch("https://upload.heygen.com/v1/asset", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
"Content-Type": contentType,
},
body: buffer,
});
const json: AssetUploadResponse = await response.json();
if (json.code !== 100) {
throw new Error(json.message ?? "Upload failed");
}
return json.data;
}
这里"两步走"的设计(先下载校验、再上传)值得注意:它把源地址的可达性问题挡在了上传之前,失败时不会消耗 HeyGen 侧的配额,也不会留下半截上传。
引用已上传的资产:三种典型用法
上传只是第一步,资产真正产生价值是在视频生成配置里被引用。文档给出了三种用法。
用法一:作为背景图片(用 asset.url)
const videoConfig = {
video_inputs: [
{
character: {
type: "avatar",
avatar_id: "josh_lite3_20230714",
avatar_style: "normal",
},
voice: {
type: "text",
input_text: "Hello, this is a video with a custom background!",
voice_id: "1bd001e7e50f421d891986aad5158bc8",
},
background: {
type: "image",
url: asset.url, // Use the URL from the upload response
},
},
],
};
背景走 background.type: "image" + URL 的方式,这是标准 v2 视频生成 API(POST https://api.heygen.com/v2/video/generate)的配置形态。
用法二:作为 Talking Photo 源(用 asset.id)
const talkingPhotoConfig = {
video_inputs: [
{
character: {
type: "talking_photo",
talking_photo_id: asset.id, // Use the ID from the upload response
},
voice: {
type: "text",
input_text: "Hello from my talking photo!",
voice_id: "1bd001e7e50f421d891986aad5158bc8",
},
},
],
};
talking photo 用的是 character.type: "talking_photo" + talking_photo_id,注意这里引用的是资产 ID 而不是 URL——因为 HeyGen 需要识别这张图并做人脸/口型驱动处理,必须按资产身份来查。
用法三:作为音频输入(用 asset.url)
const audioConfig = {
video_inputs: [
{
character: {
type: "avatar",
avatar_id: "josh_lite3_20230714",
avatar_style: "normal",
},
voice: {
type: "audio",
audio_url: asset.url, // Use the URL from the upload response
},
},
],
};
把 voice.type 从 "text" 换成 "audio" 并给出 audio_url,即可用已上传的录音(而非 TTS)驱动数字人。
三种用法的记忆要点:背景与音频按 URL 引用,talking photo 按 ID 引用。另外别忘了 Video Agent 接口还有一条引用路径:POST /v1/video_agent/generate 请求体里的 files 数组,元素形如 { "asset_id": "..." }(见 video-agent.md 的 Files Array 一节),用于让 Video Agent 在自动分镜时参考你上传的品牌素材。
完整工作流:上传背景到出片
文档给出的端到端示例把三步串起来——上传背景、组装配置、提交生成:
async function createVideoWithCustomBackground(
backgroundPath: string,
script: string
): Promise<string> {
// 1. Upload background
console.log("Uploading background...");
const background = await uploadAsset(backgroundPath, "image/jpeg");
// 2. Create video config
const config = {
video_inputs: [
{
character: {
type: "avatar",
avatar_id: "josh_lite3_20230714",
avatar_style: "normal",
},
voice: {
type: "text",
input_text: script,
voice_id: "1bd001e7e50f421d891986aad5158bc8",
},
background: {
type: "image",
url: background.url,
},
},
],
dimension: { width: 1920, height: 1080 },
};
// 3. Generate video
console.log("Generating video...");
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(config),
});
const { data } = await response.json();
return data.video_id;
}
提交后拿到 video_id 只是开始:HeyGen 的视频处理是异步的,需要按 video-status.md 轮询 GET /v2/videos/{video_id} 直到 completed 再下载 video_url,并预留 15–20 分钟的超时。分辨率取值(16:9 横屏 1920x1080 / 9:16 竖屏 / 1:1 方屏)可对照 dimensions.md;生成前建议先按 quota.md 的 GET /v2/user/remaining_quota 确认剩余积分,避免提交后才发现额度不足。
资产限制与最佳实践
文档明确的硬性约束:
- 文件大小:10MB 上限;
- 图片尺寸:建议与视频输出分辨率一致(例如 1080p 横屏配 1920x1080 背景);
- 音频时长:应与预期视频长度匹配;
- 保留策略:资产在一段时间不活跃后可能被删除——不要把 HeyGen 资产库当作长期存储。
配套的最佳实践:
- 先优化图片再上传 —— 缩放到目标视频尺寸,避免超限也避免服务端压缩损失;
- 选对格式 —— 照片用 JPEG,需要透明通道的图形用 PNG;
- 上传前本地校验 —— 先检查文件类型和大小,把失败挡在请求之前;
- 实现上传重试 —— 网络类失败加退避重试;
- 缓存资产 ID —— 同一素材在多次视频生成间复用,省流量也省时间。
第 5 条在 OpenMontage 的 Agent 化生产流程里尤其有意义:create-video 技能(SKILL.md)把"上传参考文件帮助 Agent 理解品牌/产品"列为标准工作流之一,Agent 上传一次素材后,后续多次迭代生成都引用同一批 asset_id,不必重复上传。
源码佐证:OpenMontage 工具链如何上传 HeyGen 素材
OpenMontage 的 Python 工具链并非调用上文文档中的 v1 裸二进制端点,而是走 v2 预签名(presigned upload)路径,并把 fal.ai 存储作为兜底。这条实现链路可以直接在仓库中查证:
1. 上传函数 tools/video/_shared.py 中的 upload_image_heygen:先 POST https://api.heygen.com/v2/assets/upload 申请上传凭证(请求体只含 content_type 和 file_name),拿到 upload_url 与最终 file_url 后 PUT 文件字节,成功即返回 file_url;整个过程任何一步失败都会静默降级到 upload_image_fal——后者走 fal.ai 的两步存储接口(initiate + PUT)换取公开 URL。从源码结构看,这种"主路径 + 兜底"的设计意味着 image_to_video 参考图即使 HeyGen v2 端点不可用,也能通过 fal.ai 公开 URL 完成 i2v 生成。
2. 调用链:HeyGenVideo.execute(tools/video/heygen_video.py)→ generate_heygen_video(同文件 tools/video/_shared.py)。在 generate_heygen_video 中,当 operation == "image_to_video" 且只给了本地路径 reference_image_path 时,自动调用 upload_image_heygen 把本地图换成 URL,填入 workflow 输入的 reference_image_url;若 URL 与路径都没给,则直接返回失败而非发起请求。随后 POST https://api.heygen.com/v1/workflows/executions(workflow_type: "GenerateVideoNode")拿到 execution_id,再由 poll_heygen 以 5 秒起步、按 1.2 倍递增、封顶 30 秒的间隔轮询执行状态,最长等待 600 秒,完成后下载 video_url 写入 output_path。
3. 工具契约:HeyGenVideo 声明 get_status 依赖环境变量 HEYGEN_API_KEY(未设置即 UNAVAILABLE),fallback_tools 为 wan_video、hunyuan_video 等本地/其他云方案,agent_skills 指向 ai-video-gen 与 create-video 两个技能——也就是说,本文讲解的素材上传能力与 create-video 技能、heygen_video 工具在 OpenMontage 中是同一套生产闭环的不同视角:技能文档教 Agent 用 v1 端点 + 预签名端点管理素材,工具代码在管线内部用 v2 端点 + 兜底存储完成参考图上传。
对比一下两条上传路径的取舍:
| v1 裸二进制上传(本文主体) | v2 预签名上传(仓库工具链) | |
|---|---|---|
| 交互步骤 | 一步:直接 POST 文件 | 两步:申请凭证 → PUT 文件 |
| 认证头 | X-Api-Key + Content-Type |
X-Api-Key(JSON 请求体) |
| 返回 | data.id / data.url / image_key 等完整资产信息 |
upload_url + 最终 file_url |
| 适用场景 | 需要资产 ID(talking photo、Video Agent files 引用) | 管线内只需一个可访问 URL(i2v 参考图) |
选择依据很直接:需要资产身份(ID/image_key)就用 v1,只需要一个可访问 URL 用 v2 或任意对象存储都行。
小结
HeyGen 资产体系的核心可以压缩成三句话:上传端点 POST https://upload.heygen.com/v1/asset 以裸二进制 + 正确 Content-Type 单步完成上传;成功标志是 code == 100,返回体里的 data.url 供背景/音频按 URL 引用、data.id 供 talking photo 与 Video Agent files 按 ID 引用;10MB 上限、建议匹配视频分辨率、资产会被清理这三条约束决定了"上传前本地校验、按 ID 缓存复用"的工程习惯。结合 OpenMontage 仓库中 tools/video/_shared.py 的 upload_image_heygen 兜底实现与 create-video 技能文档,你可以把"素材上传 → 配置引用 → 提交生成 → 轮询下载"整条链路在自己的 Agent 视频生产管线中完整落地。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00