cheat sheet

ffprobe

Comprehensive ffprobe reference for inspecting media files — formats, streams, frames, packets, chapters, metadata — with output formats (default, json, csv, xml, flat, ini), select_streams, show_entries, and one-liners for duration, fps, codec, resolution, and bitrate.

ffprobe — Media Stream Inspection

What it is

ffprobe is FFmpeg's companion inspection tool, installed alongside ffmpeg as part of the same package and maintained by the FFmpeg project. It reads any media file and reports detailed technical metadata — container format, codec identifiers, stream counts, duration, bitrate, frame rate, resolution, and embedded tags — in human-readable or machine-parseable formats including JSON, XML, CSV, and INI. Reach for ffprobe to understand a media file's internals before processing it with ffmpeg, or to extract metadata programmatically in shell scripts.

Version-wise, ffprobe rides the FFmpeg release train: FFmpeg 8.0 "Huffman" shipped August 2025 and added -show_stream_groups (for grouped streams such as DOVI base+enhancement layers and Opus multitrack) and -analyze_frames (enables frame-level info in -show_streams output, e.g. real nb_read_frames and closed_captions/film_grain fields). FFmpeg 8.1 "Hoare" (March 2026) added a -codec flag and slimmed the refs field when running with -show_frames. The schema-bearing fields (pix_fmt, color_*, sample_aspect_ratio, display_aspect_ratio, r_frame_rate, avg_frame_rate) are stable across versions — scripts written against FFmpeg 4.x JSON output still parse cleanly against 8.x.

Installation

bash
# ffprobe ships as part of the ffmpeg package
sudo apt install ffmpeg          # Ubuntu/Debian
brew install ffmpeg              # macOS
sudo pacman -S ffmpeg            # Arch

ffprobe -version

Output (ffprobe -version):

text
ffprobe version 8.1 Copyright (c) 2007-2026 the FFmpeg developers
built with gcc 14.2.1
configuration: --enable-gpl --enable-libx264 --enable-libsvtav1 --enable-vulkan ...
libavutil      60.  3.100
libavcodec     62.  3.100
libavformat    62.  1.100
...

Default output

Running ffprobe on a file prints a summary to stderr. The -hide_banner flag strips the FFmpeg version header.

bash
ffprobe input.mp4

# Clean output (hide the build info banner)
ffprobe -hide_banner input.mp4

# Redirect stderr so you can pipe it
ffprobe -hide_banner input.mp4 2>&1

Output (ffprobe -hide_banner input.mp4):

text
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input.mp4':
  Metadata:
    major_brand     : isom
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf60.3.100
  Duration: 00:01:30.24, start: 0.000000, bitrate: 5842 kb/s
  Stream #0:0[0x1](und): Video: h264 (High), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 5711 kb/s, 29.97 fps, 29.97 tbr, 30k tbn (default)
  Stream #0:1[0x2](eng): Audio: aac (LC), 48000 Hz, stereo, fltp, 128 kb/s (default)

Show format

Container-level information: filename, format name, duration, file size, bitrate, and tags.

bash
ffprobe -hide_banner -show_format input.mp4

Output (ffprobe -hide_banner -show_format input.mp4):

text
[FORMAT]
filename=input.mp4
nb_streams=2
nb_programs=0
format_name=mov,mp4,m4a,3gp,3g2,mj2
format_long_name=QuickTime / MOV
start_time=0.000000
duration=90.240000
size=65880064
bit_rate=5842109
probe_score=100
TAG:major_brand=isom
TAG:compatible_brands=isomiso2avc1mp41
TAG:encoder=Lavf60.3.100
[/FORMAT]

Show streams

Per-stream technical details for every stream in the file.

bash
ffprobe -hide_banner -show_streams input.mp4

Output (ffprobe -hide_banner -show_streams input.mp4 — first video stream):

text
[STREAM]
index=0
codec_name=h264
codec_long_name=H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
profile=High
codec_type=video
codec_tag_string=avc1
codec_tag=0x31637661
width=1920
height=1080
coded_width=1920
coded_height=1080
closed_captions=0
film_grain=0
has_b_frames=2
sample_aspect_ratio=1:1
display_aspect_ratio=16:9
pix_fmt=yuv420p
level=40
color_range=tv
color_space=bt709
color_transfer=bt709
color_primaries=bt709
r_frame_rate=30000/1001
avg_frame_rate=30000/1001
time_base=1/30000
start_pts=0
start_time=0.000000
duration_ts=2707200
duration=90.240000
bit_rate=5711223
nb_frames=2703
[/STREAM]

Show packets

Low-level packet/frame timing data: PTS, DTS, duration, size, flags (keyframe = K).

bash
# All packets in the file
ffprobe -hide_banner -show_packets input.mp4

# First 5 packets from video stream only
ffprobe -hide_banner -show_packets -select_streams v:0 \
        -read_intervals "%+#5" input.mp4

Output (ffprobe -hide_banner -show_packets -select_streams v:0 -read_intervals "%+#2" input.mp4):

text
[PACKET]
codec_type=video
stream_index=0
pts=0
pts_time=0.000000
dts=0
dts_time=0.000000
duration=1001
duration_time=0.033367
size=96823
pos=48
flags=K_
[/PACKET]
[PACKET]
codec_type=video
stream_index=0
pts=2002
pts_time=0.066733
dts=1001
dts_time=0.033367
duration=1001
duration_time=0.033367
size=12407
pos=97118
flags=__
[/PACKET]

Show frames

Per-decoded-frame information including picture type (I/P/B), PTS, repeat field, key frame flag.

bash
# Show all video frames
ffprobe -hide_banner -show_frames -select_streams v input.mp4

# Show specific frame entries only
ffprobe -hide_banner -show_frames -select_streams v \
        -show_entries frame=pict_type,pkt_pts_time,key_frame input.mp4 | head -20

Output (ffprobe ... -show_entries frame=pict_type,pkt_pts_time,key_frame — first 3 frames):

text
[FRAME]
key_frame=1
pkt_pts_time=0.000000
pict_type=I
[/FRAME]
[FRAME]
key_frame=0
pkt_pts_time=0.066733
pict_type=B
[/FRAME]
[FRAME]
key_frame=0
pkt_pts_time=0.033367
pict_type=P
[/FRAME]

Show chapters

Chapter markers are embedded in container formats like MKV, MP4, and M4B to divide a file into named segments. -show_chapters lists each chapter's ID, start/end timestamps, and title tag — useful before splitting a file with ffmpeg at chapter boundaries.

bash
ffprobe -hide_banner -show_chapters input.mkv

Output (ffprobe -hide_banner -show_chapters input.mkv):

text
[CHAPTER]
id=0
time_base=1/1000000000
start=0
start_time=0.000000
end=180000000000
end_time=180.000000
TAG:title=Introduction
[/CHAPTER]
[CHAPTER]
id=1
time_base=1/1000000000
start=180000000000
start_time=180.000000
end=360000000000
end_time=360.000000
TAG:title=Main Content
[/CHAPTER]

Stream selection

Use -select_streams to filter output to a specific stream type or index.

bash
# First video stream
ffprobe -hide_banner -show_streams -select_streams v:0 input.mp4

# All audio streams
ffprobe -hide_banner -show_streams -select_streams a input.mp4

# First subtitle stream
ffprobe -hide_banner -show_streams -select_streams s:0 input.mkv

# By language (video stream tagged as English)
ffprobe -hide_banner -show_streams -select_streams "v:m:language:eng" input.mkv

Output: (none — exits 0 on success)

Showing specific entries

-show_entries restricts output to a comma-separated subset of fields, which is much faster than fetching all data.

bash
# Codec + dimensions for the first video stream
ffprobe -hide_banner -show_entries stream=codec_name,width,height \
        -select_streams v:0 input.mp4

# Duration and bitrate from format section
ffprobe -hide_banner -show_entries format=duration,bit_rate input.mp4

