Common ffprobe commands and what each one prints

Probe a media file with the five ffprobe commands that cover most jobs, each shown with the output ffprobe 8.0 prints, from one field to the full JSON.

Share

Five ffprobe commands cover nearly every metadata job: one field for a script, a few stream fields for a decision, the full report for a person, the first frames for a sanity check, and a frame count when the header cannot be trusted. Each one is below with the output ffprobe 8.0 printed for the same 5-second sample clip. The flags decide what comes back and in what shape. Which one you pick did not change probe time in any way I could measure.

One field, for a script

Terminal window
ffprobe -v error -show_entries format=duration -of csv=p=0 input.mp4
# 5.013000

-show_entries format=duration asks for one field of one section. -of csv=p=0 prints it without the section name, so the output is the number and a newline. -v error keeps the version banner and info lines off stderr.

Each part earns its place. Drop p=0 and you get format,5.013000. Drop -of entirely and the default writer wraps the value in a section block:

[FORMAT]
duration=5.013000
[/FORMAT]

The video stream has its own duration, and it is not the same number. -select_streams v:0 -show_entries stream=duration prints 5.000000 for this clip. Why ffprobe reports two video durations covers which one to read and when the gap reaches half a second.

A few stream fields, to decide what to do next

Terminal window
ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,width,height,r_frame_rate \
-of json input.mp4
{
"programs": [
],
"stream_groups": [
],
"streams": [
{
"codec_name": "h264",
"width": 1280,
"height": 720,
"r_frame_rate": "24/1"
}
]
}

-select_streams v:0 picks the first video stream, and -show_entries names exactly the fields the decision needs. The empty programs and stream_groups arrays are ffprobe 8.0’s JSON writer listing sections that had nothing in them. Read streams[0] and ignore the rest.

This is the command I would put in front of a transcode. It asks for four fields, returns four fields, and parses the same way every time. If the next step is a shell script, -of default=nw=1 turns the same query into key=value lines:

codec_name=h264
width=1280
height=720
r_frame_rate=24/1

The whole report, when a person is reading

Terminal window
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4

-print_format and -of are the same flag. This prints every stream and the container. For the sample clip the video stream alone runs to about 60 lines, because each stream carries a disposition object of 19 flags and a tags object. Trimmed to what people usually read:

{
"streams": [
{
"index": 0,
"codec_name": "h264",
"profile": "High",
"codec_type": "video",
"width": 1280,
"height": 720,
"pix_fmt": "yuv420p",
"r_frame_rate": "24/1",
"duration": "5.000000",
"bit_rate": "459337",
"nb_frames": "120"
},
{
"index": 1,
"codec_name": "aac",
"codec_type": "audio",
"sample_rate": "48000",
"channels": 2,
"channel_layout": "stereo",
"duration": "5.013000"
}
],
"format": {
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"duration": "5.013000",
"size": "352121",
"bit_rate": "561932"
}
}

Numbers arrive as strings ("duration": "5.013000") except the integer fields such as width and channels. Parse them explicitly. Several fields are also easy to read wrong even when the JSON is well formed, and the ffprobe metadata gotchas walks through five of them.

The first frames, without reading the whole file

Terminal window
ffprobe -v error -read_intervals %+#5 \
-show_entries frame=media_type,pts_time,pict_type \
-of csv=p=0 input.mp4
video,0.000000,I,
audio,0.000000
video,0.041667,B
video,0.125000,P

-read_intervals %+#5 stops after the first five packets from the start, so this stays fast on a long file. -show_entries frame=... does what -show_frames does with fewer columns. It answers one question: do the first frames decode, and does the stream open on a keyframe (I)?

A frame count, when the header cannot be trusted

Terminal window
ffprobe -v error -count_frames -select_streams v:0 \
-show_entries stream=nb_read_frames -of csv=p=0 input.mp4
# 120

nb_frames in the header said 120 as well. The difference is where the number comes from. -count_frames decodes every packet in the stream and counts them, so it is right even when a header is wrong or missing. It also means the run time grows with the length of the file, where every other command on this page reads a header and stops.

Choosing the writer

json is the one for code. It nests, it escapes properly, and every language parses it.

csv suits a single value in a shell, and it does quote. A title tag of Hello, "world" came back as:

"Hello, ""world"""

