32blogby Studio Mitsu

The Complete Guide to Video Compression with FFmpeg

A thorough guide to compressing video with FFmpeg. Learn when to use CRF, bitrate targeting, presets, and 2-pass encoding — with ready-to-use settings for every use case.

by omitsu11 min read

This article contains affiliate links.

On this page

To compress a video with FFmpeg, use CRF mode: ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset slow -c:a copy output.mp4. CRF 23 with the slow preset gives you a good balance of quality and file size for most use cases, no math required.

But CRF isn't the only tool in the box. This guide covers CRF, bitrate targeting, 2-pass encoding, and presets — the four core approaches to video compression. I've spent a fair amount of time benchmarking these on real footage for 32blog, and the difference between "just compress it" and "compress it correctly" can be a 60% file size reduction with no visible quality loss. You'll find ready-to-copy commands for web delivery, social media, archiving, and mobile at the end.

Understanding Video Compression Basics

Before diving into commands, it helps to understand the core trade-off: quality vs. file size.

There are two main encoding strategies:

  • Variable bitrate (VBR) — Uses more bits for complex scenes and fewer for simple ones. Achieves better quality at smaller sizes.
  • Constant bitrate (CBR) — Maintains a fixed data rate throughout. Used when bandwidth guarantees are required, like in live streaming.

The two dominant codecs today are H.264 (libx264) and H.265 (libx265). H.265 produces roughly half the file size of H.264 at equivalent quality, but encodes more slowly and has lower playback compatibility on older devices.

The codec is specified with the -c:v flag. Use -c:v libx264 for H.264 and -c:v libx265 for H.265.

Compress with CRF (Constant Quality)

CRF — Constant Rate Factor — fixes a quality level and lets the encoder decide the file size. It's the most popular FFmpeg compression method and the right default for most use cases. The FFmpeg H.264 encoding guide recommends CRF as the primary rate control mode.

CRF compression with H.264:

bash
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a copy output.mp4

CRF compression with H.265 (smaller file size at equivalent quality):

bash
ffmpeg -i input.mp4 -c:v libx265 -crf 28 -c:a copy output.mp4

To increase quality (at the cost of file size), lower the CRF. For fine-detail footage like screen recordings or animation, try CRF 18. For social media uploads where file size matters more than pixel-perfect quality, CRF 30–35 can cut file size dramatically without noticeable degradation.

Compress with Bitrate Targeting

Bitrate targeting is the right choice when you need strict control over file size or streaming bandwidth. If you have a target like "this file must be under 50 MB," this is your method.

A rough size estimate: duration (seconds) × bitrate (bps) ÷ 8 = bytes. A 60-second video at 2 Mbps works out to about 15 MB.

Encode video at a target of 2 Mbps:

bash
ffmpeg -i input.mp4 -c:v libx264 -b:v 2M -c:a copy output.mp4

Specify both video and audio bitrates:

bash
ffmpeg -i input.mp4 -c:v libx264 -b:v 1800k -c:a aac -b:a 192k output.mp4

One limitation of bitrate targeting: the same amount of data is used regardless of scene complexity. Simple scenes will have more bits than needed; complex scenes may not have enough. Unless you have a hard size requirement, CRF usually produces better results.

Optimize with 2-Pass Encoding

2-pass encoding is a two-stage process: FFmpeg first analyzes the entire video, then encodes it using that analysis. This allows it to distribute bits more efficiently across the video. It's most beneficial for long-form content or when you need the best possible quality at a fixed bitrate.

Pass 1 (analysis only — no output video is created):

bash
ffmpeg -i input.mp4 -c:v libx264 -b:v 2M -pass 1 -an -f null /dev/null

Pass 2 (final encode):

bash
ffmpeg -i input.mp4 -c:v libx264 -b:v 2M -pass 2 -c:a aac -b:a 192k output.mp4

For H.265, use -c:v libx265 -x265-params pass=1 in the first pass.

2-pass encoding takes roughly twice as long as a single pass, but yields better quality at the same bitrate. It's especially effective for DVD authoring, broadcast delivery, or any scenario with a strict file size ceiling. For everyday compression, CRF is usually sufficient.

Control Speed and Efficiency with Presets

FFmpeg's libx264 and libx265 include a preset system that balances encoding speed against compression efficiency.

Presets from fastest to slowest:

ultrafastsuperfastveryfastfasterfastmedium (default) → slowslowerveryslow

Encoding with a preset specified:

bash
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset slow -c:a copy output.mp4

To compare multiple presets on the same input:

bash
for preset in fast medium slow veryslow; do
  ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset $preset -c:a copy output_${preset}.mp4
done

Practical recommendations: use medium (default) or slow for most compression tasks; use veryslow when you want the smallest possible file and have time to spare; use fast or veryfast for real-time processing or large batch jobs.

Optimal Settings by Use Case

Ready-to-use commands for the most common scenarios.

Use CaseCodecCRFPresetNotes
Web deliveryH.26423slowMaximum compatibility
Social media (X / Instagram)H.26428–30mediumFile size priority
Long-term archiveH.26524veryslowBest ratio for storage
Mobile playbackH.26426fastPair with resolution scaling

Web delivery (compatibility first):

bash
ffmpeg -i input.mp4 \
  -c:v libx264 -crf 23 -preset slow \
  -c:a aac -b:a 128k \
  -movflags +faststart \
  output_web.mp4

-movflags +faststart moves the moov atom to the beginning of the file, enabling playback to start before the download completes. This is a must-have flag for web video.