# Combined: stream fields + format fields
ffprobe -hide_banner \
        -show_entries "stream=codec_type,codec_name,bit_rate:format=duration" \
        input.mp4

Output (ffprobe -hide_banner -show_entries stream=codec_name,width,height -select_streams v:0 input.mp4):

text
[STREAM]
codec_name=h264
width=1920
height=1080
[/STREAM]

Output formats (-of / -print_format)

-of controls how ffprobe serializes its output. The default key=value format is readable but hard to parse in scripts; use -of json for structured output to pipe into jq, -of csv=p=0 for a bare single value, and -of flat for dot-notation keys that are trivially grep-able.

bash
# Default key=value pairs (default)
ffprobe -hide_banner -show_format -of default input.mp4

# Compact (single-line, pipe-separated)
ffprobe -hide_banner -show_streams -of compact input.mp4

# CSV
ffprobe -hide_banner -show_streams -of csv input.mp4

# Flat (dot-separated keys, easy to grep)
ffprobe -hide_banner -show_format -of flat input.mp4

# INI
ffprobe -hide_banner -show_format -of ini input.mp4

# JSON (best for scripting and jq)
ffprobe -hide_banner -show_streams -of json input.mp4

# XML
ffprobe -hide_banner -show_streams -of xml input.mp4

Output (ffprobe -hide_banner -show_entries stream=codec_name,width,height -select_streams v:0 -of csv input.mp4):

text
stream,h264,1920,1080

Output (ffprobe -hide_banner -show_entries format=duration -of flat input.mp4):

text
format.duration="90.240000"

One-liner queries

These extract a single value — ideal for shell scripts.

bash
# Duration in seconds (float)
ffprobe -v error -show_entries format=duration -of csv=p=0 input.mp4

# Duration as HH:MM:SS
ffprobe -v error -show_entries format=duration \
        -of default=noprint_wrappers=1:nokey=1 input.mp4 | \
        awk '{h=int($1/3600); m=int(($1%3600)/60); s=$1%60; printf "%02d:%02d:%05.2f\n",h,m,s}'

# Video codec name
ffprobe -v error -select_streams v:0 \
        -show_entries stream=codec_name -of csv=p=0 input.mp4

# Resolution (WxH)
ffprobe -v error -select_streams v:0 \
        -show_entries stream=width,height -of csv=p=0 input.mp4

# Frame rate (as fraction, e.g. 30000/1001)
ffprobe -v error -select_streams v:0 \
        -show_entries stream=r_frame_rate -of csv=p=0 input.mp4

# Frame rate as decimal
ffprobe -v error -select_streams v:0 \
        -show_entries stream=avg_frame_rate -of csv=p=0 input.mp4 | \
        awk -F/ '{printf "%.3f\n", $1/$2}'

# Audio codec
ffprobe -v error -select_streams a:0 \
        -show_entries stream=codec_name -of csv=p=0 input.mp4

# Audio sample rate
ffprobe -v error -select_streams a:0 \
        -show_entries stream=sample_rate -of csv=p=0 input.mp4

# Audio channels
ffprobe -v error -select_streams a:0 \
        -show_entries stream=channels -of csv=p=0 input.mp4

# Total bitrate (bps)
ffprobe -v error -show_entries format=bit_rate -of csv=p=0 input.mp4

# Number of frames (from stream metadata)
ffprobe -v error -select_streams v:0 \
        -show_entries stream=nb_frames -of csv=p=0 input.mp4

# Pixel format
ffprobe -v error -select_streams v:0 \
        -show_entries stream=pix_fmt -of csv=p=0 input.mp4

Output (ffprobe -v error -show_entries format=duration -of csv=p=0 input.mp4):

text
90.240000

Output (ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 input.mp4):

text
1920,1080

Output (ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of csv=p=0 input.mp4):

text
30000/1001

Output (ffprobe -v error -select_streams a:0 -show_entries stream=channels -of csv=p=0 input.mp4):

text
2

JSON output and jq pipelines

-of json produces machine-readable output that integrates cleanly with jq.

