媒体上传
通过预签名直传把本地图片、视频、音频送进 AllToken。
概述
视频生成与图生图都会用到媒体素材 —— 参考图、参考视频与音频、源图、蒙版。怎么送进来只有一条判据:
- 素材已在公网(你的 CDN / 对象存储)—— 直接填它的
http(s)URL,零上传开销。仅视频参考素材支持。 - 素材在本地 —— 先用预签名直传上传,再用返回的
upload_id引用。这是本地文件唯一的通路,也是图生图(源图与蒙版)唯一的通路。
警告
视频参考素材的 data: base64 内联通路已于 2026-08-06 下线,与图生图侧对齐(图生图的历史 multipart 上传更早已下线)。本地素材现在一律走预签名直传。
上传端点是 POST https://api.alltoken.ai/v1/uploads/presign(复数 uploads)。别和账户服务的 /upload/presign 搞混,后者是站内头像等用途,只收图片 MIME。
选哪条通路
| 你的场景 | 通路 | 理由 |
|---|---|---|
| 视频参考素材已在你的 CDN / OSS 上 | 填 url | 完全不用上传 —— 字节不经过我方网关与存储 |
| 视频参考素材在本地(图 / 视频 / 音频) | 预签名直传 | 本地文件唯一通路 |
图生图(/images/edits、/images/variations)的源图与蒙版 | 预签名直传 | 唯一入口,无论文件多大 |
三步直传
文件不经过我方网关。你先换一个一次性直传地址,把原始字节 PUT 到对象存储,再用返回的 upload_id 创建任务。网关只搬运一个几十字节的 ID。视频参考素材与图生图共用这套机制,区别只在 purpose 和引用它的字段。
第 1 步 —— 申请直传地址:
$POST https://api.alltoken.ai/v1/uploads/presign请求体:purpose(决定允许的 MIME 与体积,见下)、content_type(文件 MIME,小写)、content_length(精确字节数)、content_md5、checksum_sha256。
警告
两个摘要的编码方式不同:content_md5 是 base64,checksum_sha256 是 hex。写反了会在 presign 阶段就被 400 拦下。
响应返回 upload_id(upl_ 开头)、upload_url(预签名 PUT 地址)、required_headers(第 2 步原样回填)、expires_in(URL 有效期,300 秒)、claim_expires_at(upload_id 有效期,2 小时)、max_content_length。
第 2 步 —— 把原始字节 PUT 到 upload_url,并把 required_headers 里的头原样回填。body 是原始文件字节 —— 不是 base64、不是 multipart:
| 1 | PUT <upload_url> |
| 2 | Content-Type: video/mp4 |
| 3 | Content-MD5: N0xUuLBP5C0uKJ2i0fA1kA== |
| 4 | |
| 5 | <原始字节> |
警告
少带或改写任何一个必需头,对象存储会直接返 403 —— 预签名把这些头也签进去了。不要自己加 Content-Encoding、不要 gzip、不要走 multipart。
第 3 步 —— 用 upload_id 创建任务。视频在 content[].upload_id 里用它;图生图用 image_upload_id / mask_upload_id 顶层字段。网关会校验字节数与 ETag、把对象转入与该任务绑定的正式区、换成上游可访问地址提交 —— 全程对你透明,你只看到 202 + task_id。
三条必须记住的规则:
- 同一项里
upload_id与 URL 字段互斥 —— 两个都填返 400upload_url_conflict。 - presign 与创建任务必须用同一把 API Key —— 归属按(用户 + API Key + purpose)校验,换 Key 返 404
upload_not_found。 purpose必须与用途对应,用错也返 404upload_not_found。
purpose 对照
每个 purpose 固定了允许的 MIME 与单文件体积上限。
purpose | 用在 | 允许的 MIME | 单文件上限 |
|---|---|---|---|
video_input_image | 视频参考图 / 首尾帧 | image/png、image/jpeg、image/webp、image/gif | 20 MiB |
video_input_video | 视频参考视频 | video/mp4、video/quicktime、video/webm | 100 MiB |
video_input_audio | 视频参考音频 | audio/wav、audio/mpeg、audio/mp4、audio/aac、audio/ogg | 20 MiB |
image_edit_source | 图生图源图 | image/png、image/jpeg、image/webp | 25 MiB |
image_edit_mask | 图生图蒙版 | image/png、image/webp(不收 jpeg) | 25 MiB |
image_variation_source | 图生图变体源图 | image/png、image/jpeg、image/webp | 25 MiB |
蒙版不收 image/jpeg,是因为需要 alpha 通道表达「改哪里」,而 jpeg 没有透明通道。
时间窗与配额
| 时间窗 | 长度 | 过期后 |
|---|---|---|
upload_url(直传地址) | 5 分钟 | PUT 返 403 —— 重新 presign |
upload_id(素材凭据) | 2 小时 | 创建任务返 409 upload_expired |
也就是说:拿到地址就传,但传完之后有两小时从容组装并提交生成请求。
一个 upload_id 只绑定一次任务,重复使用返 409 upload_already_used。这是防重复计费的设计 —— 一份素材、一次任务、一条账单。任务创建失败时,临时对象会被自动回收。
| 限制 | 阈值 | 超出返回 |
|---|---|---|
同时未使用的 upload_id | 100 个 | 429 quota_exceeded |
| 每自然日(UTC)直传总字节 | 2 GiB | 429 quota_exceeded |
正常「传完就用」的节奏不会碰到这两条。大批量图生图请注意日上限:25 MiB × 约 80 张就到 2 GiB。
上传错误码
上传阶段(presign):
| HTTP | code | 含义 |
|---|---|---|
| 400 | invalid_purpose | purpose 拼错或不支持 |
| 400 | invalid_content_type | MIME 不在该 purpose 白名单内(蒙版传 jpeg 会中这条) |
| 400 | invalid_content_md5 | MD5 不是合法 base64(别写成 hex) |
| 400 | invalid_checksum | SHA-256 不是 64 位小写 hex(别写成 base64) |
| 413 | payload_too_large | content_length 超过该 purpose 上限 |
| 429 | quota_exceeded | pending 超 100 个,或当日超 2 GiB |
| 503 | storage_unavailable | 存储暂不可用 —— 稍后重试 |
绑定阶段(创建任务时):
| HTTP | code | 含义 |
|---|---|---|
| 404 | upload_not_found | upload_id 不存在、换了 API Key,或 purpose 不匹配 |
| 409 | upload_already_used | 已绑过任务 —— 重新 presign |
| 409 | upload_expired | 超过 2 小时 —— 重新 presign |
| 400 | upload_object_missing | 只 presign 没 PUT 就来创建任务 |
| 400 | upload_size_mismatch | 实际字节数与 content_length 不符 |
| 409 | upload_modified | 上传后对象被改动(ETag 不符) |
| 400 | upload_url_conflict | 同一项里 upload_id 与 URL 字段同时填了 |