Social media (smallest file size):

bash
ffmpeg -i input.mp4 \
  -c:v libx264 -crf 30 -preset medium \
  -vf "scale=1280:-2" \
  -c:a aac -b:a 96k \
  output_sns.mp4

Long-term archival (H.265, maximum compression):

bash
ffmpeg -i input.mp4 \
  -c:v libx265 -crf 24 -preset veryslow \
  -c:a aac -b:a 128k \
  output_archive.mp4

Mobile playback (downscale + lightweight):

bash
ffmpeg -i input.mp4 \
  -c:v libx264 -crf 26 -preset fast \
  -vf "scale=720:-2" \
  -c:a aac -b:a 96k \
  output_mobile.mp4

Common Mistakes and How to Fix Them

Mistake 1: File got larger after compression

If the source video was already compressed, re-encoding can actually increase the file size due to encoding overhead. Fix: stream-copy the video with -c:v copy, or set a target bitrate below the source's original bitrate.

Mistake 2: Green or black frames in the output

This is usually a pixel format compatibility issue. Adding -pix_fmt yuv420p resolves it in most cases:

bash
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -pix_fmt yuv420p -c:a copy output.mp4

Mistake 3: Audio quality has degraded

Mistake 4: Encoding is extremely slow

Combining veryslow preset, 4K+ resolution, and H.265 can result in encode times measured in hours. GPU encoding dramatically reduces this:

bash
# NVIDIA GPU (NVENC)
ffmpeg -i input.mp4 -c:v h264_nvenc -rc vbr -cq 23 -c:a copy output.mp4

Note that GPU encoders are generally less efficient than CPU encoders at equivalent settings — you'll get larger files or lower quality at the same nominal settings. Use GPU encoding when speed is the priority. For a deep dive, see GPU-Accelerated FFmpeg: NVENC, QSV, and AMF Compared.

If you don't want your local machine tied up with veryslow or H.265 encodes, offloading to a dedicated VPS encoding server is another option. You push the file, kick off the encode remotely, and pull back the result — your workstation stays free.

Kamatera

Enterprise-grade cloud VPS with global data centers

  • 13 data centers (US, EU, Asia, Middle East)
  • Starting at $4/month for 1GB RAM — pay-as-you-go
  • 30-day free trial available

FAQ

What's the best CRF value for FFmpeg?

There's no single "best" value — it depends on your content and goals. For general-purpose compression, CRF 23 (H.264) or CRF 28 (H.265) is the default and a solid starting point. Screen recordings and animation benefit from CRF 18–20, while social media uploads can go as high as CRF 30–35 without noticeable degradation.

CRF vs bitrate: which should I use?

Use CRF when quality matters and file size is flexible. Use bitrate targeting when you have a hard file size limit (e.g., "must be under 25 MB for Discord"). CRF produces better results in most situations because it adapts the data rate to scene complexity.

Does FFmpeg compress without quality loss?

Not with re-encoding — any lossy encoding introduces some quality reduction. However, you can use -c:v copy to remux (change container format) or trim without re-encoding, which preserves the original quality bit-for-bit. For trimming specifically, see FFmpeg Lossless Cut.

How do I compress a video to a specific file size?

Calculate the target bitrate: target_size_in_bits ÷ duration_in_seconds = target_bitrate. Then use 2-pass encoding with that bitrate. For example, to hit 50 MB for a 120-second video: 50 × 8 × 1024 ÷ 120 ≈ 3,413 kbps — use -b:v 3400k with 2-pass.

H.264 vs H.265: which codec should I pick for compression?

H.265 produces roughly half the file size at the same visual quality, but encodes 2–5× slower and has lower playback compatibility. Use H.264 when you need maximum device support (web delivery, social media). Use H.265 when storage savings matter more than compatibility (archiving, personal media servers). For a full comparison including AV1, see AV1 vs H.265 vs H.264.

Is GPU encoding better than CPU encoding?

GPU encoding (NVENC, QSV, AMF) is dramatically faster — often 5–10× — but produces larger files at equivalent visual quality. It's ideal for real-time encoding, batch processing, or situations where time matters more than file size. CPU encoding with slow or veryslow presets still wins on compression efficiency.

What does -movflags +faststart do?

It moves the MP4 metadata (moov atom) from the end of the file to the beginning. This allows browsers and media players to start playback before the entire file has downloaded. Always include it for web-delivered video.

How do I check a video's current bitrate before compressing?

Use ffprobe -v quiet -show_entries format=bit_rate -of default=noprint_wrappers=1 input.mp4. This shows the overall bitrate. Knowing the source bitrate helps you choose a target that actually reduces file size rather than accidentally increasing it.

Wrapping Up

Choosing the right compression approach makes a real difference in the results.

  • For quality-first compression, use CRF — pick a value and let FFmpeg handle the rest
  • For strict file size requirements, use 2-pass — most effective for broadcast and delivery
  • Tune speed vs. efficiency with presetsslow and veryslow meaningfully improve results
  • Don't forget -movflags +faststart for web video — it's easy to miss and important

If you're new to FFmpeg and want to get up to speed with the basics first, check out FFmpeg Usage Tutorial. If you need to cut footage without any quality loss, FFmpeg Lossless Cut covers that workflow.


Don't want to type these CRF commands every time? ffmpeg-quick lets you run npx ffmpeg-quick compress input.mp4 and get sensible defaults instantly. Use --dry-run to see the actual FFmpeg command.

Related articles: