> ## Documentation Index
> Fetch the complete documentation index at: https://rendobar-docs-compose-ref-tables.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Run any FFmpeg command

> Run any FFmpeg command in the cloud with ~120 whitelisted flags, sandboxed execution, and a signed output URL.

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
__html: JSON.stringify({
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "@id": "https://rendobar.com/docs/jobs/ffmpeg/#article",
  "headline": "Run any FFmpeg command",
  "description": "Run any FFmpeg command in the cloud. Whitelisted flags, sandboxed execution, signed download URL. Same FFmpeg syntax you already know.",
  "datePublished": "2026-03-20",
  "dateModified": "2026-06-21",
  "author": { "@type": "Organization", "@id": "https://rendobar.com/#organization" },
  "publisher": { "@type": "Organization", "@id": "https://rendobar.com/#organization" },
  "isPartOf": { "@id": "https://rendobar.com/#website" }
})
}}
/>

You write the FFmpeg command. Rendobar downloads inputs, runs the command in an isolated container, returns a signed download URL. Same syntax you would run locally.

The `inputs` map stages source files into the working directory by name. The command references them by bare name, and inline `-i` URLs also work.

<Info>
  **Accepts:** video, audio, image
</Info>

<Tip>
  From a shell? Use [the CLI](/cli): `rb ffmpeg -i input.mp4 -vf scale=1280:720 out.mp4`. Local files upload automatically.
</Tip>

## Request

<CodeGroup>
  ```ts SDK theme={null}
  import { createClient, outputUrl } from "@rendobar/sdk";

  const client = createClient({ apiKey: "rb_YOUR_KEY" });

  const job = await client.jobs.create({
    type: "ffmpeg",
    params: {
      command: "ffmpeg -i https://cdn.rendobar.com/assets/examples/sample.mp4 -vf scale=1280:720 -c:v libx264 -crf 23 -preset fast output.mp4",
    },
  });

  const result = await client.jobs.wait(job.id);
  console.log(outputUrl(result));
  ```

  ```python Python theme={null}
  import requests
  res = requests.post(
      "https://api.rendobar.com/jobs",
      headers={"Authorization": "Bearer rb_YOUR_KEY"},
      json={
          "type": "ffmpeg",
          "params": {
              "command": "ffmpeg -i https://cdn.rendobar.com/assets/examples/sample.mp4 -vf scale=1280:720 -c:v libx264 -crf 23 -preset fast output.mp4",
          },
      },
  )
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.rendobar.com/jobs \
    -H "Authorization: Bearer rb_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "type": "ffmpeg",
      "params": {
        "command": "ffmpeg -i https://cdn.rendobar.com/assets/examples/sample.mp4 -vf scale=1280:720 -c:v libx264 -crf 23 -preset fast output.mp4"
      }
    }'
  ```
</CodeGroup>

## How it runs

1. **Validate**: command parsed against 8 security layers (flag whitelist, format/filter blocklists)
2. **Download**: input URLs pulled to a sandboxed runner
3. **Substitute**: URLs replaced with local paths in the FFmpeg command
4. **Execute**: FFmpeg runs in an isolated container with network disabled
5. **Upload**: output goes to R2
6. **Return**: signed `output.file.url` available via `GET /jobs/{id}`

## Input sources

Most commands put the source URL straight in the command and skip `inputs` entirely. It is optional and defaults to an empty map:

```json theme={null}
{
  "type": "ffmpeg",
  "params": {
    "command": "ffmpeg -i https://example.com/clip.mp4 -vf scale=1280:720 output.mp4"
  }
}
```

Reach for the `inputs` map only when a file cannot go inline: a file a filter reads by name (see below), inline text you do not want to host, an already-uploaded asset, or when you need a stable filename. Each key is a filename staged under that exact name, and the command references it by bare name.

A value can be one of three shapes:

| Value                         | Use it for                                      |
| ----------------------------- | ----------------------------------------------- |
| `"https://..."`               | A remote file. Shorthand for `{ "url": "..." }` |
| `{ "url": "https://..." }`    | A remote file, explicit form                    |
| `{ "content": "...text..." }` | Inline text you do not want to host (≤64 KB)    |

A file you already uploaded is referenced by its **content URL**: the `url`
returned by the uploads endpoint, like `https://api.rendobar.com/assets/asset_abc123/content`.
Pass it like any other URL.

```json theme={null}
{
  "type": "ffmpeg",
  "inputs": {
    "clip.mp4": "https://example.com/clip.mp4",
    "logo.png": "https://api.rendobar.com/assets/asset_brandlogo/content"
  },
  "params": {
    "command": "ffmpeg -i clip.mp4 -i logo.png -filter_complex overlay=10:10 output.mp4"
  }
}
```

