halfsize / FFmpeg target size
FFmpeg: compress to an exact target size
-crf asks for quality and lets the file size land wherever it wants. When a form says
20 MB, you need the opposite: a bitrate budget, two passes, and a check that the result
actually fits.
Why -crf does not hit a size
Constant Rate Factor targets a perceptual quality level. A talking-head clip and a fireworks clip at the same CRF can differ by 4× in size, because the encoder spends bits where the picture needs them. That is the right behaviour for archiving and the wrong behaviour for an upload form with a hard cap.
You can guess, encode, check the size, and encode again. That loop is what most people mean by "just use HandBrake". It works, but it costs an encode per guess and it never tells you what the picture lost.
The bitrate budget
Video encoders think in bits per second. A file-size target converts to a bitrate:
video_kbps = (target_bytes × 8 ÷ seconds) − audio_kbps − container_overhead
A 60-second clip at 20 MB (20,000,000 bytes) is 2.67 Mbps total. Remove 128 kbps of AAC audio and roughly 1.5% for MP4 container overhead, and you have about 2.46 Mbps for the video stream.
That number is the whole trick. Everything else is bookkeeping: rotation metadata, HDR, variable frame rate, whether the source audio is already low bitrate and re-encoding it would waste bits.
Two-pass x264
Average bitrate mode in a single pass has to guess how complex the next scene will be. Two-pass fixes that: the first pass analyses the whole clip, the second spends the budget where it matters.
$ ffmpeg -y -i input.mp4 -c:v libx264 -b:v 2460k \
-pass 1 -passlogfile ffmpeg2pass -an -f mp4 /dev/null
$ ffmpeg -i input.mp4 -c:v libx264 -b:v 2460k \
-pass 2 -passlogfile ffmpeg2pass -c:a aac -b:a 128k \
-movflags +faststart output.mp4
On Windows, replace /dev/null with NUL. Delete the ffmpeg2pass* log files afterwards; they are only needed between the two passes.
Why the result still misses by a few percent
Three things bend the number:
- ABR is not CBR. x264 targets an average, and a short clip gives it less room to average out. A 10-second file can overshoot more than a 10-minute one.
- Container overhead. MP4 headers, the moov atom, and per-frame overhead are not in your bitrate maths. Budget 1–2%.
- Audio re-encoding. Copying an existing AAC track (
-c:a copy) is free; re-encoding to a lower bitrate saves space but drifts from the plan.
Landing 1–3% under the cap is the correct outcome. 19.45 MB is accepted everywhere. 20.01 MB is rejected by the same form.
Verify the size and the quality
$ ffprobe -v error -show_entries format=duration,size \
-of default=noprint_wrappers=1 output.mp4
$ ffmpeg -i output.mp4 -i input.mp4 -lavfi libvmaf -f null -
VMAF scores perceptual similarity from 0 to 100. Above 90 reads as “looks the same”. 80–90 is “slight softening”. 70–80 is visible loss. A size target without this number is a guess with extra steps.
What halfsize automates
- Probes duration, frame rate, rotation, audio bitrate and HDR flags before planning.
- Computes the budget, then picks the largest resolution that stays above a bits-per-pixel floor — 1080p when it fits, 720p or 540p when 1080p would look starved.
- Runs both passes, writes to a temporary file, and only renames it when the encode succeeds.
- Measures VMAF and SSIM, then prints a plain-language verdict instead of a number you have to interpret.
- Adds
--for discord,--for wechat,--for whatsapp,--for email,--for outlookand--for twitterso you do not have to look the cap up.
$ npx halfsize input.mp4 --to 20mb
target 20.0 MB
plan H.264 1280x720 2.46 Mbps audio 128 kbps 2-pass
scale 1920x1080 → 1280x720
output input.20mb.mp4
136 MB → 19.45 MB 7.0x smaller 2.7% under target
VMAF 88.3 SSIM 0.971 slight softening
Measured results
60-second 1080p30 source at 18 Mbps, default settings, one laptop:
| Target | Output | Resolution | Video bitrate | VMAF |
|---|---|---|---|---|
| 20 MB | 19.45 MB | 1280×720 | 2.46 Mbps | 88.3 |
| 16 MB | 15.59 MB | 960×540 | 1.94 Mbps | 84.8 |
| 10 MB | 9.77 MB | 960×540 | 1.24 Mbps | 82.9 |
| 2 MB | 1.99 MB | 960×540 | 0.25 Mbps | 79.9 |
FAQ
Is two-pass always better than CRF?
For a hard size cap, yes: it is the only mode that spends the budget predictably. For quality-first work, CRF is faster and usually better, because it does not waste bits on scenes that do not need them.
Can I just use -fs 20M?
-fs stops writing when the file reaches the limit, which can truncate mid-GOP. The file may be the right size and unplayable at the end. It also gives you no control over quality distribution.
Does H.265 help?
Usually 30–50% better compression at the same quality, so you can keep more resolution. The catch is playback: older Windows machines, many browsers, and some editors cannot decode it. For a file that must play anywhere, H.264 is still the safe answer.
Why is my 10-second clip over the target?
Short clips give the rate controller less to average over, and container overhead is a larger fraction. Add 5% margin, or set a slightly lower bitrate by hand.
How exact is “exact”?
Typically 1–3% under. The encoder cannot promise a byte count before it finishes, so a small margin is deliberate.