HiAPI
博客

教程

如何在 hiapi API 上调用 happyhorse-1.1/image-to-video:curl 与 Python 指南

2026年11月3日
hiapi.ai 博客封面图:错位堆叠的骨白纸页与一片锈红内页,扁平几何风格。

该端点做什么,以及何时使用

happyhorse-1.1/image-to-video 将单张静态图像转换为短视频。你向该端点发送一张首帧图像,模型便从该帧开始向前生成动画。其实际意义在于连续性:起始图像中的主体、构图和外观会延续到生成的片段中。这就是我们这里所说的身份保留——模型并非仅凭文本提示凭空创造新场景,而是在延续一个你已经用图像创作好的场景。

因此,当视觉身份比即兴创作更重要时,该端点非常合适:

  • 你已经有了参考静帧——人像、产品照、角色设定图——并希望生成忠于该静帧的运动。
  • 你在一个流水线中制作短片,而其中所有其他素材都是先以图像形式设计的。
  • 你需要为故事板帧制作动态分镜或运动预览。

当你希望模型从零开始自由重新诠释场景,或者没有静帧来锚定输出时,它就不太适合。在这些情况下,图像优先的工作流通常意味着先生成锚定图像,然后将该结果交给此端点。

调用本身是异步的:你提交一个任务,然后轮询直到视频就绪。本指南的其余部分将端到端走完这一生命周期。

发送请求之前

首次调用前需要准备好两件事。

API 密钥

所有调用都通过 hiapi 平台进行,API 密钥作为 bearer 凭据附加。请将密钥保存在环境变量中,而非源代码管理中,这样同一份代码可以在本地开发和 CI 中通用:

export HIAPI_API_KEY="your_key_here"

在 Python 中,运行时用 os.environ["HIAPI_API_KEY"] 读取它。切勿记录原始 header 值。

可引用的首帧图像

该端点需要一张起始图像。请提前决定如何传递它——是作为平台可抓取的 URL,还是作为上传/经 base64 编码的素材,取决于你的账户和端点文档支持的方式。重要的是,该引用在提交时是稳定且可访问的。如果任务可能排队,那么五分钟后就过期的签名 URL 是个糟糕的选择。

在调用前了解你预期的输出宽高比和时长也有帮助,因为首帧通常会约束它们。提前做好这个决定可以避免之后重新提交。

密钥导出且图像引用确定后,你就可以提交了。

构建首帧输入

首帧是模型在其后生成的一切的锚点。实际上,你应当把它视为身份契约:那张图像中清晰可见的任何内容——面部、服装、标志、色彩方案、相机角度——都是模型必须依据的。如果静帧中的某个细节模糊或被遮挡,输出也会模糊。

无论具体模型参数如何,以下几条准备原则都适用:

  • 选择清晰、光线良好的帧。 压缩伪影和严重噪点会减少模型可保留的内容。一张锐利、光照均匀的图像比一张戏剧化但模糊的图像是更好的锚点。
  • 将主体放在你希望运动开始的位置。 在静帧中裁剪和构图比指望模型重新取景更划算。
  • 保持你预期输出的宽高比。 如果最终视频是横向的,请准备横向锚定图;之后填充不会恢复丢失的构图。
  • 避免矛盾的线索。 一张读起来是一种意思、却隐含暗示画面外动作的静帧会给模型不稳定的起点。

不要假设端点文档之外的模型专属参数。最稳妥的方法让图像本身承载意图,然后让请求字段只处理它们明确覆盖的内容。

用 curl 发一个可用请求

提交任务只需一次 POST。结构是:向端点发送经过身份验证的请求,JSON 请求体携带模型标识符和图像输入。

curl -sS -X POST "https://api.hiapi.ai/v1/video/generations" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/image-to-video",
    "input": {
      "image": "https://your-bucket.example.com/first-frame.jpg"
    }
  }'

逐步说明:

  1. 端点。 POST 目标是 hiapi API 上的视频生成路由。如果你要针对多个环境编写脚本,请把它放在变量中。
  2. 认证头。 Authorization: Bearer $HIAPI_API_KEY——shell 会展开你之前导出的环境变量。
  3. 内容类型。 application/json,因为请求体是 JSON。
  4. 请求体。 model 选择 happyhorse-1.1/image-to-video;input.image 是你的首帧引用。请查阅端点参考文档,了解你账户下接受的准确图像字段名和结构。

成功的响应并不是视频本身——而是一个任务句柄。预期会得到一个标识符和一个状态字段,通常类似 queued 或 processing。记下这个 ID;你将在后续步骤中用它轮询。先运行一次命令并打印响应,这样在开始写脚本前就能看到它的结构。

用 Python 发送同一请求

Python 版本是直接移植,不是新的集成。同样的路由、同样的请求头、同样的 JSON 请求体。

import os
import requests

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

