首页
/ OpenMontage HeyGen 数字人视频资产上传实战:upload.heygen.com 直传 API 的完整指南

OpenMontage HeyGen 数字人视频资产上传实战:upload.heygen.com 直传 API 的完整指南

2026-09-05 09:55:23作者:农烁颖Land

在 OpenMontage 的 Agent 技能体系中,assets.mdavatar-video 技能(基于 HeyGen v2 API 的数字人视频制作)的核心基础参考,它定义了如何向 HeyGen 上传图片、视频、音频三类资产,并将这些资产接入后续的视频生成流程。读完本文,你将掌握 HeyGen 资产上传端点的完整请求/响应规范、多语言(curl / TypeScript / Python)实现方式、流式上传大文件的技巧,以及如何把上传产物(资产 URL、资产 ID、image_key)正确填入 /v2/video/generate 的视频配置中;同时本文结合 OpenMontage 仓库源码,展示该上传机制在工具链中的真实落地方式。

资产上传在 avatar-video 技能中的定位

OpenMontage 通过 .agents/skills/ 目录组织 700 多个 Agent 技能与生产知识文件,让 AI 编码助手具备完整的视频制作能力。其中 avatar-video 技能面向"精确控制"场景:由使用者指定数字人、配音、脚本和背景,通过 HeyGen /v2/video/generate 接口生成视频。技能入口 SKILL.md 规定了默认工作流:列出数字人 → 选择声音 → 撰写脚本 → 生成视频 → 轮询状态,并把各主题拆分到 15 个参考文档中。

在该技能的快速参考表中,"上传资产(图片、音频)"指向 assets.md,与 backgrounds.md(背景配置)、photo-avatars.md(照片数字人)构成一条数据链:

  1. 先有资产:自定义背景图、动态视频背景、自定义配音都必须是 HeyGen 可访问的资源,本地文件必须先通过上传端点入库;
  2. 再填配置:上传响应的 data.urldata.iddata.image_key 分别服务于背景 URL、talking photo ID、照片数字人创建等下游步骤;
  3. 最后生成:把资产字段填入 video_inputs,调用 /v2/video/generate 得到 video_id

技能的 frontmatter 声明了运行前提:需要环境变量 HEYGEN_API_KEYprimaryEnv: HEYGEN_API_KEY),且所有请求通过 X-Api-Key 请求头鉴权。仓库中的 PROVIDERS.md 也给出了 HeyGen 的接入说明:注册账号后在设置中生成 API Key,写入 .envHEYGEN_API_KEY 即可解锁对应能力。

上传机制:单步直传原始二进制

HeyGen 的资产上传是单步流程:直接把文件原始二进制 POST 到上传端点,不需要 JSON 包装、不需要表单字段、也不需要预签名 URL 交换。唯一的硬性约束是 Content-Type 请求头必须与文件的真实 MIME 类型一致。

端点: POST https://upload.heygen.com/v1/asset

请求头规范

