Skip to content

andrewhsugithub/hdr-sdr-converter

Repository files navigation

HDR ↔️ SDR Video Converter

A lightweight command-line toolkit for converting video between SDR (Rec.709) and HDR formats (Rec.2100 HLG or PQ). It handles color-space transforms, tone-mapping, transfer functions, and bit-depth conversions with precision. The tool also lets you compare FFmpeg-based and Python-based conversion pipelines, generate test outputs for validation, and create rewrapped files that modify metadata without using transfer functions.

Setup

See .python-version for python version used.

  1. Create a virtual environment:

    python -m venv .venv

    or if using uv:

    uv venv
  2. Activate the virtual environment:

    source .venv/bin/activate
  3. Install dependencies from pyproject.toml:

    pip install .

    or if using uv:

    uv sync
    # or
    uv pip install -r pyproject.toml

Usage

CLI

  • Run converter:
    python main.py <command> -i <path/to/input.mp4> [-o <path/to/output.mp4>]
    # or uv
    uv run main.py <command> -i <path/to/input.mp4> [-o <path/to/output.mp4>]
  • List available conversions:
    python main.py list
    # or uv
    uv run main.py list

Commands

  • sdr2pq, sdr2hlg, pq2sdr, hlg2sdr, pq2hlg, hlg2pq - convert between formats
  • rewrap — copy pixels and change metadata (use --src and --dst to specify formats)

Formats

  • sdr — BT.709, 8-bit
  • pq — BT.2100 PQ (HDR10), 10-bit
  • hlg — BT.2100 HLG, 10-bit

Default output paths (when -o omitted)

  • For conversion commands: output/test_<command>.mp4
  • For rewrap: output/rewrap/test<src>2<dst>rewrapped.mp4

Examples

  • List commands:
    python main.py list
    # or uv
    uv run main.py list
  • Convert SDR → HLG:
    python main.py sdr2hlg -i test_sdr.mp4
    # or uv
    uv run main.py sdr2hlg -i test_sdr.mp4
  • Convert PQ → SDR and write to custom file:
    python main.py pq2sdr -i test_pq.mp4 -o ./output/ffmpeg/pq2sdr.mp4
    # or uv
    uv run main.py pq2sdr -i test_pq.mp4 -o ./output/ffmpeg/pq2sdr.mp4
  • Rewrap (change metadata):
    python main.py rewrap -i test_sdr.mp4 --src sdr --dst hlg
    # or uv
    uv run main.py rewrap -i test_sdr.mp4 --src sdr --dst hlg

Notes:

  • Use -o to control the output path; parent directories will be created automatically.
  • If you rely on ffmpeg in examples, ensure ffmpeg is installed and on PATH.
  • For reproducible results, keep source/target format metadata consistent with the files you provide.

Test Video Used (test_*.mp4)

HDR PQ Test Video

HDR PQ Test Video

  1. Download at https://www.demolandia.net/downloads.html?id=654828652

  2. Save as demo_pq.mp4 in the project root directory.

  3. Downscale to 1920x1080 and trim the 32-39 seconds segment for testing:

# need to install ffmpeg first
ffmpeg -i demo_pq.mp4 -vf "fps=30,scale=1920:1080" -ss 32 -to 39 -c:v libx265 -crf 23 -preset fast -tag:v hvc1 -an test_pq.mp4
test_pq.mp4
Check PQ Test Video Details
  • Video Details from Apple QuickTimePlayer's Video Inspector (Command + I):

    Property Value
    Bit Depth 10-bit
    HDR Type HDR10
    Color Primaries ITU-R BT.2020
    Transfer Function SMPTE ST 2084 (PQ)
    YCbCr Matrix ITU-R BT.2020
  • or determine the video properties using ffprobe:

    ffprobe -v quiet -show_streams -select_streams v:0 -of json test_pq.mp4
  • Output:

    {
      "streams": [
        {
          "index": 0,
          "codec_name": "hevc",
          "codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
          "profile": "Main 10",
          "codec_type": "video",
          "codec_tag_string": "hvc1",
          "codec_tag": "0x31637668",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "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",
          "chroma_location": "left",
          "field_order": "progressive",
          "refs": 1,
          "view_ids_available": "",
          "view_pos_available": "",
          "id": "0x1",
          "r_frame_rate": "30/1",
          "avg_frame_rate": "30/1",
          "time_base": "1/15360",
          "start_pts": 0,
          "start_time": "0.000000",
          "duration_ts": 107520,
          "duration": "7.000000",
          "bit_rate": "9459573",
          "nb_frames": "210",
          "extradata_size": 2556,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "Video Media Handler",
            "vendor_id": "[0][0][0][0]",
            "encoder": "Lavc62.11.100 libx265"
          },
          "side_data_list": [
            {
              "side_data_type": "Content light level metadata",
              "max_content": 0,
              "max_average": 0
            }
          ]
        }
      ]
    }

    From the above, we can see that the video is encoded in HDR (10-bit YUV 4:2:0) and uses PQ10 as the transfer function.

