How to Compress Video Files with ffmpeg

Large video files are slow to upload, awkward to share, and expensive to store. Re-encoding with a modern codec can cut the size substantially, especially when the source uses an excessive bitrate or a resolution that you do not need.
This guide explains how to compress video with ffmpeg using quality-based H.264 and H.265 encoding, resolution scaling, and two-pass encoding when the output must fit a target size.
Before Compressing the Video
The ffprobe program is installed with ffmpeg and shows the source codec, resolution, duration, and bitrate. Inspect the input before choosing compression settings:
ffprobe -hide_banner input.mp4For example, a ten-minute 1080p video might show the following details:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
Duration: 00:10:00.00, start: 0.000000, bitrate: 8446 kb/s
Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], 8309 kb/s, 30 fps, 30 tbr, 15360 tbn (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
encoder : Lavc libx264
Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 127 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]The video uses H.264 at about 8.4 Mb/s, including audio and container overhead. There may be room for a smaller output, depending on the amount of motion and detail in the footage.
If ffprobe is not installed, follow the installation section in the main ffmpeg command guide
before continuing.
In the examples below, we use standard dynamic range (SDR) video. HDR footage needs separate color and tone-mapping settings; changing the codec or pixel format alone does not convert HDR to SDR correctly.
Keep the original file while you test the settings. The examples use lossy compression, so re-encoding discards some detail. FFmpeg asks before overwriting an existing output file; use a new filename when comparing settings.
Compress Video with H.264
H.264 offers the widest playback support across browsers, phones, televisions, and editing applications. Use the libx264 encoder with Constant Rate Factor (CRF) mode for a good balance between quality and size:
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p -c:a aac -b:a 128k -movflags +faststart output.mp4The options in this command control the important parts of the output:
-c:v libx264- Encode the video with H.264 through the x264 library.-crf 23- Set a constant quality target. Lower values produce better quality and larger files.-preset medium- Choose how much encoding time x264 spends finding efficient compression.-pix_fmt yuv420p- Use 8-bit video with 4:2:0 chroma subsampling for broad playback support.-c:a aac- Encode the audio as AAC for broad MP4 compatibility.-b:a 128k- Set the audio bitrate to 128 kb/s.-movflags +faststart- Move MP4 metadata to the beginning so playback can start before the file finishes downloading.
CRF mode lets the bitrate change with the content. A static presentation slide needs fewer bits than fast motion, so both scenes can meet the same visual quality target without being forced to use one fixed bitrate.
Choose a CRF Value
For H.264, 23 is a practical starting point. Adjust the value after checking a short sample of the actual footage:
| H.264 CRF | Typical use |
|---|---|
18-20 | High quality when preserving fine detail matters more than file size |
21-24 | Balanced quality and size for general viewing |
25-28 | Smaller files for previews or casual sharing |
CRF is not a percentage. A one-point change is small, while several points can make a clear difference in both quality and size. Source noise, motion, animation, and screen recordings all compress differently, so there is no single value that produces the same file size for every video.
To test one minute without encoding the entire file, use -ss to seek and -t to limit the duration:
ffmpeg -ss 00:02:00 -i input.mp4 -t 60 -c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p -c:a aac -b:a 128k sample.mp4Watch the sample at normal size and check fine detail, motion, and dark gradients. Raise the CRF when the sample is larger than necessary, or lower it when compression artifacts are visible.
Choose an Encoding Preset
The commonly used x264 presets run from ultrafast through veryslow. A slower preset spends more CPU time searching for efficient ways to represent the same content and often produces a smaller file at a similar quality target. The final preset, placebo, takes even longer and rarely gives enough improvement to justify the encoding time.
Use medium for a sensible starting point. Select slow when file size matters more than encoding time:
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset slow -pix_fmt yuv420p -c:a aac -b:a 128k output.mp4Avoid treating ultrafast as extra compression. It finishes quickly by doing less analysis, which generally requires more data to meet the selected quality target. The veryslow preset can take much longer than slow for a modest additional saving.
Reduce the Resolution While Compressing
Reducing resolution removes pixels before encoding and can save more space than raising CRF alone. The following command scales the video to 720 pixels high while preserving its aspect ratio:
ffmpeg -i input.mp4 -vf "scale=-2:720" -c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p -c:a aac -b:a 128k -movflags +faststart output-720p.mp4The -2 tells the scale filter to calculate the width from the source aspect ratio and round it to a value divisible by two. This avoids the odd-dimension error that commonly appears with H.264 and the yuv420p pixel format.
Do not upscale a smaller source. Increasing a 720p input to 1080p creates more pixels but does not restore detail, and the larger frame can increase the output size.
Compress Video with H.265
H.265, also called HEVC, can produce smaller files than H.264 for similar visual quality, particularly at high resolutions. Use the libx265 encoder and start around CRF 28:
ffmpeg -i input.mp4 -c:v libx265 -crf 28 -preset medium -tag:v hvc1 -c:a aac -b:a 128k -movflags +faststart output-hevc.mp4The -tag:v hvc1 option sets the codec tag that QuickTime, Safari, and iPhones expect. Without it, FFmpeg writes hev1, and Apple players may refuse to open the file.
The CRF scales of x264 and x265 are not directly interchangeable. H.265 commonly uses a higher starting value, so compare the output visually instead of copying an H.264 CRF unchanged.
The trade-off is compatibility and encoding speed. Older devices and some browser workflows do not support H.265, and libx265 generally encodes more slowly. Use H.264 when the file must play almost everywhere, and H.265 when storage matters and you control the playback devices.
Keep the Original Audio
Audio is usually a small part of a video file, but re-encoding it can still introduce an unnecessary quality loss. If the source audio codec is compatible with MP4, copy it without re-encoding:
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p -c:a copy -movflags +faststart output.mp4AAC can be copied into MP4 directly. A source using an incompatible audio codec may cause FFmpeg to reject the output container. In that case, return to -c:a aac -b:a 128k.
For spoken audio, 96 kb/s stereo or 64 kb/s mono may be enough. Music and mixed content usually benefit from 128 kb/s or more.
Compress to a Target File Size
CRF targets quality, not a predictable number of megabytes. When an upload form imposes a hard file-size limit, calculate a bitrate and use two-pass encoding instead.
Use this approximate formula, with decimal megabytes (1 MB = 1,000,000 bytes) and seconds:
Video bitrate (kb/s) = (target size in MB x 8000 / duration in seconds) - audio bitrate (kb/s)For a ten-minute video that must fit in 100 MB, reserve 5% for container overhead and bitrate variation. Using a 95 MB budget and 128 kb/s audio gives us:
(95 x 8000 / 600) - 128 = 1138.7 kb/sRound the video bitrate down to 1130k. Run the first pass without audio and discard its temporary video output:
ffmpeg -i input.mp4 -c:v libx264 -b:v 1130k -preset slow -pix_fmt yuv420p -pass 1 -passlogfile compress-pass -an -f null /dev/nullThe first pass analyzes the entire video and writes statistics to files beginning with compress-pass. The -f null muxer discards the encoded video, so /dev/null is only a placeholder output name. Choose a different pass-log prefix if you have another two-pass job running in the same directory.
Run the second pass with the same video settings and pass-log prefix, adding the final audio configuration:
ffmpeg -i input.mp4 -c:v libx264 -b:v 1130k -preset slow -pix_fmt yuv420p -pass 2 -passlogfile compress-pass -c:a aac -b:a 128k -movflags +faststart output.mp4Two-pass encoding distributes the available bitrate according to scene complexity. It is slower than CRF because FFmpeg reads the video twice, but it is the better choice when size is a fixed requirement. Any scale or frame-rate filter must be identical in both passes.
After the second pass completes successfully, you can remove the compress-pass-0.log and compress-pass-0.log.mbtree statistics files. Check the finished video size before uploading, since two-pass encoding targets an average bitrate rather than enforcing an exact byte limit.
Compare the Input and Output
Use wc -c to compare the actual file sizes in bytes:
wc -c input.mp4 output.mp4633456993 input.mp4
94991697 output.mp4
728448690 totalThe first two lines show the size of each file, and the last line shows their total. Divide the output file’s byte count by 1,000,000 to compare it with a limit expressed in decimal megabytes. The output is about 95 MB, which fits under the 100 MB limit. Unlike du -h, this measures file contents rather than allocated disk space.
File size alone does not tell you whether the result is acceptable. Play the output from beginning to end, check scenes with motion and fine detail, and confirm that audio remains synchronized. Run ffprobe -hide_banner output.mp4 when you also need to verify the codec, resolution, and bitrate.
Troubleshooting
Unknown encoder ’libx264’ or ’libx265’
Your FFmpeg build does not include that external encoder. Check available encoders with ffmpeg -encoders | grep -E 'libx26[45]', then install a fuller FFmpeg package from your distribution if the encoder is missing. On Fedora, the default ffmpeg-free package omits both encoders; enable RPM Fusion and install the full ffmpeg package.
The compressed file is larger than the input
The source may already use an efficient codec and low bitrate, or the selected CRF may be too low. Inspect both files with ffprobe, then raise CRF, use a slower preset, or reduce resolution. Re-encoding cannot guarantee a smaller file unless you constrain quality, resolution, or bitrate.
width not divisible by 2
Some encoder and pixel-format combinations require even dimensions. Use scale=-2:720 or another scale expression where -2 calculates an even value.
The output does not play on the target device
Use H.264 with -pix_fmt yuv420p and AAC audio in an MP4 container for broad playback support. H.265 files may require newer hardware or software even when the .mp4 extension is recognized, and Apple devices need the -tag:v hvc1 option.
The target-size output is slightly too large
Container overhead and bitrate variation can push the final file above the estimate. Lower the calculated video bitrate by a few percent and run both passes again.
Conclusion
Before encoding a long video, compare short samples from a scene with motion and fine detail so you can judge the quality on your target device. Keep the ffmpeg cheatsheet nearby for other conversion and filtering commands.
Tags
Linuxize Weekly Newsletter
A quick weekly roundup of new tutorials, news, and tips.
About the authors

Dejan Panovski
Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.
View author page