> ## Documentation Index
> Fetch the complete documentation index at: https://docs.madiad.com/llms.txt
> Use this file to discover all available pages before exploring further.

# FFmpeg

> Chạy các job xử lý video và âm thanh phía server thông qua MADIAD Hub API.

FFmpeg API cho phép bạn chuyển mã, cắt xén, chuyển đổi và xử lý file video, âm thanh hoàn toàn trên server. Bạn cung cấp một URL nguồn và một lệnh FFmpeg; MADIAD Hub tải file về, chạy job và cung cấp kết quả để tải về.

Không có chức năng tải lên file nhị phân, các file đầu vào phải truy cập được qua HTTPS. API trả về `job_id` ngay lập tức; quá trình xử lý là bất đồng bộ, bạn cần kiểm tra trạng thái định kỳ rồi tải về kết quả.

<Warning>
  **Không có webhook cho FFmpeg.** Kiểm tra trạng thái định kỳ (polling) là cách DUY NHẤT để biết một job đã xong. [Webhooks](/vi/concepts/webhooks) chỉ phát sự kiện cho việc đăng bài và cho thay đổi kết nối tài khoản, không có sự kiện nào cho job FFmpeg. Đừng thiết kế hệ thống của bạn dựa trên một callback FFmpeg.
</Warning>

<Note>
  Các job FFmpeg yêu cầu gói trả phí. Tài khoản chưa kích hoạt gói có **0 phút FFmpeg**. Kích hoạt gói Starter trở lên để sử dụng tính năng này.
</Note>

## Quy trình hoạt động của một job

<Steps>
  <Step title="Gửi">
    POST đến `/v1/ffmpeg/jobs` với một hoặc nhiều URL nguồn và một lệnh FFmpeg. API xác thực lệnh, đặt trước quota và đưa job vào hàng đợi. Bạn nhận được `job_id`.
  </Step>

  <Step title="Kiểm tra">
    GET `/v1/ffmpeg/jobs/:id` cho đến khi `status` là `finished`.
  </Step>

  <Step title="Tải về">
    GET `/v1/ffmpeg/jobs/:id/result` để nhận file đã xử lý dưới dạng stream.
  </Step>
</Steps>

## Gửi một job

```bash theme={null}
POST /v1/ffmpeg/jobs
Authorization: Bearer $MADIAD_API_KEY
Content-Type: application/json
```

### Body của request

