教程
如何通过 HiAPI API 使用 omnihuman-1.5:curl 和 Python 教程
音频转视频数字人生成的完整请求演练、异步轮询及输出下载
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 调用本身。