Parsing ffprobe output is harder than it looks
Read a media file's metadata with ffprobe, then watch the traps: rotation, HDR, fake image durations, missing channel layouts, and cover art as video.
ffprobe is the first thing I reach for when a video does something weird. One command and you get the codec, the resolution, the duration, and every stream, all as JSON. It’s the best tool of its kind.
Then you start trusting that JSON literally, and it bites you.
I spent a while building a normalizer for ffprobe output at Rendobar. These are the fields I stopped trusting, and why.
Rotation isn’t where you’d look
A video shot in portrait on a phone usually stores its frames in landscape and carries a rotation. Older files put that in tags.rotate. Newer files, and ffmpeg 8 in particular, put it in the stream’s side_data_list as a Display Matrix and drop the tag entirely. So if you read tags.rotate, a phone clip that plays perfectly upright reports 1080x1920 and you compute the wrong aspect ratio. You have to read the display matrix and normalize it to 0, 90, 180, or 270.
Images have a fake duration
Run ffprobe on a JPEG and the image2 demuxer hands you "duration": "0.040000" and a bitrate in the millions. That is not the image. It’s the demuxer pretending the still is one frame of a 25 fps video. A PNG comes through png_pipe with no duration at all. If you surface that 0.04 anywhere, it’s wrong. Null it out.
Channel layout goes missing
You’d expect a stereo file to say "channel_layout": "stereo". A WAV often doesn’t. It reports "channels": 2 and no layout string at all. So “stereo” is something you infer from the channel count, not something you can read off the field.
Cover art is a “video stream”
An MP3 with embedded album art has two streams: the audio, and a tiny video stream for the picture. If you count video streams to decide “is this a video,” every MP3 with a cover looks like one. The tell is disposition.attached_pic = 1. Exclude those before you decide what kind of file you’re holding.
HDR isn’t a flag
There is no "hdr": true. You work it out from the color metadata: a color_transfer of smpte2084 (HDR10) or arib-std-b67 (HLG), or color_primaries of bt2020. Miss it and you hand an HDR clip to a pipeline that treats it as SDR.
The short version
None of this is ffprobe being wrong. It’s ffprobe being literal, reporting exactly what the container claims even when the container is quietly lying. The real work is turning that into something you can act on.
That’s the work we did once so you don’t have to. Rendobar’s ffprobe job runs the real ffprobe command, either a full command or just a URL, and hands back a normalized summary with rotation, HDR, variable frame rate, and the rest already sorted out, next to the raw report when you want it.