It is correct CSV, but a naive cut -d, still splits it. For one free-text value, -of default=nw=1:nk=1 prints it raw (Hello, "world"). flat prints assignable lines (format.tags.title="Hello, \"world\""), which suit eval in a shell script that trusts its input.

An earlier version of this post said the csv writer does not quote fields. That was wrong, and the output above shows it.

Plain ffprobe versus Rendobar’s ffprobe job

Locally, ffprobe input.mp4 with no other flags writes nothing to stdout. The banner and a human-readable summary go to stderr, which is fine for eyes and useless for a parser. A bare filename is not a command at all.

Rendobar’s FFmpeg API runs the ffprobe command you send as written and fills in only what the command leaves out:

  • The leading ffprobe is optional.
  • No -v or -loglevel means -v error is added.
  • No -of or -print_format means -print_format json is added.
  • No -show_ section flag means -show_format -show_streams -show_chapters is added. Pass any section flag yourself (-show_streams alone, say) and none of the three is added.

So on the API a bare URL returns the full JSON report, and a command copied from a terminal session behaves the way it did in the terminal. Choose a non-JSON writer and the job returns ffprobe’s text as stdout. Ask for JSON with both format and streams present and it also returns a normalised summary with codec, resolution, duration, fps, rotation and audio layout already read out.

job.ts
import { createClient } from "@rendobar/sdk";
const rb = createClient({ apiKey: process.env.RENDOBAR_API_KEY });
const job = await rb.jobs.run({
type: "ffprobe",
params: {
command:
"ffprobe -v quiet -print_format json -show_format -show_streams https://cdn.rendobar.com/assets/examples/sample.mp4",
},
});
// The raw report, as ffprobe printed it.
console.log(job.output.data.format.duration); // "5.013000"
// The normalised summary alongside it.
console.log(job.output.data.summary.video.width); // 1280

Install with npm i @rendobar/sdk. jobs.run() submits and waits, so it returns the finished job in one call.

terminal
curl -X POST https://api.rendobar.com/jobs \
-H "Authorization: Bearer $RENDOBAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "ffprobe",
"params": { "command": "ffprobe -v quiet -print_format json -show_format -show_streams https://cdn.rendobar.com/assets/examples/sample.mp4" }
}'

Returns immediately with a job id. Poll GET /jobs/{id} or register a webhook rather than blocking on the request.

A probe job bills a flat $0.0010 whatever the file, about 40% of the $0.0025 median FFmpeg-command job since August 2026.

Flag choice and probe time

Not in anything I could measure. I grouped the ffprobe jobs in Rendobar’s job history by command shape and compared their run times. The median probe ran about 170 ms and the 10th to 90th percentile spanned 100 to 406 ms. The spread inside one shape dwarfed the spread between shapes: the bare-URL shape alone ran anywhere from 94 ms to 4,367 ms, depending on the file it pointed at.

Command shapeHow often it appearsMedian run
a bare URL, no flagsabout half171 ms
-show_format -show_streamsabout 1 in 6280 ms
ffprobe <url>about 1 in 20293 ms
-v quiet -print_format json -show_format -show_streamsfewer234 ms

These jobs ran on different files, on different days, over a network nobody held constant, and the rarer rows rest on few runs. Nothing in the table is a ranking. The only claim it supports is that no header-reading command here is slow. -count_frames is the exception by design, and it never appeared in the history, so it has no number here.

If I were writing a probe into a pipeline today, I would use the stream-fields command with -of json and name every field I read. It fails loudly when a field is missing, where the full report fails quietly by handing back something you did not look at.

Frequently asked questions

Why does ffprobe show bit_rate N/A?

Some containers do not store a per-stream bitrate. Remuxing the same sample clip from MP4 to MKV turned both stream bit_rate values into N/A while format.bit_rate stayed a number. Read format.bit_rate for the overall rate, or compute a stream's rate from its packet sizes.

How do I list only the audio streams with ffprobe?

Use -select_streams a. ffprobe -v error -select_streams a -show_entries stream=index,codec_name,channels,sample_rate -of csv=p=0 input.mp4 printed 1,aac,48000,2 for the sample clip, one line per audio stream.

Sources

Tags #ffprobe#ffmpeg#video-metadata#media#reference
All posts
Share
  1. FFmpeg 9 in the cloud, run it exactly as local Guides for the video API
  2. Creatomate alternative, JSON video at $0.04 a minute Guides for the video API
  3. JSON video API pricing, cheapest per minute in 2026 Guides for the video API