把 WayinVideo 接进 OpenAI 生态:一次 2API 适配实录

这两天折腾了一个小项目:把 WayinVideo 的网页视频生成功能,包成一个更容易被脚本、客户端、自动化工作流调用的 OpenAI 风格接口。

项目名字很直白:wayin-video-2api。
它不是那种“把请求转发一下就完事”的套壳。真正麻烦的地方在于:网页里按钮点一下,背后其实有模型能力表、上传流、账号态、轮询、内容兜底、错误码解释等一整套东西。把这些东西整理干净之后,客户端才可以只关心:

我给你一个 prompt,或者再给你一张图,你帮我生成视频。

而不是每次都去猜 Wayin 的内部字段应该怎么拼。

为什么要做这个

直接用网页当然也可以,但网页有几个天然限制:

  1. 不好接自动化:脚本、Bot、Cherry Studio 这类工具,不可能每次都模拟人去点网页。
  2. 接口形状不稳定:网页内部字段名和 OpenAI 常见接口差别很大。
  3. 模型很多,能力不一样:有些模型只能文生视频,有些必须带图,有些支持参考图/参考音频。
  4. 失败信息很不友好:上游经常只给一个 ERROR,不把上下文整理出来,排查起来很难受。

所以 2API 解决的问题不是“凭空创造新能力”,而是把已有网页能力整理成更稳定的调用层。

整体链路

现在客户端可以调用:

POST /v1/videos/generations

传入类似这样的请求:

{
  "model": "sora-2",
  "prompt": "一只橘猫在阳光下走路,电影感,高清",
  "duration": 4,
  "resolution": "720p"
}

如果是图生视频,则可以带:

{
  "model": "Seedance 1.0 Lite",
  "prompt": "让这张图里的人物张开双臂",
  "input_type": "image",
  "image_url": "https://example.com/frame.png"
}

2API 在中间会做这些事:

关键点一:模型能力表比想象中重要

Wayin 的模型目录里,不同模型能力差别很大。项目里把运行时 catalog 整理成了能力表,目前大致是这样:

这块最容易踩坑。比如“Seedance 1.0 Lite”看起来像一个模型,实际有文本版和图生版:

如果客户端传了图片,但仍然选择文本模型,上游就很容易失败。现在适配层会检查输入,如果发现带图,就优先路由到同系列图生模型。

关键点二:图生视频不能把本机路径直接丢给上游

这个坑很现实。

Dashboard 上传图片后,本机得到的是类似这样的临时地址:

/tmp/uploads/xxxx/frame.png

浏览器或者本机服务当然能访问它,但 Wayin 的服务器访问不到。之前图生视频失败,一个核心原因就是把这种本机临时路径直接放进了生成请求里。

后来补成了完整上传流:

POST /api/video/generate/upload
        ↓
PUT upload_url
        ↓
POST /api/external_file/refresh_url
        ↓
拿 data.url 去提交图生视频

对应到上游 payload,大概会变成:

{
  "model": "bytedance/v1-lite-image-to-video",
  "model_config": {
    "ratio": "16:9",
    "duration": "5",
    "resolution": "720p",
    "input_type": "i2v",
    "image": "https://.../remote-image.png"
  },
  "instruction": "让她张开双臂",
  "auto_prompt": true
}

这里的重点不是代码多复杂,而是边界要想清楚:

对本机可见,不等于对上游可见。

这个判断在各种“网页功能转 API”的项目里都很常见。

关键点三:生成成功以后,临时文件要删

做完图片上传之后,又冒出另一个小问题:本机临时图片会不会越堆越多?

答案是:如果不处理,会。

所以现在改成:

为什么不是上传到 Wayin 之后立刻删?因为排查失败时,有时还需要知道当时传了什么。现在的策略更保守:成功后删,失败时留一点现场。

这次排查的时间线

这次“画不了视频”的问题,最后不是一个点,而是两个问题叠在一起:

  1. 带图片时仍然可能走到文本模型。
  2. Dashboard 上传后的图片是本机临时路径,上游读不到。

看到日志里的 ERROR 时,第一反应不能是“上游坏了”。更有用的是把这些字段都留出来:

  • 请求模型是什么;
  • 实际路由到哪个上游模型;
  • 是否带图片;
  • 图片 URL 是本机路径还是远端 URL;
  • 走了哪个账号;
  • 上游返回的错误码是什么。

比如这类错误:

当前支持的接口形状

项目现在主要暴露这些 OpenAI 风格接口:

部署时最少需要注意两个环境变量:

API_KEY=你的本地访问密钥
WAYIN_ACCOUNTS_JSON=/path/to/accounts.json

如果要真实转发到 Wayin,还需要合法的会话来源,比如:

WAYIN_ACCESS_TOKEN=...
# 或
WAYIN_WEB_COOKIE=...

没有这些凭据时,项目会进入 dry-run,返回归一化后的上游请求预览,不会真的提交生成。

一个最小请求例子

文生视频:

curl -X POST http://127.0.0.1:3002/v1/videos/generations \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "sora-2",
    "prompt": "一只橘猫在阳光下走路,电影感,高清",
    "duration": 4,
    "resolution": "720p"
  }'

图生视频:

curl -X POST http://127.0.0.1:3002/v1/videos/generations \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Seedance 1.0 Lite",
    "prompt": "让画面里的人物自然地张开双臂",
    "input_type": "image",
    "image_url": "https://example.com/frame.png"
  }'

查询结果:

curl -H "Authorization: Bearer $API_KEY" \
  http://127.0.0.1:3002/v1/videos/<job_id>

如果 status 只返回了 fid,适配层会继续调用 content endpoint,把最终 MP4 URL 整理进 result.videos。

我觉得这个项目最有价值的地方

不是“能不能调通一次”。调通一次其实不难,难的是把这些细节变成稳定的工程边界:

  • 模型能力从 catalog 读,不靠拍脑袋;
  • 请求字段统一成客户端熟悉的形状;
  • 上游错误码有解释;
  • 图生视频自动处理上传流;
  • 成功后清理本机临时文件;
  • 没凭据时 dry-run,不误提交;
  • 所有修复都有测试兜底。

对我来说,这类 2API 项目最怕写成“神秘脚本”:今天能跑,明天上游一变就全炸。现在至少把关键路径拆开了,后面要替换模型、补 endpoint、接新客户端,都比较容易。

项目地址

GitHub:

https://github.com/keggin-CHN/wayin-video-2api

如果你也在做类似“网页产品能力 → 本地 API 适配层”的东西,我建议优先整理三件事:

  1. 运行时能力表;
  2. 上传/文件可见性边界;
  3. 错误码和日志结构。

这三件事做好,后面才不会一直靠猜。