Ffmpeg video compression: Desaturating colors / losing color information

colorscompressionffmpegvideo

I'm trying to compress some 4K videos (resolution: 3840×2160) that I've recorded with my Nexus 5X (running LineageOS).

My first attempt:

./ffmpeg -i VID_20190908_145514.mp4 -c:a copy -crf 23 -vf "scale=1920:-1" VID_20190908_145514.komprimiert.mp4

Problem: The colors are washed out / desaturated. The effect is quite strong. I used VLC to compare the videos side-by-side and made a screenshot for you:

Screenshot showing original video and compressed video (with desaturated colors) side-by-side

Here is the ffprobe output for the original video file:

./ffprobe VID_20190908_145514.mp4 
ffprobe version 4.1.4-tessus  https://evermeet.cx/ffmpeg/  Copyright (c) 2007-2019 the FFmpeg developers
  built with Apple LLVM version 10.0.1 (clang-1001.0.46.4)
  configuration: --cc=/usr/bin/clang --prefix=/opt/ffmpeg --extra-version=tessus --enable-avisynth --enable-fontconfig --enable-gpl --enable-libaom --enable-libass --enable-libbluray --enable-libfreetype --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libmysofa --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopus --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-version3 --pkg-config-flags=--static --disable-ffplay
  libavutil      56. 22.100 / 56. 22.100
  libavcodec     58. 35.100 / 58. 35.100
  libavformat    58. 20.100 / 58. 20.100
  libavdevice    58.  5.100 / 58.  5.100
  libavfilter     7. 40.101 /  7. 40.101
  libswscale      5.  3.100 /  5.  3.100
  libswresample   3.  3.100 /  3.  3.100
  libpostproc    55.  3.100 / 55.  3.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'VID_20190908_145514.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    creation_time   : 2019-09-08T12:58:35.000000Z
    com.android.version: 8.1.0
    com.android.manufacturer: LGE
    com.android.model: Nexus 5X
  Duration: 00:03:18.27, start: 0.000000, bitrate: 41991 kb/s
    Stream #0:0(eng): Video: h264 (Baseline) (avc1 / 0x31637661), yuvj420p(pc, bt470bg/bt470bg/smpte170m), 3840x2160, 41963 kb/s, SAR 1:1 DAR 16:9, 29.33 fps, 30 tbr, 90k tbn, 180k tbc (default)
    Metadata:
      rotate          : 180
      creation_time   : 2019-09-08T12:58:35.000000Z
      handler_name    : VideoHandle
    Side data:
      displaymatrix: rotation of -180.00 degrees
    Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, mono, fltp, 96 kb/s (default)
    Metadata:
      creation_time   : 2019-09-08T12:58:35.000000Z
      handler_name    : SoundHandle

Here is ffprobe output for the compressed video file:

./ffprobe VID_20190908_145514.komprimiert.mp4 
ffprobe version 4.1.4-tessus  https://evermeet.cx/ffmpeg/  Copyright (c) 2007-2019 the FFmpeg developers
  built with Apple LLVM version 10.0.1 (clang-1001.0.46.4)
  configuration: --cc=/usr/bin/clang --prefix=/opt/ffmpeg --extra-version=tessus --enable-avisynth --enable-fontconfig --enable-gpl --enable-libaom --enable-libass --enable-libbluray --enable-libfreetype --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libmysofa --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopus --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-version3 --pkg-config-flags=--static --disable-ffplay
  libavutil      56. 22.100 / 56. 22.100
  libavcodec     58. 35.100 / 58. 35.100
  libavformat    58. 20.100 / 58. 20.100
  libavdevice    58.  5.100 / 58.  5.100
  libavfilter     7. 40.101 /  7. 40.101
  libswscale      5.  3.100 /  5.  3.100
  libswresample   3.  3.100 /  3.  3.100
  libpostproc    55.  3.100 / 55.  3.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'VID_20190908_145514.komprimiert.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.20.100
  Duration: 00:03:18.27, start: 0.000000, bitrate: 6833 kb/s
    Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuvj420p(pc), 1920x1080 [SAR 1:1 DAR 16:9], 6742 kb/s, 30 fps, 30 tbr, 15360 tbn, 60 tbc (default)
    Metadata:
      handler_name    : VideoHandle
    Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, mono, fltp, 96 kb/s (default)
    Metadata:
      handler_name    : SoundHandle

The problem seems to be the mismatching colorspace/colortransfer/colorprimaries.

Original file is specified as:

yuvj420p(pc, bt470bg/bt470bg/smpte170m)

Compressed file is specified as:

yuvj420p(pc)

I searched for solutions online and came up with an improved command that tries to imitate/reproduce the color settings from the original video file:

./ffmpeg -i VID_20190908_145514.mp4 -vf "scale=1920:-1" -color_primaries 5 -colorspace 5 -color_trc 6 -crf 23 -c:a copy -to 10 VID_20190908_145514.komprimiert3.mp4

Unfortunately, the -color_primaries, -colorspace and -color_trc switches are not well documented by ffmpeg. Anyway, they seem to fix the color problem. At least, the above command now results in a matching ffprobe output regarding the colors:

yuvj420p(pc, bt470bg/bt470bg/smpte170m)

But the strange thing is: I don't see any difference when playing the video. Colors are still washed out / desaturated.

To be fair… during running the above command I got the following warning:

[swscaler @ 0x115075000] deprecated pixel format used, make sure you did set range correctly

I got the same warning on the initial attempt (without specifying -colorspace etc.).

How do I keep the original colors with ffmpeg? I don't want to lose color information. I just want ffmpeg to compress the video (by downscaling and using more CPU than was possible on my smartphone). To be honest, I didn't expect to run into such difficulties…


EDIT 1: The videos are not displayed differently when using ffplay instead of VLC or QuickTime. You can hardly see any difference. See screenshot:

Screenshot of three ffplay instances playing the different video files for comparison


EDIT 2: It could be a playback issue/bug in VLC/Quicktime. Maybe the issue is that they misinterpret the color metadata in the video file. I found out that VLC and ffprobe disagree…

VLC says in the 'Media information' window about VID_20190908_145514.mp4 (I can't upload screenshots currently):

...
Color primaries: ITU-R BT.2020
Color transfer function: ITU-R BT.709
Color space: ITU-R BT.2020 Range
Chroma location: Left
...

In contrast, ffprobe -show_streams VID_20190908_145514.mp4 says:

...
pix_fmt=yuvj420p
level=51
color_range=pc
color_space=bt470bg
color_transfer=smpte170m
color_primaries=bt470bg
chroma_location=left
...

Obviously, assuming the wrong (input) colorspace produces distorted (output) colors on playback. I'm starting to think that the washed out / desaturated colors are actually the correct ones.

Such a problem has been reported on ffmpeg's Trac before: https://trac.ffmpeg.org/ticket/7180

Best Answer

BT.2020, as detected by VLC, could the correct description for the color encoding of your file. You can easily change at both the container and bitstream level without re-encoding

ffmpeg -i VID_20190908_145514.komprimiert.mp4 -c copy -color_primaries bt2020 -color_trc bt709 -colorspace bt2020_ncl -color_range pc -bsf:v h264_metadata=video_full_range_flag=1:colour_primaries=9:transfer_characteristics=1:matrix_coefficients=9 out.mp4

(there are actually two possibilites for BT.2020 color space / matrix coefficients, if ncl and 9 aren't right, try bt2020_ncl and 10)

Related Question