教程
HappyHorse 1.1 参考生视频 API:可直接运行的 curl + Python 教程
HappyHorse 1.1 R2V 可将最多九张参考图加上一段文本提示词转化为一段短视频,并在多个镜头之间保持主体、服装和场景风格一致。在 HiAPI 上,该模型通过统一的异步任务端点提供服务,对应的模型 ID 为 happyhorse-1.1/reference-to-video。本教程先演示能跑通的最小请求,再展示带回调的生产级版本。
目标与前置条件
你将完成以下操作:
- 将 1-9 张参考图上传(或已托管)到 API 可访问的 HTTPS URL。
- 向
https://api.hiapi.ai/v1/tasksPOST 一个引用这些 URL 的任务。 - 当任务达到
success状态后,从data.output[0].url中读取最终的 MP4 地址。
前置条件:
- 一个 HiAPI 账号,以及以
sk-开头的 API Key。你可以从 HiAPI 控制台 获取,并阅读 认证 文档了解请求头规则。密钥需以Authorization: Bearer sk-...形式传递,绝不可放在客户端代码中。 - 图片托管要求:每张参考图须为 JPEG、PNG 或 WEBP 格式,最短边 ≥ 400px,≤ 20MB。公共 CDN URL 可用;签名 S3 URL 也可用,只要任务执行器能够访问。
最小可用请求(curl)
这是 schema 所能接受的最小请求体。它使用默认的 1080p / 16:9 / 5 秒输出,因此你只需设置 prompt 和 reference_image 两个字段。
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1/reference-to-video",
"input": {
"prompt": "[Image 1] 中的角色行走在 [Image 2] 风格的场景中,电影感镜头",
"reference_image": [
"https://static.hiapi.ai/example/ref-1.jpg",
"https://static.hiapi.ai/example/ref-2.jpg"
]
}
}'
响应是任务信封;你实际需要的唯一字段是 data.taskId:
{
"code": 200,
"data": { "taskId": "task_xxx", "status": "queued" }
}
轮询直到终态:
curl https://api.hiapi.ai/v1/tasks/task_xxx \
-H "Authorization: Bearer sk-YOUR_KEY"
当 data.status 变为 success 时,视频 URL 位于 data.output[0].url。该 URL 带有 expireAt 时间戳,因此请立即下载文件,或 将其转存至持久存储——不要在你的网站上直接热链该地址。
参数
R2V 端点会严格校验输入;未知字段会返回 400 INVALID_REQUEST。完整 schema(已针对 POST /v1/tasks 实时验证):
| 字段 | 类型 | 必填 | 可选值 | 默认值 |
|---|---|---|---|---|
model |
string | 是 | happyhorse-1.1/reference-to-video(固定值) |
- |
input.prompt |
string | 是 | 用 [Image 1]、[Image 2]…… 引用参考图,顺序与 reference_image 一致 |
- |
input.reference_image |
string[] | 是 | 1-9 个 HTTPS URL,JPEG/PNG/WEBP 格式,最短边 ≥ 400px,每张 ≤ 20MB | - |
input.resolution |
enum | 否 | 720p、1080p |
1080p |
input.aspect_ratio |
enum | 否 | 16:9、9:16、3:4、4:3、4:5、5:4、1:1、9:21、21:9 |
16:9 |
input.duration |
integer | 否 | 3-15(秒) | 5 |
callback.url |
string | 否 | 接收任务终态事件的 HTTPS URL | - |
callback.when |
enum | 否 | final(目前唯一值) |
final |
以下几项不太直观的校验规则,在它们给你带来麻烦之前值得了解:
- 即使只有一张参考图 URL,也要将
reference_image作为数组传入。直接传字符串会返回reference_image: got string, want array。 duration必须是 3 到 15(含)之间的整数。999和1都会返回明确的边界错误信息。- 顶层不存在
seed、negative_prompt、fps、cfg_scale或steps。API 会拒绝未知属性。
按分辨率和时长组合的完整价格,请参阅 HiAPI 定价页面——费用随 resolution 和 duration 两个参数同时变化。
Python:端到端 + 轮询
下面的代码块是一个自包含的生成器,提交任务、轮询至终态,并下载视频。它刻意做到零依赖(仅使用 requests),方便你复制到任何服务中。更完整的请求生命周期说明请参阅 快速入门。
import os
import time
import requests
API = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}
def submit_r2v(prompt: str, reference_images: list[str], duration: int = 5,
resolution: str = "1080p", aspect_ratio: str = "16:9") -> str:
if not 1 <= len(reference_images) <= 9:
raise ValueError("reference_image must contain 1-9 URLs")
payload = {
"model": "happyhorse-1.1/reference-to-video",
"input": {
"prompt": prompt,
"reference_image": reference_images,
"resolution": resolution,
"aspect_ratio": aspect_ratio,
"duration": duration,
},
}
r = requests.post(API, json=payload, headers=HEADERS, timeout=60)
r.raise_for_status()
data = r.json()
if data.get("code") != 200:
raise RuntimeError(f"create failed: {data}")
return data["data"]["taskId"]
def wait_for_video(task_id: str, timeout_s: int = 600, poll_s: int = 5) -> str:
deadline = time.time() + timeout_s
while time.time() < deadline:
r = requests.get(f"{API}/{task_id}", headers=HEADERS, timeout=30)
r.raise_for_status()
task = r.json()["data"]
status = task["status"]
if status == "success":
return task["output"][0]["url"]
if status == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
time.sleep(poll_s)
raise TimeoutError(f"task {task_id} did not finish in {timeout_s}s")
def download(url: str, path: str) -> None:
with requests.get(url, stream=True, timeout=300) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(1 << 16):
f.write(chunk)
if __name__ == "__main__":
task_id = submit_r2v(
prompt="保持 [Image 1] 中的主体设计,行走在 [Image 2] 的氛围中",
reference_images=[
"https://static.hiapi.ai/example/char.jpg",
"https://static.hiapi.ai/example/mood.jpg",
],
duration=5,
resolution="720p",
aspect_ratio="9:16",
)
print("submitted", task_id)
video_url = wait_for_video(task_id)
print("ready", video_url)
download(video_url, "out.mp4")
这段代码有意做了以下几件事:
- 从
HIAPI_API_KEY环境变量读取密钥,使其永远不会进入 git 或容器日志。 - 使用
r.raise_for_status()在解析 JSON 之前捕获传输错误。 - 只将
success和fail视为终态——其他所有状态(queued、processing等)都会让循环继续。 - 立即下载输出 URL。输出 URL 是签名地址且会过期;如果你稍后需要该文件,请 将其转存至持久存储 或复制到你自己的存储桶中。
生产环境:从轮询切换为 webhook
每 5 秒轮询一次对于笔记本或低流量 worker 而言没问题。对于真实服务,请在创建任务时注册回调,让 HiAPI 将终态事件推送到你的端点——这样你的 worker 可以保持空闲,也无需再为空 GET 请求付费。完整的请求 schema 请参阅 创建任务,兜底轮询请参阅 获取任务详情。
payload = {
"model": "happyhorse-1.1/reference-to-video",
"input": {
"prompt": "[Image 1] 中的角色走过 [Image 2] 中的城市街道",
"reference_image": [
"https://cdn.example.com/char.jpg",
"https://cdn.example.com/street.jpg",
],
"resolution": "1080p",
"aspect_ratio": "16:9",
"duration": 8,
},
"callback": {
"url": "https://your-domain.com/hiapi/callback",
"when": "final",
},
}
接收端点需要接入两件事:
- 按
taskId做幂等处理。success和fail都是终态,HiAPI 的重试(或你自己的重试层产生的重复)会重新投递相同的taskId。在触发任何副作用(S3 复制、Slack 消息、计费事件)之前先去重。 - 兜底核对轮询。 如果你的端点宕机,回调就丢失了。请保留一个轻量的核对任务,对所有处于
processing状态超过最坏生成时间的任务调用GET /v1/tasks/<id>。
实际会遇到的错误
以下这些值得显式处理;其余都是暂时性的 5xx,应由 SDK 或你的 HTTP 客户端以退避方式重试。
401 permission_denied——Authorization头缺失、格式错误或密钥已被吊销。请从控制台重新签发密钥,而不是修补请求头。400 INVALID_REQUEST并附带字段路径——错误信息会准确告诉你哪里失败,例如reference_image: maxItems: got 10, want 9或aspect_ratio: value must be one of ...。请在 worker 提交前校验输入,以便在本地快速失败。task failed(终态)——轮询/回调返回status: "fail",并附带error.code和error.message。常见原因:worker 无法获取的参考图 URL、触发了 NSFW 审核、或图片最短边低于 400px 阈值。
常见问题
可以引用超过 9 张图片吗? 不可以。schema 将 reference_image 限制为最多 9 张。如果你需要更多变化,请在平台外合成一张更高密度的参考图(网格图或分镜图),并作为单张输入使用。
提示词中必须使用 [Image N] 标记吗? 这些标记是你指向特定参考图的方式。如果提示词中从不指明图片,也是合法的,但模型会自由混合所有参考图——对于多镜头一致性场景,通常不是你想要的效果。
为什么收到 503 nsfw_moderation_unavailable? 这是审核服务导致的平台暂时性错误,与你的请求无关。请在短暂退避后重试。
应该用 720p 还是 1080p? 1080p 是默认值且画质更好,但费用更高、渲染更慢。对于播放器本来就会降采样的竖版社交媒体切片,720p 配合 aspect_ratio: 9:16 通常是更合适的权衡。
可以传入 seed 来使输出可复现吗? 目前不可以。API 会将 seed 视为未知属性并拒绝。如果你需要可复现性,请精确固定 prompt、reference_image、aspect_ratio、resolution 和 duration,并接受一定的运行间差异。
输出 URL 存放在哪里,有效期多久? 它们是带短 TTL 的签名 URL;响应中包含 expireAt。任务成功后请立即下载或 将其转存至持久存储。