FFmpeg: Compress and Rescale Video and Image

June 2, 2022

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.

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.

Image with Poor Aspect Ratio

Figure: Image with Poor Aspect Ratio

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,

  1. 'a' in -map 0:a:'?' stands for audio; similarly 'v' for video and 's' for subtitles.
  2. 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:

  1. 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.
  2. For each found mp4 file, the find command replaces all the brackets ({}) with its filename and then executes the formed FFmpeg command.
  3. 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.