GPT-6 Sol、Claude Opus 5.5 已上线GPT-6 Sol 价格仅为 5.6 Sol 的一半

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

一份标准库脚本跑出两条片子:代码、真实耗时和阿里云每秒价格。

Kiki, OmniaKey COO8 分钟阅读
本页内容

Wan 3.0 在阿里云 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 StudioOmniaKey
两个模型 ID都有都有
异步请求头需要不需要
视频链接有效期24 小时约 1 小时,可续
计费方式按秒按秒
PythonDashScope SDK标准库

两个 ID 用同一种请求体,模型按你发来的素材判断模式。下面的脚本用 wan3.0-video,换 Prime 只改 MODEL 那一行。Wan 3.0 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 铁匠铺片段的四帧
文生视频,5 秒 720P:第 0.3、2.2、3.5、4.7 秒的画面。

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

图生视频:纸船

首帧原图和 Wan 3.0 纸船片段的三帧
图生视频:左上是我们送进去的原图,后面是第 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 对音轨的描述,铁匠铺那条没有旁白、没有音乐,只有金属撞击的叮当声;纸船那条没有旁白,但有一段「忧郁平静的钢琴旋律」,而那句提示词明确写了不要音乐。阿里云的提示词指南(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 价格页。

模型480P720P1080P
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.0video APIPythontext-to-videoimage-to-video

相关文章

最新文章