### Auxiliary files read by a filter

Some files are not passed as `-i` arguments. Filters like `subtitles=`, `lut3d=`, and `drawtext fontfile=`, and the lists read by the `concat` demuxer, read a file by name from the working directory. Provide those through `inputs` so they are staged before the command runs.

This burns a subtitle track that lives only in the request, with no file to host:

```json theme={null}
{
  "type": "ffmpeg",
  "inputs": {
    "video.mp4": "https://example.com/video.mp4",
    "subs.srt": {
      "content": "1\n00:00:01,000 --> 00:00:04,000\nRendered on Rendobar.\n"
    }
  },
  "params": {
    "command": "ffmpeg -i video.mp4 -vf subtitles=subs.srt -c:v libx264 -c:a copy output.mp4"
  }
}
```

## Parameters

<ParamField body="inputs" type="object" default="{}">
  Working directory for the command. Optional, defaults to an empty map. Each key is a path-safe filename staged under that exact name. Each value is the source for that file. Omit it when every input is an inline `-i` URL. A value is one of:

  <Expandable title="Input source shapes">
    <ResponseField name="URL string" type="string">
      Remote file. Shorthand for `{ "url": "..." }`.
    </ResponseField>

    <ResponseField name="url" type="object">
      `{ "url": "https://..." }`. Remote file, explicit form.
    </ResponseField>

    <ResponseField name="content" type="object">
      `{ "content": "...text..." }`. Inline text staged verbatim, up to 64 KB. For subtitles, `concat` lists, and LUTs with nothing to host.
    </ResponseField>

    <ResponseField name="uploaded file" type="string">
      A file you already uploaded is referenced by its content URL: the `url` returned by the uploads endpoint, like `https://api.rendobar.com/assets/asset_abc123/content`. Pass it like any other URL string.
    </ResponseField>
  </Expandable>

  Filters that read a file by name (`subtitles=`, `lut3d=`, `drawtext fontfile=`, `concat` lists) must get that file through `inputs`. They are not `-i` arguments. See [Input sources](#input-sources).
</ParamField>

<ParamField body="command" type="string" required>
  Real FFmpeg command starting with `ffmpeg`. Input URLs go in `-i` positions, or reference staged `inputs` files by name.
</ParamField>

<ParamField body="outputFormat" type="enum" default="inferred">
  Container format. Inferred from the trailing filename if omitted. One of: `mp4`, `mkv`, `webm`, `mov`, `avi`, `ts`, `gif`, `png`, `jpg`, `mp3`, `wav`, `flac`, `ogg`, `aac`, `opus`, `m4a`, `srt`, `vtt`.
</ParamField>

<ParamField body="timeout" type="integer" default="120">
  Max execution time in seconds. Range 1–900. Plan caps apply (Free 5 min, Pro 15 min).
</ParamField>

<ParamField body="compute" type="enum" default="auto">
  Which machine class runs the job. One of `auto`, `cpu`, `gpu`.

  * `auto` (default): a command using an NVENC or CUDA encoder, like `h264_nvenc`, routes to a GPU. Everything else runs on CPU. You do not set anything.
  * `gpu`: force a GPU machine.
  * `cpu`: force a CPU machine. A command that calls an NVENC encoder is rejected with `VALIDATION_ERROR`.

  GPU jobs run the hardware encoders `h264_nvenc`, `hevc_nvenc`, and `av1_nvenc` on NVIDIA L4 GPUs, billed per second. `gpu` and `auto`-routed GPU jobs **require the Pro plan**. A GPU job on the Free plan returns `403 PLAN_LIMIT`. `cpu` and `auto` work on every plan. See [GPU acceleration](#gpu-acceleration).
</ParamField>

## Validate without executing

Free, no auth:

```bash theme={null}
curl -X POST https://api.rendobar.com/ffmpeg/validate \
  -H "Content-Type: application/json" \
  -d '{ "command": "ffmpeg -i video.mp4 -vf scale=1280:720 output.mp4" }'
```

Returns `{ data: { valid: true, args: [...], inferredOutputFormat: "mp4" } }` or `{ data: { valid: false, error: "..." } }`.

## Allowed flags

\~120 whitelisted flags. Common ones:

| Flag                   | Purpose                                                    |
| ---------------------- | ---------------------------------------------------------- |
| `-i`                   | Input file                                                 |
| `-f`                   | Force format                                               |
| `-c:v`, `-c:a`         | Video / audio codec (stream specifiers like `-c:v:0` work) |
| `-vf`, `-af`           | Video / audio filter                                       |
| `-filter_complex`      | Complex filter graph                                       |
| `-map`                 | Stream mapping                                             |
| `-ss`, `-t`            | Seek position, duration                                    |
| `-r`, `-s`             | Frame rate, resolution                                     |
| `-b:v`, `-b:a`         | Video / audio bitrate                                      |
| `-crf`, `-preset`      | Quality + preset                                           |
| `-an`, `-vn`           | Disable audio / video                                      |
| `-y`                   | Overwrite output                                           |
| `-movflags`, `-strict` | Container flags, strict standards                          |

Unrecognised flags are rejected before dispatch with `VALIDATION_ERROR`.

## GPU acceleration

Your jobs run on CPU-powered or GPU-powered machines. `compute` defaults to `auto`, so a command that uses an NVENC encoder routes to a GPU on its own. You do not send anything.

Switch one flag to move an encode to the hardware encoder. The GPU path runs `h264_nvenc`, `hevc_nvenc`, and `av1_nvenc` on NVIDIA L4 GPUs, several times faster than software `libx264` at the same resolution.

<CodeGroup>
  ```bash CPU (libx264) theme={null}
  ffmpeg -i input.mp4 -c:v libx264 -preset medium -crf 23 output.mp4
  ```

  ```bash GPU (h264_nvenc) theme={null}
  ffmpeg -i input.mp4 -c:v h264_nvenc -preset p5 -cq 23 output.mp4
  ```
</CodeGroup>

Set `compute` only to force a choice. `gpu` pins the job to a GPU. `cpu` pins it to CPU and rejects an NVENC command with `VALIDATION_ERROR`.

```json theme={null}
{
  "type": "ffmpeg",
  "params": {
    "command": "ffmpeg -i https://example.com/video.mp4 -c:v h264_nvenc -preset p5 -cq 23 output.mp4",
    "compute": "gpu"
  }
}
```

<Info>
  GPU jobs require the Pro plan and bill per second at GPU rates. A GPU job on the Free plan returns `403 PLAN_LIMIT`. CPU jobs run on every plan.
</Info>

## Security model

Every command is validated before it runs, then executed in an isolated, network-restricted sandbox.

* **Flag allowlist.** Only known-safe flags pass. Anything unrecognised is rejected with `VALIDATION_ERROR` before dispatch.
* **No reads through filters.** Filters and formats that read from disk, fetch over the network, or run external code are blocked.
* **Path containment.** Inputs are limited to the files you provide. Path traversal and access to system paths are rejected.
* **Sandboxed execution.** Each job runs in an isolated container with a clean environment, restricted I/O, and no ambient network access.

## Cost & timeout

Priced per compute second from your [credit balance](/concepts/credits), the wall-clock time FFmpeg ran, excluding file transfer.

Plan timeouts: 5 min (Free), 15 min (Pro). See [plan limits](/support/limits) for the full table. Exceeding the timeout returns `RUNNER_TIMEOUT`, and no credits are charged.

## Examples

### Scale to 720p

```json theme={null}
{
  "type": "ffmpeg",
  "params": {
    "command": "ffmpeg -i https://example.com/video.mp4 -vf scale=1280:720 -c:v libx264 -preset fast -crf 23 output.mp4"
  }
}
```

### Extract audio

```json theme={null}
{
  "type": "ffmpeg",
  "params": {
    "command": "ffmpeg -i https://example.com/video.mp4 -vn -c:a aac -b:a 128k output.m4a"
  }
}
```

### Trim 30s starting at 1:00

```json theme={null}
{
  "type": "ffmpeg",
  "params": {
    "command": "ffmpeg -i https://example.com/video.mp4 -ss 00:01:00 -t 00:00:30 -c:v libx264 -c:a aac output.mp4"
  }
}
```

### Merge video + audio tracks

```json theme={null}
{
  "type": "ffmpeg",
  "params": {
    "command": "ffmpeg -i https://example.com/video.mp4 -i https://example.com/narration.mp3 -c:v libx264 -c:a aac -map 0:v -map 1:a output.mp4"
  }
}
```

## Output files

The filenames your command writes define the output. You get back every file it produced.

Every completed job, FFmpeg or otherwise, returns the same `output` shape. [Job output](/concepts/job#the-output) documents it in full. The short version:

* `output.data`: the computed answer (a probe result, a transcript). `null` for FFmpeg jobs, which only write files.
* `output.file`: the headline result you play or download, one file or a stream manifest. `null` for pure file sets.
* `output.files`: every file the job produced, the complete list.
* `output.expiresAt`: Unix ms when the URLs expire, present when `files` is non-empty.

Each file is `{ url, path, type, size, meta? }`. The `type` is an open enum: `video`, `image`, `audio`, `captions`, `playlist`, `data`, or `other`. Tolerate values you do not recognise.

A command that writes one file sets `output.file` to that file, and `output.files` to a list of one:

```json theme={null}
{
  "data": {
    "id": "job_abc123",
    "status": "complete",
    "output": {
      "data": null,
      "file": {
        "url": "https://api.rendobar.com/dl/job_abc123?token=<token>",
        "path": "output.mp4",
        "type": "video",
        "size": 4194304,
        "meta": { "format": "mp4", "width": 1280, "height": 720, "durationMs": 30000 }
      },
      "files": [
        {
          "url": "https://api.rendobar.com/dl/job_abc123?token=<token>",
          "path": "output.mp4",
          "type": "video",
          "size": 4194304,
          "meta": { "format": "mp4", "width": 1280, "height": 720, "durationMs": 30000 }
        }
      ],
      "expiresAt": 1735689600000
    }
  }
}
```

A command that writes a stream (HLS or DASH) sets `output.file` to the manifest, typed `playlist`. Point a player at `output.file.url` directly. `output.files` carries the manifest plus every segment:

```json theme={null}
{
  "type": "ffmpeg",
  "inputs": { "video.mp4": "https://example.com/video.mp4" },
  "params": {
    "command": "ffmpeg -i video.mp4 -c:v libx264 -c:a aac -f hls -hls_time 6 -hls_segment_filename seg_%03d.ts master.m3u8"
  }
}
```

```json theme={null}
{
  "data": {
    "id": "job_abc123",
    "status": "complete",
    "output": {
      "data": null,
      "file": {
        "url": "https://api.rendobar.com/v/job_abc123/<token>/master.m3u8",
        "path": "master.m3u8",
        "type": "playlist",
        "size": 412
      },
      "files": [
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/master.m3u8", "path": "master.m3u8", "type": "playlist", "size": 412 },
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/seg_000.ts", "path": "seg_000.ts", "type": "video", "size": 1048576 },
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/seg_001.ts", "path": "seg_001.ts", "type": "video", "size": 1041203 }
      ],
      "expiresAt": 1735689600000
    }
  }
}
```

A command that writes an unordered set (image sequence, `-f segment`, a resolution ladder) has no single headline result. `output.file` is `null`. Read `output.files` for the list:

```json theme={null}
{
  "data": {
    "id": "job_abc123",
    "status": "complete",
    "output": {
      "data": null,
      "file": null,
      "files": [
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/frame_000.png", "path": "frame_000.png", "type": "image", "size": 204800 },
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/frame_001.png", "path": "frame_001.png", "type": "image", "size": 205120 },
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/frame_002.png", "path": "frame_002.png", "type": "image", "size": 203904 }
      ],
      "expiresAt": 1735689600000
    }
  }
}
```

Consuming the result is the same every time. The answer is `output.data`. The thing you play or download is `output.file.url`. The full list is `output.files`. Two invariants hold: `output.file` is always one of `output.files` (or `null`), and `output.expiresAt` is present whenever `output.files` is non-empty.

URLs expire at `output.expiresAt`. Re-fetch the job with `GET /jobs/{id}` to mint fresh ones.

## Error handling

A failed job carries an `error` object with `code`, `message`, `detail`, and `retryable`. When FFmpeg exits non-zero, `error.detail` holds the last \~2 KB of the real stderr. Fetch the job to see exactly why FFmpeg stopped.

```json theme={null}
{
  "data": {
    "id": "job_abc123",
    "status": "failed",
    "error": {
      "code": "RUNNER_ERROR",
      "message": "FFmpeg process exited with code 1",
      "detail": "[in#0 @ 0x55…] Error opening input: Invalid data found when processing input\nError opening input file clip.mp4.\n",
      "retryable": false
    }
  }
}
```

A disallowed flag is rejected before dispatch. The request fails with the error envelope, so no job is created:

```json theme={null}
{ "error": { "code": "VALIDATION_ERROR", "message": "Flag '-protocol_whitelist' is not allowed in FFmpeg commands" } }
```

Full error catalogue: [Error codes](/support/errors).

## See also

* [Job output](/concepts/job#the-output): the output shape every job returns
* [CLI](/cli): run the same job from your terminal with `rb ffmpeg`
* [Webhooks](/guides/webhooks): receive `job.completed` instead of polling
* [Credits and billing](/concepts/credits): per-compute-second pricing detail
* [Plan limits](/support/limits): file-size caps, timeouts, and concurrency per plan
* [Job lifecycle](/concepts/job): how a job moves from dispatched to complete

For a full list of FFmpeg-based operations supported by the API, see [rendobar.com/ffmpeg/](https://rendobar.com/ffmpeg/).
