视频生成
OpenAI 兼容 Videos API,异步任务式:提交任务 → 轮询状态 → 下载成片。覆盖文生视频、图生视频、参考生视频、首尾帧生视频四种模式。
视频生成是异步任务式的:提交后立即拿到任务 id,成片需要轮询到完成再下载。三个端点语义与 OpenAI 官方一致,模型库里所有视频模型共用同一套调用方式。
| 用途 | 端点 |
|---|---|
| 提交生成任务 | POST /v1/videos |
| 查询任务状态 | GET /v1/videos/{video_id} |
| 下载生成结果 | GET /v1/videos/{video_id}/content |
查询与下载不需要再传模型名:任务 id 里已经带了该任务归属的模型信息,一套代码可以对接所有视频模型。
完整流程
下面这段是所有模式共用的骨架 —— 换模式只需要改 create() 的入参,轮询与下载完全不变。
import time
from openai import OpenAI
client = OpenAI(
base_url="https://api.goodputai.cn/v1",
api_key="你的 GoodPut API Key",
)
def generate(**kwargs) -> str:
"""提交 → 轮询 → 下载,返回落盘路径。所有模式共用。"""
video = client.videos.create(**kwargs)
print(video.id, video.status) # video_xxx queued
deadline = time.time() + 15 * 60 # 整体超时,别无限轮询
while video.status in ("queued", "in_progress"):
if time.time() > deadline:
raise TimeoutError(f"超时未完成:{video.id}")
time.sleep(5)
video = client.videos.retrieve(video.id)
if video.status != "completed":
raise RuntimeError(f"生成失败:{video.error}")
path = f"{video.id[:16]}.mp4"
client.videos.download_content(video.id).write_to_file(path)
print("usage:", video.usage) # 计费依据
return path
# 最简:文生视频
generate(
model="wan/wan3.0-video",
prompt="海边日出,海浪拍打礁石,电影感运镜",
size="1280x720",
seconds="5",
)# 1) 提交任务 —— 返回体里的 id 就是后续查询用的任务 id
curl https://api.goodputai.cn/v1/videos \
-H "Authorization: Bearer $GOODPUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "wan/wan3.0-video",
"prompt": "海边日出,海浪拍打礁石",
"size": "1280x720",
"seconds": "5"
}'
# 2) 轮询状态,直到 status 变成 completed 或 failed
curl https://api.goodputai.cn/v1/videos/{video_id} \
-H "Authorization: Bearer $GOODPUT_API_KEY"
# 3) 下载成片(二进制流,直接落盘)
curl https://api.goodputai.cn/v1/videos/{video_id}/content \
-H "Authorization: Bearer $GOODPUT_API_KEY" \
-o output.mp4四种生成模式
模式由「你给了什么输入」决定,不需要额外的模式参数。
| 模式 | 怎么触发 | 典型用途 |
|---|---|---|
| 文生视频 | 只给 prompt | 从零生成 |
| 图生视频 | input_reference 或 media.first_frame | 让一张图动起来 |
| 参考生视频 | media.reference_image / reference_video / reference_audio | 保持主体、风格或音色 |
| 首尾帧生视频 | media.first_frame + media.last_frame | 指定起止画面,中间由模型补 |
用官方 OpenAI SDK 传 media 要走 extra_body。
media 不在 OpenAI 官方 videos.create() 的参数签名里(它只有 prompt /
input_reference / model / seconds / size),直接写 media=[...] 会被 SDK
以 unexpected keyword argument 拒掉。SDK 的 extra_body 会把字段合并进请求体,
所以下面的写法是等价且可用的:
extra_body={"media": [{"type": "reference_image", "url": "..."}]}用 curl / requests 等直接发 JSON 时,media 就是普通的顶层字段,不需要包一层。
不同模型走不同的路子,这一点最容易踩坑:
- wan 系列按模式拆成不同模型 ——
wan2.7-t2v只做文生、wan2.7-i2v只做图生、wan2.7-r2v只做参考生。选错模型会400。wan3.0-video是合一的,四种都支持。 minimax/h3是一个模型做四种 —— 按你给的输入自动选,不用换模型名。minimax/h3-fast与minimax/h3调用方式相同,差别见下文「参数」表:只开1344x768/768x1344两种画布、seconds取 5~15,并支持seed。出片明显更快(5 秒片约 18 秒),适合要快速迭代的场景。
具体某个模型支持哪些模式,以模型库的详情页为准。
文生视频
generate(
model="wan/wan3.0-video", # 或 wan/wan2.7-t2v、wan/wan2.6-t2v
prompt="霓虹街道,雨夜,第一人称视角向前行走",
size="720x1280", # 短边 720 → 720P 档;9:16 → 竖屏
seconds="5",
prompt_extend=False, # ⚠️ 默认 true 会改写你的提示词
)generate(
model="minimax/h3",
prompt="霓虹街道,雨夜,第一人称视角向前行走。无字幕,无文字。",
size="768x1344", # 9:16 竖屏,见下面的六种画布
seconds=6, # 4~15 的整数
)图生视频
generate(
model="wan/wan2.7-i2v", # 或 wan/wan3.0-video
prompt="镜头缓缓拉远",
# 链接 / base64 要放进 extra_body(原因见下方说明)
extra_body={"input_reference": "https://example.com/first.jpg"},
size="1280x720", # 决定档位与单价;⚠️ 实际画幅跟随这张图
seconds="5",
)wan 的 input_reference 只接受公网 URL 或 data:image/...;base64,... 字符串,不接受文件上传。
官方 OpenAI SDK 的 input_reference= 参数只收文件对象,直接传字符串会在本地报错。
所以链接或 base64 字符串要像上面这样放进 extra_body;用 curl / requests 直接发 JSON 时,
input_reference 就是普通的顶层字段。
# 形式一:公网 URL 或 data: 内联 —— 放进 extra_body
generate(
model="minimax/h3",
prompt="镜头缓缓拉远,人物自然转身",
extra_body={"input_reference": "https://example.com/first.jpg"},
size="768x768",
seconds=4,
)
# 形式二:直接上传本地文件(h3 支持,wan 不支持)
with open("first.jpg", "rb") as f:
generate(
model="minimax/h3",
prompt="镜头缓缓拉远,人物自然转身",
input_reference=f,
size="768x768",
seconds=4,
)参考生视频
用参考资产控制主体、风格或音色,而不是把它当作第一帧。
generate(
model="wan/wan2.7-r2v", # 或 wan/wan3.0-video
prompt="同一个角色在雪地里行走",
extra_body={
"media": [
{"type": "reference_image", "url": "https://example.com/character.jpg"},
]
},
size="1280x720",
seconds="5",
)wan2.7-r2v 没有 input_reference 参数,参考资产只能走 media。
# h3 可以一次给多张参考图
generate(
model="minimax/h3",
prompt="这个房间里,一名女子从左侧走入并掀开箱笼",
extra_body={
"media": [
{"type": "reference_image", "url": "https://example.com/room.jpg"},
{"type": "reference_image", "url": "https://example.com/person.jpg"},
]
},
size="1344x768",
seconds=4,
)多张参考图之间没有具名绑定。 你可以塞进多张,但没有任何机制告诉模型"这张是某某角色" —— 跨镜头的一致性只能靠模型自己从参考图里保持,不是参数能保证的契约。
首尾帧生视频
generate(
model="wan/wan3.0-video",
prompt="从白天过渡到夜晚",
extra_body={
"media": [
{"type": "first_frame", "url": "https://example.com/day.jpg"},
{"type": "last_frame", "url": "https://example.com/night.jpg"},
]
},
size="1280x720",
seconds="5",
)generate(
model="minimax/h3",
prompt="人物动作自然连贯地从起始姿态过渡到结束姿态",
extra_body={
"media": [
{"type": "first_frame", "url": "https://example.com/start.jpg"},
{"type": "last_frame", "url": "https://example.com/end.jpg"},
]
},
size="1344x768",
seconds=4,
)h3 的首尾帧不能和参考资产混用。 first_frame / last_frame 与 reference_* 走的是模型内部两条不同的生成路径,混着传会 400。要么给首尾帧,要么给参考资产。
首尾帧最多各一个;两者在数组里的先后顺序不影响结果,网关会自动排好。
任务状态
GET /v1/videos/{video_id} 返回的 status 有四个取值,其中 completed 与 failed 是终态:
| status | 含义 | 该做什么 |
|---|---|---|
queued | 已排队,尚未开始 | 继续轮询 |
in_progress | 正在生成 | 继续轮询 |
completed | 已完成 | 调下载端点取成片 |
failed | 生成失败 | 读 error.code / error.message;不计费 |
不要无限轮询。请给循环设置整体超时与最大轮询次数,超时后按失败处理并告警——否则上游异常时你的进程会一直挂着。
耗时参考:wan 5 秒 480P 实测约 1~2 分钟;h3 4 秒片实测约 1 分钟、6 秒片约 1.5 分钟;h3-fast 5 秒文生片实测约 18 秒、带参考素材约 1 分钟。轮询间隔 5 秒是合适的起点,整体超时建议留到 10 分钟以上。
画幅与时长
这一节 wan 和 h3 规则完全不同,请按你用的模型看对应一栏。
size 是一个参数管两件事:短边决定分辨率档位(档位决定每秒单价),宽高比决定画幅。
| 短边 | 档位 |
|---|---|
| 480 | 480P |
| 540 | 540P |
| 720 | 720P |
| 1080 | 1080P |
1920x1080 和 1080x1920 是同一档、同一个单价——算成本看短边,别看长边。
画幅只有五种:16:9 横屏 · 9:16 竖屏 · 1:1 方形 · 4:3 · 3:4。推荐写法是短边写档位数字、长边按画幅配:
| 画幅 | 480P | 540P | 720P | 1080P |
|---|---|---|---|---|
16:9 横屏 | 832x480 | 960x540 | 1280x720 | 1920x1080 |
9:16 竖屏 | 480x832 | 540x960 | 720x1280 | 1080x1920 |
1:1 方形 | 480x480 | 540x540 | 720x720 | 1080x1080 |
4:3 | 640x480 | 720x540 | 960x720 | 1440x1080 |
3:4 | 480x640 | 540x720 | 720x960 | 1080x1440 |
1280x720、1280*720、1280×720 三种写法都接受。
不在表里的组合会报错,不会被静默换掉。
| 你写的 | 结果 |
|---|---|
1024x1024、800x600——短边不是档位数字 | 400,提示短边应取的值 |
2560x1080——短边对上 1080P,但 21:9 不在五种画幅里 | 400,提示可用画幅 |
1920x1080 发给只开了 480P 的模型 | 400(code: unsupported_value),提示该模型实际可用的档位 |
成片的实际宽高不等于你写的 size:上游按「档位目标总像素 × 画幅」算,且宽高必须是 16 的倍数 —— 480P 的 16:9 实际是 832×480,480P 的 1:1 是 624×624。这些实际尺寸也可以直接当 size 回传。
图生视频的画幅跟着输入图走,不跟着你的 size 走。 实测:传 512×512 的图、size 写 832x480,成片是 624×624。size 仍决定档位和单价,画幅由图决定。要横屏成片就给横屏参考图。
时长:seconds 可选值随模型不同,见模型详情页。OpenAI SDK 里写成字符串如 "5"。
h3 没有档位阶梯,只有六种固定画布,短边恒为 768(21:9 因像素上限压到 672)。size 必须逐位精确命中其中一种:
| 画幅 | size |
|---|---|
21:9 | 1536x672 |
16:9 横屏 | 1344x768 |
4:3 | 1024x768 |
1:1 方形 | 768x768 |
3:4 | 768x1024 |
9:16 竖屏 | 768x1344 |
不在表里的一律 400,错误信息里会列出全部六种。不会被静默替换成相近的那一个。
六种画布单价相同 —— h3 按秒计费,与分辨率无关。
时长:seconds 取 4~15 的整数(不接受小数)。
实际成片会比你请求的秒数略长,但计费按你请求的秒数算。
模型内部的时长只能落在固定的帧数网格上,会向上吸附:请求 4 秒实际约 4.46 秒、请求 6 秒实际约 6.58 秒。多出来的部分不收费,差值最多约 0.67 秒。
seconds=8 是 4~15 里唯一正好整秒的取值(8.00 秒)。对时长有严格要求时优先用它。
h3 的成片始终带声轨,由模型一并生成,没有关闭开关 —— 这是模型结构决定的(视频与音频在同一个去噪过程里),关掉也不省算力。不需要声轨请在拿到成片后自行剥离:ffmpeg -i in.mp4 -c:v copy -an out.mp4。h3-fast 是同一套模型,声轨行为相同。
h3-fast 与 minimax/h3 是同一套模型,跑在另一批机器上,所以出片快得多,代价是只开两种画布:
| 画幅 | size |
|---|---|
16:9 横屏 | 1344x768(默认) |
9:16 竖屏 | 768x1344 |
不带 size 时按 1344x768 出片。传其余画布(包括 h3 支持的 768x768 等四种)一律 400,不会被静默替换。
单价与画布无关,两种画布同价,只按秒算。
时长:seconds 取 5~15 的整数(不接受小数)。
实际成片会比你请求的秒数略长,但计费按你请求的秒数算 —— seconds=15 除外。
和 h3 一样,时长只能落在固定的帧数网格上、向上吸附:请求 5 秒实际约 5.17 秒、13 秒实际约 13.67 秒。多出来的部分不收费,差值最多约 0.67 秒。seconds=8 是唯一正好整秒的取值(8.00 秒)。
seconds=15 是唯一的例外:模型本身最长只能出到 14.375 秒(帧数网格向上对齐后,再下一档就是 15.083 秒、超出模型允许的 15 秒上限),所以 15 会按 14 秒出片(实际约 14.38 秒)并按 14 秒计费 —— 少给的那一档不收你的钱。对时长有严格要求时请显式写 14,不要写 15。
seed 只有 h3-fast 支持:同样的输入配同一个 seed,画面构图基本一致,但不保证逐像素相同。不传时每次随机。用官方 OpenAI SDK 时放进 extra_body:extra_body={"seed": 42}。
参数
不同模型开放的参数不同,传了该模型不支持的参数一律 400 并说明原因,不会被静默忽略 —— 静默忽略意味着你以为设置生效了、实际按默认值出片,钱还照付。
| 参数 | 类型 | wan2.6/2.7-t2v | wan2.7-i2v | wan2.7-r2v | wan3.0-video | minimax/h3 | minimax/h3-fast |
|---|---|---|---|---|---|---|---|
model | string | ✅ 必填 | ✅ | ✅ | ✅ | ✅ | ✅ |
prompt | string | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
size | string | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ 仅 1344x768 / 768x1344,默认前者 |
seconds | int | ✅ | ✅ | ✅ | ✅ | ✅ 4~15 整数 | ✅ 5~15 整数,默认 5(15 按 14 秒出片与计费) |
input_reference | string / 文件 | ❌ | ✅ 仅 URL / base64 | ❌ | ✅ 仅 URL / base64 | ✅ 也可上传文件 | ✅ 也可上传文件 |
media | array | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
prompt_extend | bool | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
watermark | bool | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
seed | int | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ 0~2147483647 |
audio | bool | ❌ | ❌ | ❌ | ✅ | ❌(恒带声轨) | ❌(恒带声轨) |
说明:
prompt_extend—— ⚠️ 默认开启,会改写你的prompt;要严格按原文出片请显式传false。watermark—— 默认false,是否加 AI 生成水印。seed—— 随机种子,用于复现同一结果。minimax/h3暂不支持,同样的输入每次产出都不同;minimax/h3-fast支持:同样的输入配同一个seed,画面构图基本一致,但不保证逐像素相同。用官方 OpenAI SDK 时与media一样放进extra_body:extra_body={"seed": 42}。audio—— 是否生成声轨,开与关单价相同。
布尔参数必须是真正的布尔值:字符串 "true" 返回 400。seconds / seed 同理,必须能转成整数。
未识别的字段一律不会透传到后端。 上游请求体由网关逐字段构造,只有本表里的参数会被采纳;其余字段(无论怎么传)都会被丢弃,不会影响生成结果或计费。
所以用 extra_body 传 media 是安全且必要的(见上文「四种生成模式」),但不要指望用它传本表之外的东西 —— 那些字段不会生效。另外在裸 HTTP 里显式写一个名为 extra_body 的字段会被直接 400(那条路径语义不同,见错误信息)。
media 的类型
media 是 [{"type": "…", "url": "…"}] 数组。url 除公网地址外也接受 data:<mime>;base64,... 内联数据(图片、音频、视频都可以),这样不需要图床。注意内联会让请求体膨胀约 1/3,单个素材建议控制在几 MB 以内。
链接由模型所在的算力节点直接下载,网关不转存。所以链接必须:
-
是公网可以直接访问的
http:///https://地址,不带账号密码,不指向内网地址(否则提交时直接400); -
在任务执行时仍然有效 —— 带签名的临时链接请留足有效期(排队也要算进去);
-
响应要快,素材不要过大。
-
优先用中国大陆境内可以稳定访问的地址(如国内对象存储、CDN)—— 算力节点访问境外站点时常超时。
下载失败时任务以失败结束,不计费;minimax/h3-fast 的错误信息会指出是第几个素材出的问题(例如 media[0]: media link returned HTTP 404)。input_reference 给链接时同样适用这些规则。
type | 用途 | wan3.0-video | minimax/h3 |
|---|---|---|---|
first_frame | 首帧图(等价于 input_reference) | ✅ | ✅ |
last_frame | 尾帧图 | ✅ | ✅ |
reference_image | 参考图(控制风格 / 主体) | ✅ | ✅ |
reference_video | 参考视频 | ✅ | ✅ |
reference_audio | 参考音频(音色 / 风格) | ✅ | ✅ |
file | 文件(≤ 100 MB) | ✅ | ❌ |
link | 网页链接 | ✅ | ❌ |
first_clip | 起始片段 | ❌ | ❌ |
driving_audio | 驱动音频(口型 / 动作对齐) | ❌ | ❌ |
上表只覆盖 wan3.0-video 与 minimax/h3,因为只有这两个的可用类型是确证过的。
其余 wan 模型(wan2.7-i2v / wan2.7-r2v 等)网关不做类型收窄,收不收由上游决定 —— 请以模型库详情页为准,并按下面这条做好失败处理。
wan 系列传了该模型不收的类型,任务会创建成功、十几秒后以 InvalidParameter 失败(失败不计费)。别依赖网关拦截。
h3 会在提交时就 400,并列出它支持的类型。
计费
按成片时长计费(¥/秒),实价见模型库。
单价随分辨率档位不同,档位取 size 的短边:1920x1080 与 1080x1920 都算 1080P 档。算成本按短边对照模型详情页的档位价。
完成后 usage 带上实际产出信息,其中 duration_seconds 是计费依据:
{
"duration_seconds": 5, // 实际成片秒数 —— 按这个计费
"SR": 480, // 实际出片的分辨率档位 —— 决定单价
"ratio": "16:9",
"fps": 30,
"video_count": 1
}档位以实际出片为准:结算读 usage.SR,不是读你请求里的 size。图生视频时上游可能按参考图调整画幅,档位仍由目标总像素决定。
单价与画布无关,六种画布同价,只按秒算。
计费秒数取你请求的 seconds,不是实际成片秒数 —— 实际成片会略长(见「画幅与时长」),多出来的部分不收费。
{
"duration_seconds": 4, // = 你请求的 seconds,按这个计费
"size": "768x768",
"inference_time_s": 54.8 // 本次生成耗时,仅供参考,不参与计费
}单价与画布无关,两种画布同价,只按秒算。
计费秒数取你请求的 seconds,实际成片略长的部分不收费。seconds=15 按 14 秒计费 —— 模型最长只能出到约 14.38 秒,少给的那一档不收钱。
usage.duration_seconds 就是计费依据 —— 请求 seconds=15 的任务,这里会回 14:
{
"duration_seconds": 5, // = 你请求的 seconds(请求 15 时回 14),按这个计费
"size": "1344x768",
"inference_time_s": 17.9 // 本次生成耗时,仅供参考,不参与计费
}通用规则:
- 生成失败不计费。
- 轮询不额外计费:同一个任务只在第一次读到完成状态时结算一次,之后无论查多少次都不会重复扣费。
- 下载端点不单独计费。
逐笔扣费金额可在控制台调用日志核对。