├── .github
├── actions
│ └── setup-release-env
│ │ └── action.yml
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── assets
├── NotoSans-Regular.ttf
├── av1-rpu
│ ├── fel_orig.bin
│ ├── p5-01-ref.bin
│ ├── p5-01.bin
│ ├── p84-01-ref.bin
│ └── p84-01.bin
├── editor_examples
│ ├── active_area.json
│ ├── active_area_all.json
│ ├── crop.json
│ ├── duplicate.json
│ ├── fel_to_p81_encode.json
│ ├── l9_and_l11.json
│ ├── level6_metadata.json
│ ├── levels.json
│ ├── mode.json
│ ├── p5_to_p81.json
│ ├── remove.json
│ ├── remove_cmv4.json
│ ├── scene_cuts.json
│ └── source_rpu.json
├── generator_examples
│ ├── default_cmv29.json
│ ├── default_cmv40.json
│ ├── full_example.json
│ ├── l1_cmv29.json
│ ├── l1_cmv29_override_avg_cmv40.json
│ ├── l1_cmv40.json
│ └── no_duration.json
├── hevc_tests
│ ├── no_aud_bl.hevc
│ ├── no_aud_injected.hevc
│ ├── no_aud_muxed.hevc
│ ├── regular.hevc
│ ├── regular.mkv
│ ├── regular_bl_start_code_4.hevc
│ ├── regular_bl_start_code_4_shorter.hevc
│ ├── regular_convert_annexb.hevc
│ ├── regular_demux_bl_annexb.hevc
│ ├── regular_inject_annexb.hevc
│ ├── regular_rpu.bin
│ ├── regular_rpu_mel.bin
│ ├── regular_start_code_4.hevc
│ ├── regular_start_code_4_muxed_el.hevc
│ ├── regular_start_code_4_shorter_muxed_el.hevc
│ ├── sei-double-3byte-case.hevc
│ ├── sei-double-3byte-start-code-4.hevc
│ ├── sei-suffix-muxed-rpu.hevc
│ └── yusesope_regular_muxed.hevc
└── tests
│ ├── cmv2_9.xml
│ ├── cmv2_9_xml_with_l5_rpu.bin
│ ├── cmv40_full_rpu.bin
│ ├── cmv4_0_2.xml
│ ├── cmv4_0_2_custom_displays.xml
│ ├── cmv4_0_2_custom_displays_xml_rpu.bin
│ ├── cmv4_0_2_xml_rpu.bin
│ ├── cmv4_0_2_xml_with_l5_rpu.bin
│ ├── cmv4_2_510_xml_rpu.bin
│ ├── cmv4_2_xml_510.xml
│ ├── data_before_crc32.bin
│ ├── empty_dmv1_blocks.bin
│ ├── eof_rpu.bin
│ ├── fel_orig.bin
│ ├── fel_rpu.bin
│ ├── fel_to_81.bin
│ ├── fel_to_mel.bin
│ ├── fix_se_write.bin
│ ├── hdr10plus_metadata.json
│ ├── level6_decimals.xml
│ ├── mel_orig.bin
│ ├── mel_rpu.bin
│ ├── mel_to_81.bin
│ ├── mel_to_mel.bin
│ ├── mel_variable_l8_length13.bin
│ ├── p8_001_end_crc32.bin
│ ├── poly_coef_int_logic.bin
│ ├── profile20_apple.bin
│ ├── profile4.bin
│ ├── profile5-02.bin
│ ├── profile5.bin
│ ├── profile8.bin
│ ├── profile84.bin
│ ├── profile8_from_profile5-02.bin
│ ├── source_p5_to_p8_001_end_crc32.bin
│ ├── source_rpu_replaced_fel_orig.bin
│ ├── st2094_10_level3.bin
│ ├── trailing_bytes_rpu.bin
│ └── unordered_l8_blocks.bin
├── build.rs
├── docs
├── README.md
├── editor.md
├── generator.md
└── profiles.md
├── dolby_vision
├── .gitignore
├── CHANGELOG.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── benches
│ ├── bench_main.rs
│ └── benchmarks
│ │ ├── mod.rs
│ │ ├── parsing.rs
│ │ └── rewriting.rs
├── cbindgen.toml
├── examples
│ ├── capi_rpu_file.c
│ ├── capi_single_rpu.c
│ ├── helpers.h
│ └── simple_edit.cpp
└── src
│ ├── av1
│ ├── emdf.rs
│ └── mod.rs
│ ├── c_structs
│ ├── buffers.rs
│ ├── extension_metadata.rs
│ ├── mod.rs
│ ├── rpu.rs
│ ├── rpu_data_header.rs
│ ├── rpu_data_mapping.rs
│ ├── rpu_data_nlq.rs
│ └── vdr_dm_data.rs
│ ├── capi.rs
│ ├── lib.rs
│ ├── rpu
│ ├── dovi_rpu.rs
│ ├── extension_metadata
│ │ ├── blocks
│ │ │ ├── level1.rs
│ │ │ ├── level10.rs
│ │ │ ├── level11.rs
│ │ │ ├── level2.rs
│ │ │ ├── level254.rs
│ │ │ ├── level255.rs
│ │ │ ├── level3.rs
│ │ │ ├── level4.rs
│ │ │ ├── level5.rs
│ │ │ ├── level6.rs
│ │ │ ├── level8.rs
│ │ │ ├── level9.rs
│ │ │ ├── mod.rs
│ │ │ └── reserved.rs
│ │ ├── cmv29.rs
│ │ ├── cmv40.rs
│ │ ├── mod.rs
│ │ └── primaries.rs
│ ├── generate.rs
│ ├── mod.rs
│ ├── profiles
│ │ ├── mod.rs
│ │ ├── profile4.rs
│ │ ├── profile5.rs
│ │ ├── profile7.rs
│ │ ├── profile81.rs
│ │ └── profile84.rs
│ ├── rpu_data_header.rs
│ ├── rpu_data_mapping.rs
│ ├── rpu_data_nlq.rs
│ ├── utils.rs
│ └── vdr_dm_data.rs
│ ├── st2094_10
│ ├── itu_t35
│ │ ├── cm_data.rs
│ │ ├── dm_data.rs
│ │ └── mod.rs
│ └── mod.rs
│ ├── utils.rs
│ └── xml
│ ├── mod.rs
│ ├── parser.rs
│ └── tests.rs
├── src
├── commands
│ ├── convert.rs
│ ├── demux.rs
│ ├── editor.rs
│ ├── export.rs
│ ├── extract_rpu.rs
│ ├── generate.rs
│ ├── info.rs
│ ├── inject_rpu.rs
│ ├── mod.rs
│ ├── mux.rs
│ ├── plot.rs
│ └── remove.rs
├── dovi
│ ├── converter.rs
│ ├── demuxer.rs
│ ├── editor.rs
│ ├── exporter.rs
│ ├── general_read_write.rs
│ ├── generator.rs
│ ├── hdr10plus_utils.rs
│ ├── mod.rs
│ ├── muxer.rs
│ ├── plotter.rs
│ ├── remover.rs
│ ├── rpu_extractor.rs
│ ├── rpu_info.rs
│ └── rpu_injector.rs
├── main.rs
└── tests
│ ├── av1_rpu.rs
│ ├── mod.rs
│ └── rpu.rs
└── tests
├── hevc
├── convert.rs
├── demux.rs
├── extract_rpu.rs
├── inject_rpu.rs
├── mod.rs
├── mux.rs
└── remove.rs
├── mod.rs
└── rpu
├── editor.rs
├── export.rs
├── generate.rs
├── info.rs
├── mod.rs
└── plot.rs
/.github/actions/setup-release-env/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup release env
2 | runs:
3 | using: "composite"
4 | steps:
5 | - name: Install Rust
6 | uses: dtolnay/rust-toolchain@stable
7 |
8 | - name: Get the package versions
9 | shell: bash
10 | run: |
11 | RELEASE_PKG_VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[].version')
12 | LIBDOVI_PKG_VERSION=$(cargo metadata --format-version 1 --no-deps --manifest-path dolby_vision/Cargo.toml | jq -r '.packages[].version')
13 |
14 | echo "RELEASE_PKG_VERSION=${RELEASE_PKG_VERSION}" >> $GITHUB_ENV
15 | echo "LIBDOVI_PKG_VERSION=${LIBDOVI_PKG_VERSION}" >> $GITHUB_ENV
16 | echo "ARCHIVE_PREFIX=${{ env.RELEASE_BIN }}-${RELEASE_PKG_VERSION}" >> $GITHUB_ENV
17 |
18 | - name: Create artifacts directory
19 | shell: bash
20 | run: |
21 | mkdir ${{ env.RELEASE_DIR }}
22 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | ci:
11 | name: Check, test, rustfmt and clippy
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 |
16 | - name: Install fontconfig
17 | run: |
18 | sudo apt-get update
19 | sudo apt-get install libfontconfig-dev
20 |
21 | - name: Install Rust, clippy and rustfmt
22 | uses: dtolnay/rust-toolchain@stable
23 | with:
24 | components: clippy, rustfmt
25 |
26 | - name: Check
27 | run: |
28 | cargo check --all-features
29 |
30 | cargo check --all-features \
31 | --manifest-path dolby_vision/Cargo.toml
32 |
33 | - name: Test
34 | run: |
35 | cargo test --all-features
36 | cargo test --all-features --bins
37 |
38 | cargo test --all-features --all-targets \
39 | --manifest-path dolby_vision/Cargo.toml
40 |
41 | - name: Rustfmt
42 | run: |
43 | cargo fmt --check
44 |
45 | cargo fmt --check \
46 | --manifest-path dolby_vision/Cargo.toml
47 |
48 | - name: Clippy
49 | run: |
50 | cargo clippy --all-features \
51 | --all-targets --tests -- --deny warnings
52 |
53 | cargo clippy --all-features \
54 | --manifest-path dolby_vision/Cargo.toml \
55 | --all-targets --tests -- --deny warnings
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | **/*.rs.bk
3 | /*.mkv
4 | /*.hevc
5 | /*.txt
6 | /*.vpy
7 | /*.bin
8 | /*.ffindex
9 | /*.json
10 | /*.xml
11 | .vscode
12 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "dovi_tool"
3 | version = "2.3.0"
4 | authors = ["quietvoid"]
5 | edition = "2024"
6 | rust-version = "1.85.0"
7 | license = "MIT"
8 | repository = "https://github.com/quietvoid/dovi_tool"
9 | build = "build.rs"
10 |
11 | [[bin]]
12 | name = "dovi_tool"
13 | path = "src/main.rs"
14 |
15 | [dependencies]
16 | dolby_vision = { path = "dolby_vision", "features" = ["xml", "serde"] }
17 | bitvec_helpers = { version = "3.1.6", default-features = false, features = ["bitstream-io"] }
18 | hevc_parser = { version = "0.6.8", features = ["hevc_io"] }
19 | madvr_parse = "1.0.2"
20 | hdr10plus = { version = "2.1.3", features = ["json"] }
21 |
22 | anyhow = "1.0.98"
23 | clap = { version = "4.5.39", features = ["derive", "wrap_help", "deprecated"] }
24 | clap_lex = "*"
25 | indicatif = "0.17.11"
26 | bitvec = "1.0.1"
27 | serde = { version = "1.0.219", features = ["derive"] }
28 | serde_json = { version = "1.0.140", features = ["preserve_order"] }
29 | itertools = "0.14.0"
30 | plotters = { version = "0.3.7", default-features = false, features = ["bitmap_backend", "bitmap_encoder", "all_series"] }
31 |
32 | [dev-dependencies]
33 | assert_cmd = "2.0.17"
34 | assert_fs = "1.1.3"
35 | predicates = "3.1.3"
36 |
37 | [build-dependencies]
38 | anyhow = "1.0.98"
39 | vergen-gitcl = { version = "1.0.8", default-features = false, features = ["build"] }
40 |
41 | [features]
42 | default = ["system-font"]
43 | system-font = ["plotters/ttf"]
44 | internal-font = ["plotters/ab_glyph"]
45 |
46 | [profile.release-deploy]
47 | inherits = "release"
48 | lto = "thin"
49 | strip = "symbols"
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 quietvoid
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/assets/NotoSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/NotoSans-Regular.ttf
--------------------------------------------------------------------------------
/assets/av1-rpu/fel_orig.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/av1-rpu/fel_orig.bin
--------------------------------------------------------------------------------
/assets/av1-rpu/p5-01-ref.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/av1-rpu/p5-01-ref.bin
--------------------------------------------------------------------------------
/assets/av1-rpu/p5-01.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/av1-rpu/p5-01.bin
--------------------------------------------------------------------------------
/assets/av1-rpu/p84-01-ref.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/av1-rpu/p84-01-ref.bin
--------------------------------------------------------------------------------
/assets/av1-rpu/p84-01.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/av1-rpu/p84-01.bin
--------------------------------------------------------------------------------
/assets/editor_examples/active_area.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": 2,
3 | "active_area": {
4 | "crop": true,
5 | "presets": [
6 | {
7 | "id": 0,
8 | "left": 0,
9 | "right": 0,
10 | "top": 210,
11 | "bottom": 210
12 | }
13 | ],
14 | "edits": {
15 | "0-40": 0
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/assets/editor_examples/active_area_all.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": 0,
3 | "active_area": {
4 | "presets": [
5 | {
6 | "id": 0,
7 | "left": 0,
8 | "right": 0,
9 | "top": 210,
10 | "bottom": 210
11 | }
12 | ],
13 | "edits": {
14 | "all": 0
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/assets/editor_examples/crop.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": 2,
3 | "active_area": {
4 | "crop": true
5 | }
6 | }
--------------------------------------------------------------------------------
/assets/editor_examples/duplicate.json:
--------------------------------------------------------------------------------
1 | {
2 | "duplicate": [
3 | {
4 | "source": 0,
5 | "offset": 39,
6 | "length": 10
7 | }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/assets/editor_examples/fel_to_p81_encode.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": 2,
3 | "remove_mapping": true,
4 | "active_area": {
5 | "crop": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/assets/editor_examples/l9_and_l11.json:
--------------------------------------------------------------------------------
1 | {
2 | "level9": "BT.2020",
3 | "level11": {
4 | "content_type": 1,
5 | "whitepoint": 0,
6 | "reference_mode_flag": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/assets/editor_examples/level6_metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "level6": {
3 | "max_display_mastering_luminance": 1000,
4 | "min_display_mastering_luminance": 1,
5 | "max_content_light_level": 1000,
6 | "max_frame_average_light_level": 400
7 | }
8 | }
--------------------------------------------------------------------------------
/assets/editor_examples/levels.json:
--------------------------------------------------------------------------------
1 | {
2 | "min_pq": 7,
3 | "max_pq": 3079
4 | }
--------------------------------------------------------------------------------
/assets/editor_examples/mode.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": 2
3 | }
--------------------------------------------------------------------------------
/assets/editor_examples/p5_to_p81.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": 3
3 | }
--------------------------------------------------------------------------------
/assets/editor_examples/remove.json:
--------------------------------------------------------------------------------
1 | {
2 | "remove": [
3 | "0-39"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/assets/editor_examples/remove_cmv4.json:
--------------------------------------------------------------------------------
1 | {
2 | "remove_cmv4": true
3 | }
4 |
--------------------------------------------------------------------------------
/assets/editor_examples/scene_cuts.json:
--------------------------------------------------------------------------------
1 | {
2 | "scene_cuts": {
3 | "all": true,
4 | "0-39": false
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/assets/editor_examples/source_rpu.json:
--------------------------------------------------------------------------------
1 | {
2 | "source_rpu": "assets/tests/cmv40_full_rpu.bin",
3 | "rpu_levels": [1, 4, 6]
4 | }
5 |
--------------------------------------------------------------------------------
/assets/generator_examples/default_cmv29.json:
--------------------------------------------------------------------------------
1 | {
2 | "cm_version": "V29",
3 | "length": 10,
4 | "level6": {
5 | "max_display_mastering_luminance": 1000,
6 | "min_display_mastering_luminance": 1,
7 | "max_content_light_level": 1000,
8 | "max_frame_average_light_level": 400
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/assets/generator_examples/default_cmv40.json:
--------------------------------------------------------------------------------
1 | {
2 | "cm_version": "V40",
3 | "length": 10,
4 | "level6": {
5 | "max_display_mastering_luminance": 1000,
6 | "min_display_mastering_luminance": 1,
7 | "max_content_light_level": 1000,
8 | "max_frame_average_light_level": 400
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/assets/generator_examples/full_example.json:
--------------------------------------------------------------------------------
1 | {
2 | "cm_version": "V40",
3 | "profile": "8.1",
4 | "long_play_mode": false,
5 | "level5": {
6 | "active_area_left_offset": 0,
7 | "active_area_right_offset": 0,
8 | "active_area_top_offset": 40,
9 | "active_area_bottom_offset": 40
10 | },
11 | "level6": {
12 | "max_display_mastering_luminance": 1000,
13 | "min_display_mastering_luminance": 1,
14 | "max_content_light_level": 1000,
15 | "max_frame_average_light_level": 400
16 | },
17 | "default_metadata_blocks": [
18 | {
19 | "Level2": {
20 | "target_max_pq": 3079,
21 | "trim_slope": 2048,
22 | "trim_offset": 2048,
23 | "trim_power": 2048,
24 | "trim_chroma_weight": 2048,
25 | "trim_saturation_gain": 2048,
26 | "ms_weight": 2048
27 | }
28 | },
29 | {
30 | "Level9": {
31 | "length": 1,
32 | "source_primary_index": 0
33 | }
34 | },
35 | {
36 | "Level11": {
37 | "content_type": 4,
38 | "whitepoint": 0,
39 | "reference_mode_flag": true
40 | }
41 | }
42 | ],
43 | "shots": [
44 | {
45 | "start": 0,
46 | "duration": 10,
47 | "metadata_blocks": [
48 | {
49 | "Level1": {
50 | "min_pq": 2,
51 | "max_pq": 2938,
52 | "avg_pq": 1456
53 | }
54 | },
55 | {
56 | "Level2": {
57 | "target_max_pq": 2851,
58 | "trim_slope": 2048,
59 | "trim_offset": 2048,
60 | "trim_power": 1800,
61 | "trim_chroma_weight": 2048,
62 | "trim_saturation_gain": 2048,
63 | "ms_weight": 2048
64 | }
65 | }
66 | ],
67 | "frame_edits": [
68 | {
69 | "edit_offset": 5,
70 | "metadata_blocks": [
71 | {
72 | "Level1": {
73 | "min_pq": 0,
74 | "max_pq": 3079,
75 | "avg_pq": 1229
76 | }
77 | }
78 | ]
79 | }
80 | ]
81 | }
82 | ]
83 | }
84 |
--------------------------------------------------------------------------------
/assets/generator_examples/l1_cmv29.json:
--------------------------------------------------------------------------------
1 | {
2 | "cm_version": "V29",
3 | "level6": {
4 | "max_display_mastering_luminance": 1000,
5 | "min_display_mastering_luminance": 1,
6 | "max_content_light_level": 1000,
7 | "max_frame_average_light_level": 400
8 | },
9 | "shots": [
10 | {
11 | "start": 0,
12 | "duration": 1,
13 | "metadata_blocks": [
14 | {
15 | "Level1": {
16 | "min_pq": 0,
17 | "max_pq": 1500,
18 | "avg_pq": 506
19 | }
20 | }
21 | ]
22 | },
23 | {
24 | "start": 1,
25 | "duration": 1,
26 | "metadata_blocks": [
27 | {
28 | "Level1": {
29 | "min_pq": 0,
30 | "max_pq": 2604,
31 | "avg_pq": 1340
32 | }
33 | }
34 | ]
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/assets/generator_examples/l1_cmv29_override_avg_cmv40.json:
--------------------------------------------------------------------------------
1 | {
2 | "cm_version": "V29",
3 | "l1_avg_pq_cm_version": "V40",
4 | "level6": {
5 | "max_display_mastering_luminance": 1000,
6 | "min_display_mastering_luminance": 1,
7 | "max_content_light_level": 1000,
8 | "max_frame_average_light_level": 400
9 | },
10 | "shots": [
11 | {
12 | "start": 0,
13 | "duration": 1,
14 | "metadata_blocks": [
15 | {
16 | "Level1": {
17 | "min_pq": 0,
18 | "max_pq": 1678,
19 | "avg_pq": 948
20 | }
21 | }
22 | ]
23 | },
24 | {
25 | "start": 1,
26 | "duration": 1,
27 | "metadata_blocks": [
28 | {
29 | "Level1": {
30 | "min_pq": 0,
31 | "max_pq": 3074,
32 | "avg_pq": 1450
33 | }
34 | }
35 | ]
36 | }
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/assets/generator_examples/l1_cmv40.json:
--------------------------------------------------------------------------------
1 | {
2 | "level6": {
3 | "max_display_mastering_luminance": 1000,
4 | "min_display_mastering_luminance": 1,
5 | "max_content_light_level": 1000,
6 | "max_frame_average_light_level": 400
7 | },
8 | "shots": [
9 | {
10 | "start": 0,
11 | "duration": 1,
12 | "metadata_blocks": [
13 | {
14 | "Level1": {
15 | "min_pq": 0,
16 | "max_pq": 1678,
17 | "avg_pq": 948
18 | }
19 | }
20 | ]
21 | },
22 | {
23 | "start": 1,
24 | "duration": 1,
25 | "metadata_blocks": [
26 | {
27 | "Level1": {
28 | "min_pq": 0,
29 | "max_pq": 3074,
30 | "avg_pq": 1450
31 | }
32 | }
33 | ]
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/assets/generator_examples/no_duration.json:
--------------------------------------------------------------------------------
1 | {
2 | "cm_version": "V40",
3 | "level6": {
4 | "max_display_mastering_luminance": 1000,
5 | "min_display_mastering_luminance": 1,
6 | "max_content_light_level": 1000,
7 | "max_frame_average_light_level": 400
8 | },
9 | "default_metadata_blocks": [
10 | {
11 | "Level1": {
12 | "min_pq": 0,
13 | "max_pq": 123,
14 | "avg_pq": 39
15 | }
16 | },
17 | {
18 | "Level9": {
19 | "length": 1,
20 | "source_primary_index": 0
21 | }
22 | }
23 | ],
24 | "shots": [
25 | {
26 | "start": 0,
27 | "duration": 0,
28 | "metadata_blocks": [
29 | {
30 | "Level1": {
31 | "min_pq": 0,
32 | "max_pq": 123,
33 | "avg_pq": 39
34 | }
35 | },
36 | {
37 | "Level2": {
38 | "target_max_pq": 2851,
39 | "trim_slope": 2048,
40 | "trim_offset": 2048,
41 | "trim_power": 1800,
42 | "trim_chroma_weight": 2048,
43 | "trim_saturation_gain": 2048,
44 | "ms_weight": 2048
45 | }
46 | }
47 | ]
48 | },
49 | {
50 | "start": 0,
51 | "duration": 0,
52 | "metadata_blocks": [
53 | {
54 | "Level2": {
55 | "target_max_pq": 2851,
56 | "trim_slope": 1400,
57 | "trim_offset": 1234,
58 | "trim_power": 1800,
59 | "trim_chroma_weight": 2048,
60 | "trim_saturation_gain": 2048,
61 | "ms_weight": 2048
62 | }
63 | },
64 | {
65 | "Level5": {
66 | "active_area_left_offset": 0,
67 | "active_area_right_offset": 0,
68 | "active_area_top_offset": 276,
69 | "active_area_bottom_offset": 276
70 | }
71 | }
72 | ],
73 | "frame_edits": [
74 | {
75 | "edit_offset": 2,
76 | "metadata_blocks": [
77 | {
78 | "Level1": {
79 | "min_pq": 0,
80 | "max_pq": 3079,
81 | "avg_pq": 1229
82 | }
83 | },
84 | {
85 | "Level2": {
86 | "target_max_pq": 2851,
87 | "trim_slope": 1999,
88 | "trim_offset": 1999,
89 | "trim_power": 1999,
90 | "trim_chroma_weight": 2048,
91 | "trim_saturation_gain": 2048,
92 | "ms_weight": 2048
93 | }
94 | },
95 | {
96 | "Level2": {
97 | "target_max_pq": 3079,
98 | "trim_slope": 2048,
99 | "trim_offset": 2048,
100 | "trim_power": 2048,
101 | "trim_chroma_weight": 2048,
102 | "trim_saturation_gain": 2048,
103 | "ms_weight": 2048
104 | }
105 | }
106 | ]
107 | }
108 | ]
109 | }
110 | ]
111 | }
112 |
--------------------------------------------------------------------------------
/assets/hevc_tests/no_aud_bl.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/no_aud_bl.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/no_aud_injected.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/no_aud_injected.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/no_aud_muxed.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/no_aud_muxed.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/regular.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/regular.mkv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular.mkv
--------------------------------------------------------------------------------
/assets/hevc_tests/regular_bl_start_code_4.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular_bl_start_code_4.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/regular_bl_start_code_4_shorter.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular_bl_start_code_4_shorter.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/regular_convert_annexb.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular_convert_annexb.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/regular_demux_bl_annexb.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular_demux_bl_annexb.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/regular_inject_annexb.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular_inject_annexb.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/regular_rpu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular_rpu.bin
--------------------------------------------------------------------------------
/assets/hevc_tests/regular_rpu_mel.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular_rpu_mel.bin
--------------------------------------------------------------------------------
/assets/hevc_tests/regular_start_code_4.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular_start_code_4.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/regular_start_code_4_muxed_el.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular_start_code_4_muxed_el.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/regular_start_code_4_shorter_muxed_el.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/regular_start_code_4_shorter_muxed_el.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/sei-double-3byte-case.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/sei-double-3byte-case.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/sei-double-3byte-start-code-4.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/sei-double-3byte-start-code-4.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/sei-suffix-muxed-rpu.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/sei-suffix-muxed-rpu.hevc
--------------------------------------------------------------------------------
/assets/hevc_tests/yusesope_regular_muxed.hevc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/hevc_tests/yusesope_regular_muxed.hevc
--------------------------------------------------------------------------------
/assets/tests/cmv2_9_xml_with_l5_rpu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/cmv2_9_xml_with_l5_rpu.bin
--------------------------------------------------------------------------------
/assets/tests/cmv40_full_rpu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/cmv40_full_rpu.bin
--------------------------------------------------------------------------------
/assets/tests/cmv4_0_2_custom_displays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.2
4 |
5 |
6 | 2020-02-17T11:29:59Z
7 | Blackmagic Design
8 | DaVinci Resolve Studio
9 | 16.1.2.026
10 |
11 |
12 |
13 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/assets/tests/cmv4_0_2_custom_displays_xml_rpu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/cmv4_0_2_custom_displays_xml_rpu.bin
--------------------------------------------------------------------------------
/assets/tests/cmv4_0_2_xml_rpu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/cmv4_0_2_xml_rpu.bin
--------------------------------------------------------------------------------
/assets/tests/cmv4_0_2_xml_with_l5_rpu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/cmv4_0_2_xml_with_l5_rpu.bin
--------------------------------------------------------------------------------
/assets/tests/cmv4_2_510_xml_rpu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/cmv4_2_510_xml_rpu.bin
--------------------------------------------------------------------------------
/assets/tests/data_before_crc32.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/data_before_crc32.bin
--------------------------------------------------------------------------------
/assets/tests/empty_dmv1_blocks.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/empty_dmv1_blocks.bin
--------------------------------------------------------------------------------
/assets/tests/eof_rpu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/eof_rpu.bin
--------------------------------------------------------------------------------
/assets/tests/fel_orig.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/fel_orig.bin
--------------------------------------------------------------------------------
/assets/tests/fel_rpu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/fel_rpu.bin
--------------------------------------------------------------------------------
/assets/tests/fel_to_81.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/fel_to_81.bin
--------------------------------------------------------------------------------
/assets/tests/fel_to_mel.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/fel_to_mel.bin
--------------------------------------------------------------------------------
/assets/tests/fix_se_write.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/fix_se_write.bin
--------------------------------------------------------------------------------
/assets/tests/mel_orig.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/mel_orig.bin
--------------------------------------------------------------------------------
/assets/tests/mel_rpu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/mel_rpu.bin
--------------------------------------------------------------------------------
/assets/tests/mel_to_81.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/mel_to_81.bin
--------------------------------------------------------------------------------
/assets/tests/mel_to_mel.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/mel_to_mel.bin
--------------------------------------------------------------------------------
/assets/tests/mel_variable_l8_length13.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/mel_variable_l8_length13.bin
--------------------------------------------------------------------------------
/assets/tests/p8_001_end_crc32.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/p8_001_end_crc32.bin
--------------------------------------------------------------------------------
/assets/tests/poly_coef_int_logic.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/poly_coef_int_logic.bin
--------------------------------------------------------------------------------
/assets/tests/profile20_apple.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/profile20_apple.bin
--------------------------------------------------------------------------------
/assets/tests/profile4.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/profile4.bin
--------------------------------------------------------------------------------
/assets/tests/profile5-02.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/profile5-02.bin
--------------------------------------------------------------------------------
/assets/tests/profile5.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/profile5.bin
--------------------------------------------------------------------------------
/assets/tests/profile8.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/profile8.bin
--------------------------------------------------------------------------------
/assets/tests/profile84.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/profile84.bin
--------------------------------------------------------------------------------
/assets/tests/profile8_from_profile5-02.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/profile8_from_profile5-02.bin
--------------------------------------------------------------------------------
/assets/tests/source_p5_to_p8_001_end_crc32.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/source_p5_to_p8_001_end_crc32.bin
--------------------------------------------------------------------------------
/assets/tests/source_rpu_replaced_fel_orig.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/source_rpu_replaced_fel_orig.bin
--------------------------------------------------------------------------------
/assets/tests/st2094_10_level3.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/st2094_10_level3.bin
--------------------------------------------------------------------------------
/assets/tests/trailing_bytes_rpu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/trailing_bytes_rpu.bin
--------------------------------------------------------------------------------
/assets/tests/unordered_l8_blocks.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quietvoid/dovi_tool/4fd2b2235c9f93582dd4a00e65ee34a07800afd7/assets/tests/unordered_l8_blocks.bin
--------------------------------------------------------------------------------
/build.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use vergen_gitcl::{Emitter, GitclBuilder};
3 |
4 | fn main() -> Result<()> {
5 | let gitcl = GitclBuilder::default()
6 | .describe(true, true, Some("[0-9]*"))
7 | .build()?;
8 |
9 | let gitcl_res = Emitter::default()
10 | .idempotent()
11 | .fail_on_error()
12 | .add_instructions(&gitcl)
13 | .and_then(|emitter| emitter.emit());
14 |
15 | if let Err(e) = gitcl_res {
16 | eprintln!("error occured while generating instructions: {e:?}");
17 | Emitter::default().idempotent().fail_on_error().emit()?;
18 | }
19 |
20 | Ok(())
21 | }
22 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | Documentation related to `dovi_tool` usage
2 |
3 | #### [Editor configuration](editor.md)
4 | #### [Generator configuration](generator.md)
5 | #### [Information about Dolby Vision profiles](profiles.md)
6 |
--------------------------------------------------------------------------------
/docs/editor.md:
--------------------------------------------------------------------------------
1 | The RPUs can be edited in two ways:
2 | - Using the **`editor`** subcommand.
3 | - Using the **`--edit-config`** option when using HEVC commands.
4 |
5 | When doing HEVC operations, some capabilities are not supported:
6 | - Editing the active area for specific ranges of frames. Only `"all"` edit is supported.
7 | - Removing or duplicating RPUs.
8 | - Editing scene cuts.
9 | - Replacing metadata from a second RPU file.
10 |
11 |
12 |
13 | The editor expects a JSON config like the example below:
14 | ```json5
15 | {
16 | // Mode to convert the RPU (refer to README)
17 | "mode": int,
18 |
19 | // Removes CM v4.0 from the RPU:
20 | // - L3, L8, L9, L10 and L11 are removed
21 | // - DM v2 metadata is removed, along with L254
22 | "remove_cmv4": boolean,
23 |
24 | // Whether to remove polynomial/MMR mapping coefficients from the metadata
25 | "remove_mapping": boolean,
26 |
27 | // Source min/max PQ values to override
28 | "min_pq": int,
29 | "max_pq": int,
30 |
31 | // Configuration for active area edits
32 | // If no L5 metadata is present in the RPU, L5 metadata is inserted
33 | "active_area": {
34 | // Should be set to true when final video has no letterbox bars
35 | "crop": boolean,
36 |
37 | // Optional, specifies whether to drop some or all L5 metadata.
38 | // This produces spec non conformant RPUs.
39 | // Possible options: "all", "zeroes"
40 | // "zeroes" drops the L5 metadata blocks which have all offsets set to zero.
41 | "drop_l5": string,
42 |
43 | // List of presets to add letterbox bars
44 | "presets": [
45 | {
46 | "id": int,
47 | "left": int,
48 | "right": int,
49 | "top": int,
50 | "bottom": int
51 | }
52 | ],
53 |
54 | // List of edits
55 | "edits": {
56 | // All or a specific range of frames (inclusive) to use preset for
57 | // Edits before an "all" key can be overriden
58 | "all": presetId,
59 | "0-39": presetId
60 | }
61 | },
62 |
63 | // List of frames or frame ranges to remove (inclusive)
64 | // Frames are removed before the duplicate passes
65 | "remove": [
66 | "0-39"
67 | ],
68 |
69 | // List of duplicate operations
70 | "duplicate": [
71 | {
72 | // Frame to use as metadata source
73 | "source": int,
74 | // Index at which the duplicated frames are added (inclusive)
75 | "offset": int,
76 | // Number of frames to duplicate
77 | "length": int
78 | }
79 | ],
80 |
81 | // Set the scene cut (scene_refresh_flag) flag for specific frame ranges
82 | // Range options: "all" or formatted as "start-end"
83 | "scene_cuts": {
84 | "all": true,
85 | "0-39": false
86 | },
87 |
88 | // Level 6, ST2086 fallback metadata
89 | // Optional
90 | // Replaces existing L6 metadata values.
91 | // Otherwise, creates the L6 metadata block.
92 | "level6": {
93 | "max_display_mastering_luminance": int,
94 | "min_display_mastering_luminance": int,
95 | "max_content_light_level": int,
96 | "max_frame_average_light_level": int
97 | },
98 |
99 |
100 | // Level 9 Mastering Display Primaries
101 | // Optional, replaces existing L9.
102 | // The RPU must already be CM v4.0 for this to have any effect
103 | //
104 | // String value, must match enum.
105 | // Default: "DCIP3D65".
106 | "level9": MasteringDisplayPrimaries,
107 |
108 | // Level 11 Content type metadata
109 | // Optional, replaces existing L11
110 | // The RPU must already be CM v4.0 for this to have any effect
111 | "level11": {
112 | // 1 = Cinema, 2 = Games, 3 = Sports, 4 = User generated content
113 | "content_type": int,
114 |
115 | // WP * 375 + 6504
116 | // D65 = 0
117 | "whitepoint": int,
118 |
119 | // Whether to force reference mode or not.
120 | "reference_mode_flag": boolean
121 | },
122 |
123 | // Level 255 extension block structure
124 | "level255": ExtMetadataBlockLevel255,
125 |
126 | // Source RPU file to use metadata from
127 | // The RPUs must have the same length, after the `remove` pass.
128 | //
129 | // Path must be absolute.
130 | "source_rpu": string,
131 |
132 | // Levels to replace using metadata from `source_rpu`
133 | // List of integers representing block levels
134 | "rpu_levels": int[],
135 |
136 | // EXPERIMENTAL: Allows transferring CM v4.0 levels (L3, L8, L9, L10, L11, L254) to CM v2.9 RPU.
137 | // This implicitly converts the input RPU to CM v4.0 format, with default L254.
138 | //
139 | // WARNING: This is not normally allowed and may lead to playback issues (e.g., if L8 is not transferred).
140 | // Intended for advanced/experimental workflows only. Use at your own risk.
141 | //
142 | // Default: false
143 | "allow_cmv4_transfer": boolean,
144 | }
145 | ```
146 |
--------------------------------------------------------------------------------
/docs/generator.md:
--------------------------------------------------------------------------------
1 | The generator can create a profile 8.1 or 8.4 RPU binary.
2 |
3 | Any extension metadata can be added, but adding blocks is for advanced usage.
4 | Ideally, most custom blocks usage should be scripted, especially when shots are involved.
5 | For the structure expected for deserialization, see [metadata blocks](../dolby_vision/src/rpu/extension_metadata/blocks).
6 | The expected JSON format is the same as output by the `info` subcommand.
7 |
8 | A JSON config example:
9 |
10 | ```json5
11 | {
12 | // CM version, either "V29" or "V40".
13 | // Defaults to "V40".
14 | "cm_version": string,
15 |
16 | // Profile to generate
17 | // - 8.1: HDR10 base layer (default)
18 | // - 8.4: HLG base layer with static reshaping
19 | "profile": string,
20 |
21 | // Number of metadata frames to generate.
22 | // Optional if shots are specified, as well as for HDR10+ and madVR sourced generation.
23 | "length": int,
24 |
25 | // Source min/max PQ values to override, optional.
26 | // If not specified, derived from L6 metadata.
27 | "source_min_pq": int,
28 | "source_max_pq": int,
29 |
30 | // CM version to override the minimum L1 `avg_pq`
31 | "l1_avg_pq_cm_version": string,
32 |
33 | // L5 metadata, optional.
34 | // If not specified, L5 metadata is added with 0 offsets.
35 | "level5": {
36 | "active_area_left_offset": int,
37 | "active_area_right_offset": int,
38 | "active_area_top_offset": int,
39 | "active_area_bottom_offset": int,
40 | },
41 |
42 | // L6 metadata, required for profile 8.1.
43 | "level6": {
44 | "max_display_mastering_luminance": int,
45 | "min_display_mastering_luminance": int,
46 | "max_content_light_level": int,
47 | "max_frame_average_light_level": int,
48 | },
49 |
50 | // Metadata blocks that should be present in every RPU of the sequence.
51 | // Does not accept L5, L6 and L254 metadata.
52 | // Disallowed blocks are simply ignored.
53 | //
54 | // For HDR10+ or madVR generation, the default L1 metadata is replaced.
55 | //
56 | // Refer to assets/generator_examples/full_example.json
57 | "default_metadata_blocks": Array,
58 |
59 | // Shots to specify metadata.
60 | // Array of VideoShot objects.
61 | //
62 | // For HDR10+ or madVR generation:
63 | // - The metadata is taken from the shots in the list order.
64 | // This means that both start and duration can be 0.
65 | // - It is expected that the source metadata has the same number of shots as this list.
66 | // Missing or extra shots are ignored.
67 | //
68 | // Refer to generator examples.
69 | "shots": [
70 | {
71 | // Start frame.
72 | "start": int,
73 | // Shot frame length.
74 | "duration": int,
75 |
76 | // List of metadata blocks to use for this shot.
77 | "metadata_blocks": Array,
78 |
79 | // Metadata to use for specific frames in the shot.
80 | "frame_edits": [
81 | {
82 | // Frame offset to edit in the shot.
83 | "edit_offset": int,
84 |
85 | // List of metadata blocks to use for the frame.
86 | "metadata_blocks": Array,
87 | }
88 | ]
89 | }
90 | ],
91 | }
92 | ```
93 |
--------------------------------------------------------------------------------
/docs/profiles.md:
--------------------------------------------------------------------------------
1 | Differentiating between Dolby Vision profiles.
2 | ##### Profile 4
3 | Possibly `vdr_bit_depth_minus8` > 4
4 | ##### Profile 5
5 | `vdr_rpu_profile = 0`
6 | `bl_video_full_range_flag = 0`
7 | ##### Profile 7
8 | `vdr_rpu_profile = 1`
9 | `el_spatial_resampling_filter_flag = 1`
10 | `disable_residual_flag = 0`
11 | ##### Profile 8
12 | `vdr_rpu_profile = 1`
13 | `el_spatial_resampling_filter_flag = 0`
14 |
--------------------------------------------------------------------------------
/dolby_vision/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | **/*.rs.bk
3 | /*.mkv
4 | /*.hevc
5 | /*.txt
6 | /*.vpy
7 | /*.bin
8 | /*.ffindex
9 | /*.json
10 | /*.xml
11 |
--------------------------------------------------------------------------------
/dolby_vision/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "dolby_vision"
3 | version = "3.3.2"
4 | authors = ["quietvoid"]
5 | edition = "2024"
6 | rust-version = "1.85.0"
7 | license = "MIT"
8 | description = "Dolby Vision metadata parsing and writing"
9 | repository = "https://github.com/quietvoid/dovi_tool/tree/main/dolby_vision"
10 |
11 | [dependencies]
12 | bitvec_helpers = { version = "3.1.6", default-features = false, features = ["bitstream-io"] }
13 | anyhow = "1.0.98"
14 | bitvec = "1.0.1"
15 | crc = "3.3.0"
16 | roxmltree = { version = "0.20.0", optional = true }
17 | serde = { version = "1.0.219", features = ["derive"], "optional" = true }
18 | serde_json = { version = "1.0.140", features = ["preserve_order"], "optional" = true }
19 | tinyvec = { version = "1.9.0", features = ["rustc_1_55"] }
20 |
21 | libc = { version = "0.2", optional = true }
22 |
23 | [dev-dependencies]
24 | criterion = "0.6.0"
25 |
26 | [features]
27 | xml = ["roxmltree"]
28 | serde = ["dep:serde", "dep:serde_json", "tinyvec/serde"]
29 | capi = ["libc"]
30 |
31 | [package.metadata.docs.rs]
32 | all-features = true
33 |
34 | [package.metadata.capi.header]
35 | subdirectory = "libdovi"
36 | name = "rpu_parser"
37 |
38 | [package.metadata.capi.pkg_config]
39 | strip_include_path_components = 1
40 | subdirectory = false
41 | name = "dovi"
42 | filename = "dovi"
43 |
44 | [package.metadata.capi.library]
45 | rustflags = "-Cpanic=abort"
46 | name = "dovi"
47 |
48 | [lib]
49 | doctest = false
50 |
51 | [[bench]]
52 | name = "bench_main"
53 | harness = false
54 |
55 | [lints.rust]
56 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(cargo_c)'] }
57 |
58 | [profile.release-deploy]
59 | inherits = "release"
60 | lto = "thin"
61 | strip = "symbols"
62 |
--------------------------------------------------------------------------------
/dolby_vision/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 quietvoid
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/dolby_vision/README.md:
--------------------------------------------------------------------------------
1 | Library to read & write Dolby Vision metadata.
2 | Comes as a Rust crate and C compatible library.
3 |
4 | See [changelog](CHANGELOG.md) for API changes.
5 |
6 |
7 |
8 | ### Toolchain
9 |
10 | The minimum Rust version to use `dolby_vision` is 1.85.0.
11 |
12 |
13 |
14 | ### `libdovi`, C-API
15 |
16 | Packages
17 | - **Arch Linux**: available on the AUR, `libdovi` or `libdovi-git`.
18 |
19 |
20 |
21 | #### Building the library
22 |
23 | `libdovi` comes as a C compatible library.
24 | To build and install it you can use [cargo-c](https://crates.io/crates/cargo-c):
25 |
26 | ```sh
27 | cargo install cargo-c
28 | cargo cinstall --release
29 | ```
30 |
31 | #### Running the C-API example
32 | ```sh
33 | cd examples
34 | gcc capi_rpu_file.c -ldovi -o capi_example.o
35 | ./capi_example.o
36 | ```
37 |
--------------------------------------------------------------------------------
/dolby_vision/benches/bench_main.rs:
--------------------------------------------------------------------------------
1 | use criterion::criterion_main;
2 |
3 | mod benchmarks;
4 |
5 | criterion_main! {
6 | benchmarks::parsing::parse_rpus,
7 | benchmarks::rewriting::rewrite_rpus
8 | }
9 |
--------------------------------------------------------------------------------
/dolby_vision/benches/benchmarks/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod parsing;
2 | pub mod rewriting;
3 |
--------------------------------------------------------------------------------
/dolby_vision/benches/benchmarks/parsing.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | fs::File,
3 | io::Read,
4 | path::{Path, PathBuf},
5 | };
6 |
7 | use criterion::{Criterion, criterion_group};
8 | use dolby_vision::rpu::dovi_rpu::DoviRpu;
9 |
10 | const RPU_FILES: &[&str] = &[
11 | "profile5.bin",
12 | "profile8.bin",
13 | "fel_orig.bin",
14 | "mel_variable_l8_length13.bin",
15 | "cmv40_full_rpu.bin",
16 | "unordered_l8_blocks.bin",
17 | ];
18 |
19 | fn get_bytes>(path: P) -> Vec {
20 | let mut buf = Vec::with_capacity(500);
21 | File::open(path).unwrap().read_to_end(&mut buf).unwrap();
22 |
23 | buf
24 | }
25 |
26 | pub fn parse_single_unspec62_nalu(data: &[u8]) {
27 | DoviRpu::parse_unspec62_nalu(data).unwrap();
28 | }
29 |
30 | fn parse_single_unspec62_nalu_benchmark(c: &mut Criterion) {
31 | let lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
32 | let assets_path = lib_path.parent().unwrap().join("assets/tests");
33 |
34 | let mut group = c.benchmark_group("parse_single_unspec62_nalu");
35 |
36 | for file in RPU_FILES {
37 | let bytes = get_bytes(assets_path.join(file));
38 |
39 | group.bench_function(*file, |b| b.iter(|| parse_single_unspec62_nalu(&bytes)));
40 | }
41 | }
42 |
43 | criterion_group!(parse_rpus, parse_single_unspec62_nalu_benchmark);
44 |
--------------------------------------------------------------------------------
/dolby_vision/benches/benchmarks/rewriting.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | fs::File,
3 | io::Read,
4 | path::{Path, PathBuf},
5 | };
6 |
7 | use criterion::{Criterion, criterion_group};
8 | use dolby_vision::rpu::dovi_rpu::DoviRpu;
9 |
10 | const RPU_FILES: &[&str] = &["fel_orig.bin", "mel_variable_l8_length13.bin"];
11 |
12 | fn get_bytes>(path: P) -> Vec {
13 | let mut buf = Vec::with_capacity(500);
14 | File::open(path).unwrap().read_to_end(&mut buf).unwrap();
15 |
16 | buf
17 | }
18 |
19 | pub fn rewrite_single_unspec62_nalu(data: &[u8]) {
20 | let mut rpu = DoviRpu::parse_unspec62_nalu(data).unwrap();
21 | rpu.convert_with_mode(2).unwrap();
22 |
23 | rpu.write_hevc_unspec62_nalu().unwrap();
24 | }
25 |
26 | fn rewrite_single_unspec62_nalu_benchmark(c: &mut Criterion) {
27 | let lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
28 | let assets_path = lib_path.parent().unwrap().join("assets/tests");
29 |
30 | let mut group = c.benchmark_group("rewrite_single_unspec62_nalu");
31 |
32 | for file in RPU_FILES {
33 | let bytes = get_bytes(assets_path.join(file));
34 |
35 | group.bench_function(*file, |b| b.iter(|| rewrite_single_unspec62_nalu(&bytes)));
36 | }
37 | }
38 |
39 | criterion_group!(rewrite_rpus, rewrite_single_unspec62_nalu_benchmark);
40 |
--------------------------------------------------------------------------------
/dolby_vision/cbindgen.toml:
--------------------------------------------------------------------------------
1 | header = "// SPDX-License-Identifier: MIT"
2 | sys_includes = ["stddef.h", "stdint.h", "stdlib.h", "stdbool.h"]
3 | no_includes = true
4 | include_guard = "DOVI_H"
5 | tab_width = 4
6 | style = "Type"
7 | language = "C"
8 | cpp_compat = true
9 |
10 | [parse]
11 | parse_deps = false
12 |
13 | [export]
14 | item_types = ["constants", "enums", "structs", "unions", "typedefs", "opaque", "functions"]
15 | prefix = "Dovi"
16 |
--------------------------------------------------------------------------------
/dolby_vision/examples/capi_rpu_file.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "helpers.h"
6 |
7 | int main(void) {
8 | char *path = "../../assets/hevc_tests/regular_rpu_mel.bin";
9 | int ret;
10 |
11 | const DoviRpuOpaqueList *rpus = dovi_parse_rpu_bin_file(path);
12 | if (rpus->error) {
13 | printf("%s\n", rpus->error);
14 |
15 | dovi_rpu_list_free(rpus);
16 | return 1;
17 | }
18 |
19 | printf("Parsed RPU file: %d frames\n", rpus->len);
20 |
21 | // All the RPUs are valid at this point
22 | DoviRpuOpaque *rpu = rpus->list[0];
23 |
24 | const DoviRpuDataHeader *header = dovi_rpu_get_header(rpu);
25 |
26 | // Process the RPU..
27 | ret = process_rpu_info(rpu, header);
28 | if (ret < 0) {
29 | const char *error = dovi_rpu_get_error(rpu);
30 | printf("%s\n", error);
31 | }
32 |
33 | // Free everything
34 | dovi_rpu_free_header(header);
35 | dovi_rpu_list_free(rpus);
36 | }
37 |
--------------------------------------------------------------------------------
/dolby_vision/examples/capi_single_rpu.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "helpers.h"
6 |
7 | int main(void) {
8 | char *path = "../../assets/tests/cmv40_full_rpu.bin";
9 | int ret;
10 |
11 | size_t length;
12 | const uint8_t *buf = read_rpu_file(path, &length);
13 |
14 | DoviRpuOpaque *rpu = dovi_parse_unspec62_nalu(buf, length);
15 | free((void *) buf);
16 |
17 | // The RPU header is always present
18 | const DoviRpuDataHeader *header = dovi_rpu_get_header(rpu);
19 | if (!header) {
20 | const char *error = dovi_rpu_get_error(rpu);
21 | printf("%s\n", error);
22 |
23 | dovi_rpu_free(rpu);
24 | return 1;
25 | }
26 |
27 | // Process the RPU..
28 | ret = process_rpu_info(rpu, header);
29 | if (ret < 0) {
30 | const char *error = dovi_rpu_get_error(rpu);
31 | printf("%s\n", error);
32 | }
33 |
34 | // Free everything
35 | dovi_rpu_free_header(header);
36 | dovi_rpu_free(rpu);
37 | }
38 |
--------------------------------------------------------------------------------
/dolby_vision/examples/simple_edit.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | extern "C"
8 | {
9 | #include "helpers.h"
10 | }
11 |
12 | int main(void) {
13 | std::ifstream input("../../assets/tests/fel_orig.bin", std::ios::binary);
14 |
15 | const std::vector buf(
16 | (std::istreambuf_iterator(input)),
17 | (std::istreambuf_iterator()));
18 |
19 | input.close();
20 |
21 | auto start = std::chrono::high_resolution_clock::now();
22 |
23 | DoviRpuOpaque *rpu = dovi_parse_unspec62_nalu(buf.data(), buf.size());
24 | const DoviRpuDataHeader *header = dovi_rpu_get_header(rpu);
25 |
26 | // Only converts profile 7 as they are guaranteed to be HDR10 base
27 | if (header && header->guessed_profile == 7) {
28 | // Convert the base to 8.1 compatible
29 | // Also handles removing mapping for FEL
30 | int ret = dovi_convert_rpu_with_mode(rpu, 2);
31 |
32 | // Final video has letterboxing completely cropped
33 | ret = dovi_rpu_set_active_area_offsets(rpu, 0, 0, 0, 0);
34 |
35 | const DoviData *rpu_payload = dovi_write_unspec62_nalu(rpu);
36 |
37 | // Do something with the edited payload
38 | dovi_data_free(rpu_payload);
39 | }
40 |
41 | if (header)
42 | dovi_rpu_free_header(header);
43 |
44 | dovi_rpu_free(rpu);
45 |
46 | auto end = std::chrono::high_resolution_clock::now();
47 | std::cout << std::chrono::duration_cast(end - start).count() << " μs";
48 | }
49 |
--------------------------------------------------------------------------------
/dolby_vision/src/av1/emdf.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, ensure};
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | /// Parse the expected EMDF container with fixed values according to spec
7 | /// Returns `emdf_payload_size`
8 | pub(crate) fn parse_emdf_container(reader: &mut BsIoSliceReader) -> Result {
9 | let emdf_version = reader.get_n::(2)?;
10 | ensure!(emdf_version == 0);
11 |
12 | let key_id = reader.get_n::(3)?;
13 | ensure!(key_id == 6);
14 |
15 | let emdf_payload_id = reader.get_n::(5)?;
16 | ensure!(emdf_payload_id == 31);
17 |
18 | let emdf_payload_id_ext = parse_variable_bits(reader, 5)?;
19 | ensure!(emdf_payload_id_ext == 225);
20 |
21 | ensure!(!reader.get()?); // smploffste = 0
22 | ensure!(!reader.get()?); // duratione = 0
23 | ensure!(!reader.get()?); // groupide = 0
24 | ensure!(!reader.get()?); // codecdatae = 0
25 | ensure!(reader.get()?); // discard_unknown_payload = 1
26 |
27 | let emdf_payload_size = parse_variable_bits(reader, 8)? as usize;
28 | Ok(emdf_payload_size)
29 | }
30 |
31 | /// Write the DOVI RPU EMDF container with payload
32 | pub(crate) fn write_emdf_container_with_dovi_rpu_payload(
33 | writer: &mut BitstreamIoWriter,
34 | payload: &[u8],
35 | ) -> Result<()> {
36 | let emdf_payload_size = payload.len() as u32;
37 |
38 | write_dovi_rpu_emdf_header(writer)?;
39 | write_variable_bits(writer, emdf_payload_size, 8)?;
40 |
41 | for b in payload {
42 | writer.write_n(b, 8)?;
43 | }
44 |
45 | // emdf_payload_id and emdf_protection
46 | writer.write_n(&0, 5)?;
47 | writer.write_n(&1, 2)?;
48 | writer.write_n(&0, 2)?;
49 | writer.write_n(&0, 8)?;
50 |
51 | Ok(())
52 | }
53 |
54 | fn write_dovi_rpu_emdf_header(writer: &mut BitstreamIoWriter) -> Result<()> {
55 | writer.write_n(&0, 2)?; // emdf_version
56 | writer.write_n(&6, 3)?; // key_id
57 | writer.write_n(&31, 5)?; // emdf_payload_id
58 | write_variable_bits(writer, 225, 5)?; // emdf_payload_id_ext
59 |
60 | writer.write_n(&0, 4)?; // smploffste, duratione, groupide, codecdatae
61 | writer.write(true)?; // discard_unknown_payload
62 |
63 | Ok(())
64 | }
65 |
66 | fn parse_variable_bits(reader: &mut BsIoSliceReader, n: u32) -> Result {
67 | let mut value: u32 = 0;
68 |
69 | loop {
70 | let tmp: u32 = reader.get_n(n)?;
71 | value += tmp;
72 |
73 | // read_more flag
74 | if !reader.get()? {
75 | break;
76 | }
77 |
78 | value <<= n;
79 | value += 1 << n;
80 | }
81 |
82 | Ok(value)
83 | }
84 |
85 | fn write_variable_bits(writer: &mut BitstreamIoWriter, value: u32, n: u32) -> Result<()> {
86 | let max = 1 << n;
87 |
88 | if value > max {
89 | let mut remaining = value;
90 |
91 | loop {
92 | let tmp = remaining >> n;
93 | let clipped = tmp << n;
94 | remaining -= clipped;
95 |
96 | let byte = (clipped - max) >> n;
97 | writer.write_n(&byte, n)?;
98 | writer.write(true)?; // read_more
99 |
100 | // Stop once the remaining can be written in N bits
101 | if remaining <= max {
102 | break;
103 | }
104 | }
105 |
106 | writer.write_n(&remaining, n)?;
107 | } else {
108 | writer.write_n(&value, n)?;
109 | }
110 |
111 | writer.write(false)?;
112 |
113 | Ok(())
114 | }
115 |
--------------------------------------------------------------------------------
/dolby_vision/src/av1/mod.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, bail, ensure};
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | use crate::{
7 | av1::emdf::{parse_emdf_container, write_emdf_container_with_dovi_rpu_payload},
8 | rpu::dovi_rpu::{DoviRpu, FINAL_BYTE},
9 | };
10 |
11 | mod emdf;
12 |
13 | pub const ITU_T35_DOVI_RPU_PAYLOAD_HEADER: &[u8] =
14 | &[0x00, 0x3B, 0x00, 0x00, 0x08, 0x00, 0x37, 0xCD, 0x08];
15 | const ITU_T35_DOVI_RPU_PAYLOAD_HEADER_LEN: usize = ITU_T35_DOVI_RPU_PAYLOAD_HEADER.len();
16 |
17 | /// Parse AV1 ITU-T T.35 metadata OBU into a `DoviRpu`
18 | /// The payload is extracted out of the EMDF wrapper
19 | #[deprecated(
20 | since = "3.3.0",
21 | note = "Replaced by DoviRpu::parse_itu_t35_dovi_metadata_obu"
22 | )]
23 | pub fn parse_itu_t35_dovi_metadata_obu(data: &[u8]) -> Result {
24 | DoviRpu::parse_itu_t35_dovi_metadata_obu(data)
25 | }
26 |
27 | pub(crate) fn av1_validated_trimmed_data(data: &[u8]) -> Result<&[u8]> {
28 | if data.len() < 34 {
29 | bail!("Invalid RPU length: {}", data.len());
30 | }
31 |
32 | let data = if data[0] == 0xB5 {
33 | // itu_t_t35_country_code - United States
34 | // Remove from buffer
35 | &data[1..]
36 | } else {
37 | data
38 | };
39 |
40 | let trimmed_data = match &data[..ITU_T35_DOVI_RPU_PAYLOAD_HEADER_LEN] {
41 | ITU_T35_DOVI_RPU_PAYLOAD_HEADER => data,
42 | _ => bail!(
43 | "Invalid AV1 RPU payload header: {:?}",
44 | &data[..ITU_T35_DOVI_RPU_PAYLOAD_HEADER_LEN]
45 | ),
46 | };
47 |
48 | Ok(trimmed_data)
49 | }
50 |
51 | /// Returns the EMDF payload bytes representing the RPU buffer
52 | pub(crate) fn convert_av1_rpu_payload_to_regular(data: &[u8]) -> Result> {
53 | let mut reader = BsIoSliceReader::from_slice(data);
54 |
55 | let itu_t_t35_terminal_provider_code = reader.get_n::(16)?;
56 | ensure!(itu_t_t35_terminal_provider_code == 0x3B);
57 |
58 | let itu_t_t35_terminal_provider_oriented_code = reader.get_n::(32)?;
59 | ensure!(itu_t_t35_terminal_provider_oriented_code == 0x800);
60 |
61 | let emdf_payload_size = parse_emdf_container(&mut reader)?;
62 | let mut converted_buf = Vec::with_capacity(emdf_payload_size + 1);
63 | converted_buf.push(0x19);
64 |
65 | for _ in 0..emdf_payload_size {
66 | converted_buf.push(reader.get_n(8)?);
67 | }
68 |
69 | Ok(converted_buf)
70 | }
71 |
72 | /// Wraps a regular RPU into EMDF container with ITU-T T.35 header
73 | /// Buffer must start with 0x19 prefix.
74 | ///
75 | /// Returns payload for AV1 ITU T-T.35 metadata OBU
76 | pub fn convert_regular_rpu_to_av1_payload(data: &[u8]) -> Result> {
77 | ensure!(data[0] == 0x19);
78 |
79 | // The EMDF payload must not include any trailing bytes after 0x80 terminator
80 | let trailing_zeroes = data.iter().rev().take_while(|b| **b == 0).count();
81 | let rpu_end = data.len() - trailing_zeroes;
82 | let last_byte = data[rpu_end - 1];
83 |
84 | if last_byte != FINAL_BYTE {
85 | bail!("Invalid RPU last byte: {}", last_byte);
86 | }
87 |
88 | // Exclude 0x19 prefix
89 | let data = &data[1..rpu_end];
90 | let rpu_size = data.len();
91 | let capacity = 16 + rpu_size;
92 |
93 | let mut writer = BitstreamIoWriter::with_capacity(capacity * 2);
94 |
95 | writer.write_n(&0x3B, 16)?; // itu_t_t35_terminal_provider_code
96 | writer.write_n(&0x800, 32)?; // itu_t_t35_terminal_provider_oriented_code
97 |
98 | write_emdf_container_with_dovi_rpu_payload(&mut writer, data)?;
99 |
100 | while !writer.is_aligned() {
101 | writer.write(true)?;
102 | }
103 |
104 | Ok(writer.into_inner())
105 | }
106 |
--------------------------------------------------------------------------------
/dolby_vision/src/c_structs/mod.rs:
--------------------------------------------------------------------------------
1 | use crate::rpu::NUM_COMPONENTS;
2 |
3 | mod buffers;
4 | mod extension_metadata;
5 | mod rpu;
6 | mod rpu_data_header;
7 | mod rpu_data_mapping;
8 | mod rpu_data_nlq;
9 | mod vdr_dm_data;
10 |
11 | pub use buffers::*;
12 | pub use extension_metadata::DmData;
13 | pub use rpu::{RpuOpaque, RpuOpaqueList};
14 | pub use rpu_data_header::RpuDataHeader;
15 | pub use rpu_data_mapping::RpuDataMapping;
16 | pub use rpu_data_nlq::RpuDataNlq;
17 | pub use vdr_dm_data::VdrDmData;
18 |
--------------------------------------------------------------------------------
/dolby_vision/src/c_structs/rpu.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::CString;
2 |
3 | use libc::{c_char, size_t};
4 |
5 | use crate::rpu::dovi_rpu::DoviRpu;
6 |
7 | use super::Freeable;
8 |
9 | /// Opaque Dolby Vision RPU.
10 | ///
11 | /// Use dovi_rpu_free to free.
12 | /// It should be freed regardless of whether or not an error occurred.
13 | pub struct RpuOpaque {
14 | /// Optional parsed RPU, present when parsing is successful.
15 | pub rpu: Option,
16 | /// Error String of the parsing, in cases of failure.
17 | pub error: Option,
18 | }
19 |
20 | /// Heap allocated list of valid RPU pointers
21 | #[repr(C)]
22 | pub struct RpuOpaqueList {
23 | pub list: *const *mut RpuOpaque,
24 | pub len: size_t,
25 |
26 | pub error: *const c_char,
27 | }
28 |
29 | impl RpuOpaque {
30 | pub(crate) fn new(rpu: Option, error: Option) -> Self {
31 | Self { rpu, error }
32 | }
33 | }
34 |
35 | impl From> for RpuOpaque {
36 | fn from(res: Result) -> Self {
37 | match res {
38 | Ok(rpu) => Self::new(Some(rpu), None),
39 | Err(e) => Self::new(
40 | None,
41 | Some(CString::new(format!("Failed parsing RPU: {e}")).unwrap()),
42 | ),
43 | }
44 | }
45 | }
46 |
47 | impl Freeable for RpuOpaqueList {
48 | unsafe fn free(&self) {
49 | unsafe {
50 | let list = Vec::from_raw_parts(self.list as *mut *mut RpuOpaque, self.len, self.len);
51 | for ptr in list {
52 | drop(Box::from_raw(ptr));
53 | }
54 |
55 | if !self.error.is_null() {
56 | drop(CString::from_raw(self.error as *mut c_char));
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/dolby_vision/src/c_structs/rpu_data_header.rs:
--------------------------------------------------------------------------------
1 | use libc::c_char;
2 | use std::ptr::null;
3 |
4 | use crate::rpu::rpu_data_header::RpuDataHeader as RuRpuDataHeader;
5 |
6 | /// C struct for rpu_data_header()
7 | #[repr(C)]
8 | pub struct RpuDataHeader {
9 | /// Profile guessed from the values in the header
10 | guessed_profile: u8,
11 |
12 | /// Enhancement layer type (FEL or MEL) if the RPU is profile 7
13 | /// null pointer if not profile 7
14 | pub(crate) el_type: *const c_char,
15 |
16 | /// Deprecated since 3.2.0
17 | /// The field is not actually part of the RPU header
18 | #[deprecated(
19 | since = "3.2.0",
20 | note = "The field is not actually part of the RPU header"
21 | )]
22 | rpu_nal_prefix: u8,
23 |
24 | rpu_type: u8,
25 | rpu_format: u16,
26 | vdr_rpu_profile: u8,
27 | vdr_rpu_level: u8,
28 | vdr_seq_info_present_flag: bool,
29 | chroma_resampling_explicit_filter_flag: bool,
30 | coefficient_data_type: u8,
31 | coefficient_log2_denom: u64,
32 | vdr_rpu_normalized_idc: u8,
33 | bl_video_full_range_flag: bool,
34 | bl_bit_depth_minus8: u64,
35 | el_bit_depth_minus8: u64,
36 | vdr_bit_depth_minus8: u64,
37 | spatial_resampling_filter_flag: bool,
38 | reserved_zero_3bits: u8,
39 | el_spatial_resampling_filter_flag: bool,
40 | disable_residual_flag: bool,
41 | vdr_dm_metadata_present_flag: bool,
42 | use_prev_vdr_rpu_flag: bool,
43 | prev_vdr_rpu_id: u64,
44 | }
45 |
46 | impl From<&RuRpuDataHeader> for RpuDataHeader {
47 | fn from(header: &RuRpuDataHeader) -> Self {
48 | #[allow(deprecated)]
49 | Self {
50 | guessed_profile: header.get_dovi_profile(),
51 | el_type: null(),
52 | // FIXME: rpu_nal_prefix deprecation
53 | rpu_nal_prefix: header.rpu_nal_prefix,
54 | rpu_type: header.rpu_type,
55 | rpu_format: header.rpu_format,
56 | vdr_rpu_profile: header.vdr_rpu_profile,
57 | vdr_rpu_level: header.vdr_rpu_level,
58 | vdr_seq_info_present_flag: header.vdr_seq_info_present_flag,
59 | chroma_resampling_explicit_filter_flag: header.chroma_resampling_explicit_filter_flag,
60 | coefficient_data_type: header.coefficient_data_type,
61 | coefficient_log2_denom: header.coefficient_log2_denom,
62 | vdr_rpu_normalized_idc: header.vdr_rpu_normalized_idc,
63 | bl_video_full_range_flag: header.bl_video_full_range_flag,
64 | bl_bit_depth_minus8: header.bl_bit_depth_minus8,
65 | el_bit_depth_minus8: header.el_bit_depth_minus8,
66 | vdr_bit_depth_minus8: header.vdr_bit_depth_minus8,
67 | spatial_resampling_filter_flag: header.spatial_resampling_filter_flag,
68 | reserved_zero_3bits: header.reserved_zero_3bits,
69 | el_spatial_resampling_filter_flag: header.el_spatial_resampling_filter_flag,
70 | disable_residual_flag: header.disable_residual_flag,
71 | vdr_dm_metadata_present_flag: header.vdr_dm_metadata_present_flag,
72 | use_prev_vdr_rpu_flag: header.use_prev_vdr_rpu_flag,
73 | prev_vdr_rpu_id: header.prev_vdr_rpu_id,
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/dolby_vision/src/c_structs/rpu_data_nlq.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::CStr;
2 |
3 | use crate::rpu::{
4 | NUM_COMPONENTS,
5 | rpu_data_nlq::{DoviELType, RpuDataNlq as RuRpuDataNlq},
6 | };
7 |
8 | const FEL_CSTR: &CStr = c"FEL";
9 | const MEL_CSTR: &CStr = c"MEL";
10 |
11 | /// C struct for rpu_data_nlq()
12 | #[repr(C)]
13 | pub struct RpuDataNlq {
14 | nlq_offset: [u16; NUM_COMPONENTS],
15 | vdr_in_max_int: [u64; NUM_COMPONENTS],
16 | vdr_in_max: [u64; NUM_COMPONENTS],
17 | linear_deadzone_slope_int: [u64; NUM_COMPONENTS],
18 | linear_deadzone_slope: [u64; NUM_COMPONENTS],
19 | linear_deadzone_threshold_int: [u64; NUM_COMPONENTS],
20 | linear_deadzone_threshold: [u64; NUM_COMPONENTS],
21 | }
22 |
23 | impl DoviELType {
24 | pub const fn as_cstr(&self) -> &'static CStr {
25 | match self {
26 | DoviELType::MEL => MEL_CSTR,
27 | DoviELType::FEL => FEL_CSTR,
28 | }
29 | }
30 | }
31 |
32 | impl From<&RuRpuDataNlq> for RpuDataNlq {
33 | fn from(data: &RuRpuDataNlq) -> Self {
34 | Self {
35 | nlq_offset: data.nlq_offset,
36 | vdr_in_max_int: data.vdr_in_max_int,
37 | vdr_in_max: data.vdr_in_max,
38 | linear_deadzone_slope_int: data.linear_deadzone_slope_int,
39 | linear_deadzone_slope: data.linear_deadzone_slope,
40 | linear_deadzone_threshold_int: data.linear_deadzone_threshold_int,
41 | linear_deadzone_threshold: data.linear_deadzone_threshold,
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/dolby_vision/src/c_structs/vdr_dm_data.rs:
--------------------------------------------------------------------------------
1 | use crate::rpu::vdr_dm_data::VdrDmData as RuVdrDmData;
2 |
3 | use super::DmData;
4 |
5 | /// C struct for vdr_dm_data()
6 | #[repr(C)]
7 | pub struct VdrDmData {
8 | compressed: bool,
9 |
10 | affected_dm_metadata_id: u64,
11 | current_dm_metadata_id: u64,
12 | scene_refresh_flag: u64,
13 | ycc_to_rgb_coef0: i16,
14 | ycc_to_rgb_coef1: i16,
15 | ycc_to_rgb_coef2: i16,
16 | ycc_to_rgb_coef3: i16,
17 | ycc_to_rgb_coef4: i16,
18 | ycc_to_rgb_coef5: i16,
19 | ycc_to_rgb_coef6: i16,
20 | ycc_to_rgb_coef7: i16,
21 | ycc_to_rgb_coef8: i16,
22 | ycc_to_rgb_offset0: u32,
23 | ycc_to_rgb_offset1: u32,
24 | ycc_to_rgb_offset2: u32,
25 | rgb_to_lms_coef0: i16,
26 | rgb_to_lms_coef1: i16,
27 | rgb_to_lms_coef2: i16,
28 | rgb_to_lms_coef3: i16,
29 | rgb_to_lms_coef4: i16,
30 | rgb_to_lms_coef5: i16,
31 | rgb_to_lms_coef6: i16,
32 | rgb_to_lms_coef7: i16,
33 | rgb_to_lms_coef8: i16,
34 | signal_eotf: u16,
35 | signal_eotf_param0: u16,
36 | signal_eotf_param1: u16,
37 | signal_eotf_param2: u32,
38 | signal_bit_depth: u8,
39 | signal_color_space: u8,
40 | signal_chroma_format: u8,
41 | signal_full_range_flag: u8,
42 | source_min_pq: u16,
43 | source_max_pq: u16,
44 | source_diagonal: u16,
45 | dm_data: DmData,
46 | }
47 |
48 | impl VdrDmData {
49 | /// # Safety
50 | pub unsafe fn free(&self) {
51 | unsafe {
52 | self.dm_data.free();
53 | }
54 | }
55 | }
56 |
57 | impl From<&RuVdrDmData> for VdrDmData {
58 | fn from(data: &RuVdrDmData) -> Self {
59 | Self {
60 | compressed: data.compressed,
61 | affected_dm_metadata_id: data.affected_dm_metadata_id,
62 | current_dm_metadata_id: data.current_dm_metadata_id,
63 | scene_refresh_flag: data.scene_refresh_flag,
64 | ycc_to_rgb_coef0: data.ycc_to_rgb_coef0,
65 | ycc_to_rgb_coef1: data.ycc_to_rgb_coef1,
66 | ycc_to_rgb_coef2: data.ycc_to_rgb_coef2,
67 | ycc_to_rgb_coef3: data.ycc_to_rgb_coef3,
68 | ycc_to_rgb_coef4: data.ycc_to_rgb_coef4,
69 | ycc_to_rgb_coef5: data.ycc_to_rgb_coef5,
70 | ycc_to_rgb_coef6: data.ycc_to_rgb_coef6,
71 | ycc_to_rgb_coef7: data.ycc_to_rgb_coef7,
72 | ycc_to_rgb_coef8: data.ycc_to_rgb_coef8,
73 | ycc_to_rgb_offset0: data.ycc_to_rgb_offset0,
74 | ycc_to_rgb_offset1: data.ycc_to_rgb_offset1,
75 | ycc_to_rgb_offset2: data.ycc_to_rgb_offset2,
76 | rgb_to_lms_coef0: data.rgb_to_lms_coef0,
77 | rgb_to_lms_coef1: data.rgb_to_lms_coef1,
78 | rgb_to_lms_coef2: data.rgb_to_lms_coef2,
79 | rgb_to_lms_coef3: data.rgb_to_lms_coef3,
80 | rgb_to_lms_coef4: data.rgb_to_lms_coef4,
81 | rgb_to_lms_coef5: data.rgb_to_lms_coef5,
82 | rgb_to_lms_coef6: data.rgb_to_lms_coef6,
83 | rgb_to_lms_coef7: data.rgb_to_lms_coef7,
84 | rgb_to_lms_coef8: data.rgb_to_lms_coef8,
85 | signal_eotf: data.signal_eotf,
86 | signal_eotf_param0: data.signal_eotf_param0,
87 | signal_eotf_param1: data.signal_eotf_param1,
88 | signal_eotf_param2: data.signal_eotf_param2,
89 | signal_bit_depth: data.signal_bit_depth,
90 | signal_color_space: data.signal_color_space,
91 | signal_chroma_format: data.signal_chroma_format,
92 | signal_full_range_flag: data.signal_full_range_flag,
93 | source_min_pq: data.source_min_pq,
94 | source_max_pq: data.source_max_pq,
95 | source_diagonal: data.source_diagonal,
96 | dm_data: DmData::combine_dm_data(
97 | data.cmv29_metadata.as_ref(),
98 | data.cmv40_metadata.as_ref(),
99 | ),
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/dolby_vision/src/lib.rs:
--------------------------------------------------------------------------------
1 | /// Dolby Vision RPU (as found in HEVC type 62 NALUs) module
2 | pub mod rpu;
3 |
4 | /// Dolby Vision RPU (as found in AV1 ITU T.35 metadata OBUs)
5 | pub mod av1;
6 |
7 | /// SMPTE ST2094-10 metadata module
8 | pub mod st2094_10;
9 |
10 | /// Various utils
11 | /// cbindgen:ignore
12 | pub mod utils;
13 |
14 | /// Dolby Vision XML metadata module
15 | #[cfg(feature = "xml")]
16 | pub mod xml;
17 |
18 | /// C API module
19 | #[cfg(any(cargo_c, feature = "capi"))]
20 | mod capi;
21 |
22 | /// Structs used and exposed in the C API
23 | #[cfg(any(cargo_c, feature = "capi"))]
24 | mod c_structs;
25 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/blocks/level1.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, ensure};
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | #[cfg(feature = "serde")]
7 | use serde::{Deserialize, Serialize};
8 |
9 | use crate::rpu::vdr_dm_data::CmVersion;
10 |
11 | use super::{ExtMetadataBlock, ExtMetadataBlockInfo};
12 |
13 | /// cbindgen:ignore
14 | pub const L1_MIN_PQ_MAX_VALUE: u16 = 12;
15 | /// cbindgen:ignore
16 | pub const L1_MAX_PQ_MIN_VALUE: u16 = 2081;
17 | /// cbindgen:ignore
18 | pub const L1_MAX_PQ_MAX_VALUE: u16 = 4095;
19 | /// cbindgen:ignore
20 | pub const L1_AVG_PQ_MIN_VALUE: u16 = 819;
21 | /// cbindgen:ignore
22 | pub const L1_AVG_PQ_MIN_VALUE_CMV40: u16 = 1229;
23 |
24 | /// Statistical analysis of the frame: min, max, avg brightness.
25 | #[repr(C)]
26 | #[derive(Debug, Default, Clone)]
27 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
28 | pub struct ExtMetadataBlockLevel1 {
29 | pub min_pq: u16,
30 | pub max_pq: u16,
31 | pub avg_pq: u16,
32 | }
33 |
34 | impl ExtMetadataBlockLevel1 {
35 | pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result {
36 | Ok(ExtMetadataBlock::Level1(Self {
37 | min_pq: reader.get_n(12)?,
38 | max_pq: reader.get_n(12)?,
39 | avg_pq: reader.get_n(12)?,
40 | }))
41 | }
42 |
43 | pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
44 | self.validate()?;
45 |
46 | writer.write_n(&self.min_pq, 12)?;
47 | writer.write_n(&self.max_pq, 12)?;
48 | writer.write_n(&self.avg_pq, 12)?;
49 |
50 | Ok(())
51 | }
52 |
53 | pub fn validate(&self) -> Result<()> {
54 | ensure!(self.min_pq <= L1_MAX_PQ_MAX_VALUE);
55 | ensure!(self.max_pq <= L1_MAX_PQ_MAX_VALUE);
56 | ensure!(self.avg_pq <= L1_MAX_PQ_MAX_VALUE);
57 |
58 | Ok(())
59 | }
60 |
61 | pub fn new(min_pq: u16, max_pq: u16, avg_pq: u16) -> ExtMetadataBlockLevel1 {
62 | ExtMetadataBlockLevel1 {
63 | min_pq,
64 | max_pq,
65 | avg_pq,
66 | }
67 | }
68 |
69 | fn clamp_values_int(&mut self, cm_version: CmVersion) {
70 | let avg_min_value = match cm_version {
71 | CmVersion::V29 => L1_AVG_PQ_MIN_VALUE,
72 | CmVersion::V40 => L1_AVG_PQ_MIN_VALUE_CMV40,
73 | };
74 |
75 | self.min_pq = self.min_pq.clamp(0, L1_MIN_PQ_MAX_VALUE);
76 | self.max_pq = self.max_pq.clamp(L1_MAX_PQ_MIN_VALUE, L1_MAX_PQ_MAX_VALUE);
77 | self.avg_pq = self.avg_pq.clamp(avg_min_value, self.max_pq - 1);
78 | }
79 |
80 | // Returns a L1 metadata block clamped to valid values
81 | pub fn from_stats_cm_version(
82 | min_pq: u16,
83 | max_pq: u16,
84 | avg_pq: u16,
85 | cm_version: CmVersion,
86 | ) -> ExtMetadataBlockLevel1 {
87 | let mut block = Self::new(min_pq, max_pq, avg_pq);
88 | block.clamp_values_int(cm_version);
89 |
90 | block
91 | }
92 |
93 | pub fn clamp_values_cm_version(&mut self, cm_version: CmVersion) {
94 | self.clamp_values_int(cm_version);
95 | }
96 | }
97 |
98 | impl ExtMetadataBlockInfo for ExtMetadataBlockLevel1 {
99 | fn level(&self) -> u8 {
100 | 1
101 | }
102 |
103 | fn bytes_size(&self) -> u64 {
104 | 5
105 | }
106 |
107 | fn required_bits(&self) -> u64 {
108 | 36
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/blocks/level11.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, ensure};
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | #[cfg(feature = "serde")]
7 | use serde::{Deserialize, Serialize};
8 |
9 | use super::{ExtMetadataBlock, ExtMetadataBlockInfo};
10 |
11 | const MAX_WHITEPOINT_VALUE: u8 = 15;
12 |
13 | /// Content type metadata level
14 | #[repr(C)]
15 | #[derive(Debug, Default, Clone)]
16 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
17 | pub struct ExtMetadataBlockLevel11 {
18 | pub content_type: u8,
19 | pub whitepoint: u8,
20 | pub reference_mode_flag: bool,
21 |
22 | #[cfg_attr(feature = "serde", serde(default))]
23 | pub reserved_byte2: u8,
24 | #[cfg_attr(feature = "serde", serde(default))]
25 | pub reserved_byte3: u8,
26 | }
27 |
28 | impl ExtMetadataBlockLevel11 {
29 | pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result {
30 | let mut l11 = Self {
31 | content_type: reader.get_n(8)?,
32 | whitepoint: reader.get_n(8)?,
33 | reserved_byte2: reader.get_n(8)?,
34 | reserved_byte3: reader.get_n(8)?,
35 | ..Default::default()
36 | };
37 |
38 | if l11.whitepoint > MAX_WHITEPOINT_VALUE {
39 | l11.reference_mode_flag = true;
40 | l11.whitepoint -= MAX_WHITEPOINT_VALUE + 1;
41 | }
42 |
43 | Ok(ExtMetadataBlock::Level11(l11))
44 | }
45 |
46 | pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
47 | self.validate()?;
48 |
49 | let mut wp = self.whitepoint;
50 |
51 | if self.reference_mode_flag {
52 | wp += MAX_WHITEPOINT_VALUE + 1
53 | }
54 |
55 | writer.write_n(&self.content_type, 8)?;
56 | writer.write_n(&wp, 8)?;
57 | writer.write_n(&self.reserved_byte2, 8)?;
58 | writer.write_n(&self.reserved_byte3, 8)?;
59 |
60 | Ok(())
61 | }
62 |
63 | pub fn validate(&self) -> Result<()> {
64 | ensure!(self.content_type <= 15);
65 | ensure!(self.whitepoint <= 15);
66 | ensure!(self.reserved_byte2 == 0);
67 | ensure!(self.reserved_byte3 == 0);
68 |
69 | Ok(())
70 | }
71 |
72 | /// Cinema, reference mode, D65 whitepoint
73 | pub fn default_reference_cinema() -> Self {
74 | Self {
75 | content_type: 1,
76 | whitepoint: 0,
77 | reference_mode_flag: true,
78 | reserved_byte2: 0,
79 | reserved_byte3: 0,
80 | }
81 | }
82 | }
83 |
84 | impl ExtMetadataBlockInfo for ExtMetadataBlockLevel11 {
85 | fn level(&self) -> u8 {
86 | 11
87 | }
88 |
89 | fn bytes_size(&self) -> u64 {
90 | 4
91 | }
92 |
93 | fn required_bits(&self) -> u64 {
94 | 32
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/blocks/level2.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, ensure};
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | #[cfg(feature = "serde")]
7 | use serde::{Deserialize, Serialize};
8 |
9 | use crate::utils::nits_to_pq_12_bit;
10 |
11 | use super::{ExtMetadataBlock, ExtMetadataBlockInfo, MAX_12_BIT_VALUE};
12 |
13 | /// Creative intent trim passes per target display peak brightness
14 | #[repr(C)]
15 | #[derive(Debug, Clone)]
16 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
17 | #[cfg_attr(feature = "serde", serde(default))]
18 | pub struct ExtMetadataBlockLevel2 {
19 | pub target_max_pq: u16,
20 |
21 | pub trim_slope: u16,
22 | pub trim_offset: u16,
23 | pub trim_power: u16,
24 | pub trim_chroma_weight: u16,
25 | pub trim_saturation_gain: u16,
26 | pub ms_weight: i16,
27 | }
28 |
29 | impl ExtMetadataBlockLevel2 {
30 | pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result {
31 | let mut level2 = Self {
32 | target_max_pq: reader.get_n(12)?,
33 | trim_slope: reader.get_n(12)?,
34 | trim_offset: reader.get_n(12)?,
35 | trim_power: reader.get_n(12)?,
36 | trim_chroma_weight: reader.get_n(12)?,
37 | trim_saturation_gain: reader.get_n(12)?,
38 | ms_weight: reader.get_n::(13)? as i16,
39 | };
40 |
41 | if level2.ms_weight > MAX_12_BIT_VALUE as i16 {
42 | level2.ms_weight = level2.ms_weight.wrapping_sub(8192);
43 | }
44 |
45 | Ok(ExtMetadataBlock::Level2(level2))
46 | }
47 |
48 | pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
49 | self.validate()?;
50 |
51 | writer.write_n(&self.target_max_pq, 12)?;
52 | writer.write_n(&self.trim_slope, 12)?;
53 | writer.write_n(&self.trim_offset, 12)?;
54 | writer.write_n(&self.trim_power, 12)?;
55 | writer.write_n(&self.trim_chroma_weight, 12)?;
56 | writer.write_n(&self.trim_saturation_gain, 12)?;
57 | writer.write_signed_n(&self.ms_weight, 13)?;
58 |
59 | Ok(())
60 | }
61 |
62 | pub fn validate(&self) -> Result<()> {
63 | ensure!(self.target_max_pq <= MAX_12_BIT_VALUE);
64 | ensure!(self.trim_slope <= MAX_12_BIT_VALUE);
65 | ensure!(self.trim_offset <= MAX_12_BIT_VALUE);
66 | ensure!(self.trim_power <= MAX_12_BIT_VALUE);
67 | ensure!(self.trim_chroma_weight <= MAX_12_BIT_VALUE);
68 | ensure!(self.trim_saturation_gain <= MAX_12_BIT_VALUE);
69 | ensure!(self.ms_weight >= -1 && self.ms_weight <= (MAX_12_BIT_VALUE as i16));
70 |
71 | Ok(())
72 | }
73 |
74 | pub fn from_nits(target_nits: u16) -> ExtMetadataBlockLevel2 {
75 | ExtMetadataBlockLevel2 {
76 | target_max_pq: nits_to_pq_12_bit(target_nits),
77 | ..Default::default()
78 | }
79 | }
80 | }
81 |
82 | impl ExtMetadataBlockInfo for ExtMetadataBlockLevel2 {
83 | fn level(&self) -> u8 {
84 | 2
85 | }
86 |
87 | fn bytes_size(&self) -> u64 {
88 | 11
89 | }
90 |
91 | fn required_bits(&self) -> u64 {
92 | 85
93 | }
94 |
95 | fn sort_key(&self) -> (u8, u16) {
96 | (self.level(), self.target_max_pq)
97 | }
98 | }
99 |
100 | impl Default for ExtMetadataBlockLevel2 {
101 | fn default() -> Self {
102 | Self {
103 | target_max_pq: 2081,
104 | trim_slope: 2048,
105 | trim_offset: 2048,
106 | trim_power: 2048,
107 | trim_chroma_weight: 2048,
108 | trim_saturation_gain: 2048,
109 | ms_weight: 2048,
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/blocks/level254.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | #[cfg(feature = "serde")]
7 | use serde::{Deserialize, Serialize};
8 |
9 | use super::{ExtMetadataBlock, ExtMetadataBlockInfo};
10 |
11 | /// Metadata level present in CM v4.0
12 | #[repr(C)]
13 | #[derive(Debug, Default, Clone)]
14 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
15 | #[cfg_attr(feature = "serde", serde(default))]
16 | pub struct ExtMetadataBlockLevel254 {
17 | pub dm_mode: u8,
18 | pub dm_version_index: u8,
19 | }
20 |
21 | impl ExtMetadataBlockLevel254 {
22 | pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result {
23 | Ok(ExtMetadataBlock::Level254(Self {
24 | dm_mode: reader.get_n(8)?,
25 | dm_version_index: reader.get_n(8)?,
26 | }))
27 | }
28 |
29 | pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
30 | writer.write_n(&self.dm_mode, 8)?;
31 | writer.write_n(&self.dm_version_index, 8)?;
32 |
33 | Ok(())
34 | }
35 |
36 | pub fn cmv402_default() -> ExtMetadataBlockLevel254 {
37 | ExtMetadataBlockLevel254 {
38 | dm_mode: 0,
39 | dm_version_index: 2,
40 | }
41 | }
42 | }
43 |
44 | impl ExtMetadataBlockInfo for ExtMetadataBlockLevel254 {
45 | fn level(&self) -> u8 {
46 | 254
47 | }
48 |
49 | fn bytes_size(&self) -> u64 {
50 | 2
51 | }
52 |
53 | fn required_bits(&self) -> u64 {
54 | 16
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/blocks/level255.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | #[cfg(feature = "serde")]
7 | use serde::{Deserialize, Serialize};
8 |
9 | use super::{ExtMetadataBlock, ExtMetadataBlockInfo};
10 |
11 | /// Metadata level optionally present in CM v2.9.
12 | /// Different display modes (calibration/verify/bypass), debugging
13 | #[repr(C)]
14 | #[derive(Debug, Default, Clone)]
15 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
16 | #[cfg_attr(feature = "serde", serde(default))]
17 | pub struct ExtMetadataBlockLevel255 {
18 | pub dm_run_mode: u8,
19 | pub dm_run_version: u8,
20 | pub dm_debug0: u8,
21 | pub dm_debug1: u8,
22 | pub dm_debug2: u8,
23 | pub dm_debug3: u8,
24 | }
25 |
26 | impl ExtMetadataBlockLevel255 {
27 | pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result {
28 | Ok(ExtMetadataBlock::Level255(Self {
29 | dm_run_mode: reader.get_n(8)?,
30 | dm_run_version: reader.get_n(8)?,
31 | dm_debug0: reader.get_n(8)?,
32 | dm_debug1: reader.get_n(8)?,
33 | dm_debug2: reader.get_n(8)?,
34 | dm_debug3: reader.get_n(8)?,
35 | }))
36 | }
37 |
38 | pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
39 | writer.write_n(&self.dm_run_mode, 8)?;
40 | writer.write_n(&self.dm_run_version, 8)?;
41 | writer.write_n(&self.dm_debug0, 8)?;
42 | writer.write_n(&self.dm_debug1, 8)?;
43 | writer.write_n(&self.dm_debug2, 8)?;
44 | writer.write_n(&self.dm_debug3, 8)?;
45 |
46 | Ok(())
47 | }
48 | }
49 |
50 | impl ExtMetadataBlockInfo for ExtMetadataBlockLevel255 {
51 | fn level(&self) -> u8 {
52 | 255
53 | }
54 |
55 | fn bytes_size(&self) -> u64 {
56 | 6
57 | }
58 |
59 | fn required_bits(&self) -> u64 {
60 | 48
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/blocks/level3.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, ensure};
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | #[cfg(feature = "serde")]
7 | use serde::{Deserialize, Serialize};
8 |
9 | use super::{ExtMetadataBlock, ExtMetadataBlockInfo, MAX_12_BIT_VALUE};
10 |
11 | /// Level 1 offsets.
12 | #[repr(C)]
13 | #[derive(Debug, Clone)]
14 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
15 | #[cfg_attr(feature = "serde", serde(default))]
16 | pub struct ExtMetadataBlockLevel3 {
17 | pub min_pq_offset: u16,
18 | pub max_pq_offset: u16,
19 | pub avg_pq_offset: u16,
20 | }
21 |
22 | impl ExtMetadataBlockLevel3 {
23 | pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result {
24 | Ok(ExtMetadataBlock::Level3(Self {
25 | min_pq_offset: reader.get_n(12)?,
26 | max_pq_offset: reader.get_n(12)?,
27 | avg_pq_offset: reader.get_n(12)?,
28 | }))
29 | }
30 |
31 | pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
32 | self.validate()?;
33 |
34 | writer.write_n(&self.min_pq_offset, 12)?;
35 | writer.write_n(&self.max_pq_offset, 12)?;
36 | writer.write_n(&self.avg_pq_offset, 12)?;
37 |
38 | Ok(())
39 | }
40 |
41 | pub fn validate(&self) -> Result<()> {
42 | ensure!(self.min_pq_offset <= MAX_12_BIT_VALUE);
43 | ensure!(self.max_pq_offset <= MAX_12_BIT_VALUE);
44 | ensure!(self.avg_pq_offset <= MAX_12_BIT_VALUE);
45 |
46 | Ok(())
47 | }
48 | }
49 |
50 | impl ExtMetadataBlockInfo for ExtMetadataBlockLevel3 {
51 | fn level(&self) -> u8 {
52 | 3
53 | }
54 |
55 | fn bytes_size(&self) -> u64 {
56 | 5
57 | }
58 |
59 | fn required_bits(&self) -> u64 {
60 | 36
61 | }
62 | }
63 |
64 | impl Default for ExtMetadataBlockLevel3 {
65 | fn default() -> Self {
66 | Self {
67 | min_pq_offset: 2048,
68 | max_pq_offset: 2048,
69 | avg_pq_offset: 2048,
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/blocks/level4.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, ensure};
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | #[cfg(feature = "serde")]
7 | use serde::{Deserialize, Serialize};
8 |
9 | use super::{ExtMetadataBlock, ExtMetadataBlockInfo, MAX_12_BIT_VALUE};
10 |
11 | /// Something about temporal stability
12 | #[repr(C)]
13 | #[derive(Debug, Default, Clone)]
14 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
15 | pub struct ExtMetadataBlockLevel4 {
16 | pub anchor_pq: u16,
17 | pub anchor_power: u16,
18 | }
19 |
20 | impl ExtMetadataBlockLevel4 {
21 | pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result {
22 | Ok(ExtMetadataBlock::Level4(Self {
23 | anchor_pq: reader.get_n(12)?,
24 | anchor_power: reader.get_n(12)?,
25 | }))
26 | }
27 |
28 | pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
29 | self.validate()?;
30 |
31 | writer.write_n(&self.anchor_pq, 12)?;
32 | writer.write_n(&self.anchor_power, 12)?;
33 |
34 | Ok(())
35 | }
36 |
37 | pub fn validate(&self) -> Result<()> {
38 | ensure!(self.anchor_pq <= MAX_12_BIT_VALUE);
39 | ensure!(self.anchor_power <= MAX_12_BIT_VALUE);
40 |
41 | Ok(())
42 | }
43 | }
44 |
45 | impl ExtMetadataBlockInfo for ExtMetadataBlockLevel4 {
46 | fn level(&self) -> u8 {
47 | 4
48 | }
49 |
50 | fn bytes_size(&self) -> u64 {
51 | 3
52 | }
53 |
54 | fn required_bits(&self) -> u64 {
55 | 24
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/blocks/level5.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, ensure};
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | #[cfg(feature = "serde")]
7 | use serde::{Deserialize, Serialize};
8 |
9 | use super::{ExtMetadataBlock, ExtMetadataBlockInfo};
10 |
11 | const MAX_RESOLUTION_13_BITS: u16 = 8191;
12 |
13 | /// Active area of the picture (letterbox, aspect ratio)
14 | #[repr(C)]
15 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
16 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
17 | pub struct ExtMetadataBlockLevel5 {
18 | pub active_area_left_offset: u16,
19 | pub active_area_right_offset: u16,
20 | pub active_area_top_offset: u16,
21 | pub active_area_bottom_offset: u16,
22 | }
23 |
24 | impl ExtMetadataBlockLevel5 {
25 | pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result {
26 | Ok(ExtMetadataBlock::Level5(Self {
27 | active_area_left_offset: reader.get_n(13)?,
28 | active_area_right_offset: reader.get_n(13)?,
29 | active_area_top_offset: reader.get_n(13)?,
30 | active_area_bottom_offset: reader.get_n(13)?,
31 | }))
32 | }
33 |
34 | pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
35 | self.validate()?;
36 |
37 | writer.write_n(&self.active_area_left_offset, 13)?;
38 | writer.write_n(&self.active_area_right_offset, 13)?;
39 | writer.write_n(&self.active_area_top_offset, 13)?;
40 | writer.write_n(&self.active_area_bottom_offset, 13)?;
41 |
42 | Ok(())
43 | }
44 |
45 | pub fn validate(&self) -> Result<()> {
46 | ensure!(self.active_area_left_offset <= MAX_RESOLUTION_13_BITS);
47 | ensure!(self.active_area_right_offset <= MAX_RESOLUTION_13_BITS);
48 | ensure!(self.active_area_top_offset <= MAX_RESOLUTION_13_BITS);
49 | ensure!(self.active_area_bottom_offset <= MAX_RESOLUTION_13_BITS);
50 |
51 | Ok(())
52 | }
53 |
54 | pub fn get_offsets(&self) -> (u16, u16, u16, u16) {
55 | (
56 | self.active_area_left_offset,
57 | self.active_area_right_offset,
58 | self.active_area_top_offset,
59 | self.active_area_bottom_offset,
60 | )
61 | }
62 |
63 | pub fn get_offsets_vec(&self) -> Vec {
64 | vec![
65 | self.active_area_left_offset,
66 | self.active_area_right_offset,
67 | self.active_area_top_offset,
68 | self.active_area_bottom_offset,
69 | ]
70 | }
71 |
72 | pub fn set_offsets(&mut self, left: u16, right: u16, top: u16, bottom: u16) {
73 | self.active_area_left_offset = left;
74 | self.active_area_right_offset = right;
75 | self.active_area_top_offset = top;
76 | self.active_area_bottom_offset = bottom;
77 | }
78 |
79 | pub fn crop(&mut self) {
80 | self.active_area_left_offset = 0;
81 | self.active_area_right_offset = 0;
82 | self.active_area_top_offset = 0;
83 | self.active_area_bottom_offset = 0;
84 | }
85 |
86 | pub fn from_offsets(left: u16, right: u16, top: u16, bottom: u16) -> Self {
87 | ExtMetadataBlockLevel5 {
88 | active_area_left_offset: left,
89 | active_area_right_offset: right,
90 | active_area_top_offset: top,
91 | active_area_bottom_offset: bottom,
92 | }
93 | }
94 | }
95 |
96 | impl ExtMetadataBlockInfo for ExtMetadataBlockLevel5 {
97 | fn level(&self) -> u8 {
98 | 5
99 | }
100 |
101 | fn bytes_size(&self) -> u64 {
102 | 7
103 | }
104 |
105 | fn required_bits(&self) -> u64 {
106 | 52
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/blocks/level6.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, ensure};
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | #[cfg(feature = "serde")]
7 | use serde::{Deserialize, Serialize};
8 |
9 | use super::{ExtMetadataBlock, ExtMetadataBlockInfo};
10 |
11 | /// cbindgen:ignore
12 | pub const MAX_PQ_LUMINANCE: u16 = 10_000;
13 |
14 | /// ST2086/HDR10 metadata fallback
15 | #[repr(C)]
16 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
17 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
18 | pub struct ExtMetadataBlockLevel6 {
19 | pub max_display_mastering_luminance: u16,
20 | pub min_display_mastering_luminance: u16,
21 | pub max_content_light_level: u16,
22 | pub max_frame_average_light_level: u16,
23 | }
24 |
25 | impl ExtMetadataBlockLevel6 {
26 | pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result {
27 | Ok(ExtMetadataBlock::Level6(Self {
28 | max_display_mastering_luminance: reader.get_n(16)?,
29 | min_display_mastering_luminance: reader.get_n(16)?,
30 | max_content_light_level: reader.get_n(16)?,
31 | max_frame_average_light_level: reader.get_n(16)?,
32 | }))
33 | }
34 |
35 | pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
36 | self.validate()?;
37 |
38 | writer.write_n(&self.max_display_mastering_luminance, 16)?;
39 | writer.write_n(&self.min_display_mastering_luminance, 16)?;
40 | writer.write_n(&self.max_content_light_level, 16)?;
41 | writer.write_n(&self.max_frame_average_light_level, 16)?;
42 |
43 | Ok(())
44 | }
45 |
46 | pub fn validate(&self) -> Result<()> {
47 | ensure!(self.max_display_mastering_luminance <= MAX_PQ_LUMINANCE);
48 | ensure!(self.min_display_mastering_luminance <= MAX_PQ_LUMINANCE);
49 | ensure!(self.max_content_light_level <= MAX_PQ_LUMINANCE);
50 | ensure!(self.max_frame_average_light_level <= MAX_PQ_LUMINANCE);
51 |
52 | Ok(())
53 | }
54 |
55 | pub fn source_meta_from_l6(&self) -> (u16, u16) {
56 | let mdl_min = self.min_display_mastering_luminance;
57 | let mdl_max = self.max_display_mastering_luminance;
58 |
59 | let source_min_pq = if mdl_min <= 10 {
60 | 7
61 | } else if mdl_min == 50 {
62 | 62
63 | } else {
64 | 0
65 | };
66 |
67 | let source_max_pq = match mdl_max {
68 | 1000 => 3079,
69 | 2000 => 3388,
70 | 4000 => 3696,
71 | 10000 => 4095,
72 | _ => 3079,
73 | };
74 |
75 | (source_min_pq, source_max_pq)
76 | }
77 | }
78 |
79 | impl ExtMetadataBlockInfo for ExtMetadataBlockLevel6 {
80 | fn level(&self) -> u8 {
81 | 6
82 | }
83 |
84 | fn bytes_size(&self) -> u64 {
85 | 8
86 | }
87 |
88 | fn required_bits(&self) -> u64 {
89 | 64
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/blocks/reserved.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, bail};
2 |
3 | use bitvec::{order::Msb0, prelude::BitVec};
4 | use bitvec_helpers::{
5 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
6 | };
7 |
8 | #[cfg(feature = "serde")]
9 | use serde::{Deserialize, Serialize};
10 |
11 | use super::{ExtMetadataBlock, ExtMetadataBlockInfo};
12 |
13 | #[derive(Debug, Default, Clone)]
14 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
15 | pub struct ReservedExtMetadataBlock {
16 | pub ext_block_length: u64,
17 | pub ext_block_level: u8,
18 |
19 | #[cfg_attr(
20 | feature = "serde",
21 | serde(serialize_with = "crate::utils::bitvec_ser_bits", skip_deserializing)
22 | )]
23 | pub data: BitVec,
24 | }
25 |
26 | impl ReservedExtMetadataBlock {
27 | pub(crate) fn parse(
28 | ext_block_length: u64,
29 | ext_block_level: u8,
30 | reader: &mut BsIoSliceReader,
31 | ) -> Result {
32 | let bits = 8 * ext_block_length;
33 | let mut data = BitVec::new();
34 |
35 | for _ in 0..bits {
36 | data.push(reader.get()?);
37 | }
38 |
39 | Ok(ExtMetadataBlock::Reserved(Self {
40 | ext_block_length,
41 | ext_block_level,
42 | data,
43 | }))
44 | }
45 |
46 | pub fn write(&self, _writer: &mut BitstreamIoWriter) -> Result<()> {
47 | bail!("Cannot write reserved block");
48 | // self.data.iter().for_each(|b| writer.write(*b))?;
49 | }
50 | }
51 |
52 | impl ExtMetadataBlockInfo for ReservedExtMetadataBlock {
53 | // TODO: Level 255 is actually definded for DM debugging purposes, we may add it.
54 | fn level(&self) -> u8 {
55 | 0
56 | }
57 |
58 | fn bytes_size(&self) -> u64 {
59 | self.ext_block_length
60 | }
61 |
62 | fn required_bits(&self) -> u64 {
63 | self.data.len() as u64
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/cmv29.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, bail, ensure};
2 | use bitvec_helpers::bitstream_io_reader::BsIoSliceReader;
3 |
4 | #[cfg(feature = "serde")]
5 | use serde::{Deserialize, Serialize};
6 |
7 | use super::WithExtMetadataBlocks;
8 | use crate::rpu::extension_metadata::blocks::*;
9 |
10 | #[derive(Debug, Default, Clone)]
11 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
12 | pub struct CmV29DmData {
13 | num_ext_blocks: u64,
14 | ext_metadata_blocks: Vec,
15 | }
16 |
17 | impl WithExtMetadataBlocks for CmV29DmData {
18 | const VERSION: &'static str = "CM v2.9";
19 | const ALLOWED_BLOCK_LEVELS: &'static [u8] = &[1, 2, 4, 5, 6, 255];
20 |
21 | fn with_blocks_allocation(num_ext_blocks: u64) -> Self {
22 | Self {
23 | ext_metadata_blocks: Vec::with_capacity(num_ext_blocks as usize),
24 | ..Default::default()
25 | }
26 | }
27 |
28 | fn set_num_ext_blocks(&mut self, num_ext_blocks: u64) {
29 | self.num_ext_blocks = num_ext_blocks;
30 | }
31 |
32 | fn num_ext_blocks(&self) -> u64 {
33 | self.num_ext_blocks
34 | }
35 |
36 | fn blocks_ref(&self) -> &Vec {
37 | self.ext_metadata_blocks.as_ref()
38 | }
39 |
40 | fn blocks_mut(&mut self) -> &mut Vec {
41 | self.ext_metadata_blocks.as_mut()
42 | }
43 |
44 | fn parse_block(&mut self, reader: &mut BsIoSliceReader) -> Result<()> {
45 | let ext_block_length = reader.get_ue()?;
46 | let ext_block_level = reader.get_n(8)?;
47 |
48 | let ext_metadata_block = match ext_block_level {
49 | 1 => level1::ExtMetadataBlockLevel1::parse(reader)?,
50 | 2 => level2::ExtMetadataBlockLevel2::parse(reader)?,
51 | 4 => level4::ExtMetadataBlockLevel4::parse(reader)?,
52 | 5 => level5::ExtMetadataBlockLevel5::parse(reader)?,
53 | 6 => level6::ExtMetadataBlockLevel6::parse(reader)?,
54 | 255 => level255::ExtMetadataBlockLevel255::parse(reader)?,
55 | 3 | 8 | 9 | 10 | 11 | 254 => bail!(
56 | "Invalid block level {} for {} RPU",
57 | ext_block_level,
58 | Self::VERSION,
59 | ),
60 | _ => {
61 | ensure!(
62 | false,
63 | format!(
64 | "{} - Unknown metadata block found: Level {}, length {}, please open an issue.",
65 | Self::VERSION,
66 | ext_block_level,
67 | ext_block_length
68 | )
69 | );
70 |
71 | reserved::ReservedExtMetadataBlock::parse(
72 | ext_block_length,
73 | ext_block_level,
74 | reader,
75 | )?
76 | }
77 | };
78 |
79 | ext_metadata_block.validate_and_read_remaining::(reader, ext_block_length)?;
80 |
81 | self.ext_metadata_blocks.push(ext_metadata_block);
82 |
83 | Ok(())
84 | }
85 | }
86 |
87 | impl CmV29DmData {
88 | pub fn replace_level2_block(&mut self, block: &ExtMetadataBlockLevel2) {
89 | let blocks = self.blocks_mut();
90 |
91 | let existing_idx = blocks.iter().position(|b| match b {
92 | ExtMetadataBlock::Level2(b) => b.target_max_pq == block.target_max_pq,
93 | _ => false,
94 | });
95 |
96 | // Replace or add level 2 block
97 | if let Some(i) = existing_idx {
98 | blocks[i] = ExtMetadataBlock::Level2(block.clone());
99 | } else {
100 | blocks.push(ExtMetadataBlock::Level2(block.clone()));
101 | }
102 |
103 | self.update_extension_block_info();
104 | }
105 |
106 | /// Validates different level block counts.
107 | /// The specification requires one block of L1, L4, L5, L6 and L255.
108 | /// However they are not really required, so YMMV.
109 | pub fn validate(&self) -> Result<()> {
110 | let blocks = self.blocks_ref();
111 |
112 | let invalid_blocks_count = blocks
113 | .iter()
114 | .filter(|b| !Self::ALLOWED_BLOCK_LEVELS.contains(&b.level()))
115 | .count();
116 |
117 | let level1_count = blocks.iter().filter(|b| b.level() == 1).count();
118 |
119 | let level2_count = blocks.iter().filter(|b| b.level() == 2).count();
120 |
121 | let level255_count = blocks.iter().filter(|b| b.level() == 255).count();
122 |
123 | let level4_count = blocks.iter().filter(|b| b.level() == 4).count();
124 |
125 | let level5_count = blocks.iter().filter(|b| b.level() == 5).count();
126 |
127 | let level6_count = blocks.iter().filter(|b| b.level() == 6).count();
128 |
129 | ensure!(
130 | invalid_blocks_count == 0,
131 | format!(
132 | "{}: Only allowed blocks level 1, 2, 4, 5, 6, and 255",
133 | Self::VERSION
134 | )
135 | );
136 |
137 | ensure!(
138 | level1_count <= 1,
139 | format!(
140 | "{}: There must be at most one L1 metadata block",
141 | Self::VERSION
142 | )
143 | );
144 | ensure!(
145 | level2_count <= 8,
146 | format!(
147 | "{}: There must be at most 8 L2 metadata blocks",
148 | Self::VERSION
149 | )
150 | );
151 | ensure!(
152 | level255_count <= 1,
153 | format!(
154 | "{}: There must be at most one L255 metadata block",
155 | Self::VERSION
156 | )
157 | );
158 | ensure!(
159 | level4_count <= 1,
160 | format!(
161 | "{}: There must be at most one L4 metadata block",
162 | Self::VERSION
163 | )
164 | );
165 | ensure!(
166 | level5_count <= 1,
167 | format!(
168 | "{}: There must be at most one L5 metadata block",
169 | Self::VERSION
170 | )
171 | );
172 | ensure!(
173 | level6_count <= 1,
174 | format!(
175 | "{}: There must be at most one L6 metadata block",
176 | Self::VERSION
177 | )
178 | );
179 |
180 | Ok(())
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/dolby_vision/src/rpu/extension_metadata/mod.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, ensure};
2 | use bitvec_helpers::{
3 | bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4 | };
5 |
6 | #[cfg(feature = "serde")]
7 | use serde::{Deserialize, Serialize};
8 |
9 | pub mod blocks;
10 | pub mod cmv29;
11 | pub mod cmv40;
12 |
13 | pub mod primaries;
14 | pub use primaries::*;
15 |
16 | pub use cmv29::CmV29DmData;
17 | pub use cmv40::CmV40DmData;
18 |
19 | use blocks::ExtMetadataBlock;
20 |
21 | #[derive(Debug, Clone)]
22 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
23 | #[cfg_attr(feature = "serde", serde(untagged))]
24 | pub enum DmData {
25 | V29(CmV29DmData),
26 | V40(CmV40DmData),
27 | }
28 |
29 | pub trait ExtMetadata {
30 | fn parse(&mut self, reader: &mut BsIoSliceReader) -> Result<()>;
31 | fn write(&self, writer: &mut BitstreamIoWriter);
32 | }
33 |
34 | pub trait WithExtMetadataBlocks {
35 | const VERSION: &'static str;
36 | const ALLOWED_BLOCK_LEVELS: &'static [u8];
37 |
38 | fn with_blocks_allocation(num_ext_blocks: u64) -> Self;
39 |
40 | fn set_num_ext_blocks(&mut self, num_ext_blocks: u64);
41 | fn num_ext_blocks(&self) -> u64;
42 |
43 | fn parse_block(&mut self, reader: &mut BsIoSliceReader) -> Result<()>;
44 | fn blocks_ref(&self) -> &Vec;
45 | fn blocks_mut(&mut self) -> &mut Vec;
46 |
47 | fn sort_blocks(&mut self) {
48 | let blocks = self.blocks_mut();
49 | blocks.sort_by_key(|ext| ext.sort_key());
50 | }
51 |
52 | fn update_extension_block_info(&mut self) {
53 | self.set_num_ext_blocks(self.blocks_ref().len() as u64);
54 | self.sort_blocks();
55 | }
56 |
57 | fn add_block(&mut self, meta: ExtMetadataBlock) -> Result<()> {
58 | let level = meta.level();
59 |
60 | ensure!(
61 | Self::ALLOWED_BLOCK_LEVELS.contains(&level),
62 | "Metadata block level {} is invalid for {}",
63 | &level,
64 | Self::VERSION
65 | );
66 |
67 | let blocks = self.blocks_mut();
68 | blocks.push(meta);
69 |
70 | self.update_extension_block_info();
71 |
72 | Ok(())
73 | }
74 |
75 | fn remove_level(&mut self, level: u8) {
76 | let blocks = self.blocks_mut();
77 | blocks.retain(|b| b.level() != level);
78 |
79 | self.update_extension_block_info();
80 | }
81 |
82 | fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
83 | let num_ext_blocks = self.num_ext_blocks();
84 |
85 | writer.write_ue(&num_ext_blocks)?;
86 |
87 | // dm_alignment_zero_bit
88 | writer.byte_align()?;
89 |
90 | let ext_metadata_blocks = self.blocks_ref();
91 |
92 | for ext_metadata_block in ext_metadata_blocks {
93 | let remaining_bits =
94 | ext_metadata_block.length_bits() - ext_metadata_block.required_bits();
95 |
96 | writer.write_ue(&ext_metadata_block.length_bytes())?;
97 | writer.write_n(&ext_metadata_block.level(), 8)?;
98 |
99 | ext_metadata_block.write(writer)?;
100 |
101 | // ext_dm_alignment_zero_bit
102 | for _ in 0..remaining_bits {
103 | writer.write(false)?;
104 | }
105 | }
106 |
107 | Ok(())
108 | }
109 | }
110 |
111 | impl DmData {
112 | pub(crate) fn parse(
113 | reader: &mut BsIoSliceReader,
114 | ) -> Result