Search code examples
ffmpegvideo-processing

ffmpeg H265 to H264 colors are off?


I am wanting to convert a H265 video to H264 with as little to no quality loss possible. However, when converting the output colors are off with most noticeably the reds become orange. In general the output video colors are duller/saturated/grayer.

I believe this question is relevant, so is it possible to add color_range, color_space, color_transfer and color_primaries flags to a file without reencoding the entire thing again?

command:

ffmpeg -i input.mkv -c:v libx264 -crf 17 output.mp4

input video stream:

{
  "index": 0,
  "codec_name": "hevc",
  "codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
  "profile": "Main 10",
  "codec_type": "video",
  "codec_time_base": "1/24",
  "codec_tag_string": "[0][0][0][0]",
  "codec_tag": "0x0000",
  "width": 1920,
  "height": 1080,
  "coded_width": 1920,
  "coded_height": 1080,
  "closed_captions": 0,
  "has_b_frames": 2,
  "sample_aspect_ratio": "1:1",
  "display_aspect_ratio": "16:9",
  "pix_fmt": "yuv420p10le",
  "level": 120,
  "color_range": "tv",
  "color_space": "bt2020nc",
  "color_transfer": "smpte2084",
  "color_primaries": "bt2020",
  "refs": 1,
  "r_frame_rate": "24/1",
  "avg_frame_rate": "24/1",
  "time_base": "1/1000",
  "start_pts": 0,
  "start_time": "0.000000",
  "disposition": {
    "default": 1,
    "dub": 0,
    "original": 0,
    "comment": 0,
    "lyrics": 0,
    "karaoke": 0,
    "forced": 0,
    "hearing_impaired": 0,
    "visual_impaired": 0,
    "clean_effects": 0,
    "attached_pic": 0,
    "timed_thumbnails": 0
  },
  "tags": {
    "language": "eng",
    "BPS": "4421595",
    "DURATION": "00:41:46.417000000",
    "NUMBER_OF_FRAMES": "60154",
    "NUMBER_OF_BYTES": "1385295394",
    "_STATISTICS_WRITING_APP": "mkvmerge v62.0.0 ('Apollo') 64-bit",
    "_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
  }
}

output video stream:

{
  "index": 0,
  "codec_name": "h264",
  "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
  "profile": "High 10",
  "codec_type": "video",
  "codec_time_base": "1/48",
  "codec_tag_string": "avc1",
  "codec_tag": "0x31637661",
  "width": 1920,
  "height": 1080,
  "coded_width": 1920,
  "coded_height": 1088,
  "closed_captions": 0,
  "has_b_frames": 2,
  "sample_aspect_ratio": "1:1",
  "display_aspect_ratio": "16:9",
  "pix_fmt": "yuv420p10le",
  "level": 40,
  "chroma_location": "left",
  "refs": 1,
  "is_avc": "true",
  "nal_length_size": "4",
  "r_frame_rate": "24/1",
  "avg_frame_rate": "24/1",
  "time_base": "1/12288",
  "start_pts": 0,
  "start_time": "0.000000",
  "duration_ts": 30798848,
  "duration": "2506.416667",
  "bit_rate": "4073900",
  "bits_per_raw_sample": "10",
  "nb_frames": "60154",
  "disposition": {
    "default": 1,
    "dub": 0,
    "original": 0,
    "comment": 0,
    "lyrics": 0,
    "karaoke": 0,
    "forced": 0,
    "hearing_impaired": 0,
    "visual_impaired": 0,
    "clean_effects": 0,
    "attached_pic": 0,
    "timed_thumbnails": 0
  },
  "tags": {
    "language": "eng",
    "handler_name": "VideoHandler"
  }
}

Solution

  • Some of this metadata may also be signaled in the bitstream, but you can do a remux (copies video bits, doesn't re-encode) on the video and set the mp4 container metadata with a command like this:

    ffmpeg -i output.mp4 -c:v copy -color_primaries bt2020 -color_range tv -colorspace bt2020nc -color_trc smpte2084 output_updated.mp4
    

    This is set to match your input video settings.

    reference FFMPEG docs (search for color_trc to get to the right part)