bash
# Full JSON dump
ffprobe -hide_banner -of json -show_streams -show_format input.mp4

# Extract video codec and dimensions with jq
ffprobe -v error -of json -show_streams input.mp4 | \
    jq '.streams[] | select(.codec_type=="video") | {codec: .codec_name, width, height}'

# Get audio sample rate
ffprobe -v error -of json -show_streams input.mp4 | \
    jq '.streams[] | select(.codec_type=="audio") | .sample_rate'

# List all stream codecs and types
ffprobe -v error -of json -show_streams input.mp4 | \
    jq '.streams[] | "\(.index): \(.codec_type) — \(.codec_name)"'

# Get duration as a number (not a string)
ffprobe -v error -of json -show_format input.mp4 | \
    jq '.format.duration | tonumber'

# Check if file has any subtitle streams
ffprobe -v error -of json -show_streams input.mkv | \
    jq '[.streams[] | select(.codec_type=="subtitle")] | length'

# Extract all stream languages
ffprobe -v error -of json -show_streams input.mkv | \
    jq '.streams[] | {index, codec_type, lang: .tags.language}'

Output (ffprobe ... | jq '.streams[] | select(.codec_type=="video") | {codec: .codec_name, width, height}'):

json
{
  "codec": "h264",
  "width": 1920,
  "height": 1080
}

Output (ffprobe ... | jq '.streams[] | "\(.index): \(.codec_type) — \(.codec_name)"'):

text
"0: video — h264"
"1: audio — aac"
"2: subtitle — subrip"

Keyframe and GOP analysis

A Group of Pictures (GOP) is the sequence of frames between two I-frames (keyframes); keyframe interval directly affects seek accuracy and random-access performance. Use -show_frames with -show_entries frame=pict_type to extract frame types and measure real GOP size without decoding the full video.

bash
# List all I-frame (keyframe) timestamps in a video
ffprobe -hide_banner \
        -show_frames -select_streams v \
        -show_entries frame=pict_type,pkt_pts_time \
        -of csv=p=0 input.mp4 | \
        grep ",I$"

# Count keyframes (I-frames)
ffprobe -hide_banner \
        -show_frames -select_streams v \
        -show_entries frame=pict_type \
        -of csv=p=0 input.mp4 | \
        grep -c "^I$"

# Measure average GOP size (frames between keyframes)
ffprobe -hide_banner \
        -show_frames -select_streams v \
        -show_entries frame=pict_type \
        -of csv=p=0 input.mp4 | \
        awk 'BEGIN{gop=0;count=0} /I/{if(gop>0){total+=gop;count++};gop=0} {gop++} END{printf "avg GOP: %.1f frames\n",total/count}'

Output (grep keyframes | head -6):

text
0.000000,I
2.002000,I
4.004000,I
6.006000,I
8.008000,I
10.010000,I

Bitrate over time

Useful for identifying spikes or encoding issues.

bash
# Per-packet size and timestamp for video stream (CSV)
ffprobe -hide_banner \
        -show_packets -select_streams v:0 \
        -show_entries packet=pts_time,size \
        -of csv=p=0 input.mp4 > bitrate_data.csv

# Compute per-second bitrate with awk
ffprobe -hide_banner \
        -show_packets -select_streams v:0 \
        -show_entries packet=pts_time,size \
        -of csv=p=0 input.mp4 | \
        awk -F, '{sec=int($1); bytes[sec]+=$2} END{for(s in bytes) printf "%d\t%d kbps\n", s, bytes[s]*8/1000}' | \
        sort -n | head -20

Output (per-second bitrate sample):

text
0       6124 kbps
1       5843 kbps
2       4210 kbps
3       7392 kbps
4       5541 kbps
5       4987 kbps

Quality metrics: PSNR and SSIM

PSNR and SSIM require FFmpeg to compare two videos (reference vs compressed). The results are logged and can be read back.

bash
# PSNR — log to file
ffmpeg -i reference.mp4 -i compressed.mp4 \
       -filter_complex "psnr=stats_file=psnr.log" \
       -f null -