HDR HLG Test Video

HDR HLG Test Video

  1. Save as demo_hlg.mp4 in the project root directory.

  2. Trim the 29-32 seconds segment for testing:

# need to install ffmpeg first
ffmpeg -i test_hlg.mp4 -ss 29 -to 32 -c:v libx265 -crf 23 -preset fast -tag:v hvc1 test_hlg.mp4
test_hlg.mp4
Check HLG Test Video Details
  • Video Details from Apple QuickTimePlayer's Video Inspector (Command + I):

    Property Value
    Bit Depth 10-bit
    HDR Type HLG
    Color Primaries ITU-R BT.2020
    Transfer Function SMPTE ST 2100 (HLG)
    YCbCr Matrix ITU-R BT.2020
  • or determine the video properties using ffprobe:

    ffprobe -v quiet -show_streams -select_streams v:0 -of json test_hlg.mp4
  • Output:

    {
      "streams": [
        {
          "index": 0,
          "codec_name": "hevc",
          "codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
          "profile": "Main 10",
          "codec_type": "video",
          "codec_tag_string": "hvc1",
          "codec_tag": "0x31637668",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "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": "arib-std-b67",
          "color_primaries": "bt2020",
          "chroma_location": "left",
          "field_order": "progressive",
          "refs": 1,
          "view_ids_available": "",
          "view_pos_available": "",
          "id": "0x1",
          "r_frame_rate": "30000/1001",
          "avg_frame_rate": "30000/1001",
          "time_base": "1/30000",
          "start_pts": 990,
          "start_time": "0.033000",
          "duration_ts": 89089,
          "duration": "2.969633",
          "bit_rate": "2642966",
          "nb_frames": "89",
          "extradata_size": 2595,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "eng",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]",
            "encoder": "Lavc62.11.100 libx265"
          },
          "side_data_list": [
            {
              "side_data_type": "Mastering display metadata",
              "red_x": "34000/50000",
              "red_y": "16000/50000",
              "green_x": "13248/50000",
              "green_y": "34500/50000",
              "blue_x": "7500/50000",
              "blue_y": "3000/50000",
              "white_point_x": "15634/50000",
              "white_point_y": "16450/50000",
              "min_luminance": "50/10000",
              "max_luminance": "10000000/10000"
            },
            {
              "side_data_type": "Content light level metadata",
              "max_content": 0,
              "max_average": 0
            }
          ]
        }
      ]
    }

    From the above, we can see that the video is encoded in HDR (10-bit YUV 4:2:0) and uses HLG as the transfer function.

SDR Test Video

test_sdr.mp4
  1. Download Big Buck Bunny from https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_1MB.mp4

  2. Save as test_sdr.mp4 in the project root directory.