payload = {
    "model": "happyhorse-1.1/image-to-video",
    "input": {
        "image": "https://your-bucket.example.com/first-frame.jpg"
    },
}

resp = requests.post(
    URL,
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json=payload,
    timeout=60,
)
resp.raise_for_status()
job = resp.json()
print(job)

映射说明:

  • 使用 json=payload 的 requests 会像 curl 的 -d 字符串那样序列化字典——如果你使用 json=,就不要再手动设置 Content-Type;库会处理它。上面显式的请求头无害但多余。
  • raise_for_status() 会在你开始轮询一个并不存在的任务之前,尽早将 HTTP 层失败转为异常。
  • timeout 只是提交调用上的客户端保护。生成在服务端运行,不受其约束。

将 payload 保存在变量中并复用。以后更改模型或图像时,你只应编辑一个字典,而不是两个平行的实现。

处理异步任务与轮询

图生视频生成比请求/响应周期耗时更长。因此 API 会立即返回一个任务引用,并期望你稍后回查。生命周期是:提交、收到 ID、轮询状态、获取结果。

从提交响应中提取任务 ID——字段名取决于该端点文档化的响应模式,所以请读一次,并把键查找硬编码,而非值:

job_id = job["id"]  # check the endpoint docs for the exact field

然后按间隔轮询:

import time

poll_url = f"https://api.hiapi.ai/v1/video/generations/{job_id}"

while True:
    status = requests.get(
        poll_url,
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30,
    ).json()

    if status["status"] == "succeeded":
        break
    if status["status"] == "failed":
        raise RuntimeError(status)
    time.sleep(5)

保持轮询合理的规则:

  • 按固定间隔轮询,而不是紧凑循环。 对这种规模的任务,检查之间隔几秒就足够了;猛打端点会增加负载,却不会让结果更早产生。
  • 限制总等待时间。 没有上限的 while True 是迟早会发生的挂起。跟踪已用时间,并在合理预算后大声失败。
  • 处理两种终态。 成功和失败都是答案。将未知状态字符串视为非终态,但记录它们,以便你注意到模式变化。
  • 不要为一个任务从多个并行 worker 轮询。 每个任务一个轮询器,或通过队列协调。

一旦状态为终态,就继续下载结果。

下载并存储输出

完成的视频会在已完成的任务响应中作为素材暴露——通常是一个 URL,有时带过期时间。成功轮询后的第一步是抓取该引用,并将字节复制到你控制的存储中。

video_url = status["output"]["video_url"]  # inspect the real response shape

with requests.get(video_url, stream=True, timeout=120) as r:
    r.raise_for_status()
    with open(f"out/{job_id}.mp4", "wb") as f:
        for chunk in r.iter_content(chunk_size=1 << 20):
            f.write(chunk)

适用于任何流水线的存储指导:

  • 对持久产物要复制,而不是链接。 签名或限时 URL 是传输方式,不是存储。持久化文件,然后在数据库中把你自己对象键与任务 ID 一起存储。
  • 流式处理大文件。 iter_content 保持内存平稳;将整个视频加载到变量中会让内存上限与素材大小挂钩。
  • 记录来源。 将 job_id、输入图像引用和模型标识符与素材一起保存。当你需要复现片段时,该记录就是配方。
  • 写入前检查 HTTP 状态。 错误响应导致的部分写入比干净的失败更糟,因为它看起来像有效文件。

文件落到磁盘或对象存储后,流水线就闭环了,你可以继续处理下一帧。

需要留意的错误与陷阱

该端点的大多数失败都是可预测的。简短清单如下:

  • 首帧输入格式错误或不可达。 提交被拒最常见的原因。检查图像 URL 是否能从公网解析、格式是否为端点所接受,以及 payload 是否正确嵌套了图像字段。用 curl 调用复现,以区分认证问题和 payload 问题。
  • 在任务存在之前轮询。 从错误字段读取 ID,或轮询与提交响应不匹配的路由,会产生看起来像任务失败的 404。打印一次提交响应,并据此固定键查找。
  • 无界等待。 没有超时意味着卡住的任务会无限期挂起 worker。设置预算、失败并告警。
  • 丢失素材链接。 生成成功,但 URL 被消费一次后再未复制。在抓取时保存字节并保留你自己的键;不要依赖返回的链接作为长期存储。
  • 认证泄露。 将密钥粘贴到 notebook 中并意外提交是反复出现的问题。从环境加载,有疑虑就轮换。
  • 假设端点未文档化的参数。 如果你需要某个未列出的参数,那它就不存在。只通过首帧图像和已文档化的字段来表达意图。

处理好这些,异步流程就会变得平淡无奇——这正是你在生产流水线中想要的。关于端点特定的字段名和可接受值,请在构建时保持端点参考文档打开。

立即开始构建持久 AI 产物

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

阅读 API 文档