音乐视频生成器
输入一段音频、一张图片和一段 prompt,输出一支完整的、按节拍剪辑好的音乐视频。用你自己的 API key 在线测试完整流程,也可以先看看它是怎么工作的。
API key
请先填入 API key,才能创建或查看任务。
创建任务
以下字段与 POST /mv/task 的请求体一一对应。
创建任务不扣费(账户余额需高于 $5.00)。分镜交付素材表时按实际音频时长扣视频费,每张首帧生成成功时扣图片费。
或查看一个已有任务
请先填入 API key,才能创建或查看任务。
使用方法
为了方便开发和测试,这个页面的在线工具都面向某一个具体的音乐视频任务:用左边的表单建一支新的,或者在「加载已有任务」里粘贴一个 mv_id,它们就会出现在这里。
- 流程状态——提交任务之后,首先预处理音频和图片等信息,接着导演系统负责生成分镜信息,随后生成首帧图,接着是每段视频,最后是剪辑拼接为完整MV视频。
- 推进进度——在 images_done / videos_done 状态时,会自动暂停,以便于调整不满意的首帧图或者视频片段,通过接口可以推进到下一阶段;成片后仍可替换素材并重新剪辑
- 重新生成——按新的 prompt 重跑某一个分镜的首帧图或者视频,单独即时计费。除了重新生成,也可以自己生成,并回填,总之可以任意更新。
- 首帧图或者图生视频,可在提交任务时选择参数,自己生成并回填;音频驱动的口型同步视频由此接口系统生成,生成后同样可以用自己的片段回填替换。
请求日志
本页向 MV API 发出的每一次调用,按时间倒序排列。
还没有发出任何请求。
概览
这个系列接口,能把一首歌变成一支完整的音乐视频。你提交一段音频、一张主图和一段文本 prompt;平台会分析音轨、编写分镜脚本,为每个镜头生成首帧,再把每一帧动画化成一段视频片段,最后按节拍把所有片段剪成成片。
这是一个长耗时的流水线——一整首歌可能要跑十几分钟甚至更长——所以这里没有任何同步接口。POST /mv/task 会立即返回一个 id;生产环境推荐配置 Webhook 接口 以便于主动推进进度而避免轮询,事件流(Events)更适合开发调试,也可以直接轮询 GET /mv/task/{mv_id}。
基础配置
| 配置项 | 值 |
|---|---|
| Base URL | https://api.foxaihub.com/api/v2 |
| 鉴权头 | api-key: YOUR_API_KEY |
Warning: 请求头名字是 api-key,不是 api_key。创建 MV 还要求账户余额严格大于 $5.00——详见「计费」一节。
接口一览
| 接口 | 用途 |
|---|---|
| POST /mv/task | 创建一个 MV 任务 |
| GET /mv/task/{mv_id} | 读取一个任务及其全部素材 |
| GET /mv/task | 列出你的任务 |
| POST /mv/task/{mv_id}/assets | 回填你自己生成的素材 |
| POST /mv/task/{mv_id}/assets/{asset_id}/regenerate | 重新生成某个素材 |
| POST /mv/task/{mv_id}/advance | 推进到下一阶段 |
| POST /mv/events/{mv_id} | 追加一条事件 |
| GET /mv/events/{mv_id} | 读取事件流 |
生产流程
API 响应里的 status 始终是字符串。一共有十二种取值,除了人工关卡和随时可能发生的失败之外,状态只会单向推进。storyboard_done 的编号是 11,但流程上排在 creating 和 images_generation 之间。
| # | status | 正在发生什么 | 你能做什么 |
|---|---|---|---|
| 0 | pending | 任务已接受,等待预处理 worker 领取 | 等待 |
| 1 | processing | 正在抓取并分析音频(节拍、结构、歌词) | 等待 |
| 2 | storyboard | 等待分镜 agent 领取任务 | 等待 |
| 3 | creating | agent 正在编写分镜与每个镜头的 prompt | 等待 |
| 11 | storyboard_done | 分镜计划已交付,首帧生成在这里暂停。只有建单时传了 pause_after_storyboard: true 才会停在这里 | 先审阅素材表,再调用 POST /mv/task/{mv_id}/advance 开始生成首帧 |
| 4 | images_generation | 正在逐镜头生成首帧;有首帧失败时任务会停在这里 | 重新生成 / 回填失败的首帧 |
| 5 | images_done | 所有首帧都已生成成功;视频生成在这里暂停 | 先审阅,再调用 POST /mv/task/{mv_id}/advance,也可以回填 / 重新生成素材 |
| 6 | videos_generation | 正在把每张首帧动画化为一段片段 | 回填 / 重新生成素材 |
| 7 | videos_done | 所有片段都到达终态 | 调用 POST /mv/task/{mv_id}/advance |
| 8 | editing | 剪辑器正在拼装成片 | 等待 |
| 9 | completed | 成片已就绪 | 下载 final_cut_url;或替换素材后调用 POST /mv/task/{mv_id}/advance 重新剪辑 |
| 10 | failed | 任务已终止;已实扣的费用(导演费、首帧费)不退 | 查看最后一条事件了解原因 |
无法识别的取值会显示为 unknown。只有当任务行携带了这版 API 尚不认识的状态时才会出现这种情况。
流程图
pending -> processing -> storyboard -> creating
|
(pause_after_storyboard: true) storyboard_done
| POST /mv/task/{mv_id}/advance
v
images_generation
|
v
images_done
|
POST /mv/task/{mv_id}/advance
|
v
videos_generation
|
v
videos_done
|
POST /mv/task/{mv_id}/advance
|
v
editing -> completed
|
POST /mv/task/{mv_id}/advance (替换素材后重新剪辑)
|
editing
any state -> failed
- images_generation -> images_done 与 videos_generation -> videos_done 不是靠调用触发的。images_done 要求每一张首帧都生成成功:只要还有首帧是 failed,任务就停在 images_generation,重新生成或回填成功后自动推进。视频阶段只要不再有子任务停在 pending 或 processing,平台就会自动翻到 videos_done。
- 建单时传了 pause_after_storyboard: true 的任务,分镜交付后会停在 storyboard_done:GET /mv/task/{mv_id} 已经能拿到完整素材表(每个镜头的首帧 prompt、视频 prompt、时长),确认后调用 POST /mv/task/{mv_id}/advance 才开始生成首帧。此时只收了导演费(任务进入 videos_done 时会退还),任务可以一直停在这里,没有时间限制。不传或传 false 时,分镜交付后直接进入 images_generation。
- images_done -> videos_generation、videos_done -> editing 与 completed -> editing(重新剪辑)都需要你主动触发,走的是同一个接口:POST /mv/task/{mv_id}/advance。即便每一帧首帧都已经就绪,视频生成也绝不会自己开始——是否要在这一步花钱,由你决定。
- 建单时传 auto: true 则上面两道关卡(images_done -> videos_generation、videos_done -> editing)由平台自动放行,也不会停在 storyboard_done,全程无需调用 advance;放行前的检查与手动 advance 完全一致,素材有失败或余额不足时任务停在原关卡,补齐后自动继续。成片后的重新剪辑(completed -> editing)不受 auto 影响,仍需手动调用 advance。不传 auto 时行为不变。
- 除此之外的所有跃迁都是自动的。
- 重新生成或回填素材不会改变任务状态。任务进入 images_generation 之后,任何阶段都可以替换某一张首帧或某一段片段——任务原地不动,只重跑那一个素材(每次重新生成单独计费)。往前走始终由你调用 advance 决定。成片 completed 之后如果又改了片段,调用 advance 即可 completed -> editing 重新剪辑,重剪免费。唯一的例外是 failed 的任务:它会被拉回 images_generation / videos_generation,让素材重新跑起来。
水印
创建任务时传入的 watermark 对象会作用在两个不同的地方:
- image_enable 会把水印文字烧录进平台生成的首帧里。
- video_enable 会额外产出一份带水印的成片,通过 final_cut_watermarked_url 暴露;final_cut_url 始终是无水印的干净版本。
当 text 为空时两者都会被忽略;水印也从不作用于你自己上传的素材,或你付费重新生成的素材。
素材模型
分镜会产出一份镜头列表。每个镜头就是一个 asset,用形如 A001 的 asset_id 标识,它由两半组成:
| 半边 | 是什么 | tool |
|---|---|---|
| Image | 该镜头的首帧 | i2v 或 lip-sync |
| Video | 由首帧动画化出的片段 | i2v 或 lip-sync |
tool: "i2v" 是普通的图生视频镜头。tool: "lip-sync" 是由歌曲片段驱动的对口型演唱镜头;这类镜头同样由平台生成(生成后你仍可回填自己的片段替换),其视频 prompt 与音频窗口永远不会返回给你。
素材由谁生成
所有视频片段(包括 i2v 与 lip-sync)都由平台使用 MiniMax H3 生成,创建任务时不能选择别的视频模型,也不能让平台跳过视频、改为自己上传。首帧是否由平台生成,由 image_model 决定:
| 设置 | 含义 |
|---|---|
| image_model: "foxaihub-image" | 平台生成每一张首帧,并按张计费 |
| image_model: "none" | 平台仅编写 prompt;首帧由你自行上传(POST /mv/task/{mv_id}/assets),不计图片费用 |
自备首帧不花钱。视频费则始终按整支 MV 的计费音频时长在任务进入 videos_done 时收一次,详见「计费规则」。
Warning: awaiting_user 状态的素材(image_model 为 none 时需要你上传的首帧)会拖住阶段推进:一个阶段只有在其中没有任何子任务处于 pending、processing 或 awaiting_user 时才会推进,所以卡在等你上传首帧的任务会一直停在原地,直到你补上为止——实际表现是任务不会到达 images_done,advance 会一直返回 409,直到每一张首帧都已上传。从 images_generation 到 videos_done 这四个状态都接受上传,因此你可以随做好随传,不必攒成一批。
失败处理
- 单个镜头失败并不会致死整支 MV。该素材会变成 failed,你可以选择上传替代品,或者付费重新生成。首帧有失败时任务不会进入 images_done,要先把失败的首帧补齐。
- 但到了人工关卡就必须补齐:advance 不允许带着 failed(或仍停在 awaiting_user)的素材离开当前这半程,会返回 409 并列出缺口,任务状态保持不变。回填或重新生成这些素材后,再调用一次 advance 即可。
- 如果某个阶段的素材全部失败,整个任务会转为 failed。
- 任务停在 storyboard_done、images_done、videos_done 等待你操作时没有时间限制,不会因为超时被判失败。
任务终止时不会再做结算:导演费在素材表交付时收取,首帧费在每张生成成功时实扣,视频费在进入 videos_done 时实扣(同时退还导演费),没有预扣款需要退回。详见「计费」一节。
API 参考
以下每个接口都需要 api-key 请求头,且只能操作该 key 名下的任务。你不拥有的任务会返回 404,绝不会是 403——这样状态码本身就不会暴露一个不属于你的 mv_id 是否存在。
POST /mv/task
创建一个 MV 任务。建单不扣费,但要求余额严格大于 $5.00。
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
audio |
string | Yes | 可公开访问的音频 URL(HTTP(S)),文件大小 <= 30 MB |
audio_duration |
integer | No | 0-360 秒,默认 0(整段音频) |
main_image |
string | Yes | 可公开访问的图片 URL(HTTP(S)),文件大小 <= 30 MB |
supporting_image |
string | No | 可公开访问的图片 URL(HTTP(S)),文件大小 <= 30 MB。可选的配角参考图,每个任务最多一张,不传则只有主角 |
prompt |
string | Yes | 1-4096 字符 |
aspect_ratio |
enum | No | 1:1、4:3、3:4、16:9、9:16,默认 9:16 |
resolution |
enum | No | 768p、1080p,默认 768p |
image_model |
enum | No | none、foxaihub-image,默认 none。gemini-3.1-flash-image-preview 仍然接受,等同 foxaihub-image |
i2v_model |
enum | No | 目前只支持 minimax-h3,也是默认值,可以不传;传 none 会返回 400 |
watermark |
object | No | video_enable、image_enable、position、text、font_size |
pause_after_storyboard |
boolean | No | 默认 false。true 时分镜交付后停在 storyboard_done,等你调用 advance 才生成首帧 |
auto |
boolean | No | 默认 false,不传等同于 false。true 时全程自动:不会停在 storyboard_done,images_done、videos_done 两道关卡由平台自动放行,无需再调用 advance。素材有失败(failed / awaiting_user)或余额不足时仍会停在关卡,补齐后自动继续;成片后的重新剪辑(completed -> editing)仍需手动调用 advance |
Note:
supporting_image是与main_image(主角)分开锚定的第二个人物,用一张和主角不同的人物图。平台会把它作为配角,和主角一样用来生成首帧并保持人物外观一致。只想要一个主角时不要传这个字段;配角只能通过它传入,每个任务最多一个。
Request Body
{
"audio": "https://common.foxai.me/sample_music/3.mp3",
"audio_duration": 60,
"main_image": "https://common.foxai.me/sample_music/character.webp",
"supporting_image": "https://example.com/supporting-character.webp",
"prompt": "霓虹灯下的雨夜城市,歌手独自穿行其中",
"aspect_ratio": "9:16",
"resolution": "768p",
"image_model": "foxaihub-image",
"i2v_model": "minimax-h3",
"watermark": {
"video_enable": true,
"image_enable": false,
"position": "right-bottom",
"text": "FoxAIHub",
"font_size": "medium"
}
}
Response
{ "mv_id": "6f1f4d2e-2b23-4c4e-9d3d-2a1c4c9d1f77" }
没有 success 外壳。
Errors
| Status | When | Body |
|---|---|---|
400 |
某个字段校验失败 | Zod 校验错误列表 |
401 |
api-key 缺失或无效 | {"message":"Unauthorized"} |
402 |
余额未大于 $5.00(指定 audio_duration 时还需覆盖预估视频费) | {"success":false,"error":"..."} |
413 |
请求体超过 83951616 字节 | {"success":false,"error":"Request body exceeds 83951616 bytes"} |
500 |
素材上传失败、缺少定价配置,或任务写入失败 | {"success":false,"error":"..."} |
GET /mv/task
按创建时间倒序列出你的任务。免费。
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
limit |
integer | No | 1-100,默认 20 |
offset |
integer | No | >= 0,默认 0 |
Response
{ "success": true, "tasks": [ { "mv_id": "...", "status": "videos_generation", "assets_summary": { "images": {...}, "videos": {...} }, ... } ], "limit": 20, "offset": 0 }
assets 被替换为 assets_summary;响应里没有总数,需要一直翻页直到返回条数小于 limit。
Errors
| Status | When | Body |
|---|---|---|
400 |
limit / offset 超出范围 | Zod 校验错误列表 |
401 |
api-key 缺失或无效 | {"message":"Unauthorized"} |
GET /mv/task/{mv_id}
读取一个任务及其全部素材。免费。这是唯一一个无论成功还是失败都不带 success 外壳的 MV 接口。
Response
{ "mv_id": "...", "status": "videos_generation", "params": {...}, "final_cut_url": null, "identity_prompt": "...", "assets": [ { "asset_id": "A001", "tool": "i2v", "image": {...}, "video": {...} } ] }
Errors
| Status | When | Body |
|---|---|---|
401 |
api-key 缺失或无效 | {"message":"Unauthorized"} |
404 |
mv_id 未知、格式不对,或属于别的账号 | {"error":"task not found"} |
POST /mv/task/{mv_id}/assets
上传你自己生产的素材。免费。从 images_generation 起均可使用,不会改变任务状态(failed 的任务除外)。
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
assets |
array | Yes | 1-100 项 |
assets[].asset_id |
string | Yes | 1-32 字符 |
assets[].kind |
enum | Yes | image 或 video |
assets[].url |
string | No | HTTP(S) URL,<= 512 字符;url 与 error 二选一必填 |
assets[].error |
string | No | 1-1000 字符,将该素材标记为失败 |
Request Body
{
"assets": [
{ "asset_id": "A001", "kind": "image", "url": "https://example.com/frames/A001.png" },
{ "asset_id": "A002", "kind": "video", "url": "https://example.com/clips/A002.mp4" },
{ "asset_id": "A003", "kind": "image", "error": "这一帧我这边没能产出" }
]
}
Response
{ "success": true, "results": [ { "asset_id": "A001", "kind": "image", "ok": true } ], "status": "images_done" }
即使全部失败,HTTP 状态码依然是 200——一定要检查每一项的 ok。
Errors
| Status | When | Body |
|---|---|---|
400 |
空数组、超过 100 项、非 HTTP 链接、既没给 url 也没给 error | Zod 校验错误列表 |
401 |
api-key 缺失或无效 | {"message":"Unauthorized"} |
404 |
任务不存在 | {"success":false,"error":"task not found"} |
409 |
任务当前状态不在可写窗口内 | {"success":false,"error":"task does not accept assets (status: storyboard)"} |
POST /mv/task/{mv_id}/assets/{asset_id}/regenerate
付费重新生成一个由平台拥有的素材。立即扣费。任务状态不变(failed 的任务除外),新版本原地重跑。
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
kind |
enum | Yes | image 或 video |
prompt |
string | No | 1-4096 字符,替换本次运行使用的 prompt;留空则沿用原 prompt |
Request Body
{
"kind": "image",
"prompt": "同一个镜头,改成黄昏时分的低角度仰拍"
}
Response
{ "success": true, "asset_id": "A001", "kind": "image", "cost": 0.02, "status": "videos_done" }
Errors
| Status | When | Body |
|---|---|---|
400 |
kind 缺失或无效、prompt 过长 | Zod 校验错误列表 |
401 |
api-key 缺失或无效 | {"message":"Unauthorized"} |
402 |
余额不足以支付本次 regenerate | {"success":false,"error":"Insufficient balance"} |
403 |
素材是自备的(model: "none") | {"success":false,"error":"this asset is generated by you, use POST /mv/task/{mv_id}/assets instead"} |
404 |
任务不存在,或该 kind 下 asset_id 不存在 | {"success":false,"error":"asset not found"} |
409 |
任务当前状态不在可写窗口内 | {"success":false,"error":"task does not accept regenerate (status: storyboard)"} |
POST /mv/task/{mv_id}/advance
驱动一道人工关卡:storyboard_done -> images_generation、images_done -> videos_generation、videos_done -> editing,或 completed -> editing(替换素材后重新剪辑)。免费。请求体可省略。
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
to |
string | No | 状态名之一,会与唯一合法的下一步状态做比对 |
Request Body
{
"to": "videos_generation"
}
也可以传空对象或干脆不带请求体——关卡的下一步状态本来就只有一个。
Response
{ "success": true, "mv_id": "...", "status": "videos_generation" }
Errors
| Status | When | Body |
|---|---|---|
400 |
to 不是合法的状态名,或请求体不是合法 JSON | {"success":false,"error":"unknown status: whoops"} |
401 |
api-key 缺失或无效 | {"message":"Unauthorized"} |
404 |
mv_id 未知,或属于别的账号 | {"success":false,"error":"task not found"} |
409 |
任务不在关卡上,或 to 指向了其他状态 | {"success":false,"error":"task is not ready to advance (status: images_generation)"} |
409 |
当前这半程还有素材是 failed 或 awaiting_user。任务状态不变,补齐素材后可以再调用 | {"success":false,"error":"image assets are incomplete (3 failed of 12); regenerate them before advancing","status":"images_done","assets":{"pending":0,"processing":0,"completed":9,"failed":3,"awaiting_user":0,"total":12}} |
POST /mv/events/{mv_id}
向任务的进度事件流追加一条自定义事件。
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
type |
string | Yes | 1-64 字符 |
message |
string | object | array |
Request Body
{
"type": "progress",
"message": { "step": "review", "note": "客户已确认首帧" }
}
Response
{ "success": true, "event": { "mv_id": "...", "type": "progress", "message": "...", "created_at": "..." } }
Errors
| Status | When | Body |
|---|---|---|
400 |
type 或 message 缺失/过长 | Zod 校验错误列表 |
401 |
api-key 缺失或无效 | {"message":"Unauthorized"} |
404 |
mv_id 不是合法 UUID、未知,或属于别的账号 | {"success":false,"error":"task not found"} |
500 |
事件追加失败 | {"success":false,"error":"failed to append event"} |
GET /mv/events/{mv_id}
以「裸数组」形式读取进度事件流,按时间从旧到新排列。免费。
Response
[ { "mv_id": "...", "type": "status", "message": "pending", "created_at": "..." } ]
无论成功还是失败都没有 success 外壳——请以 HTTP 状态码来判断。
Errors
| Status | When | Body |
|---|---|---|
401 |
api-key 缺失或无效 | {"message":"Unauthorized"} |
404 |
mv_id 不是合法 UUID、未知,或属于别的账号 | {"error":"task not found"} |
Webhook
跟踪 MV 任务进度有三种方式:轮询 GET /mv/task/{mv_id}、读取上一节的事件流,或者配置 Webhook。生产环境推荐用 Webhook——你的服务器会被主动推送每一次状态变化,不需要自己维护轮询或长连接。事件流更适合开发调试:临时排查问题很方便,但只有 24 小时生命周期、接口本身不做鉴权,不适合长期依赖。
Note: Webhook 在控制台(Dashboard)中配置(一个账户对应一个接收地址),不按任务设置——配置一次,之后所有 MV 任务(以及其他任务类型)的事件都会自动推送到该 URL,直接在你的服务器上使用该地址接收即可。
投递格式
每次投递都是一次 POST,请求体是一个固定信封:
{
"event": "mv.status",
"type": "mv",
"delivery_id": "9f2c1a3e-abcd-4d11-8f00-0123456789ab",
"created_at": "2026-08-26T09:31:02.000Z",
"data": { "mv_id": "...", "type": "status", "message": "videos_done", "created_at": "..." }
}
| 请求头 | 值 |
|---|---|
| Content-Type | application/json |
| User-Agent | FoxAI-Webhook/1 |
| X-Webhook-Event | 与 body 里的 event 相同 |
| X-Webhook-Type | 与 body 里的 type 相同 |
| X-Webhook-Delivery | 与 body 里的 delivery_id 相同 |
| X-Webhook-Timestamp | 投递发起时的 Unix 秒时间戳 |
结果类型
一个地址会收到账户下所有业务的推送,顶层的 type 用来说明这条投递属于哪种结果。整条 MV 流水线无论走到哪个阶段,type 恒为 mv:
| type | 结果类型 |
|---|---|
| mv | 音乐视频流水线事件 |
| asr-serverless | 音乐转写任务到达终态 |
| ping | 在 Dashboard 触发的测试投递 |
Note: 注意区分两个 type:顶层的 type 恒为 mv,表示这是哪种业务;data.type 是事件自身的子类型(status、progress、asset、context、error)。
MV 事件
MV 任务推送的事件名是 mv. 加上事件流的 type:
| Webhook 事件名 | 对应的事件流 type |
|---|---|
| mv.status | status |
| mv.progress | progress |
| mv.asset | asset |
| mv.context | context |
| mv.error | error |
data 字段与 GET /mv/events/{mv_id} 读到的对象逐字节一致,可以直接复用同一套解析逻辑。
重试策略
- 单次超时 10 秒。
- 失败后按 5s -> 10s -> 20s 重试,最多 4 次尝试(约 35 秒内结束)。
- 只对网络错误、超时、HTTP 408、429 与 5xx 重试;其余 4xx 不重试。
- 同一条投递在重试期间保持同一个 X-Webhook-Delivery(即 body 里的 delivery_id),可以用它做幂等去重。
安全性
Warning: 当前版本不包含签名——任何知道你 Webhook 地址的人都可以伪造请求。建议在 URL 里加入足够长的随机路径片段,并始终把收到的 webhook 当作一次「该去查一下了」的提醒,用 GET /mv/task/{mv_id} 核实之后再采信,而不是把 payload 当作权威结果直接使用。
出于同样的原因,创建或更新 Webhook 地址时会拒绝 localhost、内网 IP,以及带用户名/密码的 URL。
限制与失败处理
输入限制
| 限制项 | 取值 |
|---|---|
| audio_duration | 0-360 秒;360 秒同时也是平台对所有任务的硬上限 |
| 音频或图片文件大小 | <= 30 MB |
| prompt | 1-4096 字符 |
| 片段 duration_seconds | 整数,被限制在 3-15 秒之间 |
重试与部分失败
- 单个子任务(一张图或一段片段)最多重试 3 次,之后才会被标记为 failed。
- 单个素材失败不会导致整支 MV 失败——你可以上传替代素材,也可以付费重新生成。
- 如果某个阶段的素材全部失败,整个任务会被标记为 failed;已经实扣的导演费和首帧费不退,也不会再收视频费。
- 停在人工关卡(storyboard_done / images_done / videos_done)的任务没有超时限制。
事件流限制
| 限制项 | 取值 |
|---|---|
| 事件流生命周期 | 24 小时,每次追加都会刷新 |
| 每条流的事件数上限 | 1000 条;更早的事件会被丢弃 |
| 单条事件大小上限 | 32 KB;超出会被截断,并附带 truncated: true |
Webhook
Webhook 的完整说明、投递格式与重试策略见上方「Webhook」一节。
推荐的本地表结构
如果你打算在自己的系统里持久化这些任务和素材,而不是每次都实时调用 API,下面是一套面向调用方、以 MySQL 8 为准的建议表结构。这只是一个推荐起点,不是强制规范——按你自己的业务需求增删字段即可。
任务表
把 GET /mv/task/{mv_id} 返回的整份 params 原样存进一个 JSON 列,而不是逐字段拆成十几列——这样上游给 params 加新字段时,你的表结构不用跟着改。status 用 CHECK 约束(MySQL 8.0.16+ 支持)锁定合法取值:
CREATE TABLE mv_tasks (
mv_id CHAR(36) NOT NULL PRIMARY KEY,
status VARCHAR(20) NOT NULL,
aspect_ratio VARCHAR(16) NOT NULL,
resolution VARCHAR(16) NOT NULL,
params JSON NOT NULL,
identity_prompt TEXT NULL,
final_cut_url VARCHAR(512) NULL,
final_cut_watermarked_url VARCHAR(512) NULL,
created_at DATETIME(3) NULL,
updated_at DATETIME(3) NULL,
finished_at DATETIME(3) NULL,
synced_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
CONSTRAINT chk_mv_tasks_status CHECK (status IN (
'pending','processing','storyboard','creating','storyboard_done','images_generation',
'images_done','videos_generation','videos_done','editing','completed','failed','unknown'
))
) ENGINE=InnoDB;
CREATE INDEX idx_mv_tasks_status ON mv_tasks (status);
素材表
image 和 video 各存一份 JSON,直接对应 API 里的同名子对象;用 MySQL 8 的虚拟生成列把最常用来筛选的 status 单独提出来建索引,不需要手工维护一份冗余字段:
CREATE TABLE mv_assets (
mv_id CHAR(36) NOT NULL,
asset_id VARCHAR(32) NOT NULL,
tool VARCHAR(16) NULL,
image JSON NULL,
video JSON NULL,
image_status VARCHAR(16) GENERATED ALWAYS AS (image->>'$.status') VIRTUAL,
video_status VARCHAR(16) GENERATED ALWAYS AS (video->>'$.status') VIRTUAL,
synced_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (mv_id, asset_id),
CONSTRAINT fk_mv_assets_task FOREIGN KEY (mv_id) REFERENCES mv_tasks (mv_id) ON DELETE CASCADE,
CONSTRAINT chk_mv_assets_tool CHECK (tool IS NULL OR tool IN ('i2v', 'lip-sync'))
) ENGINE=InnoDB;
CREATE INDEX idx_mv_assets_image_status ON mv_assets (image_status);
CREATE INDEX idx_mv_assets_video_status ON mv_assets (video_status);
Webhook 投递去重表
上一节「Webhook」提到,同一条投递重试时会带着相同的 delivery_id。用这张表配合 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE,避免同一次状态变化被处理多次:
CREATE TABLE mv_webhook_deliveries (
delivery_id CHAR(36) NOT NULL PRIMARY KEY,
mv_id CHAR(36) NULL,
event VARCHAR(64) NOT NULL,
payload JSON NOT NULL,
received_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
) ENGINE=InnoDB;
CREATE INDEX idx_mv_webhook_deliveries_mv_id ON mv_webhook_deliveries (mv_id);
同步策略
- 调用 POST /mv/task 成功后,立即用返回的 mv_id 插入一行 mv_tasks。
- 收到 webhook,或轮询到状态变化时,用 mv_id / asset_id 作为自然键,对 mv_tasks 和 mv_assets 做 ON DUPLICATE KEY UPDATE——不用先查再判断插入还是更新。
- 靠 status、image_status、video_status 上的索引驱动后台任务:找出停在 storyboard_done / images_done / videos_done 需要调用 advance 的任务,或者素材停在 awaiting_user 需要你上传的任务。
Note: 以上只是一个推荐起点,请按自己的业务需求调整字段和索引;本地库里除了 api-key 之外,不需要也不应该存放其他敏感凭证。
计费规则
MV 计费由「首帧图片费」和「音频时长视频费」组成,外加一笔押金性质的「导演费」,不预扣:创建任务只校验余额;分镜交付素材表时先收一笔固定导演费($0.20);每张首帧由平台生成成功时实扣这一张的图片费;整支 MV 的视频费要等任务进入 videos_done 才收,收取的同时导演费全额退还。
首帧图片可选择平台生成(按张计费)或自备上传(0 元)。视频费用按整支 MV 的计费音频时长一次性收取,平台需要执行节拍分析、分镜脚本、镜头规划和视频生成等完整制作流程。
价格标准
| 收费项目 | 计费标准 |
|---|---|
| 创建任务 | 不扣费 |
| 创建任务最低余额 | 账户余额需大于 $5.00(指定时长时需同时覆盖预估视频费) |
| 首帧图片生成(foxaihub-image) | 每张 $0.02,按平台实际成功生成的镜头计费 |
| 首帧图片自备(none) | 免费(0 元) |
| 导演费(押金) | 每支 MV $0.20,分镜交付素材表时收取,任务进入 videos_done 时全额退还 |
| 视频制作(768p) | 按计费音频时长每秒 $0.015,任务进入 videos_done 时整支 MV 仅收取一次 |
| 视频制作(1080p) | 按计费音频时长每秒 $0.03,任务进入 videos_done 时整支 MV 仅收取一次 |
| 单素材重新生成(Regenerate) | 按所选图片单价或视频片段时长即时独立扣费 |
| 阶段推进(Advance)与成片剪辑 | 免费 |
| 所有查询接口(GET) | 免费 |
| 自行上传素材 | 免费 |
扣费时机
创建任务时不扣钱,只校验余额。若创建时指定了 audio_duration,系统会提前预估视频费,余额门槛为 max(最低余额, 预估视频费)。
音频预处理完成后,系统获取音频实际有效时长(上限 360 秒),算出整支 MV 的视频费并校验余额。余额不足时任务直接终止,这一步不扣钱。
- 导演费:分镜交付素材表(GET /mv/task/{mv_id} 开始返回 assets)的那一刻收取的一笔固定费用,整支 MV 只收一次;素材表交付之前任务失败则不收。
- 视频费:任务进入 videos_done(没有片段还在生成)的那一刻实扣,整支 MV 只收一次;即使部分片段失败也按全额收取。任务如果始终没到 videos_done(停在某道关卡,或所有片段都失败),就不收视频费。
- 导演费退还:videos_done 收取视频费的同时,导演费会全额退回余额。始终到不了 videos_done 的任务,只付导演费和已生成的首帧费。
- 从 images_done 推进到 videos_generation 前,账户余额必须能覆盖视频费,否则接口返回 402。
- 首帧图片费:每张由平台生成成功时实扣这一张,生成失败的不收费。
- 实扣时若余额已不足,系统扣到余额为 0 为止,任务照常继续,差额记为欠款。
- 用户自备素材(source: user)不计费。
单素材重新生成(Regenerate)
在生成和审阅阶段,若需对某个镜头重新生成图片或视频,调用 regenerate 接口将按素材单价即时扣费。无论后续该镜头是否被成片采用,重跑费用均不退回。
费用算例
下面的算例中,$0.20 的导演费先收、在任务进入 videos_done 时退还,所以不计入最终合计。
算例 1:全自动生成 768p MV(24 个镜头由平台生成首帧,计费音频时长 90 秒):
| 计费项 | 计算过程 | 金额 |
|---|---|---|
| 首帧图片费 | 24 张 x $0.02 | $0.48 |
| 视频制作费(整支音频一次性) | 90 秒 x $0.015 | $1.35 |
| 实际总扣费 | $0.48 + $1.35 | $1.83 |
算例 2:自备首帧 + 768p 视频生成 + 1 次 5 秒视频片段重跑(音频时长 60 秒):
| 计费项 | 计算过程 | 金额 |
|---|---|---|
| 首帧图片费 | 自备上传(image_model: none) | $0 |
| 视频制作费(整支音频一次性) | 60 秒 x $0.015 | $0.9 |
| 片段重跑(调用时即时扣费) | 1 次 x 5 秒 x $0.015 | $0.075 |
| 实际总支出 | $0.9 + $0.075 | $0.975 |
账单明细说明
在用户控制台「交易记录」中,MV 任务相关的扣费明细如下:
| 交易条目 | 产生时机与说明 |
|---|---|
| MV Generation | 任务进入 videos_done 时实扣的整支 MV 视频费 |
| 导演费 / 导演费退还 | 素材表交付时收取;任务进入 videos_done 时以负数流水退还 |
| 首帧图片模型(foxaihub-image) | 每张首帧生成成功时的扣费,以及单张首帧重新生成(即时扣费) |
| MiniMax H3 | 单个视频片段重新生成(即时扣费) |