# 离线视频编辑 API

上传原视频，通过提示词、可选参考图和遮罩视频完成异步视频编辑。该能力独立提供，不会创建普通视频生成任务或写入素材业务列表。

[阅读网页版](/app/docs/offline-video) · [下载 Markdown](/v1/docs/offline-video.md)

## 开始使用

1. 联系管理员为账号开启「离线视频编辑」，并充值视频应用当前使用的钱包。
2. 使用视频应用的 API Key 上传原视频，获取素材 ID。
3. 查询预计费用，携带唯一的 `Idempotency-Key` 创建任务。
4. 每隔 10 秒查询任务；成功后下载结果。

以下示例中，`BASE_URL` 是视频应用服务地址，例如 `https://video.example.com`；`VIDEO_API_KEY` 是你的视频应用 API Key。示例域名需要替换为实际服务地址。

```bash
export BASE_URL="https://video.example.com"
export VIDEO_API_KEY="YOUR_VIDEO_API_KEY"
```

所有业务接口使用 `Authorization: Bearer YOUR_VIDEO_API_KEY`。素材和任务严格按账号隔离。账号开关关闭后，不接受新的上传、估价或任务，但已接受的任务继续处理、结算，仍可查询。

## 查询账号开通状态

`GET /v1/offline-video/access`，使用相同的 Bearer 鉴权。

未开通返回 `{"enabled":false}`；已配置账号返回 `{"enabled":true,"max_active_tasks":5,"daily_limit_usd":"10.000000"}`。此接口只表示账号权限，不代表服务实时可用。

## 上传素材

`POST /v1/offline-video/assets?kind=video`

使用 `multipart/form-data`，字段 `file` 为文件二进制。`kind` 为 `video` 或 `image`。

```bash
curl "$BASE_URL/v1/offline-video/assets?kind=video" \
  -H "Authorization: Bearer $VIDEO_API_KEY" \
  -F "file=@source.mp4"
```

HTTP 201 示例：

```json
{
  "id": "oea_0123456789abcdef0123456789abcdef",
  "kind": "video",
  "metadata": {"duration_seconds": "10", "fps": "24", "width": 1280, "height": 720},
  "expires_at": 1793232000
}
```

- 视频须为 MP4，时长 1–600 秒，帧率大于 0 且不超过 120，宽高均不超过 4096 像素。
- 参考图支持 PNG、JPEG、WebP，最多 2400 万像素；图片会标准化处理。
- 单个上传文件非空且不超过 100 MiB，每账号每小时最多 100 次成功上传；同账号同时只允许一个上传。
- 素材 ID 有效期 24 小时；只接受本账号通过此接口取得的 ID，不接受外部 URL 或其他素材系统的 ID。
- 遮罩视频使用 `kind=video` 上传。请准备与原视频时序、尺寸匹配的遮罩；复杂素材的处理效果需自行验证。

上传素材本身不扣取视频编辑费用。有效期是素材可用于新任务的期限，不代表文件立即物理删除。

## 预估费用

`POST /v1/offline-video/estimate`

请求体与创建任务相同。估价不创建任务，也不预扣余额。

```bash
curl "$BASE_URL/v1/offline-video/estimate" \
  -H "Authorization: Bearer $VIDEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"source_asset_id":"oea_0123456789abcdef0123456789abcdef","prompt":"将画面改为水彩动画风格，保留人物动作","fps":24}'
```

```json
{"currency":"USD","estimated_cost_usd":"0.050000","max_cost_usd":"0.050000","pricing_version":1,"billable_units":15}
```

示例金额仅用于说明。请以估价接口和任务创建时锁定的价格为准；估价与创建之间可能发生调价。

## 创建任务

`POST /v1/offline-video/tasks`

| 参数 | 必填 | 说明 |
| --- | --- | --- |
| `source_asset_id` | 是 | 原视频素材 ID |
| `prompt` | 是 | 编辑要求，去掉首尾空白后 1–1024 字符 |
| `reference_asset_id` | 否 | 参考图片素材 ID |
| `mask_asset_id` | 否 | 遮罩视频素材 ID |
| `quality` | 否 | 固定为 `hd`，默认 `hd`；不承诺固定输出像素尺寸 |
| `fps` | 否 | 默认 24；显式传 `null` 使用检测到的原视频帧率 |

`fps` 可选：8、10、12、15、16、18、20、22、24、25、30、45、48、50、60、72、90、100、120。23.976、29.97 等非整数源帧率应传 `null`。不接受未知参数。

必填请求头 `Idempotency-Key`：1–128 位英文字母、数字、点、冒号、横线或下划线。同账号相同 Key 和参数始终返回同一任务；相同 Key 配不同参数返回 409。连接超时时，应使用原 Key 和原请求体重试，不要更换 Key。