| Trường             | Kiểu      | Bắt buộc                           | Mô tả                                                                                         |
| ------------------ | --------- | ---------------------------------- | --------------------------------------------------------------------------------------------- |
| `file_url`         | string    | Một trong `file_url` / `file_urls` | Một URL HTTPS nguồn duy nhất                                                                  |
| `file_urls`        | string\[] | Một trong `file_url` / `file_urls` | Nhiều URL HTTPS nguồn (ví dụ để trộn nhiều track âm thanh)                                    |
| `full_command`     | string    | Có                                 | Chuỗi lệnh FFmpeg đầy đủ (xem [Quy tắc lệnh](#quy-tac-lenh))                                  |
| `output_extension` | string    | Có                                 | Container/định dạng của đầu ra (xem [Phần mở rộng được phép](#phan-mo-rong-dau-ra-duoc-phep)) |

### Response

```json theme={null}
{
  "job_id": "ffj_01HZX9G4P6R8S0T2V4W6X8Y0Z2",
  "status": "queued"
}
```

## Kiểm tra trạng thái

```bash theme={null}
GET /v1/ffmpeg/jobs/:id
Authorization: Bearer $MADIAD_API_KEY
```

### Response

```json theme={null}
{
  "job_id": "ffj_01HZX9G4P6R8S0T2V4W6X8Y0Z2",
  "status": "processing",
  "duration_sec": null
}
```

`duration_sec` là `null` cho đến khi job hoàn thành; giá trị thực tế của thời lượng đầu ra sẽ được điền vào sau khi hoàn tất.

| Giá trị `status` | Ý nghĩa                                   |
| ---------------- | ----------------------------------------- |
| `queued`         | Job đang chờ trong hàng đợi               |
| `processing`     | Job đang chạy                             |
| `finished`       | Đầu ra sẵn sàng để tải về                 |
| `failed`         | Job thất bại, hãy thử lại với lệnh đã sửa |

## Tải về kết quả

Khi `status` là `finished`, hãy stream file đầu ra:

```bash theme={null}
GET /v1/ffmpeg/jobs/:id/result
Authorization: Bearer $MADIAD_API_KEY
```

Response là một binary stream với `Content-Disposition: attachment`. Bạn có thể lưu ra file hoặc giữ trong bộ nhớ.

## Quy tắc lệnh

Trường `full_command` phải đáp ứng tất cả các quy tắc sau. Request vi phạm sẽ trả về `400 invalid_request` trước khi tiêu tốn bất kỳ quota nào.

| Quy tắc                           | Chi tiết                                                                     |
| --------------------------------- | ---------------------------------------------------------------------------- |
| Bắt đầu bằng `ffmpeg`             | Lệnh phải bắt đầu bằng từ `ffmpeg`                                           |
| Chứa placeholder `{input}`        | Dùng `{input}` cho 1 nguồn, hoặc `{input0}`, `{input1}`, ... cho nhiều nguồn |
| Chứa placeholder `{output}`       | MADIAD Hub thay thế đường dẫn đầu ra khi chạy                                |
| Độ dài tối đa                     | 4.000 ký tự                                                                  |
| Không có ký tự đặc biệt của shell | Các ký tự `;`, `\|`, `&`, `$`, `` ` ``, `<`, `>` bị chặn                     |
| Không có token nguy hiểm          | `$(`, `rm`, `rmdir`, `mkfs`, `dd` bị chặn                                    |

Ký tự xuống dòng trong lệnh được tự động chuyển thành khoảng trắng, nên bạn có thể dán lệnh nhiều dòng trực tiếp.

## Phần mở rộng đầu ra được phép

`output_extension` phải là một trong các giá trị sau (không có dấu chấm ở đầu):

| Nhóm     | Giá trị                            |
| -------- | ---------------------------------- |
| Video    | `mp4`, `mov`, `webm`, `mkv`, `gif` |
| Âm thanh | `mp3`, `wav`, `m4a`, `aac`, `ogg`  |
| Hình ảnh | `jpg`, `png`                       |

Bất kỳ giá trị nào khác sẽ trả về `400 invalid_request`.

## Quota và thanh toán

Mức sử dụng FFmpeg được tính bằng **phút FFmpeg**. Trong v1, mỗi job có chi phí cố định là **1 phút**, bất kể thời lượng thực tế của đầu ra. Quota được đặt trước tại thời điểm gửi và được hoàn lại tự động nếu job không thể vào hàng đợi.

Giới hạn hàng tháng theo gói. Hạn mức này tính **cho cả tài khoản**: không nhân theo số Profile, mọi thương hiệu của bạn dùng chung một quota.

| Gói            | Phút FFmpeg/tháng  |
| -------------- | ------------------ |
| Chưa kích hoạt | 0 (không khả dụng) |
| Starter        | 15                 |
| Growth         | 150                |
| Business       | 500                |
| Custom         | Không giới hạn     |

Khi hết quota hàng tháng, API trả về `429 quota_exceeded`. Hạn mức được đặt lại vào đúng ngày trong tháng mà gói của bạn được kích hoạt (giờ Việt Nam, UTC+7), không phải ngày mùng 1. [Mức sử dụng](/vi/concepts/usage) cho biết chính xác ngày bắt đầu và ngày gia hạn của chu kỳ hiện tại.

## Xác thực và giới hạn tốc độ

Tất cả endpoint FFmpeg đều dùng chung cơ chế xác thực Bearer API key với phần còn lại của MADIAD Hub, xem [Xác thực](/vi/authentication).

Giới hạn tốc độ là **120 request/phút** mỗi tài khoản trên tất cả endpoint FFmpeg. Vượt quá giới hạn này sẽ nhận `429` kèm header `Retry-After` cho biết thời gian cần chờ.

## Quyền sở hữu job

Job chỉ thuộc về tài khoản đã tạo ra nó. Kiểm tra trạng thái hoặc tải về job của tài khoản khác sẽ trả về `404 not_found`, không có quyền truy cập chéo tài khoản.

## Lỗi

| HTTP status | Code              | Khi nào                                                                                  |
| ----------- | ----------------- | ---------------------------------------------------------------------------------------- |
| `400`       | `invalid_request` | Thiếu hoặc URL không phải HTTPS, lệnh không hợp lệ, `output_extension` không được hỗ trợ |
| `404`       | `not_found`       | Job ID không tồn tại, hoặc job thuộc tài khoản khác                                      |
| `429`       | `quota_exceeded`  | Đã hết phút FFmpeg trong tháng                                                           |
| `429`       | `rate_limited`    | Hơn 120 request/phút, hãy dừng lại và thử lại sau `Retry-After` giây                     |
| `502`       | `upstream_error`  | Kết quả chưa có hoặc quá trình xử lý thất bại                                            |

## Ví dụ thực tế

### 1. Gửi: chuyển đổi MP4 sang WebM tối ưu cho web

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.madiad.com/v1/ffmpeg/jobs \
    -H "Authorization: Bearer $MADIAD_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
          "file_url": "https://cdn.example.com/raw/interview.mp4",
          "full_command": "ffmpeg -i {input} -c:v libvpx-vp9 -crf 33 -b:v 0 -c:a libopus {output}",
          "output_extension": "webm"
        }'
  ```

  ```js JavaScript theme={null}
  const res = await fetch("https://api.madiad.com/v1/ffmpeg/jobs", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MADIAD_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      file_url: "https://cdn.example.com/raw/interview.mp4",
      full_command:
        "ffmpeg -i {input} -c:v libvpx-vp9 -crf 33 -b:v 0 -c:a libopus {output}",
      output_extension: "webm",
    }),
  });
  const { job_id, status } = await res.json();
  // { job_id: "ffj_01HZX9G4P6R8S0T2V4W6X8Y0Z2", status: "queued" }
  ```
</CodeGroup>

### 2. Kiểm tra cho đến khi hoàn thành

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.madiad.com/v1/ffmpeg/jobs/ffj_01HZX9G4P6R8S0T2V4W6X8Y0Z2 \
    -H "Authorization: Bearer $MADIAD_API_KEY"
  # Tiếp tục kiểm tra cho đến khi "status": "finished"
  ```

  ```js JavaScript theme={null}
  let job;
  do {
    await new Promise((r) => setTimeout(r, 3000)); // chờ 3 giây giữa các lần kiểm tra
    const r = await fetch(
      `https://api.madiad.com/v1/ffmpeg/jobs/${job_id}`,
      { headers: { Authorization: `Bearer ${process.env.MADIAD_API_KEY}` } }
    );
    job = await r.json();
  } while (job.status !== "finished" && job.status !== "failed");
  ```
</CodeGroup>

### 3. Tải về kết quả

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.madiad.com/v1/ffmpeg/jobs/ffj_01HZX9G4P6R8S0T2V4W6X8Y0Z2/result \
    -H "Authorization: Bearer $MADIAD_API_KEY" \
    --output interview.webm
  ```

  ```js JavaScript theme={null}
  const dl = await fetch(
    `https://api.madiad.com/v1/ffmpeg/jobs/${job_id}/result`,
    { headers: { Authorization: `Bearer ${process.env.MADIAD_API_KEY}` } }
  );
  const buffer = await dl.arrayBuffer();
  // lưu buffer ra file hoặc stream đến client
  ```
</CodeGroup>
