While DoViBaker
is the name of the main component, the package actually includes a suite of different tools:
- DoViBaker: Bake a DolbyVision stream to a PQ stream
- DoViTonemap: static or dynamic tonemapping of a PQ stream
- DoViLutGen.exe: creates LUTs for converting PQ streams to HLG or SDR, or for converting HLG to SDR
- AVSCube: applies a LUT to a stream
- DoViCubes: chooses one of multiple LUTs to apply based on the scenes maxCll
- DoViStatsFileLoader: loads the stats-file created by
StatsFileCreator.avs
enablingDoViTonemap
orDoViCubes
to apply dynamic processing on non-DolbyVision PQ streams - StatsFileCreator.avs: analyzes a non-DolbyVision PQ stream and generates a stats-file which enables dynamic tonemapping
- DoViAnalyzer.exe: analyzes a given RPU.bin file
- LumaScaleHelper.avs: manually compare a PQ stream to a pre-existing SDR stream to extract the lumaScale which can be given to DoViTonemap
- BetterGrayscale.avsi: necessary for LumaScaleHelper.avs
For an explanation of all the terminology used and the technical concepts behind, please consult the Red, Orange and Yellow Books of UHD Forms Guidelines: https://ultrahdforum.org/guidelines/
This avisynth plugin reads the Base Layer, Enhancement Layer and RPU data from a DolbyVision stream to create a clip with all the processing indicated by these substreams baked into a PQ12 output stream.
This plugin uses the metadata from an RPU file or from the inside stream itself to compose the DolbyVision HDR picture out of the Base Layer (BL) and Enhancement Layer (EL). Display Management (DM) metadata will not be processed per default. It is however possible to further process the clip using DM data by explicitly enabling Trims or by the means of DoViTonemap or DoViCubes.
To my knowledge there are currently three source libraries that can be used. It is advisable to choose one of them in a speed test on your machine.
example.avs:
bl=LWLibavVideoSource("clip.ts", format="YUV420P10", stream_index=0)
el=LWLibavVideoSource("clip.ts", format="YUV420P10", stream_index=1)
DoViBaker(bl,el)
example.avs:
bl=FFVideoSource("clip.ts", threads=1, track=0)
el=FFVideoSource("clip.ts", threads=1, track=1)
DoViBaker(bl,el)
- Get dovi_tool: https://github.com/quietvoid/dovi_tool/releases/tag/2.1.0
- Extract the Base and Enhancement Layers separately from the initial profile 7 stream
- Extract the RPU data from the Enhancement Layer using dovi_tool
- Write a Avisynth script like the example below
example.avs:
bl=DGSource("blclip.dgi")
el=DGSource("elclip.dgi")
DoViBaker(bl,el,rpu="RPU.bin")
The output created by DoViBaker
is PQ12 in a 16-bit RGB stream. That means it is a 16-bit RGB stream employing Perceptual Quantization as the transfer function, and with 12-bit effective color depth per component on the wide color gamut specified in BT.2100.
The metadata typical for HDR10 streams can be added manually to the output stream during the encoding if needed. Using x265 this can be done with the following flags:
- --master-display "G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(40000000,50)"
- --max-cll "1442,329"
The above values are just an example and the acual values must be taken from the source stream. The values for L
of the master-display
flag are given by the frame property _dovi_static_master_display_max_luminance
multiplied by 10000 and _dovi_static_master_display_min_luminance
set by DoViBaker
. Not all DolbyVision substreams carry these values however, in which case you will need to read them from the Base Layer stream, for example using MediaInfo. The values above for G
, B
, R
and WP
are for the most usually used Display P3
color gamut.
The values for the max-cll
flag are given by the _dovi_static_max_content_light_level
and _dovi_static_max_avg_content_light_level
respectively. Again, not all DolbyVision substreams carry those, in which case you will need to rely on the values from your Base Layer.
It is possible to apply the trims available in the DolbyVision substream. Select which trim to apply using the trimPq
argument and set targetMaxNits
and targetMinNits
as necessary. Be warned however, only the typical CM v2.9 processing is implemented thus far, and most streams don't have very optimized parameters, producing suboptimal results. Thus this feature is experimental only!
Typical trim targets usually available are:
- 100 nits, with a
trimPq
of 2081 - 600 nits, with a
trimPq
of 2851 - 1000 nits, with a
trimPq
of 3079
In comparison to trims and especially for higher brightness targets like 600 nits and above, results might be better using DoViTonemap in conjunction with DoViBaker.
The following frame properties will be set:
_Matrix
set to 0, representing that the output is RGB_ColorRange
set to 0 in case of full range and 1 in case of limited range_SceneChangePrev
set to 1 for the first frame in a scene_dovi_dynamic_min_pq
the min_pq value of the current scene_dovi_dynamic_max_pq
the max_pq value of the current scene_dovi_dynamic_max_content_light_level
the equivalend value of maximal nits of the current scene_dovi_static_max_pq
the max_pq value of the whole stream_dovi_static_max_content_light_level
the value of maximal nits of the whole stream_dovi_static_max_avg_content_light_level
the maximum average nits_dovi_static_master_display_max_luminance
the mastering display maximum luminance in nits_dovi_static_master_display_min_luminance
10000 times the mastering display minimum luminance in nits
The static values are non-zero only when available in the DolbyVision substream.
You can get the current tonemapping value of max-content-light-level by reading the frame property _dovi_dynamic_max_content_light_level
:
ScriptClip("""
mcll=propGetInt("_dovi_dynamic_max_content_light_level")
subtitle("maxcll = " + string(mcll))
""")
This plugin processes the tonemapping of any HDR PQ streams to lower dynamic range targets. The implementation is based on ITU-R BT.2408-7 Annex 5 (was in ITU-R BT.2390 until revision 7), with the addition of an optional luminosity factor which scales the brightness linearily.
Color space is preserved and not converted to narrower gamut. For conversions to HLG or SDR additional processing is required, see DoViLutGen.exe.
The following arguments control the tonemapping function:
masterMaxNits
andmasterMinNits
set the white and black brightness value of the source. The values for the master brightness can be either given explicitly ormasterMaxNits
andmasterMinNits
can both be left unset (or set to-1
) which will indicate that the actual values are read from the related frame properties_dovi_dynamic_max_pq
and_dovi_dynamic_min_pq
which are set by DoViBaker or DoViStatsFileLoader, leading to a dynamic tonemapping. If not given these will default to-1
.targetMaxNits
andtargetMinNits
set the desired target capabilities. These must be given explicitly.lumScale
changes the total brightness, this can be usefull since many HDR streams are actually too dark, darker then the respective SDR streams would be. If the equivalent SDR stream is available, the properlumScale
factor can be found by using the script LumScaleHelper.avs. This setting is especially important when converting to SDR using LUTs generated by DoViLutGen.exe. It is possible to read the luminosity factor from the frame property_dovi_dynamic_luminosity_scale
by settinglumSacle
to-1
. When not given explicitly the default of1.0
is used.kneeOffset
is a parameter of the tonemapping curve, which governs the size of the region where the tonemapping function is flattened (see figure below). The mathematical validity range is [0.5, 2.0]. In report BT.2408 this value is fixed at 0.5, which leads to low highlight details while favoring max brightness. Here the default value used is0.75
which should be a better compromise overall, especially in case of dynamic tonemapping.normalizeOutput
normalizes the output from the range[targetMinNits, targetMaxNits]
to the full range. This can be usefull when the output is just an intermediate result which is further processed, since the usage of the full value range decreases rounding errors down the line. Default isfalse
.
The following example applies a dynamic tonemapping to a 1000nits target while reading the current max and min brightness values off the frame properties which are set by DoViBaker. The luminosity scale is not given thus the default of 1.0 is used.
DoViBaker(bl,el)
DoViTonemap(targetMaxNits=1000, targetMinNits=0)
If your source is just PQ and doesn't have a DolbyVision substream, there are two options:
- use static tonemapping by explicitly defining
masterMaxNits
andmasterMinNits
toDoViTonemap
- analyse the source using StatsFileCreator.avs and provide the created stats-file to DoViStatsFileLoader for a dynamic tonemapping with
DoViTonemap
Shown below is the functional form of the PQ tonemapping curve with the following fixed parameters: masterMaxNits=10000, targetMaxNits=1000, masterMinNits=0, lumscale=1.
The following frame properties will be consumed (if the related arguments masterMaxNits
, masterMinNits
and lumScale
are set to -1
):
_dovi_dynamic_max_pq
the max_pq value of the current scene_dovi_dynamic_min_pq
the min_pq value of the current scene_dovi_dynamic_luminosity_scale
the luminosity scaling factor of the current scene_ColorRange
both limited and full range RGB inputs are supported
The following frame properties will be set:
_ColorRange
set to 0, since the output is always full range RGB independently of the input
This application generates LUTs for conversions between BT.2100 PQ, BT.2100 HLG, BT.2020 SDR and BT.709 SDR. The PQ to HLG conversion is based on BT.2408-7 in conjunction with BT.2100-2. The LUTs with PQ as source will only process input values up to 1000 nits and will clip anything above that. If the PQ source has brightness levels above that, use DoViTonemap to tonemap the PQ stream to 1000 nits. Only down-conversions are supported, meaning BT.2100 PQ can be converted to BT.2100 HLG, BT.2020 SDR or BT.709 SDR, while BT.2100 HLG can be only converted to BT.2020 SDR or BT.709 SDR and BT.2020 SDR can only be converted to BT.709 SDR.
usage: DoViLutGen.exe <output_file> -s <lut_size> [-i <input_format>] [-o <output_format>] [-g <sdr_gain>] [-c <sdr_compression>] [-r <chroma_reduction_factor>]
The meaning of the arguments:
output file
the name of the to-be-generated LUT filelut size
generally a bigger LUT, is a better LUT. A good size is65
when input is default BT.2100 PQ. In all other cases50
is enough.input format
this optional argument sets the source format of the LUT:0
for BT.2100 PQ input (default)1
for BT.2100 PQ input which was re-normalized to 1000 nits2
for BT.2100 HLG input3
for BT.2020 SDR input
output format
this optional argument sets the destination format of the LUT:0
for BT.2100 HLG output (default)1
for BT.2020 SDR output2
for BT.709 SDR output with hard clipping of the color gamut3
for BT.709 SDR output with smart color mapping preventing clipping while maintaining the look as much as possbile
sdr gain
this optional argument adjusts the SDR mapping function, by setting the amount of gain of light midtones. Value range is [0.0, 1.0], default is0.0
.sdr compression
this optional argument adjusts the SDR mapping function, by setting the amount of compression of bright highlights. Value range is [0.0, 1.0], default is0.0
.chroma reduction factor
this optional argument sets the factor by which the chroma is reduced. Default is1.0
, meaning chroma is not reduced.
Comment on the special input option for PQ re-normalized to 1000 nits:
In that case, the generated LUT will expect that the input PQ was re-normalized to 1000 nits max brightness, meaning that preceeding the LUT processing DoViTonemap has to used with the option normalizeOutput
set to true
. LUTs for re-normalized inputs can be of smaller size than normal LUTs while still providing better quality. A good size for such a LUT is 50
instead of the 65
needed for the default PQ input.
The default settings for sdr_gain
and sdr_compression
try to emulate a typical SDR look in what concerns dynamic range. This is usually more toned-down (aka flatter) than a typical HDR. For an even even more toned-down look increase sdr_compression
, or, inversly and if trying to retain as much HDR feeling as possible, increase sdr_gain
. If the output seems too colorful, the chroma can be decreased by decreasing chroma_reduction_factor
. Tweaking of these settings will need to be done for each stream individually, while the defaults should be a good starting point in all instances.
Shown below is the influence of gain and compression on the functional form of the HLG->SDR tonemapping. This curve is also used for PQ->SDR, since that is implemented as PQ->HLG->SDR. As can be seen, if SDR is viewed with a max brightness of 267.6 nits, the brigtness levels of signal values below 50% match to those of HLG. Above 50% signal level HLG increases faster in brightenss, reaching 1000 nits at 100% signal level.
Generate the LUT by the following command:
DoViLutGen.exe pq2hlg.cube -s 65 -i 0 -o 0
Create the following avisyth script:
DoViBaker(bl,el)
DoViTonemap(targetMaxNits=1000, targetMinNits=0)
AVSCube("pq2hlg.cube")
z_ConvertFormat(pixel_type="YUV420P10",colorspace_op="rgb:std-b67:2020:full=>2020ncl:std-b67:2020:limited",chromaloc_op="center=>top_left")
Please be aware that in the example the parameter lumaScale
was not given to DoViTonemap
, which means that a brightness factor of 1.0
is used. You might want to have this increased if the source is too dark. And if you have the equivalent SDR source at hand, you can extract the factor using LumScaleHelper.avs.
Generate the LUT by the following command:
DoViLutGen.exe pq2sdr709.cube -s 65 -i 0 -o 3
At this stage you can experiment with the SDR looks settings gain
and compression
.
Create the following avisyth script:
z_ConvertFormat(pixel_type="RGBP16",colorspace_op="2020ncl:st2084:2020:limited=>rgb:st2084:2020:full",chromaloc_op="top_left=>center")
DoViTonemap(targetMaxNits=1000, targetMinNits=0, masterMaxNits=4000, masterMinNits=0)
AVSCube("pq2sdr709.cube")
z_ConvertFormat(pixel_type="YUV420P8",colorspace_op="rgb:709:709:full=>709:709:709:limited",chromaloc_op="center=>left")
This example uses fixed values for the masterMaxNits
and masterMinNits
settings of DoViTonemap
, meaning it is a static tonemapping. By virtue of StatsFileCreator.avs and DoViStatsFileLoader it is also possble to have the clip analyzed allowing for dynamic tonemapping (then masterMaxNits
and masterMinNits
would be left unset).
Just like in the HLG example above, the parameter lumaScale
was not given to DoViTonemap
, which means that a brightness factor of 1.0
is used. This will very rarely be the right choice. In contrast to HLG conversions, this setting is somewhat more relevant here. More often than not it will need to be above 2.0
or even higher.
Generate the LUT by the following command:
DoViLutGen.exe hlg2sdr709.cube -s 50 -i 2 -o 3
At this stage you can experiment with the SDR looks settings gain
and compression
.
Create the following avisyth script:
z_ConvertFormat(pixel_type="RGBP16",colorspace_op="2020ncl:std-b67:2020:limited=>rgb:std-b67:2020:full",chromaloc_op="top_left=>center")
AVSCube("hlg2sdr709.cube")
z_ConvertFormat(pixel_type="YUV420P8",colorspace_op="rgb:709:709:full=>709:709:709:limited",chromaloc_op="center=>left")
This is a simplified version of http://avisynth.nl/index.php/AVSCube with almost no adjustability whatsoever. It provides the same quality of image processing as the LUT processing done by DoViCubes, while supporting only a single LUT. The desire for this implementation in addition to the already existing Cube stems from the fact that the original version has non-optimal default settings.
This plugin provides LUT processing capabilites based on the frame property _dovi_dynamic_max_content_light_level
set by either DoViBaker or DoViStatsFileLoader. One out of a set of multiple LUTs is applied based on adjustable thresholds. This is done by providing a collection of LUTs and limits of validity measured in nits of max-content-light-level. (The LUT processing implentation is based on: https://github.com/sekrit-twc/timecube). It should not be used in conjunction with DoViTonemap, use AVSCube in that case.
DoViBaker(bl,el)
DoViCubes(cubes="lut_1000.cube;lut_2000.cube;lut_4000.cube",mclls="1010;2020",cubes_basepath="C:\")
This example will use the file lut_1000.cube for frames where the max-content-light-level is below or equal to 1010 nits, the file lut_2000.cube for above 1010 but below or equal 2020 nits and lut_4000.cube for all frames above 2020 nits. All cube files must be available in the path given to cubes_basepath, in this example it would be "C:\".
The following frame properties will be consumed:
_dovi_dynamic_max_content_light_level
the maximal nits value of the current scene
This plugin reads the stats-file generated by the avisynth script StatsFileCreator.avs. It can be used for sources which do not have any DolbyVision substream, but where a dynamic processing by DoViTonemap or DoViCubes is still desired.
The format of each line of the stats-file needs to be, last entry is optional:
<frame_number> <decision_if_frame_is_last_in_scene> <frame_max_pq> <frame_min_pq> <frame_lum_scale>
Additionally it is also possible to provide another scene cut file, created by other means than through StatsFileCreator.avs. In this case the scene cuts are going to be taken for that file and the stream statistics from the stats-file. The format of each line of the optional alternative scene cut file needs to be:
<frame_number_of_first_frame_after_scene_cut>
In this example the input stats-file is read feeding DoViTonemap:
DoViStatsFileReader("statsFile.txt")
DoViTonemap(targetMaxNits=1000, targetMinNits=0)
The following frame properties will be set:
_SceneChangePrev
set to 1 for the first frame in a scene_SceneChangeNext
set to 1 for the last frame in a scene_dovi_dynamic_min_pq
the min_pq value of the current scene_dovi_dynamic_max_pq
the max_pq value of the current scene_dovi_dynamic_max_content_light_level
the equivalend value of maximal nits of the current scene_dovi_dynamic_luminosity_scale
the optional luminosity scaling factor of the current scene_dovi_static_max_pq
the max_pq value of the whole stream_dovi_static_max_content_light_level
the value of maximal nits of the whole stream
This avisynth script scans through the clip and writes the stats-file needed for DoViStatsFileLoader. The stats-file includes scene cuts and per-frame max brightness values. The scene cut detection algorithm is a rather simple implementation, which is however good enough for most cases.
The format of each line of the stats-file created is:
<frame_number> <decision_if_frame_is_last_in_scene> <frame_max_pq> <frame_min_pq>
The format of each line of the optional alternative scene cut file created is:
<frame_number_of_first_frame_after_scene_cut>
The script can be run on the commandline using avs2pipemod or AVSMeter.
./avs2pipemod64.exe -benchmark StatsFileCreator.avs
./AVSMeter64.exe StatsFileCreator.avs
This application analyzes the RPU.bin file in order to show information relevant to deciding whether it is worth to use DoViBaker or if this can be skipped completely and the Base Layer can be used directly.
usage: DoViAnalyzer.exe <path_to_rpu.bin_file> <optional_scenecutfile.txt>
The output will show the following attributes:
- clip length
- overall max-content-light-level
- unusual color matrices defined by the RPU file
- mapping non-identity introduced by the RPU file
- enabled processing of the Enhancement Layer
- available trims, with the trimPq of each trim being shown in brackets
- optional warning in case that the RGB output is of limited range
Pay attention to 3-5 since these will indicate if the look of the clip will be different when DolbyVision is taken into account compared to just playing the Base Layer clip. If you are using LUTs, pay attention to 7) since in this case the LUTs provided will need to be different.
Additionally it is possible to generate a scenecutfile based on the information from the RPU file. This might be used as the optional scene cut file by DoViStatsFileLoader. Or it might be given to the encoder to improve the scene detection (using the parameter --qpfile for x265). In this case add " K" to the end of each line of the file.
Used to find lumScale
for DoViTonemap manually. This is the factor by which to mutiply the brightness of the PQ stream such that its base brightness matches that of the SDR stream. Typical factors can be 1.0 all the way to up 5.0 in very extreme cases. Also this factor might fluctuate from scene to scene. In this case it is advisable to use one best fitting factor thoughout the whole stream in order to maintain the creator's intent. For low brightness targets it might however be necessary have the factor adjusted from scene to scene.
Use AvsPmod to manually scan through the file.
Needed by LumScaleHelper.avs for showing a more correct and better comparable grayscale of PQ and SDR sources.
I had some issues linking against Timecube. I was constantly getting the following error:
fatal error C1083: Cannot open compiler generated file: 'x64\Release\timecube.asm': No such file or directory
It turnes out that this is related to the following setting: "Properties" (of the timecube project) -> "C/C++" -> "Output Files" -> "Assembler Output". Setting this to "No Listing" resolves the issue.