教程
kandinsky-6.0-pro/image-to-video 在 HiAPI 上的教程:curl + Python
在 HiAPI 上发起一次可用的图生视频请求:从首帧上传到异步轮询再到最终下载
HiAPI 上 image-to-video 流程概览
HiAPI 上的每次图生视频调用都遵循相同的三阶段生命周期。提前了解这一点,接下来的教程就会显得按部就班,而不是玄妙莫测。
阶段 1:提交任务
你向 kandinsky-6.0-pro/image-to-video 端点发送一个 POST 请求,附上首帧图像和一段描述你期望运动的简短提示词。该响应不会返回视频字节流,而是接受任务并返回一个任务标识符——后续所有操作都靠这个句柄。
阶段 2:轮询状态
视频生成需要时间,因此该端点是异步的。你用任务标识符调用状态路由,它会报告任务的当前状态:排队中、处理中、成功或失败。你每隔一小段时间重复检查,直到状态进入终态。
阶段 3:获取输出
一旦任务报告成功,状态载荷会携带成片的位置。你下载它并写入磁盘。这就是全部契约。
为什么这种形态很重要
提交与轮询的分离并非偶然。这意味着你的 HTTP 客户端无需在整个渲染期间保持套接字打开,也意味着你可以持久化任务标识符,离开后再恢复。出错时,状态字段会告诉你哪里出错,这比一个不透明的超时有用得多。
记住这三个阶段:提交、轮询、下载。下面的 curl 和 Python 示例只是同一序列的两种写法。
准备首帧和 API 凭据
大多数首次调用之前出问题的地方只有两个:密钥和图像。现在把两者都处理好,教程的其余部分就会顺利。
将 API 密钥设为环境变量
不要把密钥粘贴到源文件中。在 shell 中导出它,让 curl 和 Python 都能获取,而无需硬编码任何内容:
export HIAPI_API_KEY="your_api_key_here"
在 Windows PowerShell 中,等价写法是 $env:HIAPI_API_KEY="your_api_key_here"。继续之前,请确认 shell 能读到它:
echo "$HIAPI_API_KEY" | head -c 4; echo
你应该能看到密钥的前几个字符。如果什么都没看到,说明变量没有持久化到当前 shell 会话——这是后续出现 401 响应的常见原因。
选择有效的首帧图像
首帧决定了整个视频的主体、构图和宽高比。几个实际约束可以避免大多数拒绝:
- 格式:使用标准 Web 图像格式,如 JPEG 或 PNG。避免冷门编解码器、渐进式边缘情况或 CMYK 色彩配置文件。
- 可访问性:API 需要真正读到这些字节。如果你传 URL,确保它可公开访问或已正确鉴权;如果你传内联数据,确保编码完整。
- 尺寸:大小合理的图像验证最快。极大文件会拖慢请求,且经常是上传错误的根源。
- 内容:选择作为运动起点合理的帧——主体清晰、背景简洁。运动提示词描述的是如何远离这一帧,因此杂乱的帧会产生杂乱的视频。
决定如何引用图像
请求体允许你通过 URL 或内联编码数据指向首帧。根据图像所在位置选择:
- 如果文件已托管且可公开读取,URL 是最简洁的选项。
- 如果图像在本地或私有,内联编码能让整个请求自包含,代价是载荷更大。
无论哪种方式,在 curl 和 Python 中保持图像引用使用相同的变量形态,这样两个示例保持直接可比。
用 curl 发起请求
这是一个完整的提交请求。用你自己的图像引用和提示词替换后运行。
curl -sS -X POST "https://api.hiapi.ai/v1/kandinsky-6.0-pro/image-to-video" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "https://your-host.example/first-frame.jpg",
"prompt": "slow dolly-in on the subject, gentle ambient motion, stable camera",
"duration": 5,
"aspect_ratio": "16:9"
}'
各部分的作用
- URL 专门指向
kandinsky-6.0-pro/image-to-video模型。模型选择在路径中,而不是请求体里,因此这里拼错会得到 404,而不是友好的验证消息。 Authorization携带你导出的环境变量中的 bearer 令牌。没有其他东西对调用进行鉴权。Content-Type: application/json对任何带 JSON 体的 POST 都很重要;省略它,某些代理会在载荷到达 API 之前将其弄乱。image是你的首帧引用——这里用 URL 形式,如果图像私有则用内联编码字符串。prompt描述运动。保持它关于运动和镜头行为,而不是复述图像已包含的内容。duration和aspect_ratio塑造输出。宽高比要与首帧匹配;不匹配会裁剪或加黑边。计费按你生成的输出计量,因此这些设置也决定你的费用。
读取响应
成功提交返回任务载荷,而不是视频。从中捕获任务标识符——稍后你会把它传给状态端点:
curl -sS -X POST "https://api.hiapi.ai/v1/kandinsky-6.0-pro/image-to-video" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d @body.json | jq -r '.id'
使用 body.json 文件代替内联 -d,可以让长提示词和编码图像保持可读。jq 是可选的,但它能把响应变成一行,可直接粘贴到下一次调用。
用 Python 调用同一端点
Python 版本是相同的请求、相同的请求体,只是包在函数里以便复用。保持请求体与 curl 版本一致——这正是同时展示两者的意义。
import os
import json
import requests
API_BASE = "https://api.hiapi.ai/v1"
ENDPOINT = f"{API_BASE}/kandinsky-6.0-pro/image-to-video"
def submit_video_job(image_ref: str, prompt: str, duration: int = 5, aspect_ratio: str = "16:9") -> str:
"""Submit a first-frame image-to-video job and return the job id."""
api_key = os.environ.get("HIAPI_API_KEY")
if not api_key:
raise RuntimeError("HIAPI_API_KEY is not set in this environment")
body = {
"image": image_ref,
"prompt": prompt,
"duration": duration,
"aspect_ratio": aspect_ratio,
}
response = requests.post(
ENDPOINT,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json=body,
timeout=60,
)
if response.status_code >= 400:
raise RuntimeError(f"submit failed: {response.status_code} {response.text}")
payload = response.json()
return payload["id"]
if __name__ == "__main__":
job_id = submit_video_job(
image_ref="https://your-host.example/first-frame.jpg",
prompt="slow dolly-in on the subject, gentle ambient motion, stable camera",
)
print(json.dumps({"job_id": job_id}, indent=2))
关于 Python 版本的说明
- 密钥缺失时快速失败。显式的环境检查把最常见的错误变成本地清晰报错,而不是远端 401。
json=body与data=json.dumps(body)。传json=让 requests 为你设置内容类型,从而保持请求头最简。如果改用data=,请显式设置Content-Type。- 宽松的超时。视频提交有时涉及传输大图像载荷。过短的超时会产生虚假失败。
- 出错时读取响应体。同时抛出状态码和响应体,意味着失败消息实际告诉你 API 反对的是什么。
如果首帧是本地文件,读取它、进行 base64 编码,并用该字符串替换 submit_video_job 内的 image_ref。函数签名不变,这正是两种调用风格共享同一请求体定义的原因。
轮询任务并下载视频
已提交的任务不是成片。轮询直到任务到达终态,然后下载。
轮询循环
import time
import requests
def wait_for_video(job_id: str, interval: float = 10.0, max_wait: float = 600.0) -> str:
"""Poll the job until it finishes; return the output URL."""
import os
api_key = os.environ["HIAPI_API_KEY"]
url = f"https://api.hiapi.ai/v1/kandinsky-6.0-pro/image-to-video/{job_id}"
headers = {"Authorization": f"Bearer {api_key}"}
deadline = time.time() + max_wait
while time.time() < deadline:
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
payload = response.json()
status = payload.get("status")
if status == "succeeded":
return payload["output"]
if status == "failed":
raise RuntimeError(f"job failed: {payload.get('error', 'unknown error')}")
time.sleep(interval)
raise TimeoutError(f"job {job_id} did not finish within {max_wait}s")
三个细节让这个循环表现良好:
- 按间隔退避,而不是靠运气。固定休眠约十秒是合理起点;不要在紧循环中猛击端点。
- 处理每个终态。
succeeded给你输出,failed给你错误消息。两者都应视为退出。只检查成功的循环会在死任务上一直转到截止时间。 - 限制等待时间。
max_wait保证脚本结束,而不是在卡住的任务后永远运行。
下载成片
拿到输出 URL 后,流式写入磁盘,而不是把整个文件加载到内存:
def download_video(output_url: str, path: str = "result.mp4") -> str:
with requests.get(output_url, stream=True, timeout=120) as response:
response.raise_for_status()
with open(path, "wb") as handle:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
handle.write(chunk)
return path
把各部分组合起来:
job_id = submit_video_job(
image_ref="https://your-host.example/first-frame.jpg",
prompt="slow dolly-in on the subject, gentle ambient motion, stable camera",
)
output_url = wait_for_video(job_id)
saved = download_video(output_url, "kandinsky_result.mp4")
print(f"saved to {saved}")
在生产环境中,提交成功的那一刻就持久化 job_id。如果进程重启,你可以用存储的标识符重新进入轮询循环,而不必为新的提交付费。
排查常见的请求和任务错误
大多数失败归入三类:密钥、图像或任务状态。在改动其他任何东西之前,先隔离类别。
密钥被拒绝
症状:在创建任何任务之前出现认证或授权错误。
- 确认变量确实在当前 shell 中导出:
echo "$HIAPI_API_KEY"。 - 检查是否随密钥粘贴了空白或换行;去除它们。
- 验证请求头拼写为
Authorization,值以Bearer开头并跟一个空格。 - 如果 curl 能用但 Python 不能,从同一解释器打印
os.environ.get("HIAPI_API_KEY")——变量可能存在于你的 shell 中,但不在脚本继承的环境中。
图像被拒绝
症状:提交时出现验证错误,或任务立即失败。
- 格式错误或冷门。重新导出为标准 JPEG 或 PNG 并重试。CMYK 配置文件和奇异编解码器是常见元凶。
- URL 不可达。在隐私浏览器窗口中打开你的图像 URL;如果你拿不到字节,API 也拿不到。
- 内联编码损坏。如果你在本地进行 base64 编码,先解码一次字符串并确认它是有效图像,然后再发送。
- 宽高比不匹配。如果视频回来被裁剪或加黑边,让
aspect_ratio与首帧对齐,而不是事后与输出较劲。
任务不结束或失败
症状:状态始终停留在 queued/processing,或报告 failed。
- 读取 error 字段。失败任务携带消息。它通常指出出错的字段或条件。不要猜——记录整个状态载荷。
- 尊重异步设计。等待几秒再检查是正常的。两秒后就放弃的紧重试循环不是状态机制。
- 限制等待时间。如果任务超过合理上限,视为失败,暴露任务 id,并提交新任务,而不是永远轮询。
- 与已知良好的调用对比。如果自定义图像失败,用本教程前面的精确 curl 加一张小公开图像和短提示词运行。如果成功,问题在你的输入;如果也失败,问题在凭据或请求形态。
成功后的下载错误
如果任务报告 succeeded 但下载失败,输出 URL 可能已过期或受访问控制。重新轮询任务以获取新的输出位置,而不是重试过期的那个。
按顺序处理这些类别——密钥、然后图像、然后任务状态——每个错误都会快速缩小到一个原因。这就是整个循环:提交首帧、轮询至完成、下载结果。