Header 必填 说明
X-Api-Key 你的 HeyGen API Key(即 HEYGEN_API_KEY 环境变量的值)
Content-Type 文件的 MIME 类型(如 image/jpegvideo/mp4

请求体就是文件二进制数据本身。这一点与常见的"先申请上传凭证、再 PUT 到对象存储"的两段式方案不同:调用方少一次交互,但要求 Content-Type 如实声明,否则服务端可能按错误的媒体类型处理资产。

响应字段

字段 类型 说明
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:通用引用。作为背景图/视频背景的 background.url、作为音频输入的 voice.audio_url,都用它;
  • data.id:结构体引用。作为 talking_photo 数字人的 talking_photo_id 时用它;
  • data.image_key:仅图片返回,形如 image/{id}/original.jpg,是创建照片数字人(photo avatar)的专用输入。photo-avatars.md 中特别强调"要保存 image_key 而不是 id",因为 image_key 才是创建 avatar group 时 S3 路径语义的键。

用 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 会原样发送文件内容且不转义特殊字符,配合显式的 Content-Type 头即完成一次标准上传。

TypeScript 实现(缓冲上传)

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}`);

注意错误判定依据是业务字段 code !== 100,而不是 HTTP 状态码——这与 HeyGen 系列接口的统一包裹风格(code + data + message)一致,客户端必须同时处理两种失败信号。

TypeScript 实现(流式上传大文件)

当文件较大时,把整个文件读进内存不划算。技能文档给出了基于 Node.js 文件流的写法,要点是显式设置 Content-Length 并通过 duplex: "half" 告诉 undici/fetch 这是流式请求体:

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;
}

duplex: "half" 是流式上传的易错点:缺少它时,fetch 会拒绝以 ReadableStream 为 body 的请求。Content-Length 则让服务端在接收前就能预判载荷规模,也便于做限流与配额检查。

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 版用 requestsdata=ff 为二进制文件对象)实现与 curl 等效的直传;同样以 code != 100 作为失败判定。

支持的 Content-Type 与典型用途

类型 Content-Type 用途
JPEG image/jpeg 背景图、talking photo 源图
PNG image/png 背景图、需要透明度的叠加层
MP4 video/mp4 动态(循环)视频背景
WebM video/webm 动态(循环)视频背景
MP3 audio/mpeg 自定义音频输入
WAV audio/wav 自定义音频输入

这三类资产恰好覆盖 /v2/video/generate 的三个可定制维度:视觉背景(image/video)、数字人素材(image → talking photo / photo avatar)、语音来源(audio)。选择格式时的原则:照片类素材用 JPEG(体积更小),带透明度的图形用 PNG,配音优先用与预期成片时长匹配的 MP3/WAV。

从 URL 上传:资产已在公网时的两步法

如果资产已经托管在可公开访问的地址上,不需要走对象存储再复制,而是先下载、再直传:

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;
}

这里的实现做了一个值得注意的工程决策:只允许 HTTPS 源地址,并在拿到完整 buffer 后重新执行一次标准直传,而不是把 URL 直接交给 HeyGen。这样做的效果是:源站不可达/返回错误时会在本地立刻暴露;同时上传请求的 Content-Type 由调用方显式控制,不受源站响应头的影响。

把上传资产接入视频生成

上传成功后,资产如何被消费取决于用途,三者使用响应中的不同字段,这是最容易混淆的地方。

作为背景图 / 视频背景

background 对象使用 data.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,  // 上传响应中的 URL
      },
    },
  ],
};

这与 backgrounds.md 中的背景规范一致:background 支持 color / image / video 三种 type,其中 imagevideo 类型都要求携带 url 字段(缺失 url/value 是"背景不生效"的常见原因,该文档还给出了排查示例)。

作为 Talking Photo 源

talking photo 数字人使用 data.id,而非 URL:

const talkingPhotoConfig = {
  video_inputs: [
    {
      character: {
        type: "talking_photo",
        talking_photo_id: asset.id,  // 上传响应中的 ID
      },
      voice: {
        type: "text",
        input_text: "Hello from my talking photo!",
        voice_id: "1bd001e7e50f421d891986aad5158bc8",
      },
    },
  ],
};

v2 生成接口的字段表 也印证了这一约定:character.type"talking_photo" 时必须提供 talking_photo_id。而如果要走"照片 → 数字人 avatar group"的完整流程(photo-avatars.md),则使用 image_key 调用 POST /v2/photo_avatar/avatar_group/create。同一次上传,按下游用途取不同字段,这就是资产对象三字段并存的原因。

作为音频输入

voice 对象切换到 type: "audio" 并使用 data.url

const audioConfig = {
  video_inputs: [
    {
      character: {
        type: "avatar",
        avatar_id: "josh_lite3_20230714",
        avatar_style: "normal",
      },
      voice: {
        type: "audio",
        audio_url: asset.url,  // 上传响应中的 URL
      },
    },
  ],
};

video-generation.md 的字段表,voice 支持 text / audio / silence 三种类型,audio_url 仅在 typeaudio 时必填。

完整工作流:上传背景并生成视频

把上传与生成串起来的完整链路如下——上传背景图、组装 video_inputs、调用 /v2/video/generate、返回 video_id

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 之后,按 video-status.md 的轮询模式调用 GET /v2/videos/{video_id},等待状态变为 completed 后取下载 URL。SKILL.md 的最佳实践还提醒:开发期可设 test: true 进入测试模式(带水印、不消耗额度),且视频生成通常需要 5–15 分钟,客户端应设置充裕的超时。

资产限制与最佳实践

限制

  • 文件大小:最大 10MB;
  • 图片尺寸:建议与视频尺寸一致(如 1080p 视频配 1920×1080 背景),比例不匹配时可能被裁切或拉伸;
  • 音频时长:应与预期成片时长匹配;
  • 保留期:资产在长时间不活跃后可能被清理,不应依赖其作为永久存储。

最佳实践

  1. 上传前优化图片——先缩放到与视频输出一致的尺寸再上传,既省带宽也保证构图不被裁切;
  2. 选对格式——照片用 JPEG,需要透明通道的图形用 PNG;
  3. 本地预校验——上传前检查文件类型与大小,避免把 20MB 的文件发给一个 10MB 上限的端点;
  4. 处理上传失败——对瞬时网络错误实现重试逻辑;
  5. 缓存资产 ID/URL——同一背景在多条视频生成间复用,避免重复上传消耗时间与流量。

仓库源码印证:OpenMontage 工具链中的资产上传

技能文档描述的是 HeyGen 的直传端点,而 OpenMontage 的 Python 工具链在实现上走了"预签名上传 + 降级"的路线,两者互为补充。

tools/video/_shared.py 中,upload_image_heygen 函数的策略是:

  1. 先请求 POST https://api.heygen.com/v2/assets/upload(v2 预签名上传端点),提交 content_typefile_name,从响应中取出 upload_urlfile_url
  2. 把文件字节 PUTupload_url
  3. 任一步失败则整体 except 吞掉,降级调用 upload_image_fal 把图片放到 fal.ai 存储并返回公网 URL。

从源码结构看,这条链路的产出(一个公网可访问的图片 URL)最终喂给 HeyGen 工作流执行接口 POST https://api.heygen.com/v1/workflows/executions(见同文件 generate_heygen_video),其 reference_image_url 字段接受 URL 而非资产 ID——即工具链选择"URL 优先",与技能文档中"资产对象优先"的 v2 用法是同一能力面的两种封装。该函数的 docstring 也写明其意图:"Upload a local image to HeyGen and return a public URL"。

tools/video/heygen_video.py 中,HeyGenVideo 工具声明了与本文主题相关的运行时契约:

  • get_statusos.environ.get("HEYGEN_API_KEY") 是否存在决定工具可用/不可用(L95-L96),与 SKILL.md 的 primaryEnv: HEYGEN_API_KEY 声明一致;
  • install_instructions 提示设置该环境变量后即可解锁;
  • retry_policy 配置为最多 2 次重试、10 秒退避,可重试错误含 rate_limittimeoutserver_error——正是资产上传"处理上传错误、实现重试"这一最佳实践在工具层的制度化体现;
  • side_effects 明确标注了"calls HeyGen API",让 Agent 在执行前知晓外部调用边界。

此外,tools/video/_shared.py 中的 poll_heygen 采用 5 秒起始、1.2 倍递增、上限 30 秒的自适应轮询间隔,并在 completed 时从 output.video 提取 video_url、在 failed/error 时抛错——这套轮询语义与技能文档要求的"轮询直至 completed、设置充裕超时"相互印证。

小结

HeyGen 资产上传的设计非常克制:一个端点、两个请求头、原始二进制,换回一个包含 id / url / image_key 的统一资产对象;三者分别对接背景、talking photo 和照片数字人三条下游链路。对工程实现而言,真正需要写对的地方只有三处——如实声明 Content-Type、以 code == 100 判定业务成败、对失败做重试并缓存已上传资产。OpenMontage 将这套 API 规范固化为 Agent 技能参考文档,并在 tools/video/ 工具链中给出了预签名上传与降级的 Python 实现,二者结合覆盖了从"精确控制 v2 资产"到"工具链自动上传"的完整使用面。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384