How can I reduce a video's size with ffmpeg?
How can I use ffmpeg
to reduce the size of a video by lowering the quality (as minimally as possible, naturally, because I need it to run on a mobile device that doesn’t have much available space)?
I forgot to mention that when the video can use subtitles (*.srt or *.sub), I’d like to convert them too to fit the parameters of the converted video file.
Update 2020: This answer was written in 2009. Since 2013 a video format much better than H.264 is widely available, namely H.265 (better in that it compresses more for the same quality, or gives higher quality for the same size). To use it, replace the libx264 codec with libx265, and push the compression lever further by increasing the CRF value — add, say, 4 or 6, since a reasonable range for H.265 may be 24 to 30. Note that lower CRF values correspond to higher bitrates, and hence produce higher quality videos.
ffmpeg -i input.mp4 -vcodec libx265 -crf 28 output.mp4
To see this technique applied using the older H.264 format, see this answer, quoted below for convenience:
Calculate the bitrate you need by dividing your target size (in bits) by the video length (in seconds). For example for a target size of 1 GB (one gigabyte, which is 8 gigabits) and 10 000 seconds of video (2 h 46 min 40 s), use a bitrate of 800 000 bit/s (800 kbit/s):
ffmpeg -i input.mp4 -b 800k output.mp4
Additional options that might be worth considering is setting the Constant Rate Factor, which lowers the average bit rate, but retains better quality. Vary the CRF between around 18 and 24 — the lower, the higher the bitrate.
ffmpeg -i input.mp4 -vcodec libx264 -crf 20 output.mp4
If you’re targeting a specific file size (or bitrate) rather than a specific quality, you’ll need to use a two-pass average bitrate encoding (or “2-pass ABR” for short) to fit the video within this size without reducing the quality too drastically.
In short, for the same bitrate it achieves a better quality than a single-pass bitrate encoding (as demonstrated in this answer), because the first pass allows the codec to allocate bit usage based on needs, i.e. it can spare bits on easy frames and have more available to spend on more demanding frames. By contrast, in the single-pass mode, the codec cannot know ahead of time what is coming but still has to enforce a constrained bitrate.
See this blog post (or this archived one) for an enlightening explanation, and ffmpeg’s wiki about H.264 for a more synthetic how-to.
Typical command line:
ffmpeg -i <INPUT> -c:v libx264 -preset medium -b:v <BITRATE> -pass 1 -an -f <OUTPUT_CONTAINER> -y /dev/null &&
ffmpeg -i <INPUT> -c:v libx264 -preset medium -b:v <BITRATE> -pass 2 <AUDIO_OPTIONS> <OUTPUT>.<OUTPUT_CONTAINER>
You’ll have to pick a number of things.
- Set a target bitrate, e.g.
1M
(1 Mbit/s) or500k
(500 kbit/s). See this answer for how to compute a relevant bitrate. - Nowadays (2020), you’ll probably replace the
libx264
video codec with the newerlibx265
codec, which gives higher quality for the same size. See ffmpeg’s wiki for how to adapt the 2-pass options to that codec. - Set whatever options you want for audio encoding. For example, to re-encode with the Opus codec at 128 kbit/s, write
-c:a libopus -b:a 128k
(in 2020 I would recommend Opus over any other lossy audio codec, such as AAC). But, unless you know what you are doing, your best option may be not to re-encode audio at all, i.e. write-c:a copy
. This way, you don’t even bother and your audio won’t lose in quality. Anyway, re-encoding audio often doesn’t save much (many audio streams in the wild are already bitrated at about 128kbit/s or less) and audio streams often have a negligible size when there is a video stream. See there for more guidelines on audio re-encoding. - Pick the container format of your liking. Most relevant ones (2020) are
webm
,mkv
andmp4
.
I have a recipe I originally forged for myself in order to convert the Motion JPEG videos my old camera generates (they are very large videos, since each frame is an entire JPEG image) to h264. Here’s an adaptation for other kinds of videos (courses, etc).
I’m not using ffmpeg, but mplayer and mencoder. First, We have to demux the audio with mplayer:
mplayer -vo null -ao pcm:fast:file=<audio_pcm.wav> <video>
- The
-vo null
and-ao null
parameters tells mplayer to not extract video.
In the next steps, we’ll do a 3-pass compression with mencoder. At the first pass we’ll choose a suitable Constant Quality Mode compression (crf parameter) as a start point:
mencoder <video> -ovc x264
-x264encopts ratetol=100:preset=veryslow:crf=<value>:pass=1
-nosound -o video1.h264
-
You can add slow_firstpass parameter to the -x264encopts if you are paranoid with the final quality of the video. Mencoder manual says that this option disable some parameters that “significantly improve encoding speed while having little or no impact on the quality of the final pass”. So, use it only at the last step.
-
You should try several values for crf —
try starting from 25 and goes on increasing it until you note artifacts at the resulting video (higher values compresses more). Remember subsequent encoding passes will improve the quality you have choosed for crf. -
Alternatives for the veryslow preset are slower, slow, medium etc. See mencoder manual for the complete list.
-
ratetol controls the bitrate variation — I’m not sure if I’m doing the right thing here, but I set it to the maximum value in order to let total freedom to mencoder to choose the right bitrate for each scene.
After the first pass, you’ll note that the last line gives you the average bitrate you will use at the next steps:
(...)
x264 [info]: kb/s:526.43
Change the crf parameter, recommended at the first pass, to bitrate, required at the subsequent passes:
mencoder <video> -ovc x264
-x264encopts slow_firstpass_ratetol=100:preset=veryslow:bitrate=526:pass=3
-nosound -o video2.h264
This second pass encoding will read the statistics generated at the first pass (divx2pass.log
and divx2pass.log.mbtree
) in order to optimize the compression.
-
Note you’ll use the same video input, not the generated by the first pass — first pass’ output video is only useful to check the initial quality.
-
Note also that the
pass=3
(notpass=2
) will generate a new statistics file, so you can repeat the last step as many times you want. I usually dopass=3
twice, always paying attention to the result bitrate.
Meanwhile, you can compress the audio too, using lame
or oggenc
:
oggenc -q<n> <audio_pcm.wav>
Finally, we’ll remux audio and video
mencoder -audiofile <audio>.ogg video2.h264 -oac copy -ovc copy
-of lavf -lavfopts format=mp4 -o <video>.mp4
- The
-of lavf -lavfopts format=mp4
generatesmp4
file format using the lavopts muxers.
Unless you’re looking for a specific bit rate, I’d recommend the -crf
option.
This is most commonly used for x264
encoding as described in this article.
In short: a constant rate factor (CRF) of 23 would make a DVD quality movie (~700MB – 1GB) and lower CRF values would be higher quality (larger files).
An example from the linked article:
ffmpeg -i input.mp4 -c:v libx265 -crf 28 output.mp4
You mentioned wanting to reduce the file size
to fit more videos on a mobile device, which is my use case as well.
All the answers here are for reducing the compression quality,
but nobody has mentioned reducing the video frame size.
It’s a lot quicker, up to multiple times faster depending on your source,
and the amount of the resolution decrease,
as there are fewer pixels to be encoded.
As a result, the file size can be reduced significantly.
The downside is you also lose a large amount of quality
because fewer pixels means less image detail.
But when converting for a small device this may be acceptable.
See the FFmpeg docs on scaling for more info.
To scale to half size:
ffmpeg -i input.mkv -vf "scale=trunc(iw/4)*2:trunc(ih/4)*2" -c:v libx265 -crf 28 half_the_frame_size.mkv
One-third size:
ffmpeg -i input.mkv -vf "scale=trunc(iw/6)*2:trunc(ih/6)*2" -c:v libx265 -crf 28 a_third_the_frame_size.mkv
One-quarter size:
ffmpeg -i input.mkv -vf "scale=trunc(iw/8)*2:trunc(ih/8)*2" -c:v libx265 -crf 28 a_fourth_the_frame_size.mkv
One-fifth size:
ffmpeg -i input.mkv -vf "scale=trunc(iw/10)*2:trunc(ih/10)*2" -c:v libx265 -crf 28 a_fifth_the_frame_size.mkv
In these examples, the size is divided by twice the value and multiplied by two to ensure the pixel size is a multiple of two, which is required for some codecs, including H265.
Be aware that changing the resolution always requires re-encoding,
so all the ins and outs of the other answers apply here as well.
Note that it seems that ffmpeg
already performs some optimization when ran without options, so before trying to use settings you don’t understand or deciding to explicitly lose information, give a try to a default conversion :
ffmpeg -i input.mp4 output.mp4
In my case it reduced the bitrate of both the video and audio (you can check and compare the input and output file by running ffprobe
on them), transforming a 700 Mb video into a 60 Mb one of seemingly similar quality.
I tested most of the other proposed answers to this question. The test data conclusions are below. These are the proposed answers that I tested:
(BR) Modify the bitrate, using:
ffmpeg -i $infile -b $bitrate $newoutfile
(CR) Vary the Constant Rate Factor, using:
ffmpeg -i $infile -vcodec libx264 -crf 23 $outfile
(SZ) Change the video screen-size (for example to half its pixel size), using:
ffmpeg -i $infile -vf "scale=iw/2:ih/2" $outfile
(BL) Change the H.264 profile to “baseline”, using:
ffmpeg -i $infile -profile:v baseline $outfile
(DF) Use the default ffmpeg processing, using:
ffmpeg -i $infile $outfile
DATA
- “size” – percent pixel size of the converted video in relation to the original.
- “bitrate” – bitrates of original and converted videos.
- “definition” – pixel size of videos.
- “convert” – time to convert the video in seconds.
I calculated the target bitrate for (BL)using the proposed method.
=== File A – How Node Is Helping To Propel Angular-Fnbixa7Ts6M.mkv ===
original BR CR SZ BL DF
-------- --- -- -- -- --
size 64152 kb 214% 76% 40% 83% 76%
bitrate 411 kb/s 883 313 165 342 313
definition 1920x1080 1920x1080 1920x1080 960x540 1920x1080 1920x1080
convert -- 648 509 225 427 510
=== File B – Using GraphQL with Angular _ By – Lee Costello-OGyFxqt5INw.mkv ===
original BR CR SZ BL DF
-------- --- -- -- -- --
size 410301 kb 33% 109% 28% 143% 109%
bitrate 2687 kb/s 880 2920 764 3843 2920
definition 3840x2160 3840x2160 3840x2160 1920x1080 3840x2160 3840x2160
convert -- 2307 3188 1116 2646 3278
CONCLUSIONS
-
The (SZ) method is definitely the quickest method. It was 2X to 4X faster. This can be very much an issue on high-def videos, since all of the other methods took longer to convert than the actual length of the video! For example, The (CR) method took 53 minutes to convert the 21 minute video.
-
The (SZ) method is definitely the best method if the definition of the video is larger than the definition of the screen that will be displaying it. For example, if your phone can only display a 1080p picture, sending it a 3840×2160 video is just wasteful. It would be best to half its size to 1080p.
-
Some of the proposed answers actually INCREASED the size of some videos. For example, the (BR) method more than doubled the size of the 1080p sample. It did however make the 2160p size one-third. For the high-def sample, the (CR), (BL) and (DF) methods all INCREASED the size of the video.
Correct (or best) Answer
It is always best to first lower the resolution to the maximum supported by your target display.
If you want to reduce file size further, it will depend on personal choices. You can either reduce information content or increase compression.
-
You can lower the resolution more if that is not something that concerns you.
-
If the video doesn’t include fast action scenes, you may want to lower the frame rate.
-
If you have a powerful processor and space is the only issue, you can increase the compression rate.
-
Bit rate is a combination of multiple factors. So just telling ffmpeg to lower the bit rate may not give you the results you want.
-
Another way of lower information content is to lower the color depth. How to do this was not yet discussed.
I compressed a 40-minute HD video presentation from 505MB to 183MB
That’s like going from 100MB → 36MB.
Original video was HD and output was almost zero noticeable difference.
It’s a video file “I’d like to keep around, but HD is overkill.”
Here’s the command I used with reasons:
ffmpeg -n -loglevel error -i inputfile.mp4 -vcodec libx264 -crf 28 -preset faster -tune film outputfilename.mp4
-n
: avoid overwriting output files (safer for testing then batching)-loglevel error
: show errors and hide the rows and rows of progress-i inputfile.mp4
: input file name-vcodec libx264
: swiped from the top answer above-crf 28
: single-pass compression with minor noticeable difference (“0 = lossless, 23 = default, 51 = worst; subjectively sane range is 17–28“) ref docs-preset faster
: looks 2x faster than default encoding time of ‘medium’ ref docs-tune film
: specify input is an HQ video (other options include ‘cartoon’, ‘stillimage’..) ref docsoutputfilename.mp4
: output file name
For a directory of video files:
for i in *.{avi,flv,m4v,mov,wmv,mp4,MP4,TS,mkv}; do ffmpeg -n -loglevel error -i "$i" -vcodec libx264 -crf 28 -preset faster -tune film "cc${i}"; done
Issues:
- a cleaner way of collecting “all video files” without having all the extensions in the command
- a cleaner way to output the filename without “cc” prefix, AND being able to confirm video before deleting
.webm
files don’t work with the command. Had to swap"cc${i}"
→"${i%.*}.mp4"
I wrote a bash script for reducing the size of the video and trying automatically different crf values.
Basically you will
- choose a range of crf values
- run the script
- check the size of the generated videos and pick the one you want
This is really handy when you have a size limit you want to achieve and you don’t know what is the crf value which will allow you to do it.
I hope this will help someone. I shared with my colleagues and everyone found it helpful.
#!/bin/bash
# bigger values of crf will lead to a bigger compression (smaller video size)
#for i in 1 2 3 4 5
for i in {25..28}
do
# you can remove the option -y if you want to be asked if to overwrite a file with the same name (leave the -y only if you understand what you are doing, otherwise you might rewrite an important file)
ffmpeg -y -i NAMEOFTHEVIDEOTOCOMPRESS.mp4 -c:v libx264 -crf $i -preset veryfast -profile:v baseline -level 3.0 -strict -2 out_$i.mp4
printf "n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Done compression at crf=$i nn"
done
Here is a 2 pass example.
Pretty hard coded but it really puts on the squeeze
#!/bin/bash
ffmpeg -y -i "$1" -c:v libvpx-vp9 -pass 1 -deadline best -crf 30 -b:v 664k -c:a libopus -f webm /dev/null && ffmpeg -i "$1" -c:v libvpx-vp9 -pass 2 -crf 30 -b:v 664k -c:a libopus -strict -2 "$2"
An option not mentioned yet is -r 30
to limit framerate to 30 fps, or whatever value you like.
Needed on macOS: to preserve compatibility encoding H.265/HEVC for QuickTime
ffmpeg -i i.mp4 -c:v libx265 -preset fast -crf 28 -tag:v hvc1 -c:a eac3 -b:a 224k o.mp4
I use the mpdecimate
filter to reduce the size of my desktop captured videos because there are a lot of duplicated frames to ignore.
The command I use is:
ffmpeg -i original_file.mp4 -vf mpdecimate -vsync vfr -acodec copy mpdecimated.mp4