HiAPI
博客

教程

如何通过 HiAPI API 使用 omnihuman-1.5:curl 和 Python 教程

音频转视频数字人生成的完整请求演练、异步轮询及输出下载

2026年11月3日
深棕与燕麦色宽带交织的扁平编织纹理封面图,点缀克制黄铜色调,几何秩序感强

omnihuman-1.5 API 的功能与适用场景

omnihuman-1.5 将一段音频加一张人像图片转换为数字人说话视频:图片中的人脸会被驱动,使嘴部与头部动作跟随所提供语音变化。在 HiAPI 上,该模型以音频转视频端点形式提供——你发送音频、一张参考图片和少量生成参数,API 返回一个任务,你再轮询该任务以获取最终视频。

API 相较手动工具的优势所在

浏览器工具适合一次性剪辑。当视频生成是更大系统中的一个环节时,API 才是正确选择:

  • 批量流水线——从脚本和音轨队列中生成大量数字人片段,无需人工介入。
  • 产品功能——应用内旁白、本地化引导视频或个性化消息,每次渲染使用不同的音频和不同的人像。
  • 后端自动化——内容工作流生成视频后,将其交给 CMS、存储桶或交付服务。

请求的结构形态

HiAPI 上的每次 omnihuman-1.5 调用都遵循相同形态:一个 POST 提交任务,后续请求检查其状态。由于渲染并非瞬时完成,集成模式是提交 → 轮询 → 下载,而非提交 → 在同一响应中收到视频。本教程的其余部分先用 curl 构建该流程,再用 Python 实现。

前提条件与你的 HiAPI 凭证

首次调用前,请确认三件事。

1. 账户与 API 密钥

你需要一个 HiAPI 账户以及在控制台中创建的 API 密钥。将密钥视为机密:不要放入源代码管理、客户端代码或日志中。开发时从环境变量加载,生产时从平台的密钥管理器加载。

export HIAPI_API_KEY="your-api-key-here"

2. 网络访问与请求工具

使用 curl 时,任何较新的 shell 均可。Python 版本只需 requests 库——无需特定厂商的 SDK,因为该 API 就是基于 JSON 请求体的普通 HTTPS。

pip install requests

3. 模型所需的输入

编写请求前请准备好以下内容:

  • 音频——驱动嘴部动画的语音轨道,以 API 可读取的 URL 或已上传的文件引用形式提供,具体取决于端点接受的输入格式。
  • 人像图片——要驱动动画的人脸。清晰、正面、嘴部可见的照片能产生最可预测的结果。
  • 生成参数——模型暴露的任何分辨率、时长或质量选项。只发送你实际需要的字段;首次测试时默认值通常合理。

每次请求都以 Bearer <your-key> 形式的 Authorization 请求头传递密钥。如果首次调用就收到 401,首先检查此请求头。

使用 curl 发起请求

提交调用是一个带 JSON 请求体的 POST。omnihuman-1.5 的确切路径和字段名列在该模型的 API 参考页上;下面的结构展示了调用形态以及你将填写的字段。

curl -X POST "https://api.hiapi.ai/v1/omnihuman-1.5/generate" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "audio_url": "https://your-bucket.example.com/speech.mp3",
    "image_url": "https://your-bucket.example.com/portrait.jpg"
  }'

逐字段说明

  • Authorization——携带 API 密钥。缺少它,请求在开始任何工作之前即被拒绝。
  • Content-Type: application/json——请求体是 JSON;遗漏此请求头是导致令人困惑的校验错误的常见原因。
  • audio_url——语音轨道。使用可公开读取或以其他方式可被 API 访问的 URL,并优先选择稳定位置,而非在渲染中途过期的签名链接。
  • image_url——要驱动动画的人像。适用相同的可访问性规则。

返回内容

成功提交不会返回视频,而是返回一个任务对象,其中包含标识符(通常是 id 字段)以及 queued 或 processing 等状态。请保存该标识符;它是你后续每次状态检查所用的句柄。

{
  "id": "job_abc123",
  "status": "queued"
}

你可以通过检查 HTTP 状态码和任务标识符是否存在来确认提交成功。如果响应包含校验消息,它们会指向出错的字段——通常是缺失或格式错误的 URL。

用 Python 实现相同请求

curl 调用可直接转换为 Python。下面版本旨在直接放入服务中:密钥来自环境变量,载荷是可动态构建的字典,错误会被抛出而非被吞没。

import os
import requests

API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = "https://api.hiapi.ai/v1"

HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

def submit_omnihuman_job(audio_url: str, image_url: str, **options) -> str:
    payload = {
        "audio_url": audio_url,
        "image_url": image_url,
    }
    payload.update(options)

    response = requests.post(
        f"{BASE_URL}/omnihuman-1.5/generate",
        headers=HEADERS,
        json=payload,
        timeout=60,
    )
    response.raise_for_status()

    data = response.json()
    return data["id"]

