In this article, I will help you compress any video or image using FFmpeg with/without losing quality. All of the commands given below should work with all encoders. I will also be talking about an efficient and modern encoder x265. At the same time, I will also help you in changing your image and video resolution to some lower value.
Note: I have tested all the commands given below on Linux. For Mac OS, which is based on UNIX, they should work fine. And for Windows, you can use Windows Subsystem for Linux.
Table of Contents
- 1. Installation of FFmpeg
- 2. How to Compress a Video Using FFmpeg Without Losing Quality?
- 3. How to Change Video Resolution Using FFmpeg?
- 4. Compress/Rescale Without Losing Any Audio or Subtitle Track in an Mkv Video Using FFmpeg
- 5. Compress/Rescale All Videos in a Directory Using FFmpeg
- 6. Compress/Rescale Images using FFmpeg
- 7. Way Ahead
1. Installation of FFmpeg
FFmpeg is one of the most widely used applications. Therefore, by default, it comes installed with all major distributions/packages.
If you are not sure whether your computer has FFmpeg or not, install it using one of the following commands:
Debian based distributions ( Ubuntu, Raspbian, and Kali Linux)
~$ sudo apt-get install ffmpeg
Alpine Linux
~$ sudo apk add ffmpeg
Arch Linux based distributions (Manjaro)
~$ sudo pacman -S ffmpeg
Fedora and CentOS
~$ sudo dnf install ffmpeg
Now that you have installed FFmpeg, you can start compressing your videos.
2. How to Compress a Video Using FFmpeg Without Losing Quality?
Here, first I will talk about compression using the default encoder (x264) and then the efficient one – x265.
2.1. Compression With FFmpeg’s Default Settings
First of all, the FFmpeg’s default settings are already highly optimized. I was able to reduce the size of one of my files by 80% without any quality loss! For you, the result might be a little different and hence you should try it first.
Syntax:
~$ ffmpeg -i INPUT_VIDEO OUTPUT_VIDEO.EXT
In the above command, replace INPUT_VIDEO, OUTPUT_VIDEO, and EXT with your input video, output filename, and its extension (such as mp4, webm, and mkv) respectively.
For example:
~$ ffmpeg -i input_video.mp4 output_video.mp4
Note: this FFmpeg command can also be used to convert from one video format (say mkv) to another (say mp4) by using the appropriate EXT value.
2.2. Compression Using libx265 Encoder in FFmpeg
The name libx265 is a portmanteau for the ‘library x265’. Netflix conducted a survey in Aug 2016 and found that x265 outperforms many of its competitors both in terms of quality and file size.
To use this, use the following command:
~$ ffmpeg -i INPUT_VIDEO -codec:v libx265 OUTPUT_VIDEO.EXT
Here, v
in the -codec:v
stands for video. Similarly, there is another flag called -codec:a
for audio encoders. Changing the audio codec does not change the file size much so I will be skipping it.
In my case, I found that the above command reduced the size of one of my files by 90%! For your case, please comment below. It will help other readers.
To control the video quality, you can use x265’s CRF (Constant Rate Factor). And for this, use the -crf
flag:
~$ ffmpeg -i INPUT_VIDEO -codec:v libx265 -crf CRF OUTPUT_VIDEO.EXT
The CRF can take any value from 0 to 51. 0 means lossless compression and 51 means maximum losses. If you increase the CRF, the file size will reduce at the cost of visual quality and vice versa. If you don’t supply the -crf CRF
, FFmpeg assumes the default value of 28.
All of the above commands reduce video file size while keeping the visual quality almost the same. But nowadays videos are available in very high resolution (like 4K) and no matter how much compression you apply you cannot reduce the file size enough to fit these videos in your SSDs and smartphones. In that case, you might consider scaling down the resolution and that is explained below.
3. How to Change Video Resolution Using FFmpeg?
Before you start resizing/rescaling your video, you need to get its resolution.
3.1. Get Video Resolution Using FFmpeg
For this purpose, use the ffprobe command included in the FFmpeg package:
~$ ffprobe INPUT_VIDEO
Sample Output (I have boldened and underlined the resolution and removed unimportant information):
...
...
...
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '/home/ajay/Videos/Video.mp4':
Metadata:
major_brand : isom
minor_version : 1
compatible_brands: isomavc1
creation_time : 2014-03-30T20:52:44.000000Z
Duration: 01:42:00.94, start: 0.000000, bitrate: 1102 kb/s
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709/bt709/unknown), 1280x544 [SAR 1:1 DAR 40:17], 1004 kb/s, 23.98 fps, 23.98 tbr, 24k tbn, 47.95 tbc (default)
Metadata:
creation_time : 2014-03-30T20:52:44.000000Z
handler_name : video.264#trackID=1:fps=23.976 - Imported with GPAC 0.5.0-rev
vendor_id : [0][0][0][0]
Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 93 kb/s (default)
Metadata:
creation_time : 2014-03-30T20:52:48.000000Z
handler_name : GPAC ISO Audio Handler
vendor_id : [0][0][0][0]
3.2. Scale Video to Given Width and Height Using FFmpeg
To change video resolution to the WIDTH x HEIGHT, you need to use -filter:v scale=WIDTH:HEIGHT
as shown below.
~$ ffmpeg -i INPUT -filter:v scale=WIDTH:HEIGHT OUTPUT.EXT
So, for example, if you want to scale down your video to 1280p x 720p, use
~$ ffmpeg -i input.mp4 -filter:v scale=1280:720 output.mp4
Note 1: both WIDTH and HEIGHT should be divisible by n where n is 1 or 2 or 3… That is, if you get any error, just try to change WIDTH and HEIGHT a little bit so that both are divisible by 2. And if you again get errors, try to make them divisible by 3 or so.
Note 2: If there is little imperfection in the WIDTH and HEIGHT, you might end up with a video that is too much stretched horizontally or vertically as in the following image. To prevent this behavior, you need to tell the FFmpeg to preserve the ratio of width and height (called Aspect Ratio) as given in the next heading.
3.3. Scale Video to Given Width While Preserving the Aspect Ratio Using FFmpeg
Here, you need to set only the HEIGHT. The WIDTH will be calculated by FFmpeg based on the input video’s aspect ratio. For this purpose, set the WIDTH to some negative value.
So for example, to change video resolution to 720p, use the following command:
~$ ffmpeg -i INPUT_VIDEO -filter:v scale=-2:360 OUTPUT_VIDEO.EXT
As you can see, I am using -2 instead of -1 because the FFmpeg’s default encoder (libx264 on 5 Jan 2021 on Arch Linux) accepts only those WIDTH and HEIGHT which are divisible by 2. This divisibility by 2 is also a requirement in the libx265 mentioned above.
Similarly, you can also assign HEIGHT some negative value while setting the WIDTH your desired value.
All of the above-mentioned commands in this article only transfer one audio track from the input file to the output file. But what if you want to transfer all tracks?
4. Compress/Rescale Without Losing Any Audio or Subtitle Track in an Mkv Video Using FFmpeg
Before proceeding, you should know a little bit about mkv files. Unlike an mp4 file, an mkv file supports an unlimited number of subtitles and audio tracks. And now you can use some shortcut keys in your video player to switch between these tracks. This is especially helpful for multilingual support.
To check if your video has multiple audio and subtitle tracks, use the ffprobe command:
~$ ffprobe INPUT
Sample Output (I have boldened and underlined important information and removed unimportant information):
[ajay@lenovo ~]$ ffprobe sample.mkv
...
...
...
Input #0, matroska,webm, from '/home/ajay/sample.mkv':
Metadata:
encoder : libebml v1.3.3 + libmatroska v1.4.4
creation_time : 2018-05-22T02:41:21.000000Z
Duration: 00:23:55.11, start: 0.000000, bitrate: 731 kb/s
Stream #0:0: Video: hevc (Main), yuv420p(tv, bt709), 1280x720, SAR 1:1 DAR 16:9, 23.98 fps, 23.98 tbr, 1k tbn (default)
Metadata:
BPS : 543787
BPS-eng : 543787
DURATION : 00:23:55.017000000
DURATION-eng : 00:23:55.017000000
NUMBER_OF_FRAMES: 34406
NUMBER_OF_FRAMES-eng: 34406
NUMBER_OF_BYTES : 97543101
NUMBER_OF_BYTES-eng: 97543101
_STATISTICS_WRITING_APP: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_APP-eng: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_DATE_UTC: 2018-05-22 02:41:21
_STATISTICS_WRITING_DATE_UTC-eng: 2018-05-22 02:41:21
_STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
_STATISTICS_TAGS-eng: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
Stream #0:1(eng): Audio: aac (LC), 44100 Hz, stereo, fltp (default)
Metadata:
title : English Stereo
BPS : 88552
BPS-eng : 88552
DURATION : 00:23:50.000000000
DURATION-eng : 00:23:50.000000000
NUMBER_OF_FRAMES: 61585
NUMBER_OF_FRAMES-eng: 61585
NUMBER_OF_BYTES : 15828794
NUMBER_OF_BYTES-eng: 15828794
_STATISTICS_WRITING_APP: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_APP-eng: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_DATE_UTC: 2018-05-22 02:41:21
_STATISTICS_WRITING_DATE_UTC-eng: 2018-05-22 02:41:21
_STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
_STATISTICS_TAGS-eng: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
Stream #0:2(jpn): Audio: aac (LC), 44100 Hz, stereo, fltp
Metadata:
title : Japanese Stereo
BPS : 94767
BPS-eng : 94767
DURATION : 00:23:55.109000000
DURATION-eng : 00:23:55.109000000
NUMBER_OF_FRAMES: 61805
NUMBER_OF_FRAMES-eng: 61805
NUMBER_OF_BYTES : 17000200
NUMBER_OF_BYTES-eng: 17000200
_STATISTICS_WRITING_APP: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_APP-eng: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_DATE_UTC: 2018-05-22 02:41:21
_STATISTICS_WRITING_DATE_UTC-eng: 2018-05-22 02:41:21
_STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
_STATISTICS_TAGS-eng: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
Stream #0:3(eng): Subtitle: ass
Metadata:
title : English
BPS : 119
BPS-eng : 119
DURATION : 00:23:41.950000000
DURATION-eng : 00:23:41.950000000
NUMBER_OF_FRAMES: 335
NUMBER_OF_FRAMES-eng: 335
NUMBER_OF_BYTES : 21251
NUMBER_OF_BYTES-eng: 21251
_STATISTICS_WRITING_APP: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_APP-eng: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_DATE_UTC: 2018-05-22 02:41:21
_STATISTICS_WRITING_DATE_UTC-eng: 2018-05-22 02:41:21
_STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
_STATISTICS_TAGS-eng: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
Stream #0:4(ara): Subtitle: subrip
Metadata:
title : Arabic
BPS : 116
BPS-eng : 116
DURATION : 00:23:13.780000000
DURATION-eng : 00:23:13.780000000
NUMBER_OF_FRAMES: 348
NUMBER_OF_FRAMES-eng: 348
NUMBER_OF_BYTES : 20250
NUMBER_OF_BYTES-eng: 20250
_STATISTICS_WRITING_APP: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_APP-eng: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_DATE_UTC: 2018-05-22 02:41:21
_STATISTICS_WRITING_DATE_UTC-eng: 2018-05-22 02:41:21
_STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
_STATISTICS_TAGS-eng: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
In the above output, my video has 1 video, 2 audio, and 2 subtitle tracks.
For transferring all such tracks, you need to use -map
flag:
ffmpeg -i INPUT -map 0:a'?' -map 0:s'?' -map 0:v'?' OUTPUT.EXT
In the above command,
- ‘a’ in
-map 0:a:'?'
stands for audio; similarly ‘v’ for video and ‘s’ for subtitles. - Quoted question marks (
'?'
) next to-map 0:s
makes the subtitle mapping optional i.e. if the subtitle track does not exist in the INPUT, ignore this mapping. This is true for other quoted question marks as well.
Sample Output (I have removed unimportant information and boldened and underlined important information):
[ajay@lenovo ~]$ ffmpeg -i sample.mkv -map 0:a'?' -map 0:s'?' -map 0:v'?' output.mkv
...
...
...
Input #0, matroska,webm, from 'sample.mkv':
Metadata:
encoder : libebml v1.3.3 + libmatroska v1.4.4
creation_time : 2018-05-22T02:41:21.000000Z
Duration: 00:23:55.11, start: 0.000000, bitrate: 731 kb/s
Stream #0:0: Video: hevc (Main), yuv420p(tv, bt709), 1280x720, SAR 1:1 DAR 16:9, 23.98 fps, 23.98 tbr, 1k tbn (default)
Metadata:
BPS : 543787
BPS-eng : 543787
DURATION : 00:23:55.017000000
DURATION-eng : 00:23:55.017000000
NUMBER_OF_FRAMES: 34406
NUMBER_OF_FRAMES-eng: 34406
NUMBER_OF_BYTES : 97543101
NUMBER_OF_BYTES-eng: 97543101
_STATISTICS_WRITING_APP: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_APP-eng: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_DATE_UTC: 2018-05-22 02:41:21
_STATISTICS_WRITING_DATE_UTC-eng: 2018-05-22 02:41:21
_STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
_STATISTICS_TAGS-eng: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
Stream #0:1(eng): Audio: aac (LC), 44100 Hz, stereo, fltp (default)
Metadata:
title : English Stereo
BPS : 88552
BPS-eng : 88552
DURATION : 00:23:50.000000000
DURATION-eng : 00:23:50.000000000
NUMBER_OF_FRAMES: 61585
NUMBER_OF_FRAMES-eng: 61585
NUMBER_OF_BYTES : 15828794
NUMBER_OF_BYTES-eng: 15828794
_STATISTICS_WRITING_APP: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_APP-eng: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_DATE_UTC: 2018-05-22 02:41:21
_STATISTICS_WRITING_DATE_UTC-eng: 2018-05-22 02:41:21
_STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
_STATISTICS_TAGS-eng: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
Stream #0:2(jpn): Audio: aac (LC), 44100 Hz, stereo, fltp
Metadata:
title : Japanese Stereo
BPS : 94767
BPS-eng : 94767
DURATION : 00:23:55.109000000
DURATION-eng : 00:23:55.109000000
NUMBER_OF_FRAMES: 61805
NUMBER_OF_FRAMES-eng: 61805
NUMBER_OF_BYTES : 17000200
NUMBER_OF_BYTES-eng: 17000200
_STATISTICS_WRITING_APP: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_APP-eng: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_DATE_UTC: 2018-05-22 02:41:21
_STATISTICS_WRITING_DATE_UTC-eng: 2018-05-22 02:41:21
_STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
_STATISTICS_TAGS-eng: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
Stream #0:3(eng): Subtitle: ass
Metadata:
title : English
BPS : 119
BPS-eng : 119
DURATION : 00:23:41.950000000
DURATION-eng : 00:23:41.950000000
NUMBER_OF_FRAMES: 335
NUMBER_OF_FRAMES-eng: 335
NUMBER_OF_BYTES : 21251
NUMBER_OF_BYTES-eng: 21251
_STATISTICS_WRITING_APP: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_APP-eng: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_DATE_UTC: 2018-05-22 02:41:21
_STATISTICS_WRITING_DATE_UTC-eng: 2018-05-22 02:41:21
_STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
_STATISTICS_TAGS-eng: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
Stream #0:4(ara): Subtitle: subrip
Metadata:
title : Arabic
BPS : 116
BPS-eng : 116
DURATION : 00:23:13.780000000
DURATION-eng : 00:23:13.780000000
NUMBER_OF_FRAMES: 348
NUMBER_OF_FRAMES-eng: 348
NUMBER_OF_BYTES : 20250
NUMBER_OF_BYTES-eng: 20250
_STATISTICS_WRITING_APP: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_APP-eng: mkvmerge v9.2.0 ('Photograph') 64bit
_STATISTICS_WRITING_DATE_UTC: 2018-05-22 02:41:21
_STATISTICS_WRITING_DATE_UTC-eng: 2018-05-22 02:41:21
_STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
_STATISTICS_TAGS-eng: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
Stream mapping:
Stream #0:1 -> #0:0 (aac (native) -> vorbis (libvorbis))
Stream #0:2 -> #0:1 (aac (native) -> vorbis (libvorbis))
Stream #0:3 -> #0:2 (ass (ssa) -> ass (ssa))
Stream #0:4 -> #0:3 (subrip (srt) -> ass (ssa))
Stream #0:0 -> #0:4 (hevc (native) -> h264 (libx264))
Press [q] to stop, [?] for help
...
...
...
All of the above-mentioned commands are to help you compress/rescale a single video using FFmpeg. But what if you want to apply these commands on multiple files together i.e. batch video compression/rescaling?
5. Compress/Rescale All Videos in a Directory Using FFmpeg
For this, in Linux, you can use the find
command with its -exec
flag or for
loops. Personally, I find the find
command more useful than for
loops because it takes care of special characters such as single quotes and spaces in the input videos’ file names.
For example, to compress all mp4 videos in a ~/test-dir
using the FFmpeg’s default settings:
~$ find ~/test-dir -name '*.mp4' -exec ffmpeg -i {} {}_compressed.mp4 \;
In the above command:
- The output video files will be created in their respective directories. For example, for an input video file
~/test-dir/dir2/file.mp4
, output file~/test-dir/dir2/file.mp4_compressed.mp4
will be created. - For each found mp4 file, the find command replaces all the brackets (
{}
) with its filename and then executes the formed FFmpeg command. - Only mp4 files will be touched. The rest of the files in the
~/test-dir
will be ignored.
Fun Fact 🙂: The given find command with the -exec
flag is quite versatile. You can use this to execute any command on a group of files.
Many of the above-mentioned commands not only work for videos but also for images.
6. Compress/Rescale Images using FFmpeg
Images are nothing but videos with just one frame. This is the reason why most of the video commands are applicable here as well.
For example, to compress an image to a lower resolution, say 360p, use the following command:
~$ ffmpeg -i INPUT.jpg -filter:v scale=-2:360 OUTPUT.jpg
The above command will reduce the file size, but the visual quality will also be reduced. To prevent such reduction, use the FFmpeg’s default settings i.e. no need to supply any flags:
~$ ffmpeg -i INPUT.jpg OUTPUT.jpg
You should try to play with all other video compression methods mentioned above and see which one works for you.
7. Way Ahead
As a going-away present, you should know that although I have mentioned compression and rescaling in separate headings, you can also use them together by using their flags side by side and thus get more file size reduction. The same goes for the mapping flag ( -map
) and other flags also.
That’s all. That was the short magic of FFmpeg. FFmpeg is a very versatile tool and you can use it as a screen recorder tool, screenshot tool, and media information tool as well.
Thank you for staying so long. If there is any mistake/question/ recommendation/appreciation, please comment below. It helps the community.
how to change output folder
~$ find ~/test-dir -name ‘*.mp4’ -exec ffmpeg -i {} {}_compressed.mp4 \;
you can use the loops:
src_dir=~/test-dir
dest_dir=~/compressed-videos
for video in "$src_dir"/*.mp4; do
dest_video="$dest_dir/$(basename "$video")_compressed.mp4"
ffmpeg -i "$video" "$dest_video"
done
how to delete orignal file after converting
~$ find ~/test-dir -name ‘*.mp4’ -exec ffmpeg -i {} {}_compressed.mp4 \;
Sorry for being so late. I was little busy. To delete original files execute:
find ~/test-dir -name '*.mp4' ! -name '*_compressed.mp4' -exec rm {} \;
For being on safe side, you can use
rm -i
instead ofrm
to get confirmation before deleting the file.Pingback: How to Use Loops in Bash? | SmartTech101