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

# MCP tool reference

## transcode

Re-encode a video to a different codec, container, or quality.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "acodec": {
      "description": "Audio codec to re-encode to. Omit to keep ffmpeg's container default. aac is invalid in webm (use opus); opus muxes in mp4/webm/mkv.",
      "enum": [
        "aac",
        "opus"
      ],
      "type": "string"
    },
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "crf": {
      "default": 23,
      "maximum": 51,
      "minimum": 0,
      "type": "integer"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "pix_fmt": {
      "default": "yuv420p",
      "enum": [
        "yuv420p",
        "yuv444p"
      ],
      "type": "string"
    },
    "vcodec": {
      "default": "h264",
      "description": "Video codec: h264 (x264), vp9 (libvpx-vp9), hevc (x265), or av1 (libsvtav1).",
      "enum": [
        "h264",
        "vp9",
        "hevc",
        "av1"
      ],
      "type": "string"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## trim

Cut a clip by start offset and duration (lossless).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "duration": {
      "default": 5,
      "minimum": 0,
      "type": "number"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "start": {
      "default": 0,
      "minimum": 0,
      "type": "number"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## segment

Split a video into fixed-length segments (multi-output).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "crf": {
      "default": 23,
      "maximum": 51,
      "minimum": 0,
      "type": "integer"
    },
    "first_frame": {
      "default": false,
      "description": "Also deliver each clip's first frame as a companion still (seg_N_first.jpg|png), interleaved with the clips in output_urls. Fails the job above 999 clips; boundary-accurate only with reencode=true.",
      "type": "boolean"
    },
    "frame_format": {
      "description": "Companion-still image format (engine default jpg). Validated whenever supplied, applied only when first_frame is true.",
      "enum": [
        "jpg",
        "png"
      ],
      "type": "string"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "reencode": {
      "default": false,
      "type": "boolean"
    },
    "segment_seconds": {
      "default": 10,
      "minimum": 1,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## thumbnails

Extract frames at a fixed interval (multi-output).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "format": {
      "default": "jpg",
      "enum": [
        "jpg",
        "png"
      ],
      "type": "string"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "interval_seconds": {
      "default": 2,
      "minimum": 0.1,
      "type": "number"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## scale

Resize a video (aspect-preserving when height is -2/-1).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "height": {
      "default": -2,
      "type": "integer"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "width": {
      "default": 1280,
      "maximum": 8192,
      "minimum": 1,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## extract\_audio

Extract the audio track to mp3/wav/m4a. Optional sample\_rate + mono make the output speech-to-text ready (a fixed-rate mono track an ASR model can ingest).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "format": {
      "default": "mp3",
      "enum": [
        "mp3",
        "wav",
        "m4a"
      ],
      "type": "string"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "mono": {
      "description": "Downmix to a single channel (STT models expect mono). Omit/false keeps the source channel layout.",
      "type": "boolean"
    },
    "sample_rate": {
      "description": "Resample the audio to this rate in Hz (STT models want a fixed rate, e.g. 16000). Omit to keep the source rate.",
      "maximum": 48000,
      "minimum": 8000,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## gif

Make an animated GIF from the clip.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "fps": {
      "default": 10,
      "maximum": 50,
      "minimum": 1,
      "type": "integer"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "width": {
      "default": 480,
      "minimum": 1,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## watermark

Overlay an image/video (input\_urls = \[base, overlay]).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 2,
      "minItems": 2,
      "type": "array"
    },
    "x": {
      "default": 10,
      "minimum": 0,
      "type": "integer"
    },
    "y": {
      "default": 10,
      "minimum": 0,
      "type": "integer"
    }
  },
  "required": [
    "input_urls"
  ],
  "type": "object"
}
```

## captions

Burn subtitles (SRT/VTT/ASS) into the video (input\_urls = \[video, subtitle]).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "font_size": {
      "description": "Subtitle font size in points; omit for the source/libass default.",
      "maximum": 200,
      "minimum": 8,
      "type": "integer"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 2,
      "minItems": 2,
      "type": "array"
    },
    "position": {
      "description": "Caption anchor; omit to keep the subtitle file's own alignment.",
      "enum": [
        "bottom",
        "top"
      ],
      "type": "string"
    },
    "text_color": {
      "description": "Named (white/black/gray/grey/red/green/blue/yellow) or #RRGGBB; omit for the source default.",
      "type": "string"
    }
  },
  "required": [
    "input_urls"
  ],
  "type": "object"
}
```

## subtitle\_compose

Author + burn animated TikTok-style captions — a timed top hook + a word-styled, animated bottom transcript — from word timings. input\_urls = \[video]; the styled .ass is server-generated from words/hook/style, so no subtitle file is supplied. Pass concrete style fields directly (no preset names).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "hook": {
      "additionalProperties": false,
      "description": "Optional timed top-overlay hook line",
      "properties": {
        "end_ms": {
          "maximum": 86400000,
          "minimum": 0,
          "type": "integer"
        },
        "start_ms": {
          "maximum": 86400000,
          "minimum": 0,
          "type": "integer"
        },
        "text": {
          "type": "string"
        }
      },
      "required": [
        "text",
        "start_ms",
        "end_ms"
      ],
      "type": "object"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "style": {
      "additionalProperties": false,
      "description": "Concrete caption style fields",
      "properties": {
        "active_color": {
          "description": "Highlighted/active word colour",
          "type": "string"
        },
        "animation": {
          "description": "highlight = active-word recolour in place; fill = native karaoke colour sweep",
          "enum": [
            "highlight",
            "fill"
          ],
          "type": "string"
        },
        "combine_ms": {
          "description": "Break a page when the gap to the next word exceeds this many ms",
          "maximum": 2000,
          "minimum": 0,
          "type": "integer"
        },
        "font": {
          "description": "Caption font family (bundled in the runtime image)",
          "enum": [
            "Liberation Sans",
            "Liberation Serif",
            "Liberation Mono",
            "DejaVu Sans",
            "DejaVu Serif",
            "DejaVu Sans Mono",
            "Anton",
            "Archivo Black",
            "Bangers",
            "Inter",
            "Montserrat",
            "Montserrat Black",
            "Poppins",
            "TikTok Sans",
            "TikTok Sans Medium",
            "TikTok Display",
            "TikTok Display Medium",
            "TikTok Text",
            "TikTok Text Medium",
            "Public Sans",
            "Public Sans Medium"
          ],
          "type": "string"
        },
        "font_size_pct": {
          "description": "Caption font size as % of frame height",
          "maximum": 15,
          "minimum": 3,
          "type": "integer"
        },
        "hook_animation": {
          "description": "Hook entrance: pop = fade + scale pop (a hook long enough to wrap can re-wrap mid-animation); fade = fade-in only (never re-wraps); none = no animation",
          "enum": [
            "pop",
            "fade",
            "none"
          ],
          "type": "string"
        },
        "hook_color": {
          "type": "string"
        },
        "hook_font": {
          "enum": [
            "Liberation Sans",
            "Liberation Serif",
            "Liberation Mono",
            "DejaVu Sans",
            "DejaVu Serif",
            "DejaVu Sans Mono",
            "Anton",
            "Archivo Black",
            "Bangers",
            "Inter",
            "Montserrat",
            "Montserrat Black",
            "Poppins",
            "TikTok Sans",
            "TikTok Sans Medium",
            "TikTok Display",
            "TikTok Display Medium",
            "TikTok Text",
            "TikTok Text Medium",
            "Public Sans",
            "Public Sans Medium"
          ],
          "type": "string"
        },
        "hook_size_pct": {
          "maximum": 16,
          "minimum": 4,
          "type": "integer"
        },
        "margin_v_pct": {
          "description": "Caption distance from the frame bottom, % of height",
          "maximum": 40,
          "minimum": 5,
          "type": "integer"
        },
        "max_words": {
          "description": "Max words per caption page",
          "maximum": 6,
          "minimum": 1,
          "type": "integer"
        },
        "outline_color": {
          "type": "string"
        },
        "outline_width": {
          "maximum": 20,
          "minimum": 0,
          "type": "integer"
        },
        "shadow_depth": {
          "maximum": 10,
          "minimum": 0,
          "type": "integer"
        },
        "text_color": {
          "description": "Base word colour: named palette (white/yellow/gold/lime/green/cyan/blue/magenta/pink/red/orange/purple/black) or #RRGGBB",
          "type": "string"
        },
        "uppercase": {
          "type": "boolean"
        }
      },
      "type": "object"
    },
    "words": {
      "description": "Normalized transcript tokens, in non-decreasing start_ms order",
      "items": {
        "additionalProperties": false,
        "properties": {
          "end_ms": {
            "description": "Absolute word end, ms (>= start_ms, 24h cap)",
            "maximum": 86400000,
            "minimum": 0,
            "type": "integer"
          },
          "start_ms": {
            "description": "Absolute word start, ms (24h cap)",
            "maximum": 86400000,
            "minimum": 0,
            "type": "integer"
          },
          "text": {
            "description": "Word text (whitespace-stripped)",
            "type": "string"
          }
        },
        "required": [
          "text",
          "start_ms",
          "end_ms"
        ],
        "type": "object"
      },
      "minItems": 1,
      "type": "array"
    }
  },
  "required": [
    "input_url",
    "words"
  ],
  "type": "object"
}
```

## concat

Concatenate 2-10 clips (input\_urls = clips, normalized).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "height": {
      "default": 720,
      "minimum": 16,
      "type": "integer"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 10,
      "minItems": 2,
      "type": "array"
    },
    "width": {
      "default": 1280,
      "minimum": 16,
      "type": "integer"
    }
  },
  "required": [
    "input_urls"
  ],
  "type": "object"
}
```

## concat\_copy

Losslessly rejoin 2-50 already-encoded, SAME-codec chunks via the concat demuxer with -c copy — no re-encode (input\_urls = chunks in order). The chunks MUST share identical video+audio codecs/params; the engine probe-gate rejects a mismatch (a -c copy across divergent codecs can exit 0 yet play corrupt). For clips that differ, use concat (filter-concat re-encode) instead.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container; must match the chunks' codecs (no re-encode).",
      "enum": [
        "mp4",
        "mkv",
        "ts"
      ],
      "type": "string"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 50,
      "minItems": 2,
      "type": "array"
    }
  },
  "required": [
    "input_urls"
  ],
  "type": "object"
}
```

## speed

Change playback speed (0.25x-4x).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "factor": {
      "default": 2,
      "maximum": 4,
      "minimum": 0.25,
      "type": "number"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## volume

Adjust audio volume (0-10x). Container mp4/mkv only.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "enum": [
        "mp4",
        "mkv"
      ],
      "type": "string"
    },
    "factor": {
      "default": 2,
      "maximum": 10,
      "minimum": 0,
      "type": "number"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## loudnorm

Normalize audio loudness to a target (EBU R128). Container mp4/mkv only.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "enum": [
        "mp4",
        "mkv"
      ],
      "type": "string"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "lra": {
      "default": 11,
      "description": "Target loudness range in LU.",
      "maximum": 50,
      "minimum": 1,
      "type": "number"
    },
    "target_i": {
      "default": -14,
      "description": "Integrated loudness target in LUFS (-14 streaming, -23 broadcast).",
      "maximum": -5,
      "minimum": -70,
      "type": "number"
    },
    "true_peak": {
      "default": -1,
      "description": "Max true peak in dBTP.",
      "maximum": 0,
      "minimum": -9,
      "type": "number"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## reverse

Reverse video + audio.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## poster

Produce an mp4 + a jpg poster frame (named outputs).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "crf": {
      "default": 23,
      "maximum": 51,
      "minimum": 0,
      "type": "integer"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "thumbnail_second": {
      "default": 0,
      "maximum": 36000,
      "minimum": 0,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## rotate

Rotate 90/180/270 degrees.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "degrees": {
      "default": 90,
      "enum": [
        90,
        180,
        270
      ],
      "type": "number"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## crop

Crop a region (omit x/y to center).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "height": {
      "default": 480,
      "maximum": 8192,
      "minimum": 1,
      "type": "integer"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "width": {
      "default": 640,
      "maximum": 8192,
      "minimum": 1,
      "type": "integer"
    },
    "x": {
      "maximum": 8192,
      "minimum": 0,
      "type": "integer"
    },
    "y": {
      "maximum": 8192,
      "minimum": 0,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## pad

Pad onto a canvas with a background color (omit x/y to center).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "color": {
      "default": "black",
      "type": "string"
    },
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "height": {
      "default": 720,
      "maximum": 8192,
      "minimum": 1,
      "type": "integer"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "width": {
      "default": 1280,
      "maximum": 8192,
      "minimum": 1,
      "type": "integer"
    },
    "x": {
      "maximum": 8192,
      "minimum": 0,
      "type": "integer"
    },
    "y": {
      "maximum": 8192,
      "minimum": 0,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## reframe

Reframe to a social aspect ratio (9:16/1:1/4:5/16:9) by crop (fill) or pad (letterbox).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "aspect": {
      "default": "9:16",
      "description": "Target social aspect (maps to a standard resolution).",
      "enum": [
        "9:16",
        "1:1",
        "4:5",
        "16:9"
      ],
      "type": "string"
    },
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "mode": {
      "default": "crop",
      "description": "crop = fill + center-crop (no bars); pad = fit + letterbox.",
      "enum": [
        "crop",
        "pad"
      ],
      "type": "string"
    },
    "pad_color": {
      "default": "black",
      "description": "Letterbox color (pad mode only).",
      "enum": [
        "black",
        "white"
      ],
      "type": "string"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## reframe\_track

Pan a fixed-size crop window across the frame to follow a subject, driven over time by a crop-track sidecar (input\_urls = \[video, crop\_track]). input1 is a server-staged ffmpeg sendcmd script of timestamped crop commands. NOTE: the crop-track is emitted upstream by a saliency/face-tracking model that is NOT yet in the catalog, so this capability is INERT until that model is onboarded — it is not generally usable yet.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "height": {
      "default": 360,
      "description": "Crop window height in pixels (fixed for the clip; must not exceed the source height).",
      "maximum": 8192,
      "minimum": 1,
      "type": "integer"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 2,
      "minItems": 2,
      "type": "array"
    },
    "width": {
      "default": 640,
      "description": "Crop window width in pixels (fixed for the clip; must not exceed the source width).",
      "maximum": 8192,
      "minimum": 1,
      "type": "integer"
    }
  },
  "required": [
    "input_urls"
  ],
  "type": "object"
}
```

## fade

Fade the video (and matching audio afade) in and/or out. type out/both REQUIRE fade\_out\_start (static argv cannot derive the clip duration).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "fade_in_duration": {
      "default": 1,
      "description": "Fade-in length in seconds (from t=0); must be > 0.",
      "exclusiveMinimum": 0,
      "maximum": 3600,
      "type": "number"
    },
    "fade_out_duration": {
      "default": 1,
      "description": "Fade-out length in seconds; must be > 0.",
      "exclusiveMinimum": 0,
      "maximum": 3600,
      "type": "number"
    },
    "fade_out_start": {
      "description": "Timestamp (seconds) the fade-out begins. REQUIRED when type is out or both; ignored when type is in. Static argv cannot know the clip duration, so this is explicit rather than derived from an extra probe.",
      "maximum": 86400,
      "minimum": 0,
      "type": "number"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "type": {
      "default": "in",
      "description": "Which fades to apply. Default in: a bare fade is a 1s fade-in (st=0). out and both REQUIRE fade_out_start (static argv cannot derive the clip duration).",
      "enum": [
        "in",
        "out",
        "both"
      ],
      "type": "string"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## color

Grade a clip via ffmpeg's eq filter: brightness/contrast/saturation/gamma. A bare color (all defaults) is the identity grade — it re-encodes but leaves the picture unchanged.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "brightness": {
      "default": 0,
      "description": "Additive luma offset; 0 = unchanged.",
      "maximum": 1,
      "minimum": -1,
      "type": "number"
    },
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "contrast": {
      "default": 1,
      "description": "Contrast multiplier; 1 = unchanged, 0 = flat gray.",
      "maximum": 2,
      "minimum": 0,
      "type": "number"
    },
    "gamma": {
      "default": 1,
      "description": "Gamma curve; 1 = unchanged. Must be > 0 (schema min 0.1).",
      "maximum": 10,
      "minimum": 0.1,
      "type": "number"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "saturation": {
      "default": 1,
      "description": "Chroma saturation; 1 = unchanged, 0 = grayscale.",
      "maximum": 3,
      "minimum": 0,
      "type": "number"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## lut3d

Color-grade a clip through a caller-supplied .cube 3D-LUT via ffmpeg's lut3d filter (input\_urls = \[video, cube\_lut]).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 2,
      "minItems": 2,
      "type": "array"
    },
    "interp": {
      "description": "3D-LUT interpolation mode; omit for the ffmpeg default (tetrahedral).",
      "enum": [
        "nearest",
        "trilinear",
        "tetrahedral"
      ],
      "type": "string"
    }
  },
  "required": [
    "input_urls"
  ],
  "type": "object"
}
```

## stills

Extract a single frame as a jpg/png image. position first/last/at (default first); position at REQUIRES timestamp (static argv cannot derive the clip duration). quality applies to jpg only (png is lossless).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "format": {
      "default": "jpg",
      "description": "Image format (jpg or png).",
      "enum": [
        "jpg",
        "png"
      ],
      "type": "string"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "position": {
      "default": "first",
      "description": "Which frame to extract. first = frame at t=0; last = the final frame; at REQUIRES timestamp.",
      "enum": [
        "first",
        "last",
        "at"
      ],
      "type": "string"
    },
    "quality": {
      "description": "JPEG quality (2 = best, 31 = worst). Validated whenever supplied; applied only to jpg (png is lossless).",
      "maximum": 31,
      "minimum": 2,
      "type": "integer"
    },
    "timestamp": {
      "description": "Seconds from start of the frame to extract. REQUIRED when position is at; validated whenever supplied, used only for at.",
      "maximum": 86400,
      "minimum": 0,
      "type": "number"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## overlay

Composite a second video over a base at a corner/center anchor (input\_urls = \[base, overlay]).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 2,
      "minItems": 2,
      "type": "array"
    },
    "margin": {
      "default": 0,
      "description": "Inset in pixels from the anchored edge(s). Ignored for center.",
      "maximum": 1024,
      "minimum": 0,
      "type": "number"
    },
    "position": {
      "default": "bottom_right",
      "description": "Anchor for the overlay. Corners offset by margin; center ignores margin.",
      "enum": [
        "top_left",
        "top_right",
        "bottom_left",
        "bottom_right",
        "center"
      ],
      "type": "string"
    },
    "scale": {
      "description": "Optional factor to resize the overlay before compositing (iw/ih * factor). Omit to keep its native size.",
      "maximum": 10,
      "minimum": 0.01,
      "type": "number"
    }
  },
  "required": [
    "input_urls"
  ],
  "type": "object"
}
```

## audio\_replace

Replace or mix a video's audio track with a second input (input\_urls = \[video, audio]). Container mp4/mkv only.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container. mp4/mkv only — the video stream is copied (not re-encoded) and webm holds only VP8/VP9.",
      "enum": [
        "mp4",
        "mkv"
      ],
      "type": "string"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 2,
      "minItems": 2,
      "type": "array"
    },
    "mode": {
      "default": "replace",
      "description": "replace: input1's audio replaces input0's (output runs to the shorter of the two via -shortest). mix: input1's audio is mixed into input0's (amix, ending with the shorter audio) over the full copied video.",
      "enum": [
        "replace",
        "mix"
      ],
      "type": "string"
    },
    "normalize": {
      "description": "mix only: amix loudness normalization. true (default, no-clip-safe) scales each track by 1/n, so the original and added audio are each halved. false preserves source levels (louder, can clip if both inputs are hot). Ignored in replace mode.",
      "type": "boolean"
    },
    "offset_ms": {
      "description": "Milliseconds of silence prepended to input1's audio (adelay, all channels) before the replace/mix, so narration can start at t>0 (engine default 0). In replace mode -shortest still ends the output at min(video, offset+audio), so an offset past the video end truncates the narration.",
      "maximum": 3600000,
      "minimum": 0,
      "type": "integer"
    }
  },
  "required": [
    "input_urls"
  ],
  "type": "object"
}
```

## package\_abr

Package a video into an adaptive-bitrate streaming bundle: CMAF HLS (master.m3u8) + DASH (manifest.mpd) over shared fragmented-mp4 segments. Clear (no DRM); stream-copies (no re-encode). Input should carry both a video and an audio stream. Routed to the Shaka packager.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "segment_duration": {
      "default": 4,
      "description": "Target segment length in seconds",
      "maximum": 30,
      "minimum": 1,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## package\_abr\_ladder

Package a video into a multi-rendition adaptive-bitrate ladder: CMAF HLS (master.m3u8) + DASH (manifest.mpd) over N renditions (clear, no DRM). Re-encodes each rung, so any source codec is accepted; the input must carry both a video and an audio stream. Give an explicit renditions list OR a ladder preset (not both); omit both for a default ladder clipped to the source height. Widths are derived from the source aspect (never supplied); a rung taller than the source is rejected (no upscaling). Unlike package\_abr (single-rendition stream-copy via Shaka) this builds the full ladder; unlike hls\_ladder (HLS only) it also emits DASH.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "ladder": {
      "description": "Preset descending rung set up to this height, clipped to the source. Mutually exclusive with renditions.",
      "enum": [
        "360p",
        "480p",
        "720p",
        "1080p",
        "1440p",
        "2160p"
      ],
      "type": "string"
    },
    "renditions": {
      "description": "Explicit rungs (1..6). Duplicate heights and any height above the source are rejected. Mutually exclusive with ladder.",
      "items": {
        "additionalProperties": false,
        "properties": {
          "bitrate_kbps": {
            "description": "Target video bitrate in kbps.",
            "maximum": 50000,
            "minimum": 100,
            "type": "integer"
          },
          "height": {
            "description": "Rendition height in px; one of 360,480,720,1080,1440,2160. Width is derived from the source aspect (even-rounded).",
            "enum": [
              360,
              480,
              720,
              1080,
              1440,
              2160
            ],
            "type": "number"
          }
        },
        "required": [
          "height",
          "bitrate_kbps"
        ],
        "type": "object"
      },
      "maxItems": 6,
      "minItems": 1,
      "type": "array"
    },
    "segment_duration": {
      "default": 4,
      "description": "Target segment length in seconds.",
      "maximum": 30,
      "minimum": 1,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## hls\_ladder

Encode a TRUE multi-rendition adaptive-bitrate HLS ladder (master.m3u8 + N variant playlists/segments) in one ffmpeg pass. Re-encodes every rung, so any source codec is accepted; the input must carry an audio stream. Give an explicit renditions list OR a ladder preset (not both); omit both for a default 3-rung 720p ladder. Unlike package\_abr (single-rendition stream-copy via Shaka), this builds the full ladder over the ffmpeg baseline. Upscaling above the source is NOT rejected, so choose rungs at or below the source height.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "ladder": {
      "description": "Preset descending rung set up to this height. Mutually exclusive with renditions.",
      "enum": [
        "240p",
        "360p",
        "480p",
        "720p",
        "1080p"
      ],
      "type": "string"
    },
    "renditions": {
      "description": "Explicit rungs (1..6). Width is derived from the source aspect (even-rounded); duplicate heights are rejected. Mutually exclusive with ladder.",
      "items": {
        "additionalProperties": false,
        "properties": {
          "bitrate_kbps": {
            "description": "Target video bitrate in kbps.",
            "maximum": 50000,
            "minimum": 100,
            "type": "integer"
          },
          "height": {
            "description": "Rendition height in px; one of 144,240,360,480,720,1080,1440,2160.",
            "enum": [
              144,
              240,
              360,
              480,
              720,
              1080,
              1440,
              2160
            ],
            "type": "number"
          }
        },
        "required": [
          "height",
          "bitrate_kbps"
        ],
        "type": "object"
      },
      "maxItems": 6,
      "minItems": 1,
      "type": "array"
    },
    "segment_duration": {
      "default": 4,
      "description": "Target segment length in seconds.",
      "maximum": 30,
      "minimum": 1,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## scene\_detect

Detect hard cuts and return a structured analysis (\{scenes:\[\{time,score}]}) as the result — a metadata RESULT, not a media URL (egress 0).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "threshold": {
      "default": 0.3,
      "description": "Scene-change sensitivity (0..1): a frame whose change score exceeds it is reported as a cut. Lower finds more cuts.",
      "maximum": 1,
      "minimum": 0,
      "type": "number"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## silence\_detect

Detect silent spans in the audio and return a structured analysis (\{silences:\[\{start,end,duration}]}) as the result — a metadata RESULT, not a media URL (egress 0); an input with no silence is a valid empty result.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "min_silence_seconds": {
      "default": 0.5,
      "description": "Shortest span (seconds) reported as silence.",
      "exclusiveMinimum": 0,
      "maximum": 600,
      "type": "number"
    },
    "noise_db": {
      "default": -30,
      "description": "Silence threshold in dBFS: samples quieter than this count as silence (e.g. -30, -50).",
      "maximum": 0,
      "minimum": -80,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## probe

Probe a media file with ffprobe and return its container format + streams as a structured JSON RESULT (codecs, duration, resolution, bitrate, …) — a metadata RESULT, not a media URL (egress 0). Use it to inspect a source before choosing an operation.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## silence\_remove

Keep only the given time ranges and concatenate them into one continuous output (gaps closed) in a single ffmpeg pass — the 'act' half of silence-skip and long->short editing. Chain silence\_detect first and invert its silent spans into the kept ranges. Ranges must be sorted ascending and non-overlapping; everything outside them is dropped. Re-encodes.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "ranges": {
      "description": "Time ranges to KEEP, in seconds, sorted ascending and non-overlapping (1..100).",
      "items": {
        "additionalProperties": false,
        "properties": {
          "end": {
            "description": "Range end in seconds (must be greater than start).",
            "minimum": 0,
            "type": "number"
          },
          "start": {
            "description": "Range start in seconds.",
            "minimum": 0,
            "type": "number"
          }
        },
        "required": [
          "start",
          "end"
        ],
        "type": "object"
      },
      "maxItems": 100,
      "minItems": 1,
      "type": "array"
    }
  },
  "required": [
    "input_url",
    "ranges"
  ],
  "type": "object"
}
```

## storyboard\_sprites

Sample frames at a fixed interval and pack them into one tiled sprite sheet image PLUS a WebVTT thumbnail index (named outputs: sheet + vtt).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "columns": {
      "default": 5,
      "maximum": 20,
      "minimum": 1,
      "type": "integer"
    },
    "format": {
      "default": "jpg",
      "description": "Sprite sheet image format.",
      "enum": [
        "jpg",
        "png"
      ],
      "type": "string"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "interval_seconds": {
      "default": 2,
      "description": "Seconds between sampled thumbnails.",
      "minimum": 0.1,
      "type": "number"
    },
    "rows": {
      "default": 5,
      "maximum": 20,
      "minimum": 1,
      "type": "integer"
    },
    "tile_height": {
      "default": 90,
      "maximum": 8192,
      "minimum": 16,
      "type": "integer"
    },
    "tile_width": {
      "default": 160,
      "maximum": 8192,
      "minimum": 16,
      "type": "integer"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## slideshow

Turn 2-50 still images into one silent video (input\_urls = images): each image is shown for image\_duration, normalized to a common canvas, optionally Ken Burns-zoomed, and concatenated.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "effect": {
      "default": "none",
      "description": "none, or kenburns for a slow zoom/pan.",
      "enum": [
        "none",
        "kenburns"
      ],
      "type": "string"
    },
    "fps": {
      "default": 30,
      "maximum": 60,
      "minimum": 1,
      "type": "integer"
    },
    "height": {
      "default": 720,
      "maximum": 8192,
      "minimum": 16,
      "type": "integer"
    },
    "image_duration": {
      "default": 3,
      "description": "Seconds each image is shown.",
      "maximum": 30,
      "minimum": 0.5,
      "type": "number"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 50,
      "minItems": 2,
      "type": "array"
    },
    "width": {
      "default": 1280,
      "maximum": 8192,
      "minimum": 16,
      "type": "integer"
    }
  },
  "required": [
    "input_urls"
  ],
  "type": "object"
}
```

## crossfade

Crossfade two clips into one (input\_urls = \[first, second]): the video xfade starts offset seconds into the first clip and blends over duration seconds; the audio always acrossfades at the junction of the two tracks. Both inputs must carry a video AND an audio stream and share a frame rate. Re-encodes.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "duration": {
      "default": 1,
      "description": "Transition length in seconds, shared by the video xfade and the audio acrossfade. Must not exceed the first input's duration.",
      "maximum": 10,
      "minimum": 0.1,
      "type": "number"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 2,
      "minItems": 2,
      "type": "array"
    },
    "offset": {
      "description": "Seconds into the first input where the video transition starts. Omit for the junction (first duration minus duration, derived by probe); REQUIRED when the first input's duration cannot be probed. offset + duration must not exceed the first input's duration.",
      "maximum": 86400,
      "minimum": 0,
      "type": "number"
    },
    "transition": {
      "default": "fade",
      "description": "Which xfade wipe/blend the video transition uses; the audio always uses a plain acrossfade regardless.",
      "enum": [
        "circleclose",
        "circleopen",
        "dissolve",
        "fade",
        "fadeblack",
        "fadewhite",
        "pixelize",
        "radial",
        "slidedown",
        "slideleft",
        "slideright",
        "slideup",
        "wipedown",
        "wipeleft",
        "wiperight",
        "wipeup"
      ],
      "type": "string"
    }
  },
  "required": [
    "input_urls"
  ],
  "type": "object"
}
```

## silence\_split

Detect silences and split the file at them in one job (multi-output: seg\_000, seg\_001, ... in order): each cut lands at the END of a detected silence, so each new segment starts where sound resumes. max\_segment\_seconds force-cuts when no usable silence arrives in time. The input must carry an audio stream. Default stream-copy snaps cuts to keyframes; reencode=true gives exact boundaries (H.264, invalid in webm).

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "crf": {
      "default": 23,
      "description": "H.264 quality for reencode=true (0 best, 51 worst). Ignored when reencode is false.",
      "maximum": 51,
      "minimum": 0,
      "type": "integer"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "max_segment_seconds": {
      "description": "REQUIRED hard cap on segment length in seconds: when the next usable silence is further away, cuts are forced at exactly this spacing. With the default reencode=false a cut snaps to the next keyframe, so a segment can overrun the cap by up to one keyframe interval.",
      "maximum": 86400,
      "minimum": 1,
      "type": "number"
    },
    "min_segment_seconds": {
      "default": 0,
      "description": "A silence cut that would close a segment shorter than this many seconds is skipped (the segment keeps growing until the next usable silence or the forced cap cut). Must not exceed max_segment_seconds; the default 0 allows arbitrarily short segments.",
      "minimum": 0,
      "type": "number"
    },
    "min_silence_seconds": {
      "default": 0.5,
      "description": "Shortest span (seconds) treated as a silence.",
      "exclusiveMinimum": 0,
      "maximum": 600,
      "type": "number"
    },
    "noise_db": {
      "default": -30,
      "description": "Silence threshold in dBFS: samples quieter than this count as silence (e.g. -30, -50).",
      "maximum": 0,
      "minimum": -80,
      "type": "integer"
    },
    "reencode": {
      "default": false,
      "description": "false (default) stream-copies: near-instant and lossless, but cuts land on the keyframe at or after each computed time. true forces a keyframe at every cut for exact boundaries but re-encodes to H.264 (use mp4 or mkv — webm cannot hold H.264).",
      "type": "boolean"
    }
  },
  "required": [
    "input_url",
    "max_segment_seconds"
  ],
  "type": "object"
}
```

## sticker\_overlay

Composite a sticker/logo image (or video) over a base video inside a timed window (input\_urls = \[base, sticker]): visible from start until end (omit end to run to the source end). Same anchors as overlay; the sticker's own alpha is honored (no opacity control). Only the base's audio is kept. Re-encodes.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "end": {
      "description": "Second of the base video the sticker disappears. Omit to keep it visible until the end of the source. Must be greater than start; a value past the probed source duration is rejected (never clamped).",
      "maximum": 3600,
      "minimum": 0,
      "type": "number"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 2,
      "minItems": 2,
      "type": "array"
    },
    "margin": {
      "default": 0,
      "description": "Inset in pixels from the anchored edge(s). Ignored for center.",
      "maximum": 1024,
      "minimum": 0,
      "type": "number"
    },
    "position": {
      "default": "bottom_right",
      "description": "Anchor for the sticker. Corners offset by margin; center ignores margin.",
      "enum": [
        "top_left",
        "top_right",
        "bottom_left",
        "bottom_right",
        "center"
      ],
      "type": "string"
    },
    "scale": {
      "default": 1,
      "description": "Factor to resize the sticker before compositing (bicubic, aspect preserved). The default 1 composites at native size with no resample pass.",
      "maximum": 4,
      "minimum": 0.05,
      "type": "number"
    },
    "start": {
      "description": "REQUIRED second of the base video the sticker appears (inclusive). Must be less than end when end is supplied, and before the source's probed duration.",
      "maximum": 3600,
      "minimum": 0,
      "type": "number"
    }
  },
  "required": [
    "input_urls",
    "start"
  ],
  "type": "object"
}
```

## title\_card

Draw a text title over the video with drawtext (rendered verbatim — no ffmpeg expansion; newlines break lines; max 200 chars after normalization). Show window: omit start and end for the whole video, start alone runs to the end, end alone runs from the beginning. Re-encodes.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "container": {
      "default": "mp4",
      "description": "Output container",
      "enum": [
        "mp4",
        "webm",
        "mkv"
      ],
      "type": "string"
    },
    "end": {
      "description": "Seconds when the title stops being visible. Alone it shows from the beginning until end. Must be greater than start when both are given; an end past the probed source duration is rejected.",
      "exclusiveMinimum": 0,
      "maximum": 86400,
      "type": "number"
    },
    "font": {
      "default": "Liberation Sans",
      "description": "Font family (bundled in the runtime image).",
      "enum": [
        "Liberation Sans",
        "Liberation Serif",
        "Liberation Mono",
        "DejaVu Sans",
        "DejaVu Serif",
        "DejaVu Sans Mono",
        "Anton",
        "Archivo Black",
        "Bangers",
        "Inter",
        "Montserrat",
        "Montserrat Black",
        "Poppins",
        "TikTok Sans",
        "TikTok Sans Medium",
        "TikTok Display",
        "TikTok Display Medium",
        "TikTok Text",
        "TikTok Text Medium",
        "Public Sans",
        "Public Sans Medium"
      ],
      "type": "string"
    },
    "font_color": {
      "default": "white",
      "description": "Text color: named (black/white/gray/grey/red/green/blue/yellow) or #RRGGBB hex.",
      "type": "string"
    },
    "font_size": {
      "default": 64,
      "description": "Text size in pixels.",
      "maximum": 288,
      "minimum": 12,
      "type": "integer"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "position": {
      "default": "center",
      "description": "Anchor for the text block. Non-center anchors are inset from the frame edge by 5% of the frame dimension.",
      "enum": [
        "center",
        "top",
        "bottom",
        "top_left",
        "top_right",
        "bottom_left",
        "bottom_right"
      ],
      "type": "string"
    },
    "start": {
      "description": "Seconds when the title becomes visible. Omit (with end also omitted) to show it for the whole video; alone it shows from start to the end. Must be less than end when both are given, and before the probed source duration.",
      "maximum": 86400,
      "minimum": 0,
      "type": "number"
    },
    "text": {
      "description": "Title text; at most 200 characters after normalization. Newlines render as line breaks.",
      "minLength": 1,
      "type": "string"
    }
  },
  "required": [
    "input_url",
    "text"
  ],
  "type": "object"
}
```

## audio\_duck

Duck a music/program bed under a voice track (input\_urls = \[music, voice]): the voice drives a sidechain compressor that lowers the music while the voice is above threshold, then the same voice is mixed on top. Audio-only output (a video input contributes only its audio) running exactly as long as the music; levels are never auto-normalized. Both inputs must carry an audio stream.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "attack_ms": {
      "default": 20,
      "description": "Milliseconds for the duck to fully engage after the voice crosses threshold.",
      "maximum": 2000,
      "minimum": 0.01,
      "type": "number"
    },
    "container": {
      "default": "mp3",
      "description": "Output audio format and codec (default mp3): mp3 encodes with libmp3lame and m4a with AAC; wav writes lossless PCM.",
      "enum": [
        "mp3",
        "wav",
        "m4a"
      ],
      "type": "string"
    },
    "input_urls": {
      "description": "Source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "maxItems": 2,
      "minItems": 2,
      "type": "array"
    },
    "ratio": {
      "default": 8,
      "description": "How hard the music is reduced once the voice crosses threshold: 1 leaves it untouched, 20 flattens it. The default 8 is a firm broadcast-style duck.",
      "maximum": 20,
      "minimum": 1,
      "type": "number"
    },
    "release_ms": {
      "default": 250,
      "description": "Milliseconds for the music to recover to full level after the voice falls below threshold.",
      "maximum": 9000,
      "minimum": 0.01,
      "type": "number"
    },
    "threshold": {
      "default": 0.05,
      "description": "Voice level that triggers the duck, as LINEAR amplitude where 1 is full scale (0 dBFS); the default 0.05 is about -26 dBFS. Lower values duck on quieter voice.",
      "maximum": 1,
      "minimum": 0.000976563,
      "type": "number"
    }
  },
  "required": [
    "input_urls"
  ],
  "type": "object"
}
```

## audio\_master

Master the audio track to a distribution loudness target: a fixed gentle compressor, then single-pass EBU R128 normalization, then a true-peak limiter, written audio-only (always resampled to 44100 Hz). Give target\_lufs/ceiling\_dbtp OR a named preset — NOT both (a call supplying preset alongside target\_lufs/ceiling\_dbtp is rejected before submission); omit all three for the engine defaults (-14 LUFS / -1 dBTP). For normalization without compression use loudnorm instead. The input must carry an audio stream.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "bitrate": {
      "description": "Audio bitrate for the lossy containers mp3/m4a (engine default 320k). Rejected for wav (lossless) — omit it there.",
      "enum": [
        "128k",
        "192k",
        "256k",
        "320k"
      ],
      "type": "string"
    },
    "ceiling_dbtp": {
      "description": "True-peak ceiling in dBTP (engine default -1); the final limiter enforces it. Mutually exclusive with preset.",
      "maximum": 0,
      "minimum": -9,
      "type": "number"
    },
    "container": {
      "default": "mp3",
      "description": "Output audio format and codec (default mp3): mp3/m4a honor bitrate; wav writes lossless PCM and rejects an explicit bitrate.",
      "enum": [
        "mp3",
        "wav",
        "m4a"
      ],
      "type": "string"
    },
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    },
    "preset": {
      "description": "Named target pair: streaming = -14 LUFS / -1 dBTP, podcast = -16 / -1.5, broadcast = -23 / -2. Mutually exclusive with target_lufs/ceiling_dbtp.",
      "enum": [
        "broadcast",
        "podcast",
        "streaming"
      ],
      "type": "string"
    },
    "target_lufs": {
      "description": "Integrated loudness target in LUFS (engine default -14: streaming; -16 podcast, -23 broadcast). Mutually exclusive with preset.",
      "maximum": -8,
      "minimum": -30,
      "type": "number"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## audio\_analysis

Measure the audio track — integrated loudness (LUFS), loudness range (LU), true peak (dBTP), sample peak (dBFS), and the astats clipping indicators flat\_factor/peak\_count — via astats+ebur128, returned as a structured JSON RESULT, not a media URL (egress 0). The input MUST carry an audio stream. A field that cannot be measured is omitted (e.g. digital silence has no finite dB peak) — treat a missing field as not-measurable, never as 0.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "input_url": {
      "description": "Source media URL",
      "format": "uri",
      "type": "string"
    }
  },
  "required": [
    "input_url"
  ],
  "type": "object"
}
```

## run\_ffmpeg

Run an arbitrary ffmpeg command (full power). Put the ffmpeg arguments in `args` (the leading `ffmpeg` is added for you). Use \{input} for a single input\_url, or \{input0}/\{input1}/... for multiple input\_urls; use \{output} for the output. Subject to server-side allowlist.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "args": {
      "description": "ffmpeg arguments, e.g. ['-i','{input}','-an','{output}'] (omit the leading 'ffmpeg')",
      "items": {
        "type": "string"
      },
      "minItems": 1,
      "type": "array"
    },
    "input_url": {
      "description": "Single source media URL",
      "format": "uri",
      "type": "string"
    },
    "input_urls": {
      "description": "Multiple source media URLs",
      "items": {
        "format": "uri",
        "type": "string"
      },
      "type": "array"
    },
    "output_format": {
      "description": "Output container, e.g. mp4",
      "type": "string"
    }
  },
  "required": [
    "args"
  ],
  "type": "object"
}
```

## upload\_file

Upload a local file (max 100MB) to eachlabs storage and get a public URL usable as input\_url for the other tools. The presign call and the upload PUT do not create a prediction and are not billed as one; storage egress may bill per your plan.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "content_type": {
      "description": "MIME type of the file; inferred from common media extensions when omitted. Unrecognized extensions upload as application/octet-stream with a warning — pass this explicitly to get an extension-bearing public URL",
      "type": "string"
    },
    "expires_in_seconds": {
      "description": "Public URL lifetime in seconds, 60 to 31536000 (365 days); server default is 180 days",
      "maximum": 31536000,
      "minimum": 60,
      "type": "integer"
    },
    "file_path": {
      "description": "Path to the local file to upload",
      "type": "string"
    }
  },
  "required": [
    "file_path"
  ],
  "type": "object"
}
```

## get\_job

Fetch the current status/result of a prediction you did not wait for.

```json theme={"dark"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "prediction_id": {
      "description": "The prediction id returned by a prior tool call",
      "type": "string"
    }
  },
  "required": [
    "prediction_id"
  ],
  "type": "object"
}
```