```bash
curl "$BASE_URL/v1/offline-video/tasks" \
  -H "Authorization: Bearer $VIDEO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: edit-order-20261028-001" \
  -d '{"source_asset_id":"oea_0123456789abcdef0123456789abcdef","prompt":"将画面改为水彩动画风格，保留人物动作","quality":"hd","fps":24}'
```

HTTP 202 表示任务已经登记，不表示处理成功。返回示例：

```json
{
  "id":"oet_0123456789abcdef0123456789abcdef",
  "status":"queued",
  "created_at":1793145600,
  "updated_at":1793145600,
  "billing":{"currency":"USD","reserved_usd":"0.050000","charged_usd":null,"refunded_usd":"0.000000","settled":false}
}
```

## 查询任务与下载结果

`GET /v1/offline-video/tasks/{task_id}`

```bash
curl "$BASE_URL/v1/offline-video/tasks/oet_0123456789abcdef0123456789abcdef" \
  -H "Authorization: Bearer $VIDEO_API_KEY"
```

| 状态 | 含义 | 客户端动作 |
| --- | --- | --- |
| `queued` | 排队或正在确认余额预扣 | 继续查询 |
| `processing` | 正在处理、归档或完成结算 | 继续查询 |
| `succeeded` | 结果已归档且结算完成 | 下载 `output.video_url` |
| `failed` | 已确认失败，结算完成 | 查看错误和退款金额 |
| `review` | 提交、费用或交付状态需核实 | 保留任务 ID 联系管理员，不要创建替代任务 |

成功时新增字段：

```json
{
  "output":{"video_url":"https://media.example.com/private/result.mp4?signature=REDACTED","url_expires_at":1793149200},
  "expires_at":1793750400,
  "billing":{"currency":"USD","reserved_usd":"0.050000","charged_usd":"0.050000","refunded_usd":"0.000000","settled":true}
}
```

结果访问期为成功归档后 7 天。下载链接最长有效 1 小时，在结果访问期内重新查询即可刷新。过期后 `output.video_url` 为 `null`。链接是临时访问凭证，请勿公开传播。音轨保留、精确画质和处理耗时不作保证。

任务列表：`GET /v1/offline-video/tasks?limit=20&offset=0`。`limit` 范围 1–100，`offset` 非负；按创建时间倒序返回本账号任务，响应为 `{"data":[],"limit":20,"offset":0}`。

## 计费与额度

计费单位根据服务端检测的原视频时长和选定帧率计算：

```text
计费单位 = max(floor(1.5 × 处理帧率 / 24 × 视频秒数), 5)
费用 = 计费单位 × 单位价格
```

当前标准售价为每 600 计费单位 $2：

| 时长（24 fps） | 计费单位 | 费用（USD） |
| --- | --- | --- |
| 2 秒 | 5 | 0.016667 |
| 10 秒 | 15 | 0.050000 |
| 60 秒 | 90 | 0.300000 |

以估价和创建时锁定的价格为准。上传、估价、查询不收取编辑费用。

费用以 USD 记录，精确到六位小数，向上取整。创建时锁定价格快照；成功按实际计费单位结算，收费不超过预扣金额，差额退还。确认失败退还预扣；余额不足且未成功预扣时，退款金额为零。`review` 不等于失败，不自动退款。

部分钱包的余额确认是异步的，因此 HTTP 202 后仍可能得到 `insufficient_balance`。以 `billing.settled` 和最终收费字段为准。任务并发和滚动 24 小时消费额度由管理员按账号配置，未结算预扣也计入额度。

## 错误处理

业务错误结构：

```json
{"detail":{"error":{"code":"feature_disabled","message":"Offline video editing is not enabled for this account."}}}
```

| HTTP 状态 / 错误码 | 含义 |
| --- | --- |
| 401 | API Key 缺失或无效 |
| 403 `feature_disabled` | 账号未开通 |
| 402 `insufficient_balance` | 余额不足 |
| 404 `asset_not_found` / `task_not_found` | 资源不存在、素材过期或无权访问 |
| 409 `idempotency_conflict` | 幂等 Key 与原请求参数不一致 |
| 413 `file_too_large` | 文件为空或超过上传上限 |
| 422 | 参数或媒体格式不符合要求；参数校验错误包含字段位置 |
| 429 `active_task_limit` / `daily_limit` / `upload_rate_limit` | 达到账号额度或上传限制 |
| 503 `service_unavailable` / `upload_failed` | 服务或上传暂不可用 |

本版不提供取消、实时流、回调通知或批量创建。请勿将任务提交成功等同于视频生成成功。
