Series · Aliyun Bailian · Chapter 4

阿里云百炼实战(四):万相视频生成端到端

万相文生视频 / 图生视频生产化:异步任务模式、退避轮询、扛得住现实的 prompt 模式、URL 过期前必做的 OSS 写穿。

万相是给我们营销流水线创造最多价值、也制造最多生产事故的 API。模型本身真的好——wan2.5-t2v-plus 出 720p 片段大多数时候能冒充真实视频团队的产物——但周边是异步、原生协议、URL 会过期、限流非显然。这篇是经过六个月"凌晨两点为啥又出事"工单打磨的、文档真正实操版本。

模型阵容

三个,全都原生(无 OpenAI 兼容),全都异步:

万相模型阵容

wan2.5-t2v-plus 我用得最多——文生视频最灵活、不用设计师介入也能交清楚 brief。wan2.5-i2v-plus 用在营销已经有 hero 静态图想动起来(产品照变 5 秒 turntable)。wan2.5-kf2v-plus 用于过渡:给首末帧,得到中间运动。

端到端流程

只有一个流程,每条视频都走:

万相请求流

最小可跑 Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import os, time, requests, dashscope
from dashscope import VideoSynthesis

dashscope.api_key = os.environ["DASHSCOPE_API_KEY"]

def t2v(prompt: str, size: str = "1280*720", duration: int = 5) -> str:
    resp = VideoSynthesis.async_call(
        model="wan2.5-t2v-plus",
        prompt=prompt,
        size=size,
        duration=duration,
    )
    task_id = resp.output.task_id
    print("task:", task_id)

    delay = 5
    for _ in range(60):
        info = VideoSynthesis.fetch(task=task_id)
        status = info.output.task_status
        print(status)
        if status == "SUCCEEDED":
            return info.output.results[0].url
        if status == "FAILED":
            raise RuntimeError(info.output.message)
        time.sleep(delay)
        delay = min(delay * 1.45, 60)
    raise TimeoutError("任务未在限时内完成")

url = t2v("杭州茶园日出慢动作,无人机航拍后退,金色时刻,电影感,35mm 胶片颗粒")
print(url)

退避轮询——选个合理日程

每秒一查浪费、还会限流。每 30 秒一查浪费用户时间。我用的退避:

轮询日程

5 秒起步,每次 ×1.45,封顶 60 秒。720p 5 秒视频通常 30-90 秒完成,中位用户等 4 次轮询。

后端服务里通常不该在请求 handler 里轮询。模式应该是:

  1. 用户提交 → 你 POST 到万相,把 task_id 存数据库。
  2. 立刻返回任务 URL。
  3. 后台 worker 轮询,成功后更新 DB。
  4. 前端轮询你的 DB,不是万相。

这样就有了重试、可观察性、以及 URL 过期前的存放点。

立刻保存 URL——24 小时过期

我见过最贵的生产 bug:拿到 result_url 就显示在站点上,24 小时后页面坏了,因为 URL 失效。万相返回的是签名时限 URL。成功后立刻 copy 到自己 OSS 桶:

1
2
3
4
5
6
7
8
def archive(result_url: str, key: str) -> str:
    import oss2, requests
    r = requests.get(result_url, stream=True, timeout=60)
    r.raise_for_status()
    auth = oss2.Auth(os.environ["OSS_AK"], os.environ["OSS_SK"])
    bucket = oss2.Bucket(auth, "https://oss-cn-shanghai.aliyuncs.com", "your-bucket")
    bucket.put_object(key, r.raw)
    return f"oss://your-bucket/{key}"

我在轮询 worker 里同步做这步,archive 失败任务就不算成功。

扛得住现实的 prompt 模式

万相质量很大一部分在 prompt 上。几个月迭代后管用的结构:

[镜头类型], [主体], [动作], [场景 / 环境],
[光线], [运镜], [风格], [质量关键词]

上线过的例子:

  • 广角镜头,一杯珍珠奶茶,凝结水珠从杯壁滑下,放在大理石桌上靠窗,午后柔和逆光,缓慢推近,写实,4K,浅景深
  • 中景,一位身着汉服的年轻女子,走过杭州竹林,清晨薄雾,斑驳光影,从背后平稳跟随运镜,电影质感,35mm

伤质量的:

  • 主 prompt 里带否定词(“画面无文字”)。要否定就用 negative_prompt 参数。
  • 主体超过 ~3 个。模型会混淆。
  • 具体品牌或人名。通用描述更好。
  • 帧上文字写非中英文(西里尔、阿拉伯、天城文)。万相目前只能稳出中英文字。

图生视频和首末帧视频

同流程,换 model 和输入。I2V 接 image_url(OSS 签名 URL 可以);KF2V 接 first_frame_urllast_frame_url。视频时长上限按模型而异(通常 5 或 10 秒),生成前查模型卡。

产品演示常用的模式:

  1. 摄影师交一张 hero 静态图。
  2. Prompt:产品在转盘上慢慢旋转,影棚布光。
  3. I2V 出 5 秒 turntable。
  4. 挂到产品页。

成本一条几块钱;替代方案是占用某人半天摄影时间。

任务 SUCCEEDED 但视频不对

最常见的"模型出了东西,但忽略了一半 prompt"。原因:

  • prompt 太长。万相有软上限,激进精简有效。
  • prompt 自相矛盾(“白天,黑暗,霓虹”)。挑一个。
  • 模型变体选错。T2V 不会动你的指定图,那是 I2V 的事。
  • 比例错。size 影响构图,1280*720720*1280 出的取景不同。

关键 prompt 用不同 seed 出 3 个变体,通常其中一个对。

成本和限流

万相按视频秒数计费。720p 5 秒一条几块钱。并发任务限制按 API key —— 生产流量上线通过控制台申请配额提升。默认(上次我看是)每空间 5 并发,原型够用,真生产瞬间不够。

下一篇

第五篇收尾——Qwen-TTS-Flash,唯一让我敢上生产的中文方言语音合成。也是只能原生,所以本文模式继续适用。

Liked this piece?

Follow on GitHub for the next one — usually one a week.

GitHub