You should always know what's in a video before you process it
You have a video. You need to know what's inside it before you do anything else. Resolution, codec, bitrate, frame rate, audio channels, duration. Maybe you're validating uploads before processing them. Maybe you're trying to figure out why a file won't play. Maybe you just need to confirm the codec before deciding whether to transcode or remux.
ffprobe is the tool for this. It ships with FFmpeg, reads any format FFmpeg supports, and outputs structured data you can pipe into scripts, parse in Python, or feed to an API. If you've been opening files in VLC and squinting at the codec info dialog, there's a better way.
What is ffprobe
ffprobe is a command-line utility bundled with FFmpeg. You don't install it separately. If you have FFmpeg, you have ffprobe.
It reads a media file (or URL, or stream) and prints information about the container format, each audio/video/subtitle stream, individual packets, and individual frames. It doesn't modify anything. It reads and reports.
The distinction matters: FFmpeg processes video, ffprobe inspects it. You use ffprobe to decide what FFmpeg command to run.
Install FFmpeg and you get ffprobe automatically:
As of March 2026, the latest stable FFmpeg release is 8.1. The ffprobe flags covered in this guide have been stable since FFmpeg 4.x, so version differences rarely matter here.
Basic usage
Point ffprobe at a file:
You'll get a wall of text. FFmpeg's build configuration, library versions, then the actual file info at the bottom. The useful part looks like this:
That's your file in a nutshell: H.264 video at 1080p/30fps, AAC stereo audio, about 4.5 Mbps total.
Hide the banner
The build info is noise. Kill it:
To suppress everything except actual errors:
The -v error flag sets the log level. Options range from quiet (nothing), panic, fatal, error, warning, info (default), to verbose and debug. For scripting, -v error is the sweet spot. You still see real problems but skip the noise.
Inspecting streams
The -show_streams flag dumps detailed info about every stream in the file:
This prints codec name, profile, resolution, pixel format, frame rate, bitrate, sample rate, channel layout, and dozens of other fields wrapped in [STREAM]...[/STREAM] tags.
Select specific streams
Most files have at least two streams (video + audio). Use -select_streams to inspect just one:
The specifier v:0 means "first video stream." a:0 is first audio. s:0 is first subtitle track. If a file has multiple audio tracks (common in MKV files with multiple languages), a:1 gives you the second one.
Container format info
To see the container-level metadata (duration, bitrate, format name, tags):
Output:
If you've ever needed to figure out why a file is larger than expected, the bit_rate and duration fields here are your starting point. From there, the video compression guide covers how to bring that bitrate down without visible quality loss.
Output formats: JSON, CSV, XML
The default output format uses [SECTION]...[/SECTION] wrappers. Fine for reading, annoying for parsing. ffprobe supports several machine-readable formats.
JSON output
This is the one you'll use most:
Now you can pipe this into jq, parse it in Python, feed it to Node.js, whatever. The -print_format json flag (or -of json, same thing) is what makes ffprobe actually useful in automation. You can also use -of json=compact=1 for a denser output format.
CSV output
Each stream becomes one comma-separated line. Useful for quick spreadsheet dumps or awk processing.
XML output
If you're feeding data into something that expects XML. Some enterprise media systems still do.
Flat output
Produces lines like streams.stream.0.codec_name="h264". Each field is a dot-separated path. Grep-friendly, and particularly useful if you want to diff two files:
Extracting specific fields
You don't always need everything. The -show_entries flag lets you pull exactly the fields you want:
Output: 1920,1080
The -of csv=p=0 part means "output as CSV, don't print section headers." Clean, single-line output you can capture in a variable.
More examples:
This is how you build reliable scripts. Grab one field, check it, act on it. The FFmpeg cheat sheet has 50 commands organized by task, and knowing how to inspect files with ffprobe first makes choosing the right command much easier.
Packet and frame analysis
For debugging encoding issues, you can go deeper than stream-level info.
Packet info
Each packet includes pts, dts, duration, size, and flags. The flags field tells you if a packet contains a keyframe (K) or not. This matters when you're trimming video โ cuts on non-keyframes with -c copy result in frozen or garbled frames at the start of the clip.
Frame info
Frame-level data includes pict_type (I, P, or B frame), key_frame flag, width, height, and timestamps. If you need to understand keyframe distribution before extracting frames, this is how you find them.
Count keyframes
A quick way to count I-frames in a file:
If you're seeing too few keyframes (say, one every 10 seconds), that explains why seeking feels sluggish in your player. Too many keyframes and the file is larger than it needs to be. For a typical 30fps video, one keyframe every 2-4 seconds (every 60-120 frames) is a reasonable interval.
Practical ffprobe examples
Here are 10 commands that cover the most common real-world scenarios.
1. Quick file summary as JSON
The everything-dump. Pipe it to a file, search later.
2. Check if a file has audio
If this returns nothing, the file has no audio track. Useful for validating uploads before sending them to an FFmpeg API for processing.
3. Get video dimensions for resize decisions
4. Detect codec before transcoding
If this returns h264 and you need h265, you need to transcode. If the codec already matches your target, just remux with -c copy and save the CPU time.
5. Verify metadata was stripped
After running a metadata strip command, confirm it worked:
If the TAG: lines are gone (or only contain what you expect), the metadata stripping was successful. This step is especially relevant for removing AI metadata since tools like Runway, Kling, and HeyGen embed generation info in format tags that you'll want to verify are actually gone.
6. Check bitrate for compression decisions
If you're seeing 15 Mbps on a 1080p talking-head video, there's room to compress. If it's already at 2 Mbps, pushing further will cost you quality.
7. Get the rotation tag (phone recordings)
Phone-recorded videos often have a rotation metadata tag instead of actually rotated pixels:
If this returns 90 or 270, the video is portrait but the pixels are landscape. FFmpeg handles this automatically during transcoding, but if you're using -c copy, the rotation tag carries over as-is.
8. Check if a file is VFR (variable frame rate)
Compare the real and average frame rates:
If r_frame_rate and avg_frame_rate differ (for example, 30/1 vs 24000/1001), you're dealing with VFR content. Screen recordings and phone footage are common culprits. This matters because some editors and encoders don't handle VFR well.
9. Inspect a remote URL without downloading
ffprobe downloads just enough of the file to read the headers and metadata. For MP4 files with faststart (moov atom at the beginning), this means only the first few kilobytes. Useful for checking files in S3 or cloud storage before deciding whether to process them.
10. Compare source and output after processing
This is the "before and after" check. Run it after any FFmpeg operation to confirm you got what you expected.
ffprobe in automation scripts
This is where ffprobe earns its keep: deciding what to do before you do it.
Pre-upload validation (bash)
Batch inspection (Python)
This pattern works well as the first step in a processing pipeline. Probe the file, decide what needs to happen (transcode, compress, reject), then send the appropriate FFmpeg command to RenderIO's API for cloud processing.
Node.js probe + API pipeline
Probe locally, process in the cloud. You skip uploading files that don't need processing, and the heavy encoding runs on someone else's CPUs. The complete API guide walks through authentication, file uploads, and webhook callbacks.
n8n workflow integration
If you use n8n for automation, you can skip running ffprobe locally entirely. RenderIO's API response includes file metadata (duration, codec, resolution, file size) in the response payload. The n8n video processing guide covers how to set up these workflows. But if you need probe data before sending a job, a simple Execute Command node with the ffprobe one-liners above works just as well.
Common gotchas
r_frame_rate vs avg_frame_rate: The r_frame_rate field is the "real" frame rate based on the stream's time base. avg_frame_rate divides total frames by duration. For constant frame rate video they match. For variable frame rate (screen recordings, phone footage), they can differ significantly. Use avg_frame_rate for VFR content.
Duration in format vs stream: The format section duration covers the entire file. Individual stream durations might differ slightly (audio tracks are often a fraction of a second longer due to codec padding). Use format duration for display purposes, stream duration for precise calculations.
Bitrate might be "N/A": Some containers (like raw H.264 streams or certain MKV files) don't store bitrate in the header. ffprobe reports N/A. You can calculate it yourself: file_size_bits / duration_seconds. In bash: echo "scale=0; $(stat -c%s file.mp4) * 8 / $(ffprobe -v error -show_entries format=duration -of csv=p=0 file.mp4 | cut -d. -f1)" | bc.
-show_frames is slow on large files: This decodes the entire file to report frame-level data. On a 2-hour movie, expect it to take minutes. Use -read_intervals to limit the scan to a specific time range:
nb_frames might say "N/A" too: Some containers don't store the frame count in the header. Use -count_frames with -show_entries stream=nb_read_frames to get an exact count, but be aware this decodes the whole file, same performance caveat as -show_frames.
ffprobe vs ffmpeg -i vs MediaInfo
People sometimes use ffmpeg -i input.mp4 as a quick inspection tool. It works, but it's a hack. FFmpeg prints file info to stderr as part of its startup, then exits with an error because you didn't specify an output. ffprobe is the purpose-built tool for this and gives you structured output formats.
MediaInfo is another option. It has a GUI and reads some metadata that ffprobe doesn't (like specific HDR parameters or Dolby Vision profile details). But for command-line scripting and anything you're automating, ffprobe is simpler and already installed if you have FFmpeg.
Frequently asked questions
How do I get just the video duration with ffprobe?
This returns the duration in seconds as a decimal (e.g., 154.560000). For a human-readable format, use format=duration with the default output format and you'll see duration=154.560000.
Can ffprobe read HLS streams or RTMP URLs?
Yes. Anything FFmpeg can read, ffprobe can inspect. Pass the URL directly:
For live streams, ffprobe will read the manifest and report the stream parameters without downloading the entire broadcast.
What's the difference between -print_format json and -of json?
They're the same flag. -of is a shorter alias for -print_format. Both work identically.
How do I check if a video has subtitles?
If this returns nothing, no subtitle streams exist. Otherwise you'll see entries like subrip,subtitle or ass,subtitle.
Can I use ffprobe with multiple files at once?
Not directly. ffprobe takes one input at a time. For batch work, loop in bash:
Does ffprobe modify the file at all?
No. ffprobe is strictly read-only. It will never write to, modify, or lock your files.
Start inspecting, then process
ffprobe should be the first command you run before any FFmpeg job. Know what's in the file before you decide what to do with it. The JSON output makes it easy to script, and pairing it with a cloud FFmpeg service means you can build pipelines where inspection runs locally (fast, free) and encoding runs in the cloud.
If you want to skip managing FFmpeg infrastructure yourself, grab an API key and run commands over HTTP instead. The complete FFmpeg API guide covers authentication, uploads, and webhooks. For quick reference, the FFmpeg cheat sheet has 50 commands organized by task, and the commands reference pairs each one with the matching API call.