# Parse average PSNR from log
awk '/average/ {print "PSNR avg:", $NF}' psnr.log

# SSIM — log to file
ffmpeg -i reference.mp4 -i compressed.mp4 \
       -filter_complex "ssim=stats_file=ssim.log" \
       -f null -

# Parse average SSIM
awk '/All:/ {print "SSIM avg:", $2}' ssim.log

# Combined PSNR + SSIM in one pass
ffmpeg -i reference.mp4 -i compressed.mp4 \
       -filter_complex "[0:v][1:v]ssim=stats_file=ssim.log;[0:v][1:v]psnr=stats_file=psnr.log" \
       -f null -

Output (awk '/average/ {print "PSNR avg:", $NF}' psnr.log):

text
PSNR avg: 41.236827

Output (awk '/All:/ {print "SSIM avg:", $2}' ssim.log):

text
SSIM avg: 0.987423

Metadata extraction

Container-level tags (title, artist, encoder, date) are stored in the format section; per-stream tags (language, track title) are stored per stream. Use -show_entries format_tags or -show_entries stream_tags rather than -show_format/-show_streams to get tags alone without the full technical detail.

bash
# Show all embedded tags for the format (title, artist, encoder, etc.)
ffprobe -hide_banner -show_entries format_tags -of default input.mp4

# Show stream tags (e.g. language, title per track)
ffprobe -hide_banner -show_entries stream_tags -of default input.mkv

# Extract specific tags
ffprobe -v error -show_entries format_tags=title,artist,album \
        -of default=noprint_wrappers=1 audio.mp3

# JSON tags for scripting
ffprobe -v error -show_entries format_tags \
        -of json input.mp4 | jq '.format.tags'

Output (ffprobe -v error -show_entries format_tags=title,artist,album -of default=noprint_wrappers=1 audio.mp3):

text
TAG:title=Autumn Leaves
TAG:artist=Bill Evans Trio
TAG:album=Portrait in Jazz

Output (ffprobe ... | jq '.format.tags'):

json
{
  "title": "Autumn Leaves",
  "artist": "Bill Evans Trio",
  "album": "Portrait in Jazz",
  "date": "1960",
  "encoder": "Lavf58.76.100"
}

Stream groups (-show_stream_groups)

A stream group is a logical grouping of two or more raw streams that combine into a single presentation — typical examples are Dolby Vision base+enhancement layers, IAMF (Immersive Audio Model and Formats) presentations, and multi-track Opus tile sets. FFmpeg 8.0 added -show_stream_groups so ffprobe can surface this layered structure instead of presenting each tile as an isolated stream.

bash
# List stream groups (no output when the container has none)
ffprobe -hide_banner -show_stream_groups input.mp4

# JSON for scripting — combined with -show_streams to see member streams
ffprobe -hide_banner -of json -show_stream_groups -show_streams dolby_vision.mp4

Output (ffprobe -hide_banner -show_stream_groups dolby_vision.mp4):

text
[STREAM_GROUP]
index=0
nb_streams=2
type=IAMF_AUDIO_ELEMENT
[STREAM_GROUP_COMPONENTS]
[COMPONENT]
type=AV_IAMF_AUDIO_ELEMENT_TYPE_CHANNEL
[/COMPONENT]
[/STREAM_GROUP_COMPONENTS]
[STREAM_GROUP_STREAMS]
[STREAM]
index=0
codec_name=opus
codec_type=audio
[/STREAM]
[/STREAM_GROUP_STREAMS]
[/STREAM_GROUP]

Accurate frame counts (-count_frames, -count_packets, -analyze_frames)

nb_frames in -show_streams is read from container metadata and is often missing or wrong (notably for matroska, MPEG-TS, and any VFR source). To get a true count, force ffprobe to walk the file: -count_frames decodes every frame (slow, accurate), -count_packets only reads container packets (fast, accurate per-packet but counts B-frames as packets), and FFmpeg 8.0's -analyze_frames enables additional frame-level fields in -show_streams (e.g. real nb_read_frames, closed_captions, film_grain) without needing a separate -show_frames pass.

