Contents
Before you process a video, you need to know what it contains. With ffprobe, you can read its duration, resolution, frame rate, codec and bitrate without opening a video player.
This guide shows one command that returns those values as JSON, individual commands for each value, and a Python script for local files. You will also learn how to get the same metadata from a URL using Shotstack, without downloading the video to your machine.
TL;DR
- Use ffprobe, the inspection tool included with FFmpeg, to read local video metadata.
- Combine
-show_entrieswith-of jsonto request specific fields in a machine-readable format. - Use container duration and bitrate for the whole file, and stream fields for an individual video or audio track.
- Parse frame rates as fractions.
24000/1001is approximately 23.976 fps. - Use Shotstack’s Probe endpoint for remote assets. URL-encode the asset URL and send your API key in the
x-api-keyheader.
What is ffprobe?
ffprobe is the media inspection tool that ships with FFmpeg. It reads information about a file’s container and its individual streams, such as video, audio and subtitles.
If you do not have it installed, follow the installation steps in the FFmpeg guide. The examples use a 26-second test clip from Shotstack. Download it to follow along:
curl -L -o scott-ko.mp4 https://github.com/shotstack/test-media/raw/main/captioning/scott-ko.mp4
Read all five values as JSON
Run this command to retrieve duration, resolution, frame rate, codec and bitrate:
ffprobe -v error \
-select_streams V:0 \
-show_entries stream=codec_name,width,height,r_frame_rate,avg_frame_rate,duration,bit_rate:format=duration,bit_rate \
-of json \
scott-ko.mp4
For the test clip, ffprobe 8.1 prints:
{
"programs": [
],
"stream_groups": [
],
"streams": [
{
"codec_name": "h264",
"width": 1920,
"height": 1080,
"r_frame_rate": "24000/1001",
"avg_frame_rate": "24000/1001",
"duration": "25.859167",
"bit_rate": "10044458"
}
],
"format": {
"duration": "25.877000",
"bit_rate": "10360770"
}
}
The empty programs and stream_groups arrays are part of the JSON writer’s output. Your code can ignore them.
Each option has a specific job:
| Option | Purpose |
|---|---|
-v error | Suppresses routine logging while retaining errors. |
-select_streams V:0 | Selects the first video stream that is not an attached picture, thumbnail or cover art. |
-show_entries | Limits the output to the requested stream and container fields. |
-of json | Formats the result as JSON for parsing in code. |
The stream selector doesn’t affect the format fields, so you get the container values too.
For additional selectors and output writers, see the official ffprobe documentation.
Read individual values with one-liners
For shell scripts, plain output is often easier to use than JSON. The default writer’s noprint_wrappers=1 removes section markers, while nokey=1 removes field names.
Duration
Read the container duration in seconds:
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 scott-ko.mp4
25.877000
This is the whole file’s duration, not one track’s.
Resolution
Read the selected video stream’s width and height:
ffprobe -v error -select_streams V:0 -show_entries stream=width,height -of csv=s=x:p=0 scott-ko.mp4
1920x1080
The CSV writer uses s=x to separate fields with x. The p=0 option removes the section prefix, leaving a compact resolution string.
Frame rate
Read the average frame rate:
ffprobe -v error -select_streams V:0 -show_entries stream=avg_frame_rate -of default=noprint_wrappers=1:nokey=1 scott-ko.mp4
24000/1001
This is a fraction, not a decimal string. If you are choosing an output rate rather than inspecting an existing file, see the video frame rate guide.
Codec name
Read the video codec’s short name:
ffprobe -v error -select_streams V:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 scott-ko.mp4
h264
This describes the video stream, not the container. For background on the distinction and available codecs, read the FFmpeg codecs guide.
Bitrate
Read the container bitrate in bits per second:
ffprobe -v error -show_entries format=bit_rate -of default=noprint_wrappers=1:nokey=1 scott-ko.mp4
10360770
To request only the video bitrate, use -select_streams V:0 -show_entries stream=bit_rate instead. For advice on selecting an encoding target, see the video bitrate guide.
Interpret the values correctly
Container values versus stream values
The format object describes the container. Each object in streams describes one track.
For the example asset:
| Scope | Duration in seconds | Bitrate in bits per second |
|---|---|---|
| Container | 25.877000 | 10360770 |
| Video stream | 25.859167 | 10044458 |
| Audio stream | 25.877000 | 317375 |
Track timing can differ. Here the audio runs slightly longer than the video, and the container duration matches the audio. Container bitrate covers the whole file, including audio and container overhead.
Stream bitrate is not available for every file. Plain output may show N/A, while JSON can omit the field, so do not assume that bit_rate always exists or contains a number.
Average frame rate versus base frame rate
avg_frame_rate reports the stream’s average frame rate. r_frame_rate reports a base rate that can represent the stream’s timestamps and may be a guessed value.
These fields often match for constant-frame-rate video. For variable-frame-rate video, they can differ, and neither field describes every individual frame’s timing.
Both fields use fractions. Divide the numerator by the denominator, or use Python’s Fraction; never pass metadata to eval(). Treat a zero denominator, such as 0/0, as an unavailable rate.
Lowercase v versus uppercase V
-select_streams v:0 selects the first video stream, which can be an attached picture such as cover art.
-select_streams V:0 skips attached pictures, thumbnails and cover art. Use uppercase V when you want the first actual video track.
Read ffprobe JSON with Python
This script takes a local file path and prints the five values: container duration and bitrate, plus resolution, average frame rate and codec from the video stream. It needs only Python’s standard library and ffprobe on your PATH.
import json
import subprocess
import sys
from fractions import Fraction
def read_metadata(path):
command = [
"ffprobe", "-v", "error", "-select_streams", "V:0",
"-show_entries",
"stream=codec_name,width,height,avg_frame_rate:"
"format=duration,bit_rate",
"-of", "json", path,
]
result = subprocess.run(
command, capture_output=True, text=True, check=True
)
return json.loads(result.stdout)
def frame_rate(value):
try:
return f"{float(Fraction(value)):.3f}"
except (ValueError, ZeroDivisionError, TypeError):
return "unavailable"
if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit("Usage: python metadata.py FILE")
metadata = read_metadata(sys.argv[1])
streams = metadata.get("streams", [])
if not streams:
raise SystemExit("No video stream found")
video = streams[0]
container = metadata.get("format", {})
print("Duration (seconds):", container.get("duration", "unavailable"))
print("Resolution:", f"{video['width']}x{video['height']}")
print("Frame rate (fps):", frame_rate(video.get("avg_frame_rate")))
print("Codec:", video.get("codec_name", "unavailable"))
print("Bitrate (bits/s):", container.get("bit_rate", "unavailable"))
Save it as metadata.py and run:
python metadata.py scott-ko.mp4
Duration (seconds): 25.877000
Resolution: 1920x1080
Frame rate (fps): 23.976
Codec: h264
Bitrate (bits/s): 10360770
Passing arguments as a list avoids shell interpolation of the file path. check=True raises CalledProcessError if ffprobe fails; your application can catch it and read its stderr for the error message.
For a JavaScript implementation, follow the guide to FFmpeg in Node.js.
Read metadata from a URL with Shotstack
The Shotstack Probe endpoint uses FFprobe under the hood. Its metadata value contains the same output FFprobe returns, converted to JSON. For the test clip, it returns the same values as the local commands above.
Shotstack reads the file from its URL, so you don’t download it. The URL must be reachable from the internet.
Use the production endpoint, /edit/v1, and URL-encode the entire asset URL as one path component:
curl --fail-with-body \
-H "x-api-key: ${SHOTSTACK_API_KEY}" \
"https://api.shotstack.io/edit/v1/probe/https%3A%2F%2Fgithub.com%2Fshotstack%2Ftest-media%2Fraw%2Fmain%2Fcaptioning%2Fscott-ko.mp4"
The response wraps the metadata inside response.metadata. Read the five values from these locations:
| Value | Location within response.metadata |
|---|---|
| Duration | format.duration |
| Resolution | Selected video stream’s width and height |
| Frame rate | Selected video stream’s avg_frame_rate |
| Codec | Selected video stream’s codec_name |
| Bitrate | format.bit_rate |
Unlike the filtered local command, this response includes multiple streams. Select a stream with codec_type equal to video, excluding entries whose disposition.attached_pic is set.
Call the endpoint from Python
This function reads SHOTSTACK_API_KEY from your environment and returns the metadata object:
import json
import os
from urllib.parse import quote
from urllib.request import Request, urlopen
def probe_url(asset_url):
encoded_url = quote(asset_url, safe="")
request = Request(
f"https://api.shotstack.io/edit/v1/probe/{encoded_url}",
headers={"x-api-key": os.environ["SHOTSTACK_API_KEY"]},
)
with urlopen(request, timeout=60) as response:
payload = json.load(response)
if not payload.get("success"):
raise RuntimeError(payload.get("message", "Probe failed"))
return payload["response"]["metadata"]
Using safe="" also encodes slashes and query-string delimiters. Pass the original URL to this function, not an already encoded version.
See the Probe endpoint guide for the response details. For a browser-based alternative, read about inspecting media metadata in the Shotstack dashboard.
Choose local ffprobe or the Probe endpoint
- Use local ffprobe for files already on disk, upload-processing workers and CI jobs where FFmpeg is installed.
- Use the Probe endpoint for remote files you want to inspect without managing a local download or ffprobe process.
ffprobe can also accept supported HTTP URLs directly. The Probe endpoint is useful when you want an HTTP API to handle the inspection instead.
Make informed video decisions with Shotstack
Read metadata before deciding how to handle a video. Local ffprobe gives you direct command-line access, while Shotstack’s Probe endpoint gives you remote inspection alongside the video editing API.
Create a Shotstack account to get an API key and inspect your remote assets.
Frequently asked questions (FAQs)
How do I inspect multiple video tracks?
Remove -select_streams to include all stream types, or use -select_streams V to include all video streams except attached pictures, thumbnails and cover art. Each selected stream appears as a separate object in the JSON streams array.
Does a successful probe mean the entire video is readable?
No. Reading metadata does not require decoding every frame, so a successful probe can miss corruption later in the file. Treat metadata inspection and full decoding as separate operations.
Why can a portrait video report landscape dimensions?
The stored frame dimensions can differ from the intended display orientation. See the guide to rotating videos with FFmpeg for handling rotation metadata.