Wan 3.0 API 教程:用 Python 做文生视频和图生视频
一份标准库脚本跑出两条片子:代码、真实耗时和阿里云每秒价格。
本页内容
Wan 3.0 在阿里云 Model Studio 上有两个模型 ID:wan3.0-video 和更快的 wan3.0-video-prime。每次调用都是异步的:先建任务,再按 ID 轮询,最后下载 MP4。我们用一份 Python 脚本,5 秒 720P 的文生视频 2 分 42 秒出片,图生视频 3 分 20 秒出片,两条都带声音。
- 把 key 放进
OMNIAKEY_API_KEY。 - POST
https://api.omniakey.com/v1/media/tasks,带上模型、操作、提示词、参数和一个新的Idempotency-Key。 - 轮询
GET /v1/media/tasks/{id},状态变成 succeeded 后立刻下载assets[0].url。
官方有 Wan 3.0 API 吗,该用哪个模型?
有。阿里云 Model Studio 就是官方接口,wan3.0-video 和 wan3.0-video-prime 都在里面(接口文档 10 月 15 日更新)。那边建任务要带 X-DashScope-Async: enable 请求头,Python 走 DashScope SDK 2.0.1 或更新版本。
| 阿里云 Model Studio | OmniaKey | |
|---|---|---|
| 两个模型 ID | 都有 | 都有 |
| 异步请求头 | 需要 | 不需要 |
| 视频链接有效期 | 24 小时 | 约 1 小时,可续 |
| 计费方式 | 按秒 | 按秒 |
| Python | DashScope SDK | 标准库 |
两个 ID 用同一种请求体,模型按你发来的素材判断模式。下面的脚本用 wan3.0-video,换 Prime 只改 MODEL 那一行。Wan 3.0 Prime 我们没跑,OmniaKey 文档说它能力和标准版一样、耗时大约一半(10 月 31 日核对)。
第一次调用前要准备什么?
三样东西,第一样是 key:
-
一个 OmniaKey API key,在终端里导出:
bashexport OMNIAKEY_API_KEY="your-key" -
Python 3。脚本只用标准库,装了 python3 就能跑,不用再装别的。
-
图生视频要一张首帧图:约 1024 px 宽的 JPEG,或者一个公开的 HTTPS 地址。文件超过 700,000 字节脚本会直接拒绝,因为 Base64 会让体积多出约三分之一,而整个请求体上限是 1 MiB(文档,10 月 31 日核对)。
哪些参数最要紧?
真正起作用的是七个字段,都放在 parameters 里。
| 参数 | 作用 | 默认值 |
|---|---|---|
| 时长 | 片子多长,按秒 | 5 |
| 分辨率 | 画质档位 | 1080P |
| 比例 | 画面形状 | adaptive |
| 声音 | 要不要音轨 | 开 |
| 随机种子 | 复现提示 | -1 |
| 提示词扩写 | 自动改写提示词 | 开 |
| 水印 | 角落标记 | 关 |
脚本只设时长、分辨率和比例,其余走默认值。它要 720P,而阿里云默认是 1080P:按标价,1080P 每秒贵一倍。
Python 代码长什么样?
下面的脚本就是 10 月 31 日原样跑的那份:只用标准库,两种模式,换模型只改一行 MODEL。
import base64
import json
import os
import time
import urllib.error
import urllib.request
import uuid
API = "https://api.omniakey.com/v1/media/tasks"
KEY = os.environ["OMNIAKEY_API_KEY"]
MODEL = "wan3.0-video"
def request(method, url, body=None, extra_headers=None):
headers = {"Authorization": f"Bearer {KEY}", "User-Agent": "omniakey-example/1"}
data = None
if body is not None:
headers["Content-Type"] = "application/json"
data = json.dumps(body).encode()
headers.update(extra_headers or {})
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=120) as resp:
return json.load(resp)
def poll(url, attempts=5):
for attempt in range(attempts):
try:
return request("GET", url)
except urllib.error.HTTPError as err:
if err.code < 500 or attempt == attempts - 1:
raise
except urllib.error.URLError:
if attempt == attempts - 1:
raise
time.sleep(5 * (attempt + 1))
def run_task(body, out_path):
task = request("POST", API, body, {"Idempotency-Key": str(uuid.uuid4())})["id"]
print("task", task, flush=True)
for _ in range(180): # 5-second polls, up to 15 minutes
time.sleep(5)
state = poll(f"{API}/{task}")
if state["status"] not in ("queued", "processing"):
break
else:
raise TimeoutError(f"{task} is still {state['status']}: poll it again later, do not resubmit")
if state["status"] != "succeeded":
raise RuntimeError(state.get("error") or state["status"])
asset_url = state["assets"][0]["url"] # signed link: short-lived, so download now
req = urllib.request.Request(asset_url, headers={"User-Agent": "omniakey-example/1"})
with urllib.request.urlopen(req, timeout=300) as resp, open(out_path, "wb") as f:
f.write(resp.read())
return task
def text_to_video(prompt, out_path, duration=5, resolution="720P", ratio="16:9"):
body = {
"model": MODEL,
"type": "video",
"operation": "text_to_video",
"input": {"prompt": prompt},
"parameters": {"duration": duration, "resolution": resolution, "ratio": ratio},
}
return run_task(body, out_path)
def image_to_video(prompt, image_path, out_path, duration=5, resolution="720P"):
with open(image_path, "rb") as f:
raw = f.read()
if len(raw) > 700_000: # base64 adds a third, and the whole body must stay under 1 MiB
raise ValueError("first frame too large: save it as a JPEG about 1024 px wide")
frame = "data:image/jpeg;base64," + base64.b64encode(raw).decode()
body = {
"model": MODEL,
"type": "video",
"operation": "image_to_video",
"input": {"prompt": prompt, "media": [{"type": "first_frame", "url": frame}]},
"parameters": {"duration": duration, "resolution": resolution, "ratio": "adaptive"},
}
return run_task(body, out_path)
if __name__ == "__main__":
print("done", text_to_video(
"A blacksmith hammers a glowing horseshoe on an anvil in a dark forge. Sparks fly at each "
"strike and light his leather apron. The camera slowly pushes in. Sound of the hammer ringing "
"on steel and the fire crackling. No dialogue, no music, no text.",
"forge.mp4",
))
print("done", image_to_video(
"Image 1 is the first frame. The paper boat drifts slowly to the right across the puddle. "
"Light rain makes small ripples and the cafe lights shimmer in the water. The camera stays "
"low and still. Sound of light rain. No dialogue, no music, no text.",
"boat.jpg",
"boat-drifts.mp4",
))
各部分做了什么:
run_task给每个任务生成新的Idempotency-Key,同一个 key 配同一份请求体重发,会拿回原任务,不重复扣费(文档,10 月 31 日核对)。- 每 5 秒查一次,最多查 15 分钟,超时抛
TimeoutError。 - 超时的意思是过一会儿用同一个 ID 再查。重新提交会生成新 key,等于又建一个任务、又扣一次费。
- 拿到结果就立刻下载
assets[0].url,签名链接存活时间很短。 image_to_video拒绝超过 700,000 字节的首帧:Base64 会多出约三分之一,请求体上限是 1 MiB。- 图生视频把
ratio设成adaptive,片子跟着原图的形状走。
脚本打印出来的内容:
task task_<id>
done task_<id>
task task_<id>
done task_<id>
两次调用各出了什么?
两个文件都是 5.04 秒的 MP4:150 帧、30 fps、H.264,带一条 44.1 kHz 的 AAC 立体声轨。
文生视频:铁匠铺

片子照着提示词走:穿深色衬衫和皮围裙的铁匠在砧上锤一块烧红的马蹄铁,2.2 秒砸出火花,镜头同时慢慢推进。唯一的瑕疵是砧侧面有一处印上去的字母,糊得看不清,而提示词写明不要文字。从开始到文件落盘 2 分 42 秒,MP4 是 1280×720、6.6 MB。
图生视频:纸船

首帧是 Nano Banana 2.1 出的一张图:雨后水洼里的一只红纸船,压成 1024×571 的 JPEG(158 KB),以 data URL 内联发送。片子保留了黄昏的光和湿石板,船按提示词从左往右漂,1.6 秒时旁边起了一圈涟漪,镜头一直压低不动。比例用自适应,片子跟着原图变成 1284×716,耗时 3 分 20 秒,7.4 MB。
声音是默认开的吗,「不要音乐」管用吗?
声音默认开着。按 Gemini 3.8 Flash 对音轨的描述,铁匠铺那条没有旁白、没有音乐,只有金属撞击的叮当声;纸船那条没有旁白,但有一段「忧郁平静的钢琴旋律」,而那句提示词明确写了不要音乐。阿里云的提示词指南(2026 年 10 月 16 日更新)建议写成 "No BGM" 或 "No background music",我们的提示词两种都没用。
把 "audio": false 传进去,文件就只剩视频轨:2 秒 480P 的灯笼片子完全没有声音轨,阿里云的价格也不变。
| 片子 | 耗时 | 尺寸 | 声音 |
|---|---|---|---|
| 铁匠铺(文生) | 2 分 42 秒 | 1280×720 | 叮当声,无音乐 |
| 纸船(图生) | 3 分 20 秒 | 1284×716 | 写了不要音乐,仍有钢琴 |
| 灯笼(关声音) | 4 分 36 秒 | 854×480 | 没有声音轨 |
排队时间会变:2 秒的关声音任务反而用了 4 分 36 秒,比两条 5 秒 720P 都长。提示词怎么写,可以看 Wan 3.0 提示词指南,里面有写法和示例。
视频文件怎么拿,链接能放多久?
轮询 GET /v1/media/tasks/{id},等到状态变成 succeeded、failed 或 canceled,跑的过程中是 queued 和 processing。任务结束后返回 assets[0].url,这是签名链接,大约一小时失效(OmniaKey 文档,10 月 31 日核对)。所以要马上把文件拉下来;过期之后再查一次同一个任务就有新链接,任务记录保留 24 小时。
文件是 H.264 的 MP4,30 fps;直接调阿里云的话,返回的视频地址有效期 24 小时。还有一点要注意:取消 Wan 3.0 任务不会停下渲染,取消后渲染成功照样计费,所以提交前先把时长和分辨率确认好。
会碰到哪些报错?
大部分在任务建出来之前就返回了,改请求或者换 key 就好。
- 400
model_price_error— 时长不在 2 到 30 秒之间。我们把"duration": 1发过去,0.9 秒就返回 "media duration must be between 2 and 30 seconds",没有建任务,也没有扣费。 - 400
missing_idempotency_key— 建任务没带 Idempotency-Key 请求头。补上,最长 255 个字符。 - 409
idempotency_key_reused— 同一个 key 配了不同的请求体。换一次提示词就换一个新 key。 - 403
model_not_allowed— 这个 key 不能调用那个模型。先核对model的值,再检查这个 key 的设置。 - 403 提示余额或消费上限 — 预付余额不够,或者到了单个 key 的上限。充值或者调高上限。
- 404
task_not_found— ID 写错,或者任务超过 24 小时。用建任务时返回的那个 ID,当天查。 - 409
task_state_conflict— 任务已经结束,比如成功之后才发取消。先读状态再动手。 - 429 — 请求太密。OmniaKey 没有公布固定限速,按指数退避加随机抖动重试。
- 503
no_available_channel— 这个操作没有可用通道,比如视频转视频。改用文生视频或图生视频。 - 脚本抛
TimeoutError— 15 分钟后任务还在跑。过一会儿用同一个 ID 再查;重新提交会再建一个任务、再扣一次费。
一条 Wan 3.0 视频多少钱?
阿里云按视频秒数计费,失败的请求不计费(价格页 10 月 29 日更新)。下表是阿里云新加坡区的每秒标价,10 月 31 日核对,来自阿里云 Model Studio 价格页。
| 模型 | 480P | 720P | 1080P |
|---|---|---|---|
| Wan 3.0 | $0.05 | $0.1 | $0.2 |
| Wan 3.0 Prime | $0.068 | $0.14 | $0.28 |
按这个标价,5 秒 720P 在 Wan 3.0 上是 $0.50(5 × $0.1),Prime 是 $0.70(5 × $0.14)。Wan 3.0 的三档价格都带「Limited-time 30% off」标记,页面没说这个数字里有没有算进折扣。声音开或关,价格一样。
免费额度只有 30 秒(输入加输出),限新加坡区的 wan3.0-video,Prime 没有。中国(北京)区标的标准版美元价更低:480P、720P、1080P 每秒分别是 $0.041256、$0.082513、$0.165025。
OmniaKey 也按秒计费,当前费率在 Wan 3.0 和 Wan 3.0 Prime 两个模型页上。
国内怎么用
OmniaKey 支持人民币直充(支付宝、微信支付),一个 key 同时调用 Wan 3.0 和 Wan 3.0 Prime,不用自己再开阿里云账号。换模型只改脚本里的 MODEL 一行,提示词和参数都不用动。模型页:Wan 3.0 和 Wan 3.0 Prime。
常见问题
一次调用要多久?
阿里云说一般 1 到 5 分钟,建议大约每 15 秒查一次(接口文档,10 月 31 日核对)。我们三次分别是 2 分 42 秒、3 分 20 秒、4 分 36 秒。
怎么把声音关掉?
在 parameters 里写 "audio": false,MP4 就只剩视频轨,我们那条 2 秒的灯笼片子就是这样。价格不变。
wan3.0-video 和 wan3.0-video-prime 选哪个?
按阿里云标价,wan3.0-video 更低,新加坡区还有 30 秒免费额度;更在意出片速度再上 Prime。OmniaKey 文档说 Prime 能力相同、耗时大约一半,我们这几次跑的都是标准版。
视频链接能放多久?
OmniaKey 上大约一小时,过期后再查一次任务就有新链接,任务记录保留 24 小时。直接调阿里云,返回的视频地址有效期 24 小时。
参考来源
- Wan 3.0 视频生成接口文档,2026 年 10 月 31 日核对
- Wan 3.0 视频生成指南,2026 年 10 月 31 日核对
- Wan 3.0 提示词指南,2026 年 10 月 31 日核对
- 阿里云 Model Studio 模型价格,2026 年 10 月 31 日核对
- OmniaKey 视频接口文档,2026 年 10 月 31 日核对
- OmniaKey 媒体接口总览,2026 年 10 月 31 日核对
- OmniaKey 错误码,2026 年 10 月 31 日核对

