首页
/ OpenMontage HeyGen 素材管理实战:自定义背景、会说话照片与音频上传完整指南

OpenMontage HeyGen 素材管理实战:自定义背景、会说话照片与音频上传完整指南

2026-09-06 17:58:56作者:宗隆裙

在 OpenMontage 的 create-video 技能(基于 HeyGen Video Agent 的提示词驱动视频生成)中,想让 AI 生成的视频带上品牌素材——自定义背景、会说话的照片(talking photo)、定制音频——前提是把本地文件上传为 HeyGen 资产(Asset)。本文以 .agents/skills/create-video/references/assets.md 这份参考文档为主线,完整讲解资产上传端点、请求/响应协议与多语言调用实现,并结合 OpenMontage 工具链中 tools/video/_shared.pytools/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 imagevideoaudio
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 字段(响应体里同时有 msgmessage 两个字段,文档示例取的是 message);函数返回的是 data 对象,调用方随后就能拿到 asset.idasset.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/promisesstat 拿到文件大小以填写 Content-Lengthbody 传入 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.mdGET /v2/user/remaining_quota 确认剩余积分,避免提交后才发现额度不足。

资产限制与最佳实践

文档明确的硬性约束:

  • 文件大小:10MB 上限;
  • 图片尺寸:建议与视频输出分辨率一致(例如 1080p 横屏配 1920x1080 背景);
  • 音频时长:应与预期视频长度匹配;
  • 保留策略:资产在一段时间不活跃后可能被删除——不要把 HeyGen 资产库当作长期存储。

配套的最佳实践:

  1. 先优化图片再上传 —— 缩放到目标视频尺寸,避免超限也避免服务端压缩损失;
  2. 选对格式 —— 照片用 JPEG,需要透明通道的图形用 PNG;
  3. 上传前本地校验 —— 先检查文件类型和大小,把失败挡在请求之前;
  4. 实现上传重试 —— 网络类失败加退避重试;
  5. 缓存资产 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_typefile_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.executetools/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/executionsworkflow_type: "GenerateVideoNode")拿到 execution_id,再由 poll_heygen 以 5 秒起步、按 1.2 倍递增、封顶 30 秒的间隔轮询执行状态,最长等待 600 秒,完成后下载 video_url 写入 output_path

3. 工具契约HeyGenVideo 声明 get_status 依赖环境变量 HEYGEN_API_KEY(未设置即 UNAVAILABLE),fallback_toolswan_videohunyuan_video 等本地/其他云方案,agent_skills 指向 ai-video-gencreate-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.pyupload_image_heygen 兜底实现与 create-video 技能文档,你可以把"素材上传 → 配置引用 → 提交生成 → 轮询下载"整条链路在自己的 Agent 视频生产管线中完整落地。

登录后查看全文
热门项目推荐
相关项目推荐