视频生成 API 集成:任务、轮询与内容下载
用创建、轮询和内容下载流程集成视频生成,覆盖编辑与延长端点、Webhook、时长上限和授权媒体交付。
视频生成 API 集成是一种异步媒体工作流:先创建任务,等待完成或失败,再通过可能过期且必须授权的内容 URL 下载字节。遵循 Videos 表面的 OpenAI 兼容网关通常提供 POST /v1/videos、GET /v1/videos/{task_id} 和 GET /v1/videos/{task_id}/content。OpenAI 官方文档现在把编辑、延长、角色、Webhook 与 Batch 和创建/查询并列,而旧 remix 端点已不再适合新项目。本文基于仓库可见行为和官方文档说明如何安全集成,不虚构延迟或价格结论。
日期背景(2026-07-15): OpenAI 已宣布 Videos API 与 Sora 2 模型别名弃用,并将在 2026-09-24 关闭(参见 OpenAI 弃用公告)。不要仅基于 Sora 2 启动新的长期产品。如果仍通过网关承载 Sora 流量,应把当前视为迁移窗口:保持创建/轮询/内容下载可用,不为 remix-only 流程构建新 UX,并立即规划双供应商别名。
直接答案:把视频当作任务,而不是聊天补全
# 1) create
curl -sS https://YOUR_GATEWAY/v1/videos \
-H "Authorization: Bearer $GATEWAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "YOUR_VIDEO_MODEL_ALIAS",
"prompt": "Slow aerial push-in over a misty cedar forest at dawn",
"seconds": "4",
"size": "1280x720"
}'
# 2) poll (prefer webhooks when the upstream supports them; see below)
curl -sS https://YOUR_GATEWAY/v1/videos/TASK_ID \
-H "Authorization: Bearer $GATEWAY_KEY"
# 3) download when status=completed
curl -sS https://YOUR_GATEWAY/v1/videos/TASK_ID/content \
-H "Authorization: Bearer $GATEWAY_KEY" \
-o forest.mp4
应显式处理的状态包括 queued、in_progress、completed 和 failed,这些状态也存在于本代码库的视频 DTO。任务对象常见字段包括 id、model、status、progress、created_at、completed_at、expires_at、seconds、size、可选 error,以及派生任务存在时的 remixed_from_video_id 等 lineage 字段。
始终在供应商当前文档中重新检查参数矩阵,例如 OpenAI 的 视频生成指南 和 Videos API 参考。其他供应商,如 Google Veo / Gemini 视频表面、Runway、Luma、Kling、ByteDance/Volcengine,可能使用不同任务形状。网关也可能根据通道适配器暴露 /v1/video/generations 或 Kling 风格 /kling/v1/videos/* 等供应商专属路由。网关支持时,应用代码应优先使用归一化的 OpenAI 风格任务 API。
OpenAI 官方表面与本网关当前注册能力
| 能力(OpenAI 文档,2026-07-15) | 官方路由/事件 | 本仓库网关现状 |
|---|---|---|
| 创建视频 | POST /v1/videos | 支持,作为 OpenAI 风格适配器的主要任务提交路径 |
| 查询/轮询 | GET /v1/videos/{id} | 支持 |
| 下载内容 | GET /v1/videos/{id}/content | 支持,并检查所有权 |
| 编辑现有视频 | POST /v1/videos/edits(推荐;remix 已弃用) | 当前尚未归一化为一等网关路由集 |
| 延长视频 | POST /v1/videos/extensions | 当前尚未归一化 |
| 角色 | POST /v1/videos/characters + create 上的 characters | 当前尚未归一化 |
| 旧 remix | POST /v1/videos/{id}/remix(新集成已弃用) | 现有 Sora 风格适配器仍检测/注册 remix 路径 |
| Webhook | video.completed、video.failed | OpenAI 上游功能;网关客户端仍应实现轮询 fallback |
| Batch | Batch API 中指向 POST /v1/videos 的行 | 上游功能;不能替代产品任务表 |
架构评审中必须明确这个缺口:只理解创建/轮询/内容下载的产品代码更能承受多供应商路由;把 remix 硬编码为“编辑”按钮的代码会同时撞上 OpenAI 弃用和网关尚未完整归一化编辑/延长/角色能力的问题。
为什么视频集成比图片更难
| 关注点 | 图片(典型) | 视频(典型) |
|---|---|---|
| 延迟 | 秒级 | 数十秒到数分钟 |
| API 形状 | 通常单次响应 | 创建 + 等待 + 内容 |
| 载荷大小 | KB–MB | 很容易达到数 MB |
| 失败模式 | 审核/尺寸错误 | 长 timeout、部分进度、内容过期 |
| 成本风险 | n 和分辨率 | 时长、分辨率、编辑/延长链 |
| 创建后的 AuthZ | 可选 | 内容下载必须检查 |
如果直接套用聊天补全 timeout,例如 60 秒硬取消且不轮询,那么即使任务之后成功,视频集成看起来也会“坏掉”。从第一天就在应用中设计任务表。
推荐的应用数据模型
video_jobs
id (your id)
provider_task_id
model
prompt_hash / prompt_text (policy-dependent)
status
progress
seconds
size
billing_owner_type / billing_owner_id
team_id / project_id (nullable)
content_expires_at
storage_key (nullable until persisted)
source_task_id (edit/extension/remix lineage)
last_error
created_at / updated_at
Worker 从轮询或 Webhook 更新 status 与 progress。状态变为 completed 后,下载 worker 只获取一次 /content,保存到对象存储并设置 storage_key。状态为 failed 时,暴露 error.message / error.code,不要无限重试。
时长与计费上限
时长是计费乘数。本仓库使用 MaxTaskDurationSeconds = 3600 限制用户输入的视频时长,并在校验阶段拒绝越界 seconds。即使平台允许更长,产品也应设置更严格的 UX 上限,例如预览视频限制为 4–12 秒。OpenAI 官方创建示例通常使用离散秒数桶,当前 Sora 文档也包含较长的 16/20 秒变体;不要虚构任意时长。
集成规则:
- 谨慎解析
seconds,DTO 可能把它作为字符串传递 - 在服务器重新校验前,不要信任客户端时长来展示价格
- 向计费所有者(用户或团队)预扣,而不只是执行者
- 任务在预扣后失败时进行结算或退款
- 编辑/延长/remix 链也要采用同样规则,防止用户无意放大成本
所有者与执行者分离见 多租户 LLM 网关架构,归因字段见 LLM 使用日志与成本追踪。
等待策略:Webhook 优先,轮询兜底
OpenAI 视频指南建议大约每 10–20 秒退避轮询,并在可用时优先使用 Webhook(video.completed / video.failed)。文档说明下载 URL 生命周期很短,在 OpenAI 指南中约为生成后 1 小时,因此应尽快复制到自有存储。
产品 worker 的实用默认值:
- 当供应商及网关/运维链路支持签名交付时注册 Webhook。
- 用轮询兜底:从约 10s 开始,加入 jitter,在高负载或连续
in_progress时退避到 20–30s。 - 有
Retry-After时遵守它。 - 为总等待时间设置硬 timeout,并写入终态
expired/failed。 - 不要让每个浏览器标签页每几百毫秒轮询一次。
type VideoTask = {
id: string;
status: "queued" | "in_progress" | "completed" | "failed" | string;
progress?: number;
expires_at?: number;
error?: { message?: string; code?: string };
};
async function sleep(ms: number) {
await new Promise((r) => setTimeout(r, ms));
}
async function pollVideo(taskId: string, timeoutMs = 15 * 60_000): Promise<VideoTask> {
const started = Date.now();
let interval = 10_000;
while (Date.now() - started < timeoutMs) {
const res = await fetch(`${process.env.GATEWAY_BASE}/v1/videos/${taskId}`, {
headers: { Authorization: `Bearer ${process.env.GATEWAY_KEY}` },
});
if (res.status === 429) {
const ra = Number(res.headers.get("retry-after"));
await sleep(Number.isFinite(ra) ? ra * 1000 : interval);
interval = Math.min(interval + 5_000, 30_000);
continue;
}
if (!res.ok) throw new Error(await res.text());
const task = (await res.json()) as VideoTask;
if (task.status === "completed" || task.status === "failed") return task;
const jitter = Math.floor(Math.random() * 2_000);
await sleep(interval + jitter);
interval = Math.min(Math.floor(interval * 1.2), 30_000);
}
throw new Error("video poll timeout");
}
调优必须依据你的模型和地区的实测完成时间,而不是其他团队的博客。
内容下载 AuthZ 与工作台作用域
创建任务不等于之后有权流式读取字节。在多租户网关中,/v1/videos/{id}/content 必须检查所有权:创建任务时的同一用户/团队/项目作用域,或显式共享策略。跨租户猜测任务 ID 必须失败关闭。
本仓库中的工作台路径:
- UI:视频工作台
/api/user/video-workbench下的个人 API/api/teams/:team_id/video-workbench下带项目作用域操作的团队 API- 生成视频与管理视频资产等不同授权
这不同于仅个人作用域的 图片工作台。如果持久工作台播放依赖对象存储,应在承诺永久历史前完成配置。指向 /v1/videos/{id}/content 的 fallback 播放 URL 仍需实时 AuthZ,并可能受 expires_at 限制。
编辑、延长、角色与旧版 remix
OpenAI 对新集成的官方指导:
- 编辑: 使用
POST /v1/videos/edits,通过 prompt 和源video做定向修改;remix 已不再承担这项新工作。 - 延长: 使用
POST /v1/videos/extensions延续已完成片段;总时长会拼接,且 OpenAI 指南说明 extension 不支持 characters。 - 角色: 使用
POST /v1/videos/characters,然后在 create 中引用 character ID 以保持一致性。 - Batch: 离线队列由指向
POST /v1/videos的 JSONL 组成;OpenAI 文档中 batch 输出有独立留存窗口。
任何派生任务都应遵守以下集成规则,包括本网关仍保留的旧 remix:
- 只有 caller 能读取源任务时,才允许创建派生任务
- 从源任务复制租户字段,不要把派生任务留在错误项目下
- 明确告诉用户编辑/延长/remix 是一个新的计费任务,不是免费调整
- 为支持和审核持久化 lineage
- 对供应商专属 metadata bag 使用 feature flag;用全局上限校验时长与尺寸
在网关完成编辑/延长/角色归一化之前,应把这些调用放在显式适配器开关或原生上游客户端后面,不要假装 remix 是现代 OpenAI 路径。
持久化策略
应假设:
- 上游内容 URL 甚至网关临时代理都会过期(
expires_at是一等字段;OpenAI 记录交互式下载约 1 小时) - 产品页面需要你的对象键
- 缩略图应在下载后生成,而不是临时抓取供应商 CDN
- 团队离开或用户行使留存权利时必须能够删除
安全流水线:
completed → 授权内容获取 → 必要时病毒/类型嗅探 → 对象存储写入 → 数据库 storage_key → 通过带应用鉴权的 CDN 提供。
“等需要时再存储”会让 demo 变成事故工单;如果上游 Videos API 本身还在退役,这种问题尤其痛苦。
可观测性与支持
至少记录网关 request ID / task ID、模型别名、秒数、尺寸、状态转换、计费所有者和执行者、Webhook 或轮询路径,以及最终错误码。Dashboard 应把视频失败类别与聊天失败分开。支持工具应能打开任务和使用日志,而不把完整 MP4 下载到工单系统。
可能遇到的其他路由
根据通道适配器,运维人员还可能启用:
/v1/video/generations风格端点- Kling
/kling/v1/videos/* - Gemini/Vertex Veo 任务适配器
- Jimeng 或相似路由后的其他供应商任务 API
应用团队不应 hardcode 每条供应商路径。优先使用一个归一化客户端(/v1/videos + 稳定别名),由网关映射通道。如果某项能力只存在于供应商路径,例如 motion brush 或电影预设,应放在 feature flag 后并记录不可移植契约。
相关阅读
来源
- OpenAI 视频生成指南
- OpenAI Videos API 参考
- OpenAI 弃用公告(Videos API / Sora 2 → 2026-09-24)
- OpenAI Webhook 指南
- Google Gemini 视频生成
- Runway 开发者文档
- Luma AI API 文档
- OWASP Top 10 for LLM Applications
运维问答
可以阻塞 HTTP 请求直到视频完成吗?
只适合短时内部工具。产品 UI 应创建任务记录,并使用 Webhook 或轮询。长同步请求会放大网关和负载均衡器 timeout。
应使用什么轮询间隔?
默认从约 10 秒开始并加入 jitter,退避到 20–30 秒,遵守 Retry-After,可用时优先使用 video.completed / video.failed Webhook。OpenAI 指南给出的轮询区间约为 10–20 秒。
状态已完成后,为什么内容下载仍失败?
常见原因包括内容已过期、鉴权作用域错误、任务属于另一个项目,或存储/代理配置错误。重新生成前先检查 expires_at 与租户字段。
新产品应该使用 remix 吗?
不应把它作为主要编辑路径。OpenAI 官方文档将新编辑工作指向 POST /v1/videos/edits,remix 已弃用。本网关可能仍为兼容性接受 remix 风格路由,应把它当作旧版桥梁,而不是绿地设计。
已经调用 /v1/videos 后还需要视频工作台吗?
纯后端流水线不需要。当人类需要共享历史、预设或 UI 中的团队作用域资产管理时需要。
首个应交付的 Worker
实现一个 Worker:如果已有稳定的非 Sora 别名,就用它创建短视频;否则使用明确标为临时的 Sora 别名,并写下 2026-09-24 前的迁移日期。以 10s + jitter 轮询(Webhook 可选),只下载一次 /content,上传到对象存储,并证明第二个团队的密钥无法下载该任务 ID。完成这些验证后再把 happy path 接入产品 UI。