# 上传音频和视频

> 不超过95MB的文件可以直接提交；更大的文件分块上传，最大2GB，中断后可续传。

Canonical: https://transcript1.com/zh/docs/uploads
Language: zh-Hans
Last updated: 2026-10-14

本地音频和视频都可以上传后交给AI转录。文件不使用平台字幕，因此`source`只能设为`auto`或`stt`。语言和说话人分离由`language`、`diarize`控制，详见[来源与转录设置](https://transcript1.com/zh/docs/sources-and-options)。

## 文件格式和大小限制

支持的文件后缀为`mp3`、`wav`、`m4a`、`flac`、`ogg`、`opus`、`mp4`、`mov`、`webm`和`mkv`。提交其他后缀会收到`422 unsupported_media`。文件中必须有音轨，否则任务会报`unsupported_media`。

| 上传方式或限制 | 上限 |
|---|---|
| 直接上传（`multipart/form-data`） | 95MB |
| 分块上传的文件大小 | 2GB（2,000,000,000字节），具体套餐的上限可能更低。 |
| 每个团队同时等待完成的分块上传 | 20 |

文件超出上限时，接口返回`413 media_too_large`，并用`max_bytes`给出允许的最大字节数。

## 小文件直接上传

文件不超过95MB时，直接调用`POST /v1/transcriptions`，用`multipart/form-data`发送，文件字段名为`file`。需要调整设置时，在表单中添加`language`、`diarize`（`true`或`false`）、`source`（`auto`或`stt`）或`webhook_url`，这些字段均可省略。

```bash
curl -X POST "https://api.transcript1.com/v1/transcriptions?wait=60" \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY" \
  -F file=@interview.m4a \
  -F language=en \
  -F diarize=true
```

接口返回转录对象，其中`source.platform`为`upload`。状态码与提交视频链接时一致：仍在处理则返回`202`，任务结束则返回`200`。提交空文件会收到`422 invalid_request`。

## 大文件分块上传

大文件可以分块发送。如果上传中断，只需补传缺失的分块，不必从头开始。流程如下。

### 1. 申请上传

先用JSON提交文件名和文件大小，`mime_type`可以不填。带上`Idempotency-Key`请求头，后续就能安全重试这次创建操作：

```bash
curl -X POST "https://api.transcript1.com/v1/transcriptions" \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY" \
  -H "Idempotency-Key: interview-2026-10-13" \
  -H "Content-Type: application/json" \
  -d '{"upload":{"filename":"interview.m4a","size":48210334,"mime_type":"audio/mp4"},"language":"auto","diarize":true}'
```

创建成功会返回`201`。此时任务状态为`awaiting_upload`，上传信息在`upload`对象中：

```json
{
  "id": "tr_8fK2Lm0PqRsTuVwXyZaBcD",
  "object": "transcription",
  "status": "awaiting_upload",
  "upload": {
    "chunk_url_template": "https://api.transcript1.com/v1/transcriptions/tr_8fK2Lm0PqRsTuVwXyZaBcD/upload/chunks/{index}",
    "headers": { "X-Upload-Token": "t1u_..." },
    "expires_at": "2026-10-13T10:30:00.000Z",
    "chunks": [
      { "index": 0, "offset": 0, "size": 8388608 },
      { "index": 1, "offset": 8388608, "size": 8388608 }
    ]
  }
}
```

无需自行决定如何分块。服务器会在`chunks`中列出每块的范围和大小，每块约8MiB（8,388,608字节）；上例只展示了部分清单。`X-Upload-Token`仅用于本次上传，应妥善保管。它的有效期为1小时，过期时间见`expires_at`。

### 2. 按清单上传分块

逐块读取文件：从`offset`位置开始，取出`size`个字节。将`chunk_url_template`中的`{index}`替换为该块的`index`，向这个地址发送`PUT`请求。请求体使用`application/octet-stream`，字节数必须与清单中的`size`完全一致。认证只需带`X-Upload-Token`，不需要`Authorization`请求头。

```bash
curl -X PUT "https://api.transcript1.com/v1/transcriptions/tr_8fK2Lm0PqRsTuVwXyZaBcD/upload/chunks/0" \
  -H "X-Upload-Token: t1u_..." \
  -H "Content-Type: application/octet-stream" \
  --data-binary @chunk-0.bin
```

上传成功后，每块都会收到类似`{ "object": "upload_chunk", "index": 0, "size": 8388608 }`的响应。重复上传同一块会覆盖之前的数据。字节数不符或`index`不在清单中时，返回`422 invalid_request`；未带Token或Token已过期时，返回`401 unauthorized`。

### 3. 从中断处继续

调用`GET /v1/transcriptions/:id/upload`，检查服务器已收到哪些分块：

```json
{
  "object": "upload",
  "status": "awaiting_upload",
  "total_bytes": 48210334,
  "uploaded_bytes": 16777216,
  "received_chunks": [0, 1],
  "chunks": [ { "index": 0, "offset": 0, "size": 8388608 } ],
  "expires_at": "2026-10-13T10:30:00.000Z"
}
```

对照`received_chunks`，补传尚未收到的分块即可。如果Token已经过期，重新发送步骤1的创建请求，保持`Idempotency-Key`和所有参数不变。接口会返回原来的转录任务，并附上新的上传Token。

### 4. 确认上传完成

```bash
curl -X POST "https://api.transcript1.com/v1/transcriptions/tr_8fK2Lm0PqRsTuVwXyZaBcD/upload/complete" \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY"
```

所有分块发送完毕后，调用`POST /v1/transcriptions/:id/upload/complete`。服务器会逐块核对是否收到、大小是否正确，再合并文件并将转录任务加入队列。成功时返回`202`和转录对象。

如果返回`409 upload_incomplete`，按`missing_chunks`中的索引补传缺失分块，再次确认完成。上传已经完成或已取消时，会返回`409 invalid_state`。开始转录后，用`GET /v1/transcriptions/:id?wait=60`轮询状态，或通过[Webhook](https://transcript1.com/zh/docs/webhooks)接收通知。

### 用Python完成上传流程

```python
import os
import requests

API = "https://api.transcript1.com"
auth = {"Authorization": f"Bearer {os.environ['TRANSCRIPT1_API_KEY']}"}
path = "interview.m4a"

created = requests.post(
    f"{API}/v1/transcriptions",
    headers={**auth, "Idempotency-Key": "interview-2026-10-13"},
    json={"upload": {"filename": os.path.basename(path), "size": os.path.getsize(path)}},
)
created.raise_for_status()
transcription = created.json()
upload = transcription["upload"]

with open(path, "rb") as file:
    for chunk in upload["chunks"]:
        file.seek(chunk["offset"])
        url = upload["chunk_url_template"].replace("{index}", str(chunk["index"]))
        response = requests.put(
            url,
            data=file.read(chunk["size"]),
            headers={**upload["headers"], "Content-Type": "application/octet-stream"},
        )
        response.raise_for_status()

done = requests.post(f"{API}/v1/transcriptions/{transcription['id']}/upload/complete", headers=auth)
done.raise_for_status()
print(done.json()["status"])
```

## 上传配额和文件保留时间

- 每个团队同时最多有20个上传处于`awaiting_upload`状态。超过这个数量，创建接口会返回`429 too_many_active_jobs`，并带上`Retry-After`响应头。
- 上传创建后24小时内未完成，系统会取消任务并删除已上传的分块。
- 任务进入`completed`、`failed`或`canceled`状态24小时后，系统会删除上传的音频或视频。转录文本会保留，直到调用`DELETE /v1/transcriptions/:id`删除。
- 如果转录失败或被取消，就不会扣费。文件时长确定后，按录音或视频的分钟数向上取整扣费。详见[计费与限制](https://transcript1.com/zh/docs/billing-and-limits)。
