It is easy to assume an MP4's size is "the video, plus a bit". Then you ask x264 for a 10 MB file, get 10.9 MB back, and the extra 0.9 MB has to be somewhere. ffprobe can answer that in about ten lines, so I counted.
Sizes below are bytes, and 1 MB means 1,000,000 bytes. Some tools, browser compressors among them, use 1 MB = 1,048,576 bytes, so a "10 MB" target can differ by about 5% depending on whose definition you pick.
An MP4 is three piles of bytes
Every file has video packets, audio packets, and everything else: the moov index, the ftyp header, the mdat wrapper. ffprobe reports each packet's size, so the first two piles are a sum, and the third is whatever is left of the file size after you subtract them.
My test clip is 60 seconds of 1080p street performance by Isak Zvegelj (CC BY 3.0, Wikimedia Commons). I cut it to 60 seconds and re-encoded it at phone-like bitrate, so it is not a phone original. Encoder: x264 medium, AAC 128 kbps, M4 Mac, FFmpeg 7.1.
Sum the packets with ffprobe
ffprobe -v error -show_entries format=size -of csv=p=0 a10.mp4
for t in v a; do
ffprobe -v error -select_streams $t:0 -show_entries packet=size -of csv=p=0 a10.mp4 \
| awk -v t=$t '{s+=$1} END{print t, NR, s}'
done
9965325
v 1800 8926656
a 2814 971835
The first line is the file. The next two are packet count and total payload bytes for video and audio. Container overhead is 9,965,325 − 8,926,656 − 971,835 = 66,834 bytes.
What the three piles say
For this clip, audio is 971,835 bytes, which is 9.7% of a 10 MB target. The container is 66,834 bytes, under 0.7%. In my own box parser, the moov alone was 66,786 of those. Why moov is that size, I did not look into; my guess is the per-packet tables, since there are 4,614 packets.
The file above is the version where audio was budgeted for. The naive run asked for a mean video bitrate of 1,333,333 bit/s, which is 10,000,000 × 8 / 60, and that budget alone would give 10,000,000 bytes of video. Its video packets summed to 9,878,415 bytes, so x264 undershot its video target by 121,585 bytes, while audio added 971,835 and the container 66,834, for 10,917,084 in total. The overshoot did not come from the video encoder. It came from audio I forgot to budget.
Subtract the audio first
Naive version: set the video bitrate to target × 8 / duration. On this clip that produced 10,917,084 bytes, 9.17% over the 10 MB target. Subtract 128 kbps for audio first, so the video gets 1,205,333 bit/s, and the same encode lands at 9,965,325 bytes, 0.35% under. The sum above is that file.
At 25 MB the same naive formula overshoots by 3.14% instead, because the same 0.97 MB of audio is a smaller share of a bigger target.
Two-pass did not land closer here
Two-pass is usually expected to be tighter. With audio subtracted it ended 1.74% over at the 10 MB target and 0.28% over at 25 MB, which is 69,942 bytes past the limit. Single-pass was 0.35% and 0.66% under, and a CRF search over seven full encodes was 1.45% and 0.86% under, but each of those encodes took 157 to 217 seconds on this machine, against about 23 seconds for single-pass. If the cap is hard, I would leave a percent or two of room.
This only holds for this clip
Everything above is one 60-second action clip, one machine, one run each. A synthetic 60-second screen recording I also tried behaved differently: at 10 MB, two-pass came out 12.0% over and single-pass 8.7% under. It was generated, so it is not evidence about real screen captures. Run the ffprobe sum on your own file before trusting my percentages.
Where a browser tool fits
If you only want a smaller file and do not need a byte target, a browser tool is enough. I used ImgIng (https://imging.ai/), which runs in a desktop browser and whose video page says processing happens locally. A network capture over 11 runs showed no upload request. It has presets and no "compress to X MB" field. Its smallest-size preset gave me 10,487,546 bytes on this clip, at 1080×608 and 24 fps, so picking a target size still means doing the arithmetic above yourself. I did not test batch mode or phones.
Top comments (0)