RTFM
There is semi-official documentation for exactly what we are trying to do: https://trac.ffmpeg.org/wiki#Encoding
Other's have linked to parts of the FFmpeg wiki but not this particular portion which is what you actually need.
There is also the official documentation, but it's less beginner-friendly.
From there, we're in business. The codec matters a lot, so we have to select the right one for us, the right one always depends on the person and their use case.
H265/HEVC and AV1 are the 'best' codecs, they have the best quality to size ratios. If your only goal is to make the file smaller they are usually the way to go, but support for them is not that great. I believe AV1 provides better compression (but worse overall quality) however it takes orders of magnitude more time to encode with, at least when encoding with the CPU.
H264 and VP9 are the second-best ones, they are the most commonly supported. If you want to upload your videos, if you want them to be playable in apps like discord or signal, you will likely want to use one of these two. The files will be bigger relative to their quality, but you'll be able to play them more or less everywhere.
No other codec than these 4 are really worth considering for our purposes at the time of writing.
Audio codecs also to a lesser degree matter for file size (the longer the file the more it starts to matter), setting the audio codec to libopus is a great option if you want to play with this, it's also necessary if you wish to encode to WebM.
You need to pick a codec based on your needs and follow the encoding guide for it.
Tip: H264 to an .mp4 container is the best configuration for maximum compatibility with everything, it's the closest you'll get to a file that can be played anywhere.
Obtain codec information
You can use
ffmpeg -v 0 -encoders
To list all possible audio and video codec options/all codec variants.
There is a way for ffmpeg to tell you the major options available for any codec, it's this command:
ffmpeg -v 0 -h encoder=codec
where you replace codec with the codec you want to see options for for instance ffmpeg -v 0 -h encoder=av1 will list most options for all av1 encoders.
If you run:
ffmpeg -v 0 -h encoder='av1' | head -1
It will give you this output:
Encoder libaom-av1 [libaom AV1]:
Which tells us the default software encoder used when you use the -c:v av1 argument is libaom-av1.
Now knowing what encoder is actually being used for software encoding, we can do this:
ffmpeg -v 0 -h encoder='libaom-av1'
And this will tell us all available options for the default av1 encoder.
If we want the options for some specific other variant, like say hevc_nvenc, we just type in the name of that encoder instead:
ffmpeg -v 0 -h encoder='hevc_nvenc'
Example Commands
Here are a few generic commands that should work well for most people.
I have excluded AV1 examples because it just takes way too long to process on a CPU and I didn't have the patience for it. I left out hardware encoding VP9 and AV1 because neither of my GPUs support those, so I can't test it.
VP9 is normally supposed to encode in 2 passes but I only do one pass using -crf in conjunction with bv:0 to make it turn out better when done that way. This means there is room for improvement there if you want to use that codec.
All of these example commands are aiming for very high quality, there should be as little perceivable quality loss with these presets as reasonably possible. This means the files will be bigger, to make them smaller you need to adjust the quality value, which is always either -crf or -qp in those commands. A bigger number makes the file smaller at the cost of quality.
Example file 1: 82.1MB, H264/AAC 720p@60fps animation 1.5 minutes.
Example file 2: 96.6MB, H264/AAC 1080p@30fps recording 1.5 minutes.
# Cpu Encoding:
High Quality Hevc
Example file 1: 26.7MB in 39 seconds.
Example file 2: 47.1MB in 35 seconds.
ffmpeg -threads $(awk '/cores/{print $4;exit}' /proc/cpuinfo) -i INPUT_FILE -movflags +faststart -c:a libopus -c:v hevc -crf 17 -preset:v fast OUTPUT_FILE
High Quality Hevc 2D Animation (according to all docs, hevc doesn't support this, but as you can see from my test, it does.)
Example file 1: 21.3MB in 36 seconds.
ffmpeg -threads $(awk '/cores/{print $4;exit}' /proc/cpuinfo) -i INPUT_FILE -movflags +faststart -c:a libopus -c:v hevc -crf 17 -preset:v fast -tune:v animation OUTPUT_FILE
High Quality 264
Example file 1: 31.9MB in 14 seconds.
Example file 2: 88.9MB in 25s
ffmpeg -threads $(awk '/cores/{print $4;exit}' /proc/cpuinfo) -i INPUT_FILE -movflags +faststart -c:a copy -c:v hevc -crf 17 -preset:v fast OUTPUT_FILE
High Quality 264 2D Animation
Example file 1: 31MB in 17 seconds.
ffmpeg -threads $(awk '/cores/{print $4;exit}' /proc/cpuinfo) -i INPUT_FILE -movflags +faststart -c:a copy -c:v h264 -crf 17 -preset:v fast -tune:v animation OUTPUT_FILE
High Quality VP9
Example file 1 (-cpu-used 4): 42.7MB in 2 minutes 12 seconds.
Example file 1 (-cpu-used 1): 39.4MB in 3 minutes 42 seconds.
Example file 2 (-cpu-used 4): 58.7MB in 2 minutes 44 seconds.
Example file 2 (-cpu-used 1): 53.2MB in 5 minutes 21 seconds.
ffmpeg -i INPUT_FILE -cpu-used 1 -movflags +faststart -c:a libopus -c:s webvtt -c:v vp9 -crf 17 -preset 5 -aq-mode 2 -b:v 0 OUTPUT_FILE.webm
Hardware Encoding:
High Quality HEVC Nvenc
Example file 1: 32.3MB in 19s
Example file 2: 62.5MB in 21s
ffmpeg -i INPUT_FILE -movflags +faststart -c:a libopus -c:v hevc_nvenc -spatial-aq 1 -multipass fullres -tune:v hq -rc:v constqp -qp 19 -preset:v p7 OUTPUT_FILE
High Quality H264 Nvenc
Example file 1: 39.4MB in 20s
Example file 2: 91.6MB in 19s
ffmpeg -i INPUT_FILE -movflags +faststart -c:a libopus -c:v hevc_nvenc -spatial-aq 1 -multipass fullres -tune:v hq -rc:v constqp -qp 19 -preset:v p7 OUTPUT_FILE
High Quality HEVC VAAPI
Example file 1: 41MB in 9s
Example file 2: 59.6 in 8s
ffmpeg -i INPUT_FILE -movflags +faststart -vf format=nv12,hwupload -pix_fmt vaapi -c:a libopus -c:v hevc_vaapi -vaapi_device /dev/dri/renderD128 -rc_mode CQP -qp 20 OUTPUT_FILE
High Quality H264 VAAPI
Example file 1: 39.4MB in 15s
Example file 2: 53.7 in 14s
ffmpeg -i INPUT_FILE -movflags +faststart -vf format=nv12,hwupload -pix_fmt vaapi -c:a libopus -c:v h264_vaapi -vaapi_device /dev/dri/renderD128 -rc_mode CQP -qp 20 OUTPUT_FILE
Setting the audio codec to libopus saves me about 300-500KB on filesize, but it is also lowering the bitrate and quality of the audio.
My example files are already very poorly optimized, so for you, you might not see as much reduction when running these commands, in fact it might get bigger instead worst case scenario (this would mean your file is already quite optimized).
For -crf ideally stay within a range of 18-28 with 18-20 still being very high quality and 21-23 still very decent.
For vaapi I used one of the worst available methods (CQP), it's better to use QVBR or ICQ, which you can use instead of CQP by replacing -rc_mode CQP -qp 20 with -rc_mode ICQ -qp 20 where 20 is the quality setting (lower number = higher quality). The reason I didn't include those methods in the example is that QVBR is a lot more complicated than the other methods in use, and ICQ isn't supported by my drivers.
If you're confused on how to use vaapi, here is a short guide to get vaapi working if you have problems with it.
Option Cheatsheet
It's time to learn how to fish. Every option matters. Every option here is something that could potentially reduce filesize for you.
-c:v Allows you to select the video codec, run ffmpeg -v 0 -encoders | grep "^ V" to find possible options.
-c:a Allows you to select the audio codec, run ffmpeg -v 0 -encoders | grep "^ A" to find possible options. Common options include aac, libopus, ac3, libvorbis, flac and libmp3lame. You can also set it to copy to copy the audio directly from the source (no re-encoding it).
-qp Is a setting that directly controls frame quality, and it sticks to that setting from start to finish (every frame will have the same quality). It's only recommended to use this option for codecs that do not support -crf. If -crf is unavailable you can sometimes use this in conjunction with -rc:v (Rate Control) setting to get decent results, ICQ and QVBR tend to give the best results if available, with them -qp may even be better than -cfr.
-crf does the same thing as -qp except that it adjusts the frame quality based on the frame content, this leads to better compression at no perceivable loss of quality. If you want very high quality results, you set it to 17-18, if you want good quality you set it to 21-23 if you don't care all that much about quality you can go up to 28. This is usually recommended over -qp if it is available (it's not always available)
-preset is an option that controls how meticulously the quality to size ratio will be handled (this does not mean slower presets always produce smaller files, they can be bigger, but their quality will be higher relative to size). So you will have a higher quality and better compression ratios with the slower presets, whereas the faster presets will encode faster. for CPU H264 and HEVC the options are 1-7 or: veryslow,slower,slow,medium,fast,faster,veryfast. Some codecs and some variants either do not support this option or have different available options.
-tune is an option for H264,HEVC (and av1 but less useful for that) that can dramatically reduce filesize at no perceivable quality loss when correctly configured, however each codec supports a different set of options for this setting. You can read the ffmpeg docs for encoding with each codec on what these options are, or you can use x264 --fullhelp or x265 --fullhelp, Some tune options also have additional settings specifically to tweak them further.
-b:v Allows you to manually define the video bitrate, it's an older method compared to qp and crf and usually inferior too. For VP9 this setting controls average bitrate instead, which is slightly better. To use -crf this way with VP9 -b:v 0 must be set, otherwise it works a little different.
-b:a Allows you to manually define the audio bitrate, it can be useful to reduce filesize, a value of 128k is a good compromise
-r Sets the framerate (fps), if you have a 60fps video and you use -r 30 it will shave down the video size by half, the standard framerate for film and tv is 24, this is the lowest I can recommend setting this unless you're dealing with animation, animation is usually done at far lower framerates, such as anime for instance tends to be done at 8-12fps, for animation you can experiment with such lower values.
-vf/-lavfi/-filter_complex are 3 separate commands for roughly the same thing, it's to allow you to build a filter graph, this is a tool for pre and post-processing. The format is -vf "filter,filter2=stuff,filter3" although the quotation marks are not typically necessary (only if there are spaces or weird symbols in an argument do you need them).
-vf scale=N:N allows you to rescale the video, for example -vf scale=-1:720 rescales video to 720p. You can also scale a video down based on it's size, it was explained in another answer.
-vf mpdecimate removes duplicate frames, it can in some cases lead to a dramatic filesize reduction with no perceivable visual quality loss, for instance if I use this while converting a clip to a GIF with ffmpeg, it produces a smoother GIF that is significantly smaller than otherwise, an actual quality upgrade. In most cases though it will lead to a mild perceivable drop in quality. For more advanced application you can read a guide on mpdecimate.
-max_frame_size sets the maximum size per frame in bytes, this is a rather niche setting I don't believe very many people use it but it can be used to set a hard limit for the filesize (e.g. you count the frames in the file, divide the desired maximum size in bytes between those frames, and then set the result as -max_frame_size) I haven't personally tested this option yet, there might be untapped potential here.
-aq-mode 2 improves quality and compression rate at the cost of time for AV1 and VP9.
-spatial-aq 1 improves quality by adjusting -qp based on spatial characteristics, it also tends to mildly improve the overall compression for AV1 and HEVC_NVENC and H264_NVENC.
-multipass fullres increases quality at the cost of time for AV1 and HEVC_NVENC and H264_NVENC.
Some other important options:
-threads controls the number of threads used for CPU encoding with H264 and HEVC. I recommend setting this value to the number of physical cores you have or less. awk '/cores/{print $4;exit}' /proc/cpuinfo tells you the number of physical cores you have. Increasing the number of threads increases the speed, but once you pass a certain number of threads the speed starts decreasing instead due to the overhead of managing the extra threads. The number of physical cores is usually the cutoff point.
-cpu-used is what VP9 and AV1 use instead of -threads, however increasing the number reduces quality and only supports a maximum value of 8, provides diminishing returns beyond 4 and reduces compression too. Setting this to 8 was slower than 4, even on a machine with 16 physical cores.
-profile:v is a setting for H264 and HEVC that controls the profile used by the codec, by default this is automatically configured but you can use it if you want to encode in a specific profile like high10 or baseline. Setting specific profiles can affect filesize but it's somewhat situational.
-pfmt is a universal setting for the pixel format, it functions somewhat similar to profile. And is usually best left unconfigured or set to + for auto. Like with profiles it's situational.
Helpful options (these don't affect filesize):
-movflags +faststart this places the moov atom at the start of the file instead of the end which is great for compatibility, especially for web-based video players.
-c:s subtitle codec. .webm only supports webvtt, .mp4 only supports mov_text, this is a necessary setting to carry softsubs over to those formats.
-vf "subtitles=INPUT_FILE:force_style=Fontsize=24" convert softsubs to hardsubs, this way you can make the subtitles show up in your .mp4 files when uploading them online.
ffmpegman page shows a-fsoption to limit the output size, does something likeffmpeg -i in.avi -fs 100M out.aviwork? – Kevin Jan 10 '12 at 23:41.aviis not the main issue..aviis just a container. The main issue is which codecs you use.. Many (most?).avivids use older style codecs (eg XviD) which are fine, but are larger for the same quality when compared to the later generation of codecs .. You can typically get a tight encoding by using theH.264video compression standard (eg. codecx264) andaaccompression for audio.. The container and codecs you use is up to you and your phone... The.mp4container is well accepted.. (but can your phone handle it: see this link – Peter.O Jan 11 '12 at 01:16480petc. – Sridhar Sarnobat Aug 29 '18 at 20:10-fsoption seems to crop the video for me, rather than keeping it the same length, but reducing quality. – Garrett Nov 26 '21 at 22:18