# Wan 3.0 API 教程：用 Python 做文生视频和图生视频

> 用 Python 调 Wan 3.0 做文生视频和图生视频：10 月 31 日跑通的脚本、参数、真实出片、报错，以及阿里云每秒价格。

- Canonical HTML: https://omniakey.com/zh/blog/wan-3-0-api
- Markdown: https://omniakey.com/zh/blog/wan-3-0-api.md
- Published: 2026-10-31 (updated 2026-10-31)
- Author: Kiki, COO at OmniaKey
- Language: zh
- Category: API setup
- Reading time: 8 min
- All posts: https://omniakey.com/blog/llms.txt

---

[Wan 3.0](https://omniakey.com/zh/wan3-0-video) 在阿里云 Model Studio 上有两个模型 ID：`wan3.0-video` 和更快的 `wan3.0-video-prime`。每次调用都是异步的：先建任务，再按 ID 轮询，最后下载 MP4。我们用一份 Python 脚本，5 秒 720P 的文生视频 2 分 42 秒出片，图生视频 3 分 20 秒出片，两条都带声音。

1. 把 key 放进 `OMNIAKEY_API_KEY`。
2. POST `https://api.omniakey.com/v1/media/tasks`，带上模型、操作、提示词、参数和一个新的 `Idempotency-Key`。
3. 轮询 `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](https://omniakey.com/zh/wan3-0-video-prime) 我们没跑，OmniaKey 文档说它能力和标准版一样、耗时大约一半（10 月 31 日核对）。

## 第一次调用前要准备什么？

三样东西，第一样是 key：

1. 一个 OmniaKey API key，在终端里导出：

   ```bash
   export OMNIAKEY_API_KEY="your-key"
   ```

2. Python 3。脚本只用标准库，装了 python3 就能跑，不用再装别的。

3. 图生视频要一张首帧图：约 1024 px 宽的 JPEG，或者一个公开的 HTTPS 地址。文件超过 700,000 字节脚本会直接拒绝，因为 Base64 会让体积多出约三分之一，而整个请求体上限是 1 MiB（文档，10 月 31 日核对）。

## 哪些参数最要紧？

真正起作用的是七个字段，都放在 `parameters` 里。

|参数|作用|默认值|
|-|-|-|
|时长|片子多长，按秒|5|
|分辨率|画质档位|1080P|
|比例|画面形状|adaptive|
|声音|要不要音轨|开|
|随机种子|复现提示|-1|
|提示词扩写|自动改写提示词|开|
|水印|角落标记|关|

脚本只设时长、分辨率和比例，其余走默认值。它要 720P，而阿里云默认是 1080P：按标价，1080P 每秒贵一倍。

## Python 代码长什么样？

下面的脚本就是 10 月 31 日原样跑的那份：只用标准库，两种模式，换模型只改一行 MODEL。

```python
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`，片子跟着原图的形状走。

脚本打印出来的内容：

```text
task task_<id>
done task_<id>
task task_<id>
done task_<id>
```

## 两次调用各出了什么？

两个文件都是 5.04 秒的 MP4：150 帧、30 fps、H.264，带一条 44.1 kHz 的 AAC 立体声轨。

### 文生视频：铁匠铺

![Wan 3.0 铁匠铺片段的四帧](https://omniakey.com/_img/blog/wan-3-0-api/wan-forge-grid.4c1b058b.1288.webp)

_文生视频，5 秒 720P：第 0.3、2.2、3.5、4.7 秒的画面。_

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

### 图生视频：纸船

![首帧原图和 Wan 3.0 纸船片段的三帧](https://omniakey.com/_img/blog/wan-3-0-api/wan-paper-boat-grid.e2ffd154.1288.webp)

_图生视频：左上是我们送进去的原图，后面是第 1.6、3.5、4.7 秒的画面。_

首帧是 Nano Banana 2.1 出的一张图：雨后水洼里的一只红纸船，压成 1024×571 的 JPEG（158 KB），以 data URL 内联发送。片子保留了黄昏的光和湿石板，船按提示词从左往右漂，1.6 秒时旁边起了一圈涟漪，镜头一直压低不动。比例用自适应，片子跟着原图变成 1284×716，耗时 3 分 20 秒，7.4 MB。

### 声音是默认开的吗，「不要音乐」管用吗？

声音默认开着。按 Gemini 3.8 Flash 对音轨的描述，铁匠铺那条没有旁白、没有音乐，只有金属撞击的叮当声；纸船那条没有旁白，但有一段「忧郁平静的钢琴旋律」，而那句提示词明确写了不要音乐。阿里云的[提示词指南](https://www.alibabacloud.com/help/en/model-studio/wan3-video-generation-prompt-guide)（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 提示词指南](https://omniakey.com/zh/blog/wan-3-0-prompt-guide)，里面有写法和示例。

## 视频文件怎么拿，链接能放多久？

轮询 `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 价格页](https://www.alibabacloud.com/help/en/model-studio/model-pricing)。

|模型|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](https://omniakey.com/zh/wan3-0-video) 和 [Wan 3.0 Prime](https://omniakey.com/zh/wan3-0-video-prime) 两个模型页上。

## 国内怎么用

OmniaKey 支持人民币直充（支付宝、微信支付），一个 key 同时调用 Wan 3.0 和 Wan 3.0 Prime，不用自己再开阿里云账号。换模型只改脚本里的 `MODEL` 一行，提示词和参数都不用动。模型页：[Wan 3.0](https://omniakey.com/zh/wan3-0-video) 和 [Wan 3.0 Prime](https://omniakey.com/zh/wan3-0-video-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 视频生成接口文档](https://www.alibabacloud.com/help/en/model-studio/wan3-video-generation-api-reference)，2026 年 10 月 31 日核对
- [Wan 3.0 视频生成指南](https://www.alibabacloud.com/help/en/model-studio/wan3-video-generation-guide)，2026 年 10 月 31 日核对
- [Wan 3.0 提示词指南](https://www.alibabacloud.com/help/en/model-studio/wan3-video-generation-prompt-guide)，2026 年 10 月 31 日核对
- [阿里云 Model Studio 模型价格](https://www.alibabacloud.com/help/en/model-studio/model-pricing)，2026 年 10 月 31 日核对
- [OmniaKey 视频接口文档](https://docs.omniakey.com/en/media/video)，2026 年 10 月 31 日核对
- [OmniaKey 媒体接口总览](https://docs.omniakey.com/en/media/overview)，2026 年 10 月 31 日核对
- [OmniaKey 错误码](https://docs.omniakey.com/en/api/errors)，2026 年 10 月 31 日核对
