即梦 Seedance 2.0、可灵 Kling、通义万相 Wan 2.5 这类视频生成模型,官方接口各有各的参数命名和鉴权方式,但调用形态高度一致:都是提交任务 → 拿 task_id → 轮询结果的异步模型。本文给出三家 API 的接入步骤、参数对照表与一份可运行的统一调用封装,并说明如何用 OpenAI 兼容端点把它们收敛到一处管理。视频生成类接口的接入与计费可参考 🚀 某中转平台 API 聚合模型站。
一、为什么视频生成 API 值得单独讲
文本模型是「请求即返回」,视频模型不是。一条 5 秒 1080p 视频的生成耗时通常在几十秒到几分钟,任何一次同步调用都会把 HTTP 连接拖死。这带来三个和文本 API 完全不同的工程问题:
- 必须异步:所有主流视频生成 API 都采用「创建任务 + 轮询/回调」,没有例外。
- 参数维度多:文本模型只有 prompt 和 max_tokens,视频模型多了分辨率、宽高比、时长、帧率、运动强度、首尾帧、镜头控制等一整组参数。
- 失败率高且贵:视频任务排队、超时、额度不足是常态,而一次失败的成本远高于一次文本请求,重试策略必须单独设计。
也正因为参数杂、各家命名不统一,把多家视频模型收敛到一个兼容端点的收益比文本模型更大——你只需要维护一套轮询逻辑和一套错误处理。
二、三家主流视频 API 的参数对照
| 能力 | 即梦Seedance 2.0 | 可灵 Kling | 通义万相 Wan 2.5 |
|---|---|---|---|
| 文生视频 | 支持 | 支持 | 支持 |
| 图生视频 | 支持(首帧) | 支持(首帧 + 尾帧) | 支持 |
| 分辨率 | 480p / 720p / 1080p | 720p / 1080p | 480p / 720p / 1080p |
| 时长 | 5s / 10s | 5s / 10s | 5s(部分档位 10s) |
| 宽高比 | 16:9 / 9:16 / 1:1 | 16:9 / 9:16 / 1:1 | 16:9 / 9:16 / 1:1 |
| 运动控制 | 镜头运动参数 | Motion Control(轨迹/运镜) | 基础运镜 |
| 返回形态 | task_id 轮询 | task_id 轮询 | task_id 轮询 |
| 鉴权 | API Key | API Key(部分需签名) | API Key |
各家参数命名与可选值迭代很快,以上为接入形态层面的对照,具体字段名请以官方最新文档为准。
可以看到三家的差异主要在能力上限(谁能控运镜、谁能给首尾帧),而不是调用范式。这就给统一封装留出了空间。
三、异步任务模型的标准流程
无论哪家,流程都是这四步:
1. POST /v1/video/generations → 提交任务,立即返回 task_id
2. GET /v1/video/tasks/{id} → 查询状态:queued / running / succeeded / failed
3. 轮询直到终态( succeeded 或 failed )
4. 从响应里取 video_url,下载并转存( URL 通常有有效期 )
关键点:第 1 步返回不等于生成完成。很多初接的人在这里踩坑,拿到 task_id 就去取 URL,结果拿到空值。
四、统一调用封装(Python)
下面这段代码把「提交 → 轮询 → 取结果」封装成一个函数,用 OpenAI 兼容端点做统一入口,切换模型只改 model 参数。
import os, time, requests
BASE_URL = os.getenv("VIDEO_BASE_URL", "https://easy88ai.com/v1")
API_KEY = os.getenv("VIDEO_API_KEY")
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def create_video_task(prompt: str, model: str, **kwargs) -> str:
"""提交视频生成任务,返回 task_id"""
payload = {
"model": model,
"prompt": prompt,
"resolution": kwargs.get("resolution", "1080p"),
"aspect_ratio": kwargs.get("aspect_ratio", "16:9"),
"duration": kwargs.get("duration", 5),
}
# 各家扩展参数(首尾帧、运镜等)按需透传
payload.update(kwargs.get("extra", {}))
r = requests.post(f"{BASE_URL}/video/generations",
headers=HEADERS, json=payload, timeout=30)
r.raise_for_status()
return r.json()["task_id"]
def wait_for_video(task_id: str,
interval: int = 10,
timeout: int = 900) -> dict:
"""轮询任务直到终态,返回完整结果"""
deadline = time.time() + timeout
while time.time() < deadline:
r = requests.get(f"{BASE_URL}/video/tasks/{task_id}",
headers=HEADERS, timeout=30)
r.raise_for_status()
data = r.json()
status = data.get("status")
if status == "succeeded":
return data
if status == "failed":
raise RuntimeError(f"生成失败:{data.get('error')}")
time.sleep(interval)
raise TimeoutError(f"任务 {task_id} 超过 {timeout}s 未出结果")
if __name__ == "__main__":
tid = create_video_task(
prompt="雨后的青石板路,镜头缓慢推进,水洼倒映着暖黄路灯",
model="seedance-2.0", # 换成 kling / wan-2.5 即可切换模型
duration=5,
resolution="1080p",
)
print("task_id:", tid)
result = wait_for_video(tid)
print("video_url:", result["video_url"])
三个细节值得注意:
wait_for_video里用固定间隔 + 总超时而不是无限轮询。固定间隔实现简单,但如果想更优雅,可以改成指数退避(首次 5s,逐步加到 30s 封顶),减少无效请求。video_url一般有有效期(常见 24 小时)。拿到后要立刻转存到自己的对象存储,别直接把厂商 URL 存进业务数据库。extra字段用来透传各家独有参数(比如可灵的轨迹控制、即梦的镜头运动),这样主逻辑不用为每家写分支。
五、踩坑清单
1. 轮询太密被限流 视频任务本来就要跑几十秒,5 秒一次轮询纯属浪费。建议起步 10 秒,超过 60 秒还没完成再放慢到 20–30 秒。
2. 忽略了排队态
queued 和 running 是两回事。高峰期 queued 可能持续几分钟,如果你的超时设成 60 秒,会误判成失败。
3. 重试导致重复扣费
提交任务这一步如果超时,你并不知道服务端到底建没建任务。不要用同一个请求体盲目重试,应该先用 prompt + 时间窗口 去查一次任务列表确认,否则容易生成两条一样的视频、扣两次钱。
4. 参数组合不被支持
不是所有 分辨率 × 时长 × 宽高比 组合都合法,比如某些档位只支持 5 秒。非法组合有的厂商在提交时就报 400,有的要到任务执行阶段才失败。接入前建议把常用组合跑一遍摸底。
5. 中文 prompt 的编码 部分厂商对 prompt 长度和字符有校验,长中文描述先做截断和清洗,避免提交时直接 400。
六、统一端点带来的实际收益
把多家视频模型收敛到一个兼容地址后,业务侧的变化是:
- 一套轮询逻辑:不用为每个厂商写一套状态机。
- 一个 Key 管多家:密钥轮换、额度监控集中在一处。
- 失败可降级:某家排队过长或失败时,换个
model参数就能切到备用模型,业务代码零改动。 - 成本可横向对比:同样的调用量,不同模型的实际消耗能放在一起看,方便选型。
对刚起步的团队,建议先用兼容端点把流程跑通,等业务量上来、对某个模型形成强依赖后,再考虑是否直连官方。
七、小结
视频生成 API 的接入难点不在「怎么发请求」,而在异步状态管理、失败重试和参数兼容这三件事上。先把提交-轮询-转存的骨架搭稳,再把各家差异收敛到 extra 透传字段里,后面加新模型就只是加一个模型名而已。
本文代码示例基于 OpenAI 兼容调用形态编写,不同服务商在字段命名上会有差异,请以实际接入文档的「模型名与参数」章节为准。
