All posts
Tutorial9 min read

Extract video metadata as JSON with FFprobe, MediaInfo, or yt-dlp

Copy-paste ffprobe, MediaInfo, and yt-dlp commands to extract duration, codecs, resolution, bitrate, chapters, and YouTube metadata as JSON — plus batch scripts for whole folders.

By · Updated

To extract metadata from a video file, run ffprobe -v quiet -print_format json -show_format -show_streams video.mp4. The JSON includes duration, bitrate, container tags, video codec, resolution, framerate, audio codec, sample rate, channel layout, and stream-level metadata.

Video metadata splits into two layers. The container (MP4, MOV, MKV, AVI) carries duration, codecs, bitrate, and any custom tags. The streams inside carry per-stream metadata: video resolution, framerate, color space, audio sample rate, language tags. Both matter, and there's a right tool for each.

FFprobe: the reference tool

Comes with FFmpeg. The one-liner that gives you everything as JSON:

ffprobe -v quiet -print_format json -show_format -show_streams video.mp4

Pipe to jq for filtering. -show_chapters adds chapter markers if present. The output covers duration, bitrate, codec name and parameters, color profile, audio channel layout — every field a media pipeline cares about.

MediaInfo: nicer output for humans

Cross-platform GUI and CLI. mediainfo --Output=JSON video.mp4 produces output similar to ffprobe but with friendlier field names. The GUI is good for one-off inspection; the CLI is good for batch.

yt-dlp: metadata from a URL without downloading

yt-dlp -j "https://youtube.com/watch?v=..." returns a single JSON blob with title, description, upload date, duration, view count, like count, channel info, and every available format/quality/codec combination. The --write-info-json flag saves it alongside a download.

Useful for pipelines that catalog videos before deciding whether to download them, or that just need YouTube/Vimeo metadata for a search index.

Python: ffmpeg-python or pymediainfo

import json, subprocess out = subprocess.check_output([ "ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", "video.mp4", ]) meta = json.loads(out) print(meta["format"]["duration"], meta["streams"][0]["width"])

Wrapping ffprobe in subprocess is usually simpler than the dedicated bindings. If you're processing thousands of files, ffmpeg-python's Probe interface is a bit tidier.

What's worth extracting

  • Duration, framerate, resolution — for catalog and search
  • Codec and bitrate — for transcode planning
  • Audio language tracks — for subtitle/dub workflows
  • Creation date — sometimes carried in QuickTime/MOV containers, often not in MP4
  • Camera make/model — present in iPhone/Android-recorded video, accessible via -show_entries stream_tags

What you can't get

Speech transcripts aren't metadata — they're content. For those, see the YouTube transcript post or run the audio through Whisper. Same for visual content: object detection, OCR on visible frames, scene changes — these all require separate processing pipelines on the actual video stream.

FFprobe field reference

Field pathWhat it tells youTypical use
format.durationLength in secondsCatalog, billing, trim planning
format.bit_rateOverall bitrateQuality check, CDN sizing
format.tags.creation_timeContainer creation timestampArchival sorting (often missing in re-encoded MP4)
streams[n].codec_nameh264, hevc, aac, etc.Transcode compatibility
streams[n].width / heightPixel dimensionsThumbnail generation, aspect ratio
streams[n].r_frame_rateFramerateSync, slow-motion detection
streams[n].tags.rotateDisplay rotation (mobile)Fix sideways playback before publishing
streams[n].tags.languageAudio/subtitle track languageLocalization workflows

Batch: metadata for every file in a folder

for f in *.mp4; do ffprobe -v quiet -print_format json -show_format -show_streams "$f" > "${f%.mp4}.json" done

For a single combined catalog, jq-merge the outputs or use a Python loop that appends to a list and writes catalog.json at the end. Add -show_entries format_tags if you only need container tags and want smaller JSON.

exiftool vs ffprobe for camera metadata

Phone-recorded MOV/MP4 often carries EXIF-like tags (Make, Model, GPS) that ffprobe surfaces under stream_tags but exiftool presents more readably:

exiftool -json -G video.mp4 | jq '.[0] | {Make, Model, CreateDate, GPSLatitude, GPSLongitude}'

Use exiftool when the question is "what device shot this?" Use ffprobe when the question is "what codecs and bitrates do I have?" For YouTube URLs, skip both and use yt-dlp -j.

Rotation and display matrix

iPhone videos often store 1920×1080 dimensions with a rotate=90 tag. Players apply the rotation at display time; naive thumbnail extractors do not. Check streams[].side_data_list for Display Matrix or the rotate tag before assuming width/height match what viewers see.

Building a catalog.json from a folder

For a media DAM or transcode queue, you want one JSON array with the fields your pipeline actually uses — not the full ffprobe dump:

import json, subprocess, pathlib def probe(path): out = subprocess.check_output([ "ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", str(path), ]) meta = json.loads(out) v = next(s for s in meta["streams"] if s["codec_type"] == "video") a = next((s for s in meta["streams"] if s["codec_type"] == "audio"), None) return { "file": path.name, "duration_s": float(meta["format"]["duration"]), "width": v["width"], "height": v["height"], "vcodec": v["codec_name"], "acodec": a["codec_name"] if a else None, } catalog = [probe(p) for p in pathlib.Path(".").glob("*.mp4")] pathlib.Path("catalog.json").write_text(json.dumps(catalog, indent=2))

Skip files where ffprobe exits non-zero — truncated downloads and zero-byte placeholders are common in upload folders.

ffprobe troubleshooting

SymptomLikely causeFix
duration is N/ALive stream or incomplete fileWait for download to finish; use -show_packets for partial files
No audio stream listedSilent video or separate audio fileCheck for external .m4a; merge with ffmpeg -i
codec_name unknownProprietary or corrupt streamTry ffmpeg -i for human-readable error
creation_time null on MP4Re-encoded without tag copyRead exiftool CreateDate on source camera file
width/height swappedRotation tag presentRead tags.rotate or side_data display matrix

Choosing fields for downstream systems

Transcode planners care about codec, profile, pixel format, and color range (tv vs pc). CDN sizing cares about bitrate and duration. Search indexes want title and description from container tags or yt-dlp for web sources. Don't store the entire ffprobe JSON in Postgres — extract the dozen fields you query on and keep the raw dump in object storage if you need forensics.

Frequently asked questions

Does ffprobe work on MKV, MOV, and WebM?+

Yes — ffprobe reads any container FFmpeg supports. WebM metadata is sparser than MOV; don't expect camera tags in browser-recorded WebM.

Can I get metadata without installing FFmpeg?+

MediaInfo has a standalone installer with no FFmpeg dependency. On macOS, brew install mediainfo. For YouTube-only workflows, yt-dlp -j is enough.

Why is creation_time missing from my MP4?+

Re-encoding strips or overwrites container tags. The original camera timestamp may only exist in the source MOV. Always extract metadata from the earliest file in your pipeline.

Related reading

Stop reading, start extracting

Drop a PDF or image into ExtractFox and get structured data back in seconds.

Try a free extraction →