为何采用这种写法

  • json=payload 让 requests 序列化请求体并设置内容类型,因此你不会忘记 JSON 请求头。
  • **options 保持函数灵活:添加生成参数时以关键字参数传入,而不必修改载荷字面量。
  • timeout=60 防止挂起的连接无限期阻塞工作进程。提交是快速调用;长时间等待应放在轮询步骤,而非此处。
  • raise_for_status() 将非 2xx 响应转换为可捕获和记录的异常,并可获取响应体用于诊断。

保留返回的任务标识符并传给轮询辅助函数——下一节将构建该辅助函数。

处理异步生命周期:轮询直至完成

视频生成需要时间,因此 API 按设计是异步的。提交后,你反复查询任务状态,直到其达到终止状态。

轮询循环

import time

def wait_for_job(job_id: str, interval: int = 5, max_wait: int = 600) -> dict:
    waited = 0
    while waited < max_wait:
        response = requests.get(
            f"{BASE_URL}/omnihuman-1.5/jobs/{job_id}",
            headers=HEADERS,
            timeout=30,
        )
        response.raise_for_status()
        job = response.json()
        status = job["status"]

        if status == "succeeded" or status == "completed":
            return job
        if status == "failed" or status == "error":
            raise RuntimeError(f"Job {job_id} failed: {job}")

        time.sleep(interval)
        waited += interval

    raise TimeoutError(f"Job {job_id} did not finish within {max_wait}s")

决定何时停止

三种结果会结束循环,你的代码必须处理每一种:

  • 成功——任务报告完成状态并包含输出视频的引用。退出并继续下载。
  • 失败——任务报告错误状态。不要盲目重试;先检查错误消息,因为失败通常由不可读取的输入 URL 或不支持的音频引起。
  • 超时——达到你自己的实际耗时上限。将其视为与失败不同的情况:任务可能在服务端仍会完成,因此需有意决定是继续轮询、延长时间窗口还是放弃。

轮询礼仪

使用适度的间隔而非紧密循环。对于视频渲染,检查之间间隔几秒即可,并能让请求量可预测。若预期长时间排队,请逐步退避,并始终设置上限,使卡住的工作进程不会永远轮询。在生产环境中,优先使用后台工作进程或任务队列,而非在 Web 请求处理器内轮询。

下载并存储输出视频

轮询返回已完成的任务后,响应会携带视频引用——通常是结果对象内的 URL 字段。请获取它并持久化到你自己的存储中,而不要长期依赖该链接。

def download_video(job: dict, destination: str) -> None:
    video_url = job.get("output_url") or job.get("result", {}).get("video_url")
    if not video_url:
        raise ValueError(f"No video reference in job payload: {job}")

    with requests.get(video_url, stream=True, timeout=120) as response:
        response.raise_for_status()
        with open(destination, "wb") as file:
            for chunk in response.iter_content(chunk_size=8192):
                file.write(chunk)

节省调试时间的注意事项

  • 流式下载。视频体积大;分块写入避免将整个文件加载到内存,这在内存受限的工作进程中很重要。
  • 使用确定性文件名。由任务标识符加时间戳派生,使运行可追溯且重跑不会冲突。
  • 先下载到临时文件,再移动。写入临时路径并在完成后重命名,这样部分下载永远不会被误认为完整文件。
  • 将返回的 URL 视为临时。将字节复制到你自己的存储桶或文件系统,并将任务标识符与存储对象一同记录以便日后查找。
  • 声明成功前先验证。在数据库中标记任务完成之前,检查下载文件非空且大小合理。

排查常见的请求与轮询故障

大多数集成问题可归为几类。请按顺序逐一排查。

认证与请求错误

  • 401 Unauthorized——密钥缺失、格式错误或未以 Bearer 令牌发送。仔细检查请求头,包括 Bearer 后的空格。
  • 403——密钥有效但无权访问此端点;确认密钥属于你预期的项目。
  • 422 / 400 校验错误——阅读消息。常见原因包括缺少 audio_url、缺少 image_url、URL 不可读取或不支持的音频格式。
  • 响应中出现意外 HTML——你访问了错误的主机或路径。从 API 参考中核实基础 URL,而非凭记忆重建。

任务失败或永不完成

  • API 无法访问输入 URL——在浏览器中有效的链接可能被私有网络、过期签名或防盗链阻止。先用稳定的公共 URL 测试。
  • 音频与图片不匹配——静音、极长或格式不支持的音轨可能导致渲染停滞或失败。先用短小、标准格式的片段验证流水线。
  • 轮询过于频繁——紧密循环可能触发速率限制并产生看似任务失败的错误。放慢速度并加入退避。
  • 轮询时间过短——对排队任务几秒后就放弃会产生错误的超时。设置切合实际的最大等待时间,并记录停止前看到的状态。
  • 未处理终止状态——如果循环只检查成功,失败会一直空转到超时。请显式地对失败状态分支处理。

最小化验证习惯

出问题时,缩减到最小测试:一个短音频片段、一张小肖像、一次提交,以及几秒后的状态检查。如果这能成功,故障就出在你的载荷、轮询逻辑或输入托管中——而非 API 调用本身。

立即开始构建持久 AI 产物

获取 API 密钥,通过一个基于任务(task)的 API 调用图像、视频和音频模型。

阅读 API 文档