Check HLG Test Video Details
  • Determine the video properties using ffprobe:

    ffprobe -v quiet -show_streams -select_streams v:0 -of json test_sdr.mp4
  • Output:

    {
      "streams": [
        {
          "index": 0,
          "codec_name": "h264",
          "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
          "profile": "High",
          "codec_type": "video",
          "codec_tag_string": "avc1",
          "codec_tag": "0x31637661",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "sample_aspect_ratio": "1:1",
          "display_aspect_ratio": "16:9",
          "pix_fmt": "yuv420p",
          "level": 51,
          "chroma_location": "left",
          "field_order": "progressive",
          "refs": 1,
          "is_avc": "true",
          "nal_length_size": "4",
          "id": "0x1",
          "r_frame_rate": "30/1",
          "avg_frame_rate": "30/1",
          "time_base": "1/15360",
          "start_pts": 0,
          "start_time": "0.000000",
          "duration_ts": 153600,
          "duration": "10.000000",
          "bit_rate": "24559867",
          "bits_per_raw_sample": "8",
          "nb_frames": "300",
          "extradata_size": 47,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }

    From the above, we can see that the video is encoded in SDR (8-bit YUV 4:2:0).

Results

The converted videos are saved in the output/ directory:

  1. HDR (PQ10) ➡️ SDR Conversion
  • output/ffmpeg/pq2sdr.mp4: Converted to SDR using ffmpeg command:

    ffmpeg -i test_pq.mp4 -vf "zscale=t=linear:npl=100, format=gbrpf32le, zscale=p=bt709, zscale=t=bt709:m=bt709:r=tv, format=yuv420p" ./output/ffmpeg/pq2sdr.mp4
    Video Details from Apple QuickTimePlayer's Video Inspector (Command + I):
    Property Value
    Color Primaries ITU-R BT.709
    Transfer Function ITU-R BT.709
    YCbCr Matrix ITU-R BT.709
  • output/test_pq2sdr.mp4: Converted using main.py command:

    python main.py pq2sdr -i test_pq.mp4
    # or using uv
    uv run main.py pq2sdr -i test_pq.mp4
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "h264",
          "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
          "profile": "High",
          "codec_type": "video",
          "codec_tag_string": "avc1",
          "codec_tag": "0x31637661",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p",
          "level": 40,
          "color_range": "tv",
          "color_space": "bt709",
          "color_transfer": "bt709",
          "color_primaries": "bt709",
          "chroma_location": "left",
          "field_order": "progressive",
          "refs": 1,
          "is_avc": "true",
          "nal_length_size": "4",
          "id": "0x1",
          "r_frame_rate": "30/1",
          "avg_frame_rate": "30/1",
          "time_base": "1/15360",
          "start_pts": 0,
          "start_time": "0.000000",
          "duration_ts": 107520,
          "duration": "7.000000",
          "bit_rate": "24181306",
          "bits_per_raw_sample": "8",
          "nb_frames": "210",
          "extradata_size": 50,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • output/rewrap/test_pq2sdr_rewrapped.mp4: Rewrap (convert without using transfer functions, changed metadata) using main.py command:

    python main.py rewrap -i test_pq.mp4 --src pq --dst sdr
    # or using uv
    uv run main.py rewrap -i test_pq.mp4 --src pq --dst sdr
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "h264",
          "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
          "profile": "High",
          "codec_type": "video",
          "codec_tag_string": "avc1",
          "codec_tag": "0x31637661",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p",
          "level": 40,
          "color_range": "tv",
          "color_space": "bt709",
          "color_transfer": "bt709",
          "color_primaries": "bt709",
          "chroma_location": "left",
          "field_order": "progressive",
          "refs": 1,
          "is_avc": "true",
          "nal_length_size": "4",
          "id": "0x1",
          "r_frame_rate": "30/1",
          "avg_frame_rate": "30/1",
          "time_base": "1/15360",
          "start_pts": 0,
          "start_time": "0.000000",
          "duration_ts": 107520,
          "duration": "7.000000",
          "bit_rate": "15408329",
          "bits_per_raw_sample": "8",
          "nb_frames": "210",
          "extradata_size": 50,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • Comparison

    Original HDR PQ Converted SDR using ffmpeg Converted SDR using main.py Rewrapped SDR using main.py
    test_pq.mp4
    pq2sdr.mp4
    test_pq2sdr.mp4
    test_pq2sdr_rewrapped.mp4
  1. HDR (PQ10) ➡️ HDR (HLG) Conversion
  • output/ffmpeg/pq2hlg.mp4: Converted to HLG using ffmpeg command:

    ffmpeg -i test.mp4 -vf "zscale=t=arib-std-b67" -c:v libx265 -crf 20 -preset fast -tag:v hvc1 ./output/ffmpeg/pq2hlg.mp4
    Video Details from Apple QuickTimePlayer's Video Inspector (Command + I):
    Property Value
    Bit Depth 10-bit
    HDR Type HLG
    Color Primaries ITU-R BT.2020
    Transfer Function SMPTE ST 2100 (HLG)
    YCbCr Matrix ITU-R BT.2020
  • output/test_pq2hlg.mp4: Converted using main.py command:

    python main.py pq2hlg -i test_pq.mp4
    # or using uv
    uv run main.py pq2hlg -i test_pq.mp4
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "hevc",
          "codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
          "profile": "Main 10",
          "codec_type": "video",
          "codec_tag_string": "hvc1",
          "codec_tag": "0x31637668",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p10le",
          "level": 120,
          "color_range": "tv",
          "color_space": "bt2020nc",
          "color_transfer": "arib-std-b67",
          "color_primaries": "bt2020",
          "chroma_location": "left",
          "refs": 1,
          "view_ids_available": "",
          "view_pos_available": "",
          "id": "0x1",
          "r_frame_rate": "30/1",
          "avg_frame_rate": "30/1",
          "time_base": "1/15360",
          "start_pts": 0,
          "start_time": "0.000000",
          "duration_ts": 107520,
          "duration": "7.000000",
          "bit_rate": "12564515",
          "nb_frames": "210",
          "extradata_size": 2438,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • output/rewrap/test_pq2hlg_rewrapped.mp4: Rewrap (convert without using transfer functions, changed metadata) using main.py command:

    python main.py rewrap -i test_pq.mp4 --src pq --dst hlg
    # or using uv
    uv run main.py rewrap -i test_pq.mp4 --src pq --dst hlg
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "hevc",
          "codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
          "profile": "Main 10",
          "codec_type": "video",
          "codec_tag_string": "hvc1",
          "codec_tag": "0x31637668",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p10le",
          "level": 120,
          "color_range": "tv",
          "color_space": "bt2020nc",
          "color_transfer": "arib-std-b67",
          "color_primaries": "bt2020",
          "chroma_location": "left",
          "refs": 1,
          "view_ids_available": "",
          "view_pos_available": "",
          "id": "0x1",
          "r_frame_rate": "30/1",
          "avg_frame_rate": "30/1",
          "time_base": "1/15360",
          "start_pts": 0,
          "start_time": "0.000000",
          "duration_ts": 107520,
          "duration": "7.000000",
          "bit_rate": "10413285",
          "nb_frames": "210",
          "extradata_size": 2438,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • Comparison

    Original HDR PQ Converted HLG using ffmpeg Converted HLG using main.py Rewrapped HLG using main.py
    test_pq.mp4
    pq2hlg.mp4
    test_pq2hlg.mp4
    test_pq2hlg_rewrapped.mp4
  1. HDR (HLG) ➡️ SDR Conversion
  • output/ffmpeg/hlg2sdr.mp4: Converted to SDR using ffmpeg command:

    ffmpeg -i test_hlg.mp4 -vf "zscale=t=linear:npl=100, format=gbrpf32le, zscale=p=bt709, zscale=t=bt709:m=bt709:r=tv, format=yuv420p" ./output/ffmpeg/hlg2sdr.mp4
    Video Details from Apple QuickTimePlayer's Video Inspector (Command + I):
    Property Value
    Color Primaries ITU-R BT.709
    Transfer Function ITU-R BT.709
    YCbCr Matrix ITU-R BT.709
  • output/test_hlg2sdr.mp4: Converted using main.py command:

    python main.py hlg2sdr -i test_hlg.mp4
    # or using uv
    uv run main.py hlg2sdr -i test_hlg.mp4
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "h264",
          "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
          "profile": "High",
          "codec_type": "video",
          "codec_tag_string": "avc1",
          "codec_tag": "0x31637661",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p",
          "level": 40,
          "color_range": "tv",
          "color_space": "bt709",
          "color_transfer": "bt709",
          "color_primaries": "bt709",
          "chroma_location": "left",
          "field_order": "progressive",
          "refs": 1,
          "is_avc": "true",
          "nal_length_size": "4",
          "id": "0x1",
          "r_frame_rate": "30000/1001",
          "avg_frame_rate": "30000/1001",
          "time_base": "1/30000",
          "start_pts": 990,
          "start_time": "0.033000",
          "duration_ts": 89089,
          "duration": "2.969633",
          "bit_rate": "6882767",
          "bits_per_raw_sample": "8",
          "nb_frames": "89",
          "extradata_size": 49,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • output/rewrap/test_hlg2sdr_rewrapped.mp4: Rewrap (convert without using transfer functions, changed metadata) using main.py command:

    python main.py rewrap -i test_hlg.mp4 --src hlg --dst sdr
    # or using uv
    uv run main.py rewrap -i test_hlg.mp4 --src hlg --dst sdr
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "h264",
          "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
          "profile": "High",
          "codec_type": "video",
          "codec_tag_string": "avc1",
          "codec_tag": "0x31637661",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p",
          "level": 40,
          "color_range": "tv",
          "color_space": "bt709",
          "color_transfer": "bt709",
          "color_primaries": "bt709",
          "chroma_location": "left",
          "field_order": "progressive",
          "refs": 1,
          "is_avc": "true",
          "nal_length_size": "4",
          "id": "0x1",
          "r_frame_rate": "30000/1001",
          "avg_frame_rate": "30000/1001",
          "time_base": "1/30000",
          "start_pts": 990,
          "start_time": "0.033000",
          "duration_ts": 89089,
          "duration": "2.969633",
          "bit_rate": "4849657",
          "bits_per_raw_sample": "8",
          "nb_frames": "89",
          "extradata_size": 49,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • Comparison

    Original HDR HLG Converted SDR using ffmpeg Converted SDR using main.py Rewrap SDR using main.py
    test_hlg.mp4
    hlg2sdr.mp4
    test_hlg2sdr.mp4
    test_hlg2sdr_rewrapped.mp4
  1. HDR (HLG) ➡️ HDR (PQ10) Conversion
  • output/ffmpeg/hlg2pq.mp4: Converted to HDR (PQ10) using ffmpeg command:

    ffmpeg -i test_hlg.mp4 -vf "zscale=t=smpte2084" -c:v libx265 -crf 20 -preset fast -tag:v hvc1 ./output/ffmpeg/hlg2pq.mp4
    Video Details from Apple QuickTimePlayer's Video Inspector (Command + I):
    Property Value
    Bit Depth 10-bit
    HDR Type HDR10
    Color Primaries ITU-R BT.2020
    Transfer Function SMPTE ST 2084 (PQ)
    YCbCr Matrix ITU-R BT.2020
  • output/test_hlg2pq.mp4: Converted using main.py command:

    python main.py hlg2pq -i test_hlg.mp4
    # or using uv
    uv run main.py hlg2pq -i test_hlg.mp4
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "hevc",
          "codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
          "profile": "Main 10",
          "codec_type": "video",
          "codec_tag_string": "hvc1",
          "codec_tag": "0x31637668",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p10le",
          "level": 120,
          "color_range": "tv",
          "color_space": "bt2020nc",
          "color_transfer": "smpte2084",
          "color_primaries": "bt2020",
          "chroma_location": "left",
          "refs": 1,
          "view_ids_available": "",
          "view_pos_available": "",
          "id": "0x1",
          "r_frame_rate": "30000/1001",
          "avg_frame_rate": "30000/1001",
          "time_base": "1/30000",
          "start_pts": 990,
          "start_time": "0.033000",
          "duration_ts": 89089,
          "duration": "2.969633",
          "bit_rate": "2574192",
          "nb_frames": "89",
          "extradata_size": 2442,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • output/rewrap/test_hlg2pq_rewrapped.mp4: Rewrap (convert without using transfer functions, changed metadata) using main.py command:

    python main.py rewrap -i test_hlg.mp4 --src hlg --dst pq
    # or using uv
    uv run main.py rewrap -i test_hlg.mp4 --src hlg --dst pq
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "hevc",
          "codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
          "profile": "Main 10",
          "codec_type": "video",
          "codec_tag_string": "hvc1",
          "codec_tag": "0x31637668",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p10le",
          "level": 120,
          "color_range": "tv",
          "color_space": "bt2020nc",
          "color_transfer": "smpte2084",
          "color_primaries": "bt2020",
          "chroma_location": "left",
          "refs": 1,
          "view_ids_available": "",
          "view_pos_available": "",
          "id": "0x1",
          "r_frame_rate": "30000/1001",
          "avg_frame_rate": "30000/1001",
          "time_base": "1/30000",
          "start_pts": 990,
          "start_time": "0.033000",
          "duration_ts": 89089,
          "duration": "2.969633",
          "bit_rate": "2855908",
          "nb_frames": "89",
          "extradata_size": 2442,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • Comparison

    Original HDR HLG Converted PQ using ffmpeg Converted PQ using main.py Rewrapped PQ using main.py
    test_hlg.mp4
    hlg2pq.mp4
    test_hlg2pq.mp4
    test_hlg2pq_rewrapped.mp4
  1. SDR ➡️ HDR (PQ10) Conversion
  • output/ffmpeg/sdr2pq.mp4: Converted to HDR (PQ10) using ffmpeg command:

    ffmpeg -i "test_sdr.mp4" -vf "format=gbrpf32le, zscale=tin=bt709:pin=bt709:t=smpte2084:p=bt2020:m=bt2020nc, format=yuv420p10le" -c:v libx265 -crf 20 -preset fast -tag:v hvc1 ./output/ffmpeg/sdr2pq.mp4
    Video Details from Apple QuickTimePlayer's Video Inspector (Command + I):
    Property Value
    Bit Depth 10-bit
    HDR Type HDR10
    Color Primaries ITU-R BT.2020
    Transfer Function SMPTE ST 2084 (PQ)
    YCbCr Matrix ITU-R BT.2020
  • output/test_sdr2pq.mp4: Converted using main.py command:

    python main.py sdr2pq -i test_sdr.mp4
    # or using uv
    uv run main.py sdr2pq -i test_sdr.mp4
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "hevc",
          "codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
          "profile": "Main 10",
          "codec_type": "video",
          "codec_tag_string": "hvc1",
          "codec_tag": "0x31637668",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p10le",
          "level": 120,
          "color_range": "tv",
          "color_space": "bt2020nc",
          "color_transfer": "smpte2084",
          "color_primaries": "bt2020",
          "chroma_location": "left",
          "refs": 1,
          "view_ids_available": "",
          "view_pos_available": "",
          "id": "0x1",
          "r_frame_rate": "30/1",
          "avg_frame_rate": "30/1",
          "time_base": "1/15360",
          "start_pts": 0,
          "start_time": "0.000000",
          "duration_ts": 153600,
          "duration": "10.000000",
          "bit_rate": "4456511",
          "nb_frames": "300",
          "extradata_size": 2438,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • output/rewrap/test_sdr2pq_rewrapped.mp4: Rewrap (convert without using transfer functions, changed metadata) using main.py command:

    python main.py rewrap -i test_sdr.mp4 --src sdr --dst pq
    # or using uv
    uv run main.py rewrap -i test_sdr.mp4 --src sdr --dst pq
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "hevc",
          "codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
          "profile": "Main 10",
          "codec_type": "video",
          "codec_tag_string": "hvc1",
          "codec_tag": "0x31637668",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p10le",
          "level": 120,
          "color_range": "tv",
          "color_space": "bt2020nc",
          "color_transfer": "smpte2084",
          "color_primaries": "bt2020",
          "chroma_location": "left",
          "refs": 1,
          "view_ids_available": "",
          "view_pos_available": "",
          "id": "0x1",
          "r_frame_rate": "30/1",
          "avg_frame_rate": "30/1",
          "time_base": "1/15360",
          "start_pts": 0,
          "start_time": "0.000000",
          "duration_ts": 153600,
          "duration": "10.000000",
          "bit_rate": "7674006",
          "nb_frames": "300",
          "extradata_size": 2438,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • Comparison

    Original SDR Converted PQ using ffmpeg Converted PQ using main.py Rewrapped PQ using main.py
    test_sdr.mp4
    sdr2pq.mp4
    test_sdr2pq.mp4
    test_sdr2pq_rewrapped.mp4
  1. SDR ➡️ HDR (HLG) Conversion
  • output/ffmpeg/sdr2hlg.mp4: Converted to HDR (HLG) using ffmpeg command:

    ffmpeg -i "test_sdr.mp4" -vf "format=gbrpf32le, zscale=tin=bt709:pin=bt709:t=arib-std-b67:p=bt2020:m=bt2020nc, format=yuv420p10le" -c:v libx265 -crf 20 -preset fast -tag:v hvc1 ./output/ffmpeg/sdr2hlg.mp4
    Video Details from Apple QuickTimePlayer's Video Inspector (Command + I):
    Property Value
    Bit Depth 10-bit
    HDR Type HLG
    Color Primaries ITU-R BT.2020
    Transfer Function ITU-R BT.2100 HLG
    YCbCr Matrix ITU-R BT.2020
  • output/test_sdr2hlg.mp4: Converted using main.py command:

    python main.py sdr2hlg -i test_sdr.mp4
    # or using uv
    uv run main.py sdr2hlg -i test_sdr.mp4
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "hevc",
          "codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
          "profile": "Main 10",
          "codec_type": "video",
          "codec_tag_string": "hvc1",
          "codec_tag": "0x31637668",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p10le",
          "level": 120,
          "color_range": "tv",
          "color_space": "bt2020nc",
          "color_transfer": "arib-std-b67",
          "color_primaries": "bt2020",
          "chroma_location": "left",
          "refs": 1,
          "view_ids_available": "",
          "view_pos_available": "",
          "id": "0x1",
          "r_frame_rate": "30/1",
          "avg_frame_rate": "30/1",
          "time_base": "1/15360",
          "start_pts": 0,
          "start_time": "0.000000",
          "duration_ts": 153600,
          "duration": "10.000000",
          "bit_rate": "5601019",
          "nb_frames": "300",
          "extradata_size": 2438,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • output/rewrap/test_sdr2hlg_rewrapped.mp4: Rewrap (convert without using transfer functions, changed metadata) using main.py command:

    python main.py rewrap -i test_sdr.mp4 --src sdr --dst hlg
    # or using uv
    uv run main.py rewrap -i test_sdr.mp4 --src sdr --dst hlg
    Video Details using ffprobe (same as above command just diff file):
    {
      "streams": [
        {
          "index": 0,
          "codec_name": "hevc",
          "codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
          "profile": "Main 10",
          "codec_type": "video",
          "codec_tag_string": "hvc1",
          "codec_tag": "0x31637668",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1080,
          "has_b_frames": 2,
          "pix_fmt": "yuv420p10le",
          "level": 120,
          "color_range": "tv",
          "color_space": "bt2020nc",
          "color_transfer": "arib-std-b67",
          "color_primaries": "bt2020",
          "chroma_location": "left",
          "refs": 1,
          "view_ids_available": "",
          "view_pos_available": "",
          "id": "0x1",
          "r_frame_rate": "30/1",
          "avg_frame_rate": "30/1",
          "time_base": "1/15360",
          "start_pts": 0,
          "start_time": "0.000000",
          "duration_ts": 153600,
          "duration": "10.000000",
          "bit_rate": "7674006",
          "nb_frames": "300",
          "extradata_size": 2438,
          "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,
            "non_diegetic": 0,
            "captions": 0,
            "descriptions": 0,
            "metadata": 0,
            "dependent": 0,
            "still_image": 0,
            "multilayer": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler",
            "vendor_id": "[0][0][0][0]"
          }
        }
      ]
    }
  • Comparison

    Original SDR Converted HLG using ffmpeg Converted HLG using main.py Rewrapped HLG using main.py
    test_sdr.mp4
    sdr2hlg.mp4
    test_sdr2hlg.mp4
    test_sdr2hlg_rewrapped.mp4

References

https://www.itu.int/rec/R-REC-BT.2100-3-202502-I/en

https://www.itu.int/rec/R-REC-BT.709-6-201506-I/en

https://www.itu.int/rec/R-REC-BT.1886-0-201103-I/en

https://www.itu.int/rec/R-REC-BT.2087/en

https://www.itu.int/rec/R-REC-BT.2020-2-201510-I/en

https://www.itu.int/pub/R-REP-BT.2408-8-2024

About

HDR (PQ10, HLG) ⇔ SDR Video Converter

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages