diff --git a/jobs/ffprobe.mdx b/jobs/ffprobe.mdx index f916498..f4bc7b8 100644 --- a/jobs/ffprobe.mdx +++ b/jobs/ffprobe.mdx @@ -26,12 +26,16 @@ tag: "Live" }} /> -You send a `command`: the input media URL, optionally with extra ffprobe flags on top. Rendobar runs `ffprobe` and returns a normalized `summary` you branch on directly, plus the full raw `format`, `streams`, and `chapters` for anything the summary leaves out. +You write the `command`. Rendobar runs `ffprobe` essentially as written, the same model as [FFmpeg](/jobs/ffmpeg), auto-filling only the defaults you didn't specify. The result is a normalized `summary` you branch on directly, plus the full raw `format`, `streams`, and `chapters` for anything the summary leaves out. **Live.** Accepts video, audio, and image URLs. + +From a shell? Use [the CLI](/cli): `rb ffprobe https://example.com/video.mp4`. Local files upload automatically. + + ## Probe a file @@ -86,7 +90,7 @@ curl -X POST "https://api.rendobar.com/jobs" \ -The job returns an id, and the answer lands in its `output.data` once it completes. The `summary` is the part you branch on. The raw `format`, `streams`, and `chapters` sit next to it for the fields the summary does not lift out. +The job returns an id, and the answer lands in its `output.data` once it completes. The command above is just a URL, so Rendobar auto-fills `-v error`, `-print_format json`, and `-show_format -show_streams -show_chapters` on top of it. The full structured result comes back even though you asked for nothing extra. See [The command](#the-command). The `summary` is the part you branch on. The raw `format`, `streams`, and `chapters` sit next to it for the fields the summary does not lift out. An MCP client reaches the same job through the generic `submit_job` tool with `type: "ffprobe"`. See the [MCP server](/mcp-server). @@ -94,32 +98,43 @@ An MCP client reaches the same job through the generic `submit_job` tool with `t ## The command -`command` is additive, not a full ffprobe invocation. The runner always injects the core sections and JSON output for you (`-show_format -show_streams -show_chapters -of json`), so the minimal `command` is just the URL: +Your `command` runs essentially as written, honored flag-for-flag, the same model as [FFmpeg's `command`](/jobs/ffmpeg#request). A leading `ffprobe` token is fine either way. + +Rendobar auto-fills only what you didn't specify: + +| You didn't pass | Rendobar adds | +|---|---| +| `-v` or `-loglevel` | `-v error` | +| A writer flag (`-of`, `-print_format`, `-output_format`) | `-print_format json` | +| Any `-show_*` flag | `-show_format -show_streams -show_chapters` | + +So the minimal command is just the URL, and auto-fill still returns the full structured report: ``` https://cdn.rendobar.com/assets/examples/sample.mp4 ``` -Anything you add stacks on top of that guaranteed core. Add `-show_frames` to get per-frame data for a bounded range: +Write a full command and every flag you pass is honored, not stripped or normalized away: ``` --show_frames -read_intervals %+#5 https://cdn.rendobar.com/assets/examples/sample.mp4 +ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate -print_format json https://cdn.rendobar.com/assets/examples/sample.mp4 ``` -This adds a `frames` array (the first 5 frames) to `output.data`, next to the `summary`, `format`, `streams`, and `chapters` you always get. A leading `ffprobe` token is fine either way. - -Two categories of flag get normalized instead of rejected, because they would fight the additive model: +This picks the primary video stream, reads 4 fields off it, and returns exactly that instead of the full report. `-select_streams`, `-show_entries`, `-show_frames`, your own `-v`/`-loglevel`, and any writer you choose (`-print_format csv`, `xml`, `flat`) all pass through untouched. See [The output shape](#the-output-shape) for what comes back for each writer. -- **Writer flags** (`-of`, `-print_format`, `-output_format`) are stripped. The result is always JSON. -- **Restricting flags** (`-select_streams`, `-show_entries`) are stripped. You already get the full structured result back, so filter it client-side instead of asking ffprobe to narrow it. + +Auto-fill checks by category, not by flag. Pass any `-show_*` flag yourself and the whole auto-filled trio is suppressed, not just the one you named. `-show_frames -read_intervals %+#5` alone returns only `frames`, no `summary`. Add `-show_format -show_streams` yourself to keep both: -Each strip adds a `WRITER_NORMALIZED` or `QUERY_NORMALIZED` entry to `output.data.warnings` rather than failing the job. +``` +-show_frames -show_format -show_streams -read_intervals %+#5 https://cdn.rendobar.com/assets/examples/sample.mp4 +``` + -`-protocol_whitelist`, `-protocol_blacklist`, and shell control characters (`>`, `|`, `;`) are rejected outright with `VALIDATION_ERROR` before the job is created. ffprobe's output is always returned to you, never written to a file. +The security floor is unchanged: `-protocol_whitelist`, `-protocol_blacklist`, shell control characters (`>`, `|`, `;`), and `-o`/`-output_file` are rejected outright with `VALIDATION_ERROR` before the job is created. ffprobe's output is always returned to you, never written to a file. ## The summary -`summary` normalizes the parts of ffprobe that are awkward to read raw: rotation buried in side data, fractional frame rates, HDR spread across three color fields, cover art masquerading as a video stream. +`summary` normalizes the parts of ffprobe that are awkward to read raw: rotation buried in side data, fractional frame rates, HDR spread across three color fields, cover art masquerading as a video stream. It's best-effort: present only when your run produced both `format` and `streams`, whether by auto-fill or your own `-show_format -show_streams`. A command that asks for something else, like `-show_frames` alone or a non-JSON writer, omits it. ```json { @@ -184,7 +199,7 @@ Every `summary` field name is stable. Write your checks against `summary.video.w ## Parameters - The input media URL, optionally prefixed with additive ffprobe flags. See [The command](#the-command). + A raw ffprobe command, run essentially as written. Minimal form is just the input media URL. The `ffprobe` prefix is optional. See [The command](#the-command). @@ -229,7 +244,7 @@ const summaries = await Promise.all( 1. **Probe remote.** `ffprobe` reads the URL directly over HTTPS. A faststart MP4 needs only its header, so nothing downloads, and a file larger than your plan's input limit still probes. 2. **Fall back to download.** When a server does not support range requests, or the `moov` atom sits at the end of a non-faststart MP4, the runner downloads the file, then probes it. Your plan's input-size limit applies to this path. -3. **Normalize.** The raw ffprobe JSON becomes the `summary`, and the raw `format`, `streams`, and `chapters` pass through untouched with ffprobe's own field names. +3. **Normalize.** For a JSON run (the default, or an explicit `-print_format json`), the raw ffprobe JSON becomes the `summary`, and the raw `format`, `streams`, and `chapters` pass through untouched with ffprobe's own field names. For any other writer you chose, ffprobe's raw text output returns as-is in `output.data.stdout`. ## The output shape @@ -240,7 +255,14 @@ Every job returns the same `output` object. [Job output](/concepts/job#the-outpu - `output.files`: `[]`. - `output.expiresAt`: `null`. -`output.data` guarantees `summary`, `format`, `streams`, `chapters`, and `warnings` on every response, consume them with no optional chaining. `programs`, `stream_groups`, `frames`, `packets`, `reportUrl`, and `stderr` are honest optionals: present only when your `command` asked for that section, or when there's something worth surfacing. +`output.data`'s fields depend on which writer your command ends up using, the same JSON-or-not split [The command](#the-command) auto-fills around: + +- **JSON writer** (no writer flag, or an explicit `-print_format json`/`-of json`): `format`, `streams`, `chapters`, and any other section ffprobe produced (`programs`, `stream_groups`, `frames`, `packets`) come back parsed, present exactly when ffprobe emitted them. `summary` is best-effort, present only when the run has both `format` and `streams`. Nothing here needs optional chaining beyond a plain `?` check for whether the field exists. +- **Non-JSON writer** (`-print_format csv`/`xml`/`flat`, or ffprobe's own default text), a writer you chose explicitly: nothing is parsed. `output.data.stdout` carries ffprobe's raw text output verbatim, and `summary` is omitted. + +`warnings` and `stderr` can appear on either shape, present only when there's something worth surfacing. + +The default command (just a URL, everything auto-filled) returns the JSON shape: ```json { @@ -294,6 +316,26 @@ Every job returns the same `output` object. [Job output](/concepts/job#the-outpu } ``` +A command that picks a non-JSON writer, like `-print_format csv`, returns raw text instead. No `summary`, no parsed sections: + +```json +{ + "data": { + "id": "job_abc123", + "type": "ffprobe", + "status": "complete", + "output": { + "data": { + "stdout": "format|mp4,QuickTime / MOV,12.480000,4271822,4213000\nstream|0,video,h264,1920,1080\nstream|1,audio,aac,48000,2\n" + }, + "file": null, + "files": [], + "expiresAt": null + } + } +} +``` + ### Warnings `output.data.warnings` carries machine-readable notes about a probe that succeeded with a caveat. Each entry is `{ code, message }`. Branch on `code`, not the message text. @@ -301,9 +343,7 @@ Every job returns the same `output` object. [Job output](/concepts/job#the-outpu | `code` | Meaning | |---|---| | `DURATION_UNKNOWN` | The container has no duration, a common sign of truncation | -| `WRITER_NORMALIZED` | A writer flag (`-of`, `-print_format`) was stripped from your command | -| `QUERY_NORMALIZED` | A restricting flag (`-select_streams`, `-show_entries`) was stripped from your command | -| `REPORT_SPILLED` | `frames`/`packets` were too large to return inline and were dropped; re-run with a narrower `-read_intervals` | +| `REPORT_SPILLED` | The report was too large to return inline. On a JSON writer, `frames`/`packets` were dropped; on a non-JSON writer, `stdout` was truncated. Re-run with a narrower `-read_intervals` | ## Error handling @@ -340,7 +380,7 @@ Full error catalogue: [Error codes](/support/errors). - **Route portrait and landscape.** Branch on `summary.video.rotation` and the display dimensions. - **Skip silent renditions.** `summary.audio === null` tells you there is no audio track. - **Meter by duration.** `summary.durationSec` for billing or limits, without downloading the file. -- **Inspect a bounded frame range.** Add `-show_frames -read_intervals %+#5` to check keyframe placement on the first 5 seconds without pulling the whole file. +- **Inspect a bounded frame range.** `-show_frames -read_intervals %+#5` checks keyframe placement on the first 5 seconds without pulling the whole file. Add `-show_format -show_streams` too if you also want the `summary`. ## See also