DEV Community

CompressVid
CompressVid

Posted on

Target-Size Video Compression Is Just Division: The Bitrate Math (With Real Results)

Disclosure: I work on CompressVid, an online video compressor. The numbers below are from our own test runs, recorded while making tutorial videos. I've noted which figures come from the tool's UI and which are my own arithmetic.

"Compress this video to 24 MB" sounds like a fuzzy, magic request. It isn't. Because a compressed video's size is almost entirely set by its bitrate, a target size is just a bitrate budget, and you get the budget by dividing.

The one formula

file size (bits) ≈ (video bitrate + audio bitrate) × duration + container overhead
Enter fullscreen mode Exit fullscreen mode

Rearrange it, and a size target becomes a bitrate target:

video bitrate ≈ (target bytes × 8) / duration − audio bitrate
Enter fullscreen mode Exit fullscreen mode

To keep the arithmetic simple, this post uses 1 MB = 1,000,000 bytes and 1 Mbps = 1,000,000 bits/s. CompressVid actually displays sizes in binary megabytes (MiB, 1,048,576 bytes, about 4.9% bigger), so the true bitrates of the files below are roughly 5% higher than the figures in the table. The ratios and conclusions don't change. The MB/MiB gap matters when a limit is hard, though, and it's one reason to leave headroom.

A worked example in JavaScript

// Returns the average video bitrate (kbps) needed to hit a target size.
function videoBitrateForTarget({ targetMB, durationSec, audioKbps = 128, overheadPct = 2 }) {
  const usableBits = targetMB * 1e6 * 8 * (1 - overheadPct / 100);
  const totalKbps = usableBits / durationSec / 1000;
  return Math.max(0, Math.floor(totalKbps - audioKbps));
}

videoBitrateForTarget({ targetMB: 24, durationSec: 30 });  // ≈ 6143 kbps
videoBitrateForTarget({ targetMB: 24, durationSec: 45 });  // ≈ 4053 kbps
videoBitrateForTarget({ targetMB: 96, durationSec: 180 }); // ≈ 4053 kbps
Enter fullscreen mode Exit fullscreen mode

The 2% overhead and 128 kbps audio here are assumptions for illustration. Real overhead depends on the container and the stream count.

Look at the last two calls: a 24 MB target for 45 seconds and a 96 MB target for 3 minutes give the same bitrate budget. Size limits are really bits-per-second limits in disguise. A longer clip under the same cap simply gets fewer bits for every second.

With ffmpeg, the classic way to spend a budget precisely is two-pass encoding:

ffmpeg -y -i in.mp4 -c:v libx264 -b:v 4053k -pass 1 -an -f mp4 /dev/null
ffmpeg -i in.mp4 -c:v libx264 -b:v 4053k -pass 2 -c:a aac -b:a 128k out.mp4
Enter fullscreen mode Exit fullscreen mode

What actually happened on real files

Here are four runs from our tests. Input and output sizes are as the tool reported them. Bitrates are my own division, using size × 8 / duration.

Input Target Output Input avg bitrate Budget Output avg bitrate
135.95 MB, 30 s, 1080p60 MOV 24 MB 7.48 MB ~36.3 Mbps ~6.4 Mbps ~2.0 Mbps
65.05 MB, 45 s, 1080p MP4 24 MB 21.08 MB ~11.6 Mbps ~4.3 Mbps ~3.7 Mbps
185.24 MB, 3:00, 1080p MP4 96 MB 32.08 MB ~8.2 Mbps ~4.3 Mbps ~1.4 Mbps
185.24 MB, 3:00, 1080p MP4 "70% smaller" (55.6 MB) 23.86 MB ~8.2 Mbps ~2.5 Mbps ~1.1 Mbps

Before and after: a 65.05 MB clip compressed to 21.08 MB with a 24 MB target

The 45-second clip used about 88% of its budget. The iPhone MOV used about 31%. Same tool, same target, very different outcomes. Why?

The target is a ceiling, not a goal

A size target is a maximum. Modern encoders don't pad a file just to reach a number. In quality-driven modes (CRF in x264/x265, or a capped CRF with -maxrate/-bufsize), the encoder spends bits only where the picture needs them:

  • Phone footage is over-provisioned. The 1080p60 MOV started at ~36 Mbps, a bitrate chosen so the phone can encode in real time with little effort. A slower encoder can describe the same scene in far fewer bits, so it never needed the 6.4 Mbps it was allowed.
  • Content complexity decides the rest. Static or slow-moving shots compress very well. Lots of motion, grain or fine texture eats bits. The 45-second clip sat much closer to its ceiling.
  • Same input, different targets, both under. The 3-minute file landed at 32.08 MB with a 96 MB cap and at 23.86 MB with a 55.6 MB cap. Both runs stayed at 1080p and kept the full 3:00.

The '96 MB Chat' target selected for a 3-minute 1080p clip

If you're building this yourself, a robust pattern is: compute the bitrate ceiling from the target, then let a quality-based rate control work beneath it. Pure CBR or ABR will hit the number more literally, but it wastes bits on easy footage and can starve hard scenes.

Why you still need headroom

Even with the maths right, the final file can overshoot slightly:

  1. Container overhead (MP4 moov index, per-sample tables) grows with duration and frame count.
  2. Rate control isn't exact. Single-pass ABR can drift by a few percent, and VBV buffers allow short bursts.
  3. MB vs MiB confusion, as described above.

We learned this the hard way. An early build produced 20.58 MB from a 19 MB target. That has since been fixed, and the reason presets like "19 MB" for a 20 MB limit or "24 MB" for 25 MB exist at all is to leave a margin for exactly these effects. My rule of thumb: aim about 5% under any hard limit.

Takeaways

  • Target size ÷ duration = bitrate budget. Subtract audio and overhead, and you have your video bitrate.
  • Equal budgets can hide behind different-looking limits (24 MB for 45 s ≈ 96 MB for 3 min).
  • Treat the target as a ceiling. Good encoders often finish well under it, especially on high-bitrate phone footage.
  • Always leave headroom for container overhead and rate-control drift.

If you want to watch the ceiling-versus-result behaviour on your own clips without writing any ffmpeg, the CompressVid video compressor lets you type a target size and shows input and output sizes side by side. I'd be curious how close your footage gets to its budget. Drop your numbers in the comments.

Top comments (0)