bash
# True decoded frame count (slow — touches every frame)
ffprobe -v error -count_frames -select_streams v:0 \
        -show_entries stream=nb_read_frames -of csv=p=0 input.mp4

# Packet count (fast)
ffprobe -v error -count_packets -select_streams v:0 \
        -show_entries stream=nb_read_packets -of csv=p=0 input.mp4

# FFmpeg 8.0+: enrich -show_streams with frame-derived fields in one pass
ffprobe -v error -analyze_frames -select_streams v:0 \
        -show_entries stream=nb_read_frames,closed_captions,film_grain \
        -of default=noprint_wrappers=1 input.mp4

Output (ffprobe -v error -analyze_frames -select_streams v:0 -show_entries stream=nb_read_frames,closed_captions,film_grain -of default=noprint_wrappers=1 input.mp4):

text
closed_captions=0
film_grain=0
nb_read_frames=2703

Versions and capabilities

-show_pixel_formats, -show_versions, -show_program_version, and -show_library_versions print the build's static capabilities rather than information about a specific input file — useful for scripting against a known FFmpeg version or asserting that a build supports the pixel format you need before launching a long encode.

bash
# All supported pixel formats with flags + component layout
ffprobe -hide_banner -show_pixel_formats | head -30

# Single specific pixel format
ffprobe -hide_banner -show_pixel_formats -of json | \
    jq '.pixel_formats[] | select(.name=="yuv420p10le")'

# Combined program + library versions (JSON)
ffprobe -hide_banner -of json -show_versions

Output (ffprobe -hide_banner -show_pixel_formats -of json | jq '.pixel_formats[] | select(.name=="yuv420p10le")'):

json
{
  "name": "yuv420p10le",
  "nb_components": 3,
  "log2_chroma_w": 1,
  "log2_chroma_h": 1,
  "bits_per_pixel": 15,
  "flags": {
    "big_endian": 0,
    "palette": 0,
    "bitstream": 0,
    "hwaccel": 0,
    "planar": 1,
    "rgb": 0,
    "alpha": 0
  }
}

Modern alternatives

ffprobe is the de-facto inspection tool, but two alternatives are worth knowing:

  • mediainfo — friendlier human-readable output, better at surfacing container-level oddities (DTS-HD descriptors, HDR10+ metadata, advanced subtitle tracks). Often reports a more accurate average frame rate on variable-frame-rate (VFR) files than ffprobe does, because it inspects the container's frame-rate atoms rather than computing from packet timestamps. JSON output via mediainfo --Output=JSON file.mp4.
  • mkvinfo / mp4info — container-specific tools; lower-level than ffprobe, useful when you need to inspect raw box/element structure (e.g. MP4 atoms, MKV EBML elements) rather than codec semantics.
bash
# mediainfo equivalent of a common ffprobe one-liner
mediainfo --Output="Video;%Width%x%Height%" input.mp4

# Pure JSON
mediainfo --Output=JSON input.mp4 | jq '.media.track[] | select(."@type"=="Video")'

Output (mediainfo --Output="Video;%Width%x%Height%" input.mp4):

text
1920x1080

Useful flag combinations cheatsheet

GoalCommand
Silence banner + version info-hide_banner or -v error
JSON output-of json
Single value (no wrapper)-of csv=p=0
Single value, no key-of default=noprint_wrappers=1:nokey=1
Video streams only-select_streams v
Audio streams only-select_streams a
First subtitle-select_streams s:0
Specific fields only-show_entries stream=codec_name,width
Read only first N packets-read_intervals "%+#N"
Read interval 10 s–20 s-read_intervals "10%20"
Show everything-show_format -show_streams -show_chapters
Stream groups (8.0+)-show_stream_groups
Frame-derived stream fields (8.0+)-analyze_frames -show_streams
Accurate decoded frame count-count_frames -show_entries stream=nb_read_frames
Build's pixel formats-show_pixel_formats
Build versions (program + libs)-show_versions -of json

Sources