Inspect a Video File Before Using It as Pitch Material
Inspect a Video File Before Using It as Pitch Material
A clip arrives with the rest of the pitch material. It opens. It plays. The filename says something like interview_final.mov, and you have no way to tell from any of that whether the voice you're hearing is the lapel mic or the camera's own microphone twelve feet away in a hallway.
That is the situation this article is about: a file that behaves perfectly well and still doesn't tell you what's in it. The useful answer is not "convert it and see." It's a short, read-only inventory — look at the wrapper, look at each thing inside the wrapper, write down what the tool can't tell you instead of deciding for it, and confirm the intended sound by listening to it on purpose rather than by accepting whatever your player picked.
The whole idea fits in one sentence. A container is a package. The picture, the sound, the subtitles and the timing live in separate streams inside it, and those streams are what you'll actually be working with.
Ask one question of the file you were given
Before you run anything, put two things in a plain text note: the original path of the file, and the decision the inspection is supposed to support. For pitch work, that decision usually has a single shape — which picture and which audio should enter the pitch workflow? Narrow it like that on purpose. "Is this file okay?" has no answer. "Which of these two audio streams is the interview?" does.
Then leave the file alone. ffprobe reports; it doesn't rewrite, remux or repair. If you plan to copy the file somewhere else to work on it, keep the original untouched and record its path, because from here on the note is the thing you'll be reading, not the file.
A word about the three terms, since they do most of the work here.
The container is the wrapper: the file on disk with its overall duration, size and format family. A stream is one continuous sequence inside it — generally one video stream, one or more audio streams, sometimes subtitles or data. The stream index is the position the container assigns to each one, counting from zero. It's the number a player uses when it offers you a track menu, and the number you'll cite when you tell a collaborator which sound you mean.
None of that comes from the filename. .mov tells you which demuxer to expect, not how many audio tracks are inside or what language they claim to be. And a file that opens successfully tells you only that something in your player could handle it — not that the stream you want exists, and certainly not which one it is.
The container is a wrapper; the streams are the content
One command reads the whole structure. This is the shape to keep in your notes:
ffprobe -v error -show_format -show_streams -of json interview_final.mov
-show_format asks for the container section. -show_streams asks for one entry per stream. -of json selects the structured writer, which matters later when you want to compare two reports or pull a value out mechanically. -v error keeps the tool quiet so the output is the report and nothing else.
Record the version alongside it — ffprobe -version prints a line you can paste into your note. Option spellings and reported fields have shifted over the tool's life, and a report with no version attached is hard to compare against anyone else's, including a report you made yourself a year ago.
The example below is drawn to match an invented clip, not captured from a file. I'll use that clip throughout: a 72-second interview, four streams, two of them audio. The commands are the part worth keeping; your own report is the one that matters.
{
"streams": [
{ "index": 0, "codec_name": "h264", "codec_type": "video",
"width": 1920, "height": 1080,
"r_frame_rate": "25/1", "avg_frame_rate": "25/1",
"time_base": "1/12800", "duration": "72.480000", "nb_frames": "1812" },
{ "index": 1, "codec_name": "aac", "codec_type": "audio",
"sample_rate": "48000", "channels": 2, "channel_layout": "stereo",
"tags": { "language": "eng", "handler_name": "Camera Mic" } },
{ "index": 2, "codec_name": "aac", "codec_type": "audio",
"sample_rate": "48000", "channels": 2, "channel_layout": "stereo",
"tags": { "handler_name": "Track 2" } },
{ "index": 3, "codec_name": "mov_text", "codec_type": "subtitle" }
],
"format": {
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"format_long_name": "QuickTime / MOV",
"duration": "72.480000", "size": "184099200",
"nb_streams": 4, "bit_rate": "20320000"
}
}
Read the bottom half first. The container section tells you how the file is wrapped and how big it is overall. Notice that format_name lists a whole family — mov,mp4,m4a,3gp,3g2,mj2 — because one demuxer handles all of those. So even the report declines to tell you "this is exactly a QuickTime movie." It says: this family, four streams, 72.48 seconds, about 20 Mb/s.
Then read the streams, one at a time, and ask each field a specific question.
- Which of these is picture?
codec_type, on every stream. The type is the role;codec_nameis only how that role is encoded. On a foundation-level pass you mostly need the type. - How big is the picture?
widthandheight. - What rate does it declare?
r_frame_rate, withavg_frame_ratenext to it.time_basein the same entry tells you the smallest time unit the stream's timestamps are expressed in. - How long is it?
format.durationcovers the whole container. Individual streams may or may not carry their ownduration, and it is entirely normal for an audio stream to have none. - What is the sound like?
sample_rate,channels,channel_layout. - What language does it claim?
tags.language, when the entry has it.
That last one is the whole reason this article exists, so hold onto it.
Save the raw report next to the readable summary, in the same folder, with the same base name. The summary is your interpretation; the raw output is what you fall back on when the summary turns out to be wrong about something, which it will.
An empty field is a finding, not a gap to fill
Stream 2 in the example has no tags.language. Three tempting things to do with that, all of them wrong: assume it's English because the shoot was in an English-speaking city; assume it because the other audio track is tagged eng; assume it because your player played it by default. The file does not say. Leave it not saying.
This is a general habit rather than a rule about language tags. A field that the tool could not determine is omitted from the JSON entirely, or printed as N/A in the default text writer — same finding, two spellings. Either one goes into your note as an open question. The one thing that must not happen is a plausible value appearing in your summary that isn't in the source, because the next person to read your summary will treat it as something the file said.
The same discipline applies to the timing fields, which answer narrower questions than they look like they do.
The stream index is a position in the container's list. It is not the same as a track number, and it is not permanent — remux the file and the numbering can change. Cite the index for this file, in this location, and say so.
r_frame_rate is a declared rate. It tells you what the stream says about its own cadence. It does not tell you that consecutive frames are evenly spaced, and it does not tell you what any individual frame's timestamp is. avg_frame_rate sitting at the same value is mildly reassuring — it means the average matched the declaration across the stream — but an average is still not a per-frame measurement. Neither field settles a question like "is frame 900 later than the audio at the same timestamp?" Only timestamps do that, and you only need to look at them if you have a reason.
Duration is the same kind of thing. When the container's duration and a stream's duration disagree, that is a finding, and it goes in the note as one. It is not automatically an error in the file and it is not automatically an error in the report. You did not measure anything by noticing it.
Which brings you to the useful output of this section, and it isn't a diagnosis. It's a question, written specifically enough to be answerable:
Stream 2 in
interview_final.movhas no language tag. Stream 1 is taggedengand looks like the camera mic. Is stream 2 the intended interview audio?
That question can be answered by the person who recorded it. "Something seems off with the audio" cannot.
Listen before you name the track
You already have a working hypothesis about stream 2. Now test it against the actual sound, because metadata is not evidence about content and the wrong guess here is expensive later.
Open the file in a player that lets you choose an audio track — most desktop players have a track list somewhere under an audio menu, though the wording varies. Select stream 2 alone. Play ten seconds of continuous speech, somewhere with a bit of room tone behind it. Then select stream 1 and play the same ten seconds.
In the invented clip, they are not close. Stream 1 is the camera's built-in mic: voice further away, traffic and air handling behind it. Stream 2 is close and dry. That contrast is the sort of thing you can only get by listening, and it takes about a minute.
Here's the part worth being precise about. Suppose your player opened the file on stream 1. That could be because it defaults to the first audio stream, or because it has a preferred-language setting and stream 1 is the only one carrying a language tag at all. Either way, a default that picked a track for you is not information about which track you wanted. It's the reason to check.
Now: what did you actually establish?
You established that in this player, on this machine, over those ten seconds, stream 2 decodes to the sound you expected, and that it's in English. That is a real and useful thing to know. It is not a statement that every second of stream 2 decodes cleanly, that the sync holds for the whole clip, or that a collaborator on another machine will be able to play the file at all. Playback is a sample, not a verdict. Say which passage you played and how long it was, so the limit of the claim travels with the claim.
Only now, and only if playback raised something specific, is it worth looking at packets. Say the audio turned out to run a quarter-second ahead of picture, and you wanted to see whether the offset is constant. A sync question needs both sides of the comparison, which means two bounded reads over the same window. The sound first:
ffprobe -v error -select_streams a:1 -read_intervals 00:00:20%+10 \
-show_entries packet=pts_time,duration_time -of json interview_final.mov
Then the picture:
ffprobe -v error -select_streams v:0 -read_intervals 00:00:20%+10 \
-show_entries packet=pts_time,duration_time -of json interview_final.mov
a:1 is the second audio stream — stream 2 in our numbering — and v:0 is the first video stream, stream 0. -read_intervals restricts each read to a window starting at 20 seconds and running ten seconds further, and -show_entries limits the output to the two timestamp fields under discussion. One line per packet, no codec parameters, and nothing about the streams that read doesn't name. Two reports, one window, and -of json is what lets you line them up without reading numbers off a screen. -read_intervals can only seek to the nearest point the container makes reachable, so treat the window as approximate.
Comparing the two reports tells you whether the gap between the audio timestamps and the picture timestamps holds steady across the window or opens and closes. Call that what it is: a reading of the times the file declares — which is more than an eye on playback gave you, and still not the same thing as having measured the sound against the picture.
If you have no specific timing question, skip this entirely. A long unfiltered dump is not more thorough than a focused check; it's just harder to read, and it invites you to draw conclusions from numbers nobody asked about.
Hand over an inventory, not a diagnosis
The deliverable from all of this is short. It should fit on a screen, and it should let someone else act without asking you what you meant.
Source: pitch_source/interview_final.mov
(original path recorded; working copy separate)
Report: interview_final.ffprobe.json (ffprobe version: ____)
Container: MOV family, 4 streams, 72.48 s, ~20 Mb/s
Intended picture: stream 0 — h264, 1920x1080
Intended sound: stream 2 — aac, 48 kHz stereo
no language tag in the file
Also present: stream 1 — aac, tagged "eng" (camera mic;
room tone and traffic, not intended)
stream 3 — subtitle, no language tag
Inspected: stream 2 played alone, 00:00:20–00:00:30.
Confirmed as the interview voice, in English.
Stream 1 played over the same passage for contrast.
Open: language of streams 2 and 3 is unrecorded in the file.
Confirmation above came from listening, not from metadata.
Next: confirm intended sound with the recordist before
preparing pitch media.
Read that last pair of lines again, because they're the point of the whole exercise. Listening told you what the audio is. It did not correct the file, and the note must not imply that it did. Stream 2 still has no language tag. If you later hand the file to someone else, their tooling will see exactly what yours saw.
From here there are three honest next moves. Ask the specific question and wait. Take the file to an existing guide for the conversion you now know you need, armed with the stream numbers. Or decide nothing needs changing, which is a perfectly good outcome and the most common one. What isn't on the list is quietly transcoding the source to make the ambiguity go away — that produces a second file with its own uncertainties and leaves you with two things to explain instead of one.
Keep the raw report beside the summary, keep the version string with both, and keep the missing field missing. An inventory that says "unknown" in the right place is worth more to the next person than one that reads cleanly because somebody guessed.
Frequently asked questions
How can I tell which audio stream is the interview if the file opens and plays fine?
Opening and playing only shows that something in your player could handle the file. Use ffprobe to list the container and each stream, record stream indices and codec types, then listen to each audio stream alone over the same passage. A player default is not information about which track you wanted. Playback in one player on one machine over sampled seconds is a sample, not a verdict for every second or another machine. Confirm the intended sound with the recordist.
What does the ffprobe command with -show_format -show_streams -of json actually report?
It reports the container wrapper and one entry per stream. The container section gives format family, duration, size, stream count and overall bit rate. Each stream entry gives index, codec type and name, dimensions, frame rates, time base, sample rate, channels and tags. ffprobe reports; it does not rewrite, remux or repair. Save the raw JSON and a readable summary together, and record the ffprobe version.
A language tag is missing. Can I assume the audio is English because the shoot was in an English-speaking place or because the other track is tagged eng?
No. The file does not say. The same goes for assuming from a player default. A field the tool could not determine is omitted or shown as N/A; either way it is a finding and belongs in the note as an open question. Do not let a plausible value appear in your summary that is not in the source. Write the specific question for the person who recorded it.
Should I inspect packet timestamps to check sync?
Only if playback raised a specific timing question. Packet reads can compare audio and video timestamps over the same bounded window using select_streams and read_intervals with show_entries. But those are the times the file declares, not a measurement of sound against picture. If you have no specific timing question, skip it. A long unfiltered dump is not more thorough, just harder to read.
What should a handover inventory contain?
It should name the source path and separate working copy, the raw report and ffprobe version, the container summary, the intended picture and sound streams with indices, other present streams, what was inspected by listening and over which passage, open questions such as missing language tags, and the next action. Keep the raw report beside the summary. Listening told you what the audio is; it did not correct the file, and the note must not imply that it did.