Log in

View Full Version : FranceBB LUT Collection


Pages : 1 2 3 4 5 6 [7] 8

FranceBB
24th November 2024, 04:55
MediaInfo says 'Color prmaries BT.2020' on output.

But that may be because of setting (mandatory) 'matrix' in core.resize.Spline64


Yes, that's exactly right, it is because you're setting the right matrix on the RGB to YUV conversion which isn't optional in VapourSynth. That will also set the frame properties.


I can't really tell what comes out of the BT709_to_HLG line (though it should be BT.2020).


It is absolutely and positively BT2020 in terms of the mapped points in the output after the transformation, but in terms of frame properties the answer is: nothing. The frame properties values are just not set in the RGB roundtrip.
The "good news" however is that VapourSynth - unlike Avisynth - expects the user to set them in the function so that they can also be set in the frame properties.
This means that up until this point as long as you set them correctly in the YUV->RGB->YUV roundtrip, then you're good to go. ;)



So, should I change something?

Nope, you're doing everything right already, the "issue" is limited to Avisynth in which we have to use PropSet().
In general, we can use this rule of thumb after going back to YUV.

For BT601 PAL outputs:


propSet("_ColorRange", 1) #Limited TV Range
propSet("_Matrix", 5) #BT601 PAL
propSet("_Transfer", 6) #BT601 PAL
propSet("_Primaries", 5) #BT601 PAL


For BT601 NTSC outputs:


propSet("_ColorRange", 1) #Limited TV Range
propSet("_Matrix", 6) #BT601 NTSC
propSet("_Transfer", 6) #BT601 NTSC
propSet("_Primaries", 6) #BT601 NTSC


For BT709 outputs:


propSet("_ColorRange", 1) #Limited TV Range
propSet("_Matrix", 1) #BT709
propSet("_Transfer", 1) #BT709
propSet("_Primaries", 1) #BT709


For BT2020 outputs:


propSet("_ColorRange", 1) #Limited TV Range
propSet("_Matrix", 9) #BT2020
propSet("_Transfer", 14) #BT2020
propSet("_Primaries", 9) #BT2020


For BT2020 HLG outputs:


propSet("_ColorRange", 1) #Limited TV Range
propSet("_Matrix", 9) #BT2020
propSet("_Transfer", 18) #HLG
propSet("_Primaries", 9) #BT2020


For BT2020 PQ outputs:


propSet("_ColorRange", 1) #Limited TV Range
propSet("_Matrix", 9) #BT2020
propSet("_Transfer", 16) #PQ
propSet("_Primaries", 9) #BT2020


For DCI P3 outputs:


propSet("_ColorRange", 1) #Limited TV Range
propSet("_Matrix", 2)
propSet("_Transfer", 17) #DCI P3
propSet("_Primaries", 11) #DCI P3



Tested and working as expected in AVSPmod mod:

https://i.imgur.com/aH9t1F2.png
https://i.imgur.com/FdfjADj.png
https://i.imgur.com/P30dqzs.png
https://i.imgur.com/ZUGWJ6j.png
https://i.imgur.com/vyAZhoQ.png
https://i.imgur.com/KYPV65Y.png


Maybe you would want to add LUts for videos from DJI drones?

Yeah, that's actually something I wanted to do for quite some time now. I did actually make a first implementation of D-Log to BT709, however I didn't particularly like the results that came out of it. On that note, most of the log conversions I made were targeting BT709, however nowadays log is mostly used to produce BT2020 HLG outputs, at least internally, so I should really start updating those. Anyway, I have a few samples of D-Log footage, so perhaps if I get enough samples of D-Log M footage as well I can make both D-Log to BT709 and D-Log M to BT709.

Hopefully come next year I'll have a bit more time to make things like Slog3 to HLG and to PQ which is kinda important right now as more and more productions shot in Slog3 are then graded and converted in either HLG or PQ for the final release (in our case, it's always HLG but we're a broadcasting company, so we're biased).

asarian
24th November 2024, 06:13
Nope, you're doing everything right already, the "issue" is limited to Avisynth in which we have to use PropSet().
In general, we can use this rule of thumb after going back to YUV.


I cannot thank you enough for your, once again, very extensive and helpful post! :thanks:

FranceBB
24th December 2024, 12:29
Linear Transformation v2.7 Released! (https://github.com/FranceBB/LinearTransformation/releases)

Changelog:


- Added Frame Properties support for Primaries
- BT601 transfer frame properties are now correctly signaled as smpte170m
- Introduced correct signalling of smpte432 primaries and smpte428 transfer for the DCI P3 XYZ Gamma 2.6 conversion



Merry Christmas!

FranceBB
20th February 2025, 22:00
In the next release we're gonna have two new conversions that are gonna be added:

- BT2020 SDR to BT709 SDR
- BT709 SDR to BT2020 SDR

Let's start from the first one and let's start by saying that other people have been working on this before me, in fact William (the creator and maintainer of HLG Tools) released a version based on Oklab (https://bottosson.github.io/posts/oklab/) here: Link (https://forum.doom9.net/showthread.php?t=182499&page=22). There's absolutely nothing wrong with his LUTs and you can go ahead and use them. What I'm doing is something much more simplistic, in fact the BT2020 SDR to BT709 SDR conversion is actually based on the most simple calculation illustrated in the ITU recommendation 2407.

The conversion is performed as follows:

[0.4124 0.3576 0.1805]^-1 [0.6370 0.1446 0.1689]
[0.2126 0.7152 0.0722] [0.2627 0.6780 0.0593] =
[0.0193 0.1192 0.9505] [0.0000 0.0281 1.0610]


Let's take the matrix for which we have to calculate the inverse and let's create the augmented matrix


[0.4124 0.3576 0.1805 1.0000 0.0000 0.0000]
[0.2126 0.7152 0.0722 0.0000 1.0000 0.0000]
[0.0193 0.1192 0.9505 0.0000 0.0000 1.0000]

Now that we have the identity matrix on the right, we have to perform the operations to "bring it to the left" so that what we're gonna be left with is gonna be the inverse.

We make the pivot in the 1° column by dividing the first row by 0.4124

[1.0000 0.8671 0.4376 2.4248 0.0000 0.0000]
[0.2126 0.7152 0.0722 0.0000 1.0000 0.0000]
[0.0193 0.1192 0.9505 0.0000 0.0000 1.0000]

we can eliminate the first column

[1.0000 0.8671 0.4376 2.4248 0.0000 0.0000]
[0.0000 0.5308 -0.0208 -0.5155 1.0000 0.0000]
[0.0000 0.1024 0.9420 -0.0467 0.0000 1.0000]

we can make the pivot in the 2° column by dividing the second row by 0.5308

[1.0000 0.8671 0.4376 2.4248 0.0000 0.0000]
[0.0000 1.0000 -0.0392 -0.9711 1.8837 0.0000]
[0.0000 0.1024 0.9420 -0.0467 0.0000 1.0000]

we can eliminate the second column:

[1.0000 0.0000 0.4717 3.2669 -1.6334 0.0000]
[0.0000 1.0000 -0.0392 -0.9711 1.8837 0.0000]
[0.0000 0.0000 0.9460 0.0527 -0.1930 1.0000]

at this point, we're almost there, we make the pivot in the 3° column by dividing the third row by 0.9460

[1.0000 0.0000 0.4717 3.2669 -1.6334 0.0000]
[0.0000 1.0000 -0.0392 -0.9711 1.8837 0.0000]
[0.0000 0.0000 1.0000 0.0557 -0.2040 1.0569]

we can therefore eliminate third column and we're gonna be left with the inverse matrix on the right as we brought the identity to the left:

[1.0000 0.0000 0.0000 3.2408 -1.5372 -0.4986]
[0.0000 1.0000 0.0000 -0.9689 1.8757 0.0415]
[0.0000 0.0000 1.0000 0.0557 -0.2040 1.0569]

so the inverse is:

[3.2408 -1.5372 -0.4986]
[-0.9689 1.8757 0.0415]
[0.0557 -0.2040 1.0569]

we still have to multiply it with the other matrix, though:

[3.2408 -1.5372 -0.4986] [0.6370 0.1446 0.1689]
[-0.9689 1.8757 0.0415] [0.2627 0.6780 0.0593] =
[0.0557 -0.2040 1.0569] [0.0000 0.0281 1.0610]

we can label the output as:

[A B C]
[D E F]
[G H I]

and they're calculated as:

A= 3.2408 x 0.637 + (-1.5372) x 0.2627 + (-0.4986) x 0 = 1.6605

B= 3.2408 x 0.1446 + (-1.5372) x 0.678 + (-0.4986) x 0.0281 = -0.5876

C= 3.2408 x 0.1689 + (-1.5372) x 0.0593 + (-0.4986) x 1.061 = -0.0727

D= -0.9689 x 0.637 + 1.8757 x 0.2627 + 0.0415 x 0 = -0.1244

E= -0.9689 x 0.1446 + 1.8757 x 0.678 + 0.0415 x 0.0281 = 1.1327

F= -0.9689 x 0.1689 + 1.8757 x 0.0593 + 0.0415 x 1.061 = -0.0083

G= 0.0557 x 0.637 + (-0.204) x 0.2627 + 1.0569 x 0 = -0.0181

H= 0.0557 x 0.1446 + (-0.204) x 0.678 + 1.0569 x 0.0281 = -0.1005

I= 0.0557 x 0.1689 + (-0.204) x 0.0593 + 1.0569 x 1.061 = 1.1186

so our final matrix is:

[ 1.6605 -0.5876 -0.0727]
[-0.1244 1.1327 -0.0083]
[-0.0181 -0.1005 1.1186]


Let's put this theory to the test and compare it against HDRTools:


test1=LWLibavVideoSource("Source1.mxf")
test2=LWLibavVideoSource("Source2.mxf")
test3=LWLibavVideoSource("Source3.mxf")
test4=LWLibavVideoSource("Source4.mxf")

test1++test2++test3++test4

propClearAll()

ConvertBits(16)
bt2020=Subtitle("BT2020 SDR")
bt2020_sdr=last

ConvertToPlanarRGB(bt2020_sdr)
Cube("A:\Ingest\MEDIA\temp\BT2020_to_BT709.cube", interp=1, fullrange=1)
ConverttoYUV444(matrix="Rec709")
Subtitle("BT709 SDR LUT")
bt709_cube=last

ConvertYUVtoXYZ(bt2020_sdr, Color=1)
ConvertXYZtoYUV(pColor=1)
Subtitle("BT709 SDR HDRTools")
bt709_hdrtools=last

StackHorizontal(bt2020, bt709_cube, bt709_hdrtools)


https://i.imgur.com/ChCa5iB.png

Looks fine.
To spot the actual differences between this method and the XYZ roundtrip made by HDRTools we have to check solid colors like in those commercials in which the clipping of values outside the BT709 realm done by the LUT becomes very apparent:

https://i.imgur.com/13xNDx8.png
https://i.imgur.com/toQXUX5.png

The difference is pretty clear and it can be seen in both the Colgate and EE backgrounds, with HDRTools actually performing a much better approximation, while the LUT is clipping several values out as it's a simple onto conversion (as every element in the range has at least one corresponding element in the domain) but clearly not one to one (because more elements map to the same destinations). This is also due to the normalizations of value above 1 and below 0. Speaking of values below 0, inside the LUT you're gonna be able to see a bunch of zeroes, that's to avoid issues with non legal values, in fact everything negative has been normalized to 0 to avoid a repetition of frank's issue and more specifically the lut3d implementation (https://forum.doom9.org/showthread.php?t=176091&page=10).

When we test with real life examples, however, the difference becomes much more slim:

https://i.imgur.com/zsKrc5w.png
https://i.imgur.com/yieRpH4.png
https://i.imgur.com/KjfscKN.png
https://i.imgur.com/hUdc4ya.png
https://i.imgur.com/SEhEj3h.png
https://i.imgur.com/hwbX1t7.png


For those who wanna start testing:

BT2020_to_BT709.cube (https://github.com/FranceBB/LinearTransformation/blob/master/BT2020_to_BT709.cube)

wswartzendruber
21st February 2025, 04:34
Regarding my BT.2020-to-BT.709 conversion LUT he mentioned, I have a new one I just generated:

https://wswartzendruber.net/uploads/bt2020to709-2.cube

It should be very similar, but the math is a little tighter. It still uses Oklab/Oklch, so it's as perceptually accurate as that is.

EDIT: I mainly use it for viewing HLG on BT.709 SDR. I may as well include screenshots and peddle my wares!

https://wswartzendruber.net/images/bt2020to709-2/0.jpg

https://wswartzendruber.net/images/bt2020to709-2/1.jpg

https://wswartzendruber.net/images/bt2020to709-2/2.jpg

https://wswartzendruber.net/images/bt2020to709-2/3.jpg

https://wswartzendruber.net/images/bt2020to709-2/4.jpg

https://wswartzendruber.net/images/bt2020to709-2/5.jpg

wswartzendruber
21st February 2025, 17:17
And now I learn that Google has a colorspace called HCT which is supposed to be even better than Oklab. It's based on CAM16, though, so it may be a bit of a doozie to compute.

FranceBB
8th March 2025, 18:02
It feels like a good time to make a release, so... here we are.

Linear Transformation v2.8 Released! (https://github.com/FranceBB/LinearTransformation/releases)

Changelog:

- Implemented a new BT2020 SDR to BT709 SDR conversion.
- Implemented a new BT709 SDR to BT2020 SDR conversion.


To apply those, simply use:

LinearTransformation(Input="Linear_BT2020", Output="Linear_BT709", Interpolation="Tetrahedral")

and

LinearTransformation(Input="Linear_BT709", Output="Linear_BT2020", Interpolation="Tetrahedral")

Or just apply the individual LUTs as always. :)
The roundtrip was also tested (https://i.imgur.com/tRE4Tea.png) and it should work.

Oh and before anyone says anything, yes, I did actually check to make sure we're populating frame properties correctly eheheheh

LWLibavVideoSource("test.mxf")

https://i.imgur.com/9m8K9KY.png

LinearTransformation(Input="Linear_BT2020", Output="Linear_BT709", Interpolation="Tetrahedral"))

https://i.imgur.com/nVq98HN.png

(and I've just realized that I screwed up the screenshot but whatever).

FranceBB
6th April 2025, 14:40
wh0phd pointed out some less than optimal results when performing the conversions to and from BT601 (both PAL and NTSC). Being little used nowadays, the original conversions from 4 years ago didn't quite achieve the expected results, especially for the conversions to and from BT2020 as well as the HLG (arib-std-b67) and PQ (smpte2084) conversions. A review of those was long overdue and as I'm trying to tidy things up I decided to finally spend time on those.

I recreated the matrices by inserting the BT601 PAL, BT601 NTSC, BT709 and BT2020 SDR primaries:

https://i.imgur.com/pOXnOCW.png
https://i.imgur.com/10Nwcqp.png
https://i.imgur.com/hnhrbtd.png
https://i.imgur.com/zmDTG7j.png

The white point stays the same for all of them, so BT601 PAL and BT601 NTSC as well as BT709 and BT2020 SDR, namely
x white = 0.3127
y white = 0.3290

The values for red, green and blue however change.

BT601 PAL has
x red = 0.640
y red = 0.330
x green = 0.290
y green = 0.600
x blue = 0.150
y blue = 0.060

BT601 NTSC has
x red = 0.630
y red = 0.340
x green = 0.310
y green = 0.595
x blue = 0.155
y blue = 0.070

BT709 has
x red = 0.640
y red = 0.330
x green = 0.300
y green = 0.600
x blue = 0.150
y blue = 0.060

BT2020 has
x red = 0.708
y red = 0.292
x green = 0.170
y green = 0.797
x blue = 0.131
y blue = 0.046


The idea here is to repurpose the BT709 conversions that have been created a long time ago and that have been tested over and over again by the community to produce the BT601 ones by just changing the primaries. Let's take for instance the old BT2020 HLG to BT601 PAL conversion: it was remapping the 75% reference white in the luma to 100% and clipping the rest. As to the chroma, it was converting the primaries from the BT2020 ones to the BT601 PAL ones. Now, this is NOT what we're doing in the BT2020 HLG to BT709 SDR conversion as we're keeping the luma in place and only converting the primaries (which is the whole purpose behind HLG given that it would be what a user would see if he bought a BT2020 UHD TV that can't interpret the transfer). So, in the new BT2020 HLG to BT601 PAL conversion we're using the same logic as the BT2020 HLG to BT709 SDR conversion, namely keeping the luma in place and converting the primaries.

This is the old BT2020 HLG to BT601 PAL conversion:
https://i.imgur.com/uejdLUN.png

and this is the new one:
https://i.imgur.com/nkQovET.png

Using the same logic, I could now fill the gap and add the missing BT601 conversions for Slog3, Vlog, LogC and Clog3 as well.


LWLibavVideoSource("C9128.MP4")

ConvertBits(16)
ConvertToPlanarRGB()
Cube("A:\MEDIA\temp\Slog3_to_BT601_PAL", interp=1, fullrange=1)
ConverttoYUV444(matrix="Rec601")

VideoTek()


https://i.imgur.com/jXoAexp.png


LWLibavVideoSource("A002C001_200527_RNMK.mxf")

ConvertBits(16)
ConvertToPlanarRGB()
Cube("A:\MEDIA\temp\LogC_to_BT601_PAL.cube", interp=1, fullrange=1)
ConverttoYUV444(matrix="Rec601")

VideoTek()


https://i.imgur.com/w754Q1J.png


LWLibavVideoSource("A006C002_191214R7_CANON.MXF")

ConvertBits(16)
ConvertToPlanarRGB()
Cube("A:\MEDIA\temp\Clog3_to_BT601_PAL.cube", interp=1, fullrange=1)
ConverttoYUV444(matrix="Rec601")

VideoTek()


https://i.imgur.com/ymJiCMY.png


I also fixed an issue in the old BT601 NTSC to BT601 PAL conversion as well as BT601 PAL to BT601 NTSC. Both LUTs have been regenerated using CIECAT02 instead of the Bradford Chromatic Adaptation. Taking z_ConvertFormat() as a safe reference point (i.e avsresize), I started testing the two conversions.


#BT601 PAL to BT601 NTSC
ColorBars(848, 480, pixel_type="YUV444P16")

SinPowerResize(848, 480)
bt601_pal=z_ConvertFormat(colorspace_op="709:709:709=>470bg:601:470bg", resample_filter_uv="spline64", dither_type="error_diffusion")
bt601_ntsc=z_ConvertFormat(colorspace_op="709:709:709=>170m:240m:240m", resample_filter_uv="spline64", dither_type="error_diffusion")

ConvertBits(bt601_pal, 16)
ConvertToPlanarRGB()
Cube("A:\MEDIA\temp\BT601_PAL_to_BT601_NTSC.cube", interp=1, fullrange=1)
ConverttoYUV444(matrix="Rec601")
Histogram("color2")
ntsc_cube=last

ConvertBits(bt601_pal, 16)
z_ConvertFormat(colorspace_op="470bg:601:470bg=>170m:240m:240m", resample_filter_uv="spline64", dither_type="error_diffusion")
ConverttoYUV444(matrix="Rec601")
Histogram("color2")
ntsc_avsresize=last


StackVertical(ntsc_cube, ntsc_avsresize)


https://i.imgur.com/k35wjJU.png

and the other way round as well


#BT601 NTSC to BT601 PAL
ColorBars(848, 480, pixel_type="YUV444P16")

SinPowerResize(848, 480)
bt601_pal=z_ConvertFormat(colorspace_op="709:709:709=>470bg:601:470bg", resample_filter_uv="spline64", dither_type="error_diffusion")
bt601_ntsc=z_ConvertFormat(colorspace_op="709:709:709=>170m:240m:240m", resample_filter_uv="spline64", dither_type="error_diffusion")

ConvertBits(bt601_ntsc, 16)
ConvertToPlanarRGB()
Cube("A:\MEDIA\temp\BT601_NTSC_to_BT601_PAL.cube", interp=1, fullrange=1)
ConverttoYUV444(matrix="Rec601")
Histogram("color2")
pal_cube=last

ConvertBits(bt601_ntsc, 16)
z_ConvertFormat(colorspace_op="170m:240m:240m=>470bg:601:470bg", resample_filter_uv="spline64", dither_type="error_diffusion")
ConverttoYUV444(matrix="Rec601")
Histogram("color2")
pal_avsresize=last


StackVertical(pal_cube, pal_avsresize)


https://i.imgur.com/ag4PxAW.png

You can already test the new versions on Github, but I'm planning to make a new release next week.

FranceBB
27th April 2025, 18:31
Linear Transformation v2.9 Released! (https://github.com/FranceBB/LinearTransformation/releases/)

Changelog:

- Introduced the new conversions

#Slog3 to BT601 PAL
LinearTransformation(Input="SLog3", Output="Linear_BT601_PAL")

#Slog3 to BT601 NTSC
LinearTransformation(Input="SLog3", Output="Linear_BT601_NTSC")



#Vlog to BT601 PAL
LinearTransformation(Input="VLog", Output="Linear_BT601_PAL")

#Vlog to BT601 NTSC
LinearTransformation(Input="VLog", Output="Linear_BT601_NTSC")



#LogC to BT601 PAL
LinearTransformation(Input="LogC", Output="Linear_BT601_PAL")

#LogC to BT601 NTSC
LinearTransformation(Input="LogC", Output="Linear_BT601_NTSC")



#Clog3 to BT601 PAL
LinearTransformation(Input="CLog3", Output="Linear_BT601_PAL")

#Clog3 to BT601 NTSC
LinearTransformation(Input="CLog3", Output="Linear_BT601_NTSC")

- Improved the existing BT601 conversions (both PAL and NTSC).
- Fixed an issue in the old BT601 NTSC to BT601 PAL conversion as well as BT601 PAL to BT601 NTSC. Both have been regenerated using CIECAT02 instead of the Bradford Chromatic Adaptation.

FranceBB
15th May 2025, 13:56
Linear Transformation v3.0 Released!! (https://github.com/FranceBB/LinearTransformation/releases)


Changelog:


- Added new conversions between the normal BT601 NTSC SMPTE C (1987, smpte170m) and the old BT601 NTSC Japan (1953, bt470m)

#BT601 NTSC SMPTE C (1987) to BT601 NTSC Japan (1953)
LinearTransformation(Input="Linear_BT601_NTSC", Output="Linear_BT601_NTSC-J", Interpolation="Tetrahedral")

#BT601 NTSC Japan (1953) to BT601 NTSC SMPTE C (1987)
LinearTransformation(Input="Linear_BT601_NTSC-J", Output="Linear_BT601_NTSC", Interpolation="Tetrahedral")

- Primaries are now correctly updated in the Avisynth frame properties even for inputs that have an Alpha Channel



This feature was requested by ossnorry.
As a bit of history, in 1953 the NTSC version of BT601 was born, however the tube phosphors originally used by the TVs of the time had issues displaying those values and the standard was later changed (reduced) in 1987 to make life easier for the hardware of the time, however one country never stopped using the original NTSC version and that country is Japan, which is why the original NTSC version has become known as "NTSC-J" which stands for "Japan".

Anyway, the old BT601 NTSC Japan (1953) is based on the following values:

x white = 0.3100
y white = 0.3160
x red = 0.670
y red = 0.330
x green = 0.210
y green = 0.710
x blue = 0.140
y blue = 0.080

and is indicated with
--colormatrix smpte170m --transfer bt470m --colorprim bt470m

while the normal BT601 NTSC SMPTE C (1987) that all other NTSC countries adopted has the following values:

x white = 0.3127
y white = 0.3290
x red = 0.630
y red = 0.340
x green = 0.310
y green = 0.595
x blue = 0.155
y blue = 0.070

and is indicated with:
--colormatrix smpte170m --transfer smpte170m --colorprim smpte170m


As you can see, all values are different, including the white point.

All the LUTs created up until this point that referred to BT601 NTSC were targeting the normal BT601 NTSC SMPTE C (1987), namely smpte170m.

This release allows the conversion between the two BT601 NTSC standards: the old BT601 NTSC Japan (1953), namely bt470m, and the normal BT601 NTSC SMPTE C (1987), namely smpte170m.




Left is BT601 NTSC SMPTE C (1987) - Right is BT601 NTSC-Japan (1953):
https://i.imgur.com/XDTVFAU.png

mojie126
18th May 2025, 07:27
May I ask why there is no conversion from SDR (BT709) to Dolby Vision (DV)?

huhn
18th May 2025, 21:45
that's simple what even is DV?

it's just PQ bt 2020.

DV is the same as HDR10+ just encoded with licensing stuff and custom meta data representing the same stuff.

FranceBB
18th May 2025, 22:55
it's just PQ bt 2020.

The one you're referring to is the dual layer, the most common version of Dolby Vision. You're right, in the dual layer you have the first layer as a normal H.265 3840x2160 4:2:0 HDR PQ 10bit planar track and the second layer as a fake H.265 1920x1080 4:2:0 10bit which is actually carrying metadata (it would look green if you tried to decode it on its own). Normal players ignore the second layer and just play the first one as a normal HDR10. Dolby Vision players can read the metadata track and combine it with the first to output 12bit HDR PQ with dynamically changing metadata.


Now, obviously making a LUT for such a thing wouldn't make sense, which is why I'm pretty sure that mojie126 is actually referring to dvhe0509. You see, dvhe0509 is actually a proprietary Dolby space (IPTc2) and the videos encoded with it are not supposed to be played back by normal devices. If you were to playback such a file, you would see the luma resemble a pseudo SDR curve and the chroma being with totally different values.

See here: https://forum.doom9.org/showthread.php?p=1964646

And here in this thread: https://forum.doom9.org/showthread.php?p=1964642#post1964642

May I ask why there is no conversion from SDR (BT709) to Dolby Vision (DV)?

I should actually give you a bit more context over how we made the dvhe0509 to BT709 LUT. Essentially, that dates back to February 2022 when algia (Livio Aloja, one of my colleagues, now retired) and I worked on it. Essentially, given that there was no documentation whatsoever, we couldn't really properly revert it and get an appropriate output in BT709. I mean, sure, we knew how things like the grass or the sky should roughly look like but it was really mostly guesswork and we were not really going anywhere. That changed when we got our hands on the Harmonic samples. You see, Harmonic published a few videos encoded in both dvhe0509 and BT709 so, with a lot of patience, armed with a lot of dedication and good will, we went through those one by one and we essentially mapped each and every value found in the dvhe0509 IPTc2 samples to the corresponding value in the BT709 SDR samples and from there we got the LUT. As to the luma, given that it resembled an SDR curve, we left it alone. The results were actually pretty decent even if we were ignoring the Dolby metadata alongside that.

LinearTransformation(Input="dvhe0509", Output="Linear_BT709")

https://i.imgur.com/PGtRZQm.png

Now, surely we could just reverse it so that we could potentially use the same logic to go from BT709 to a "fake" dvhe0509 IPTc2 and we could probably even get the values somewhat right but we would have no way of writing the correct metadata as we still wouldn't be able to generate the appropriate RPU. I'm pretty sure that no Dolby Vision decoder would accept a stream without the relevant metadata, so I guess we're stuck with this... :(

Z2697
18th May 2025, 23:13
The essential part is dynamic metadata and the colormatrix. (the latter is less essential)
Both are not under the charge of this LUT collection.

Z2697
18th May 2025, 23:29
The one you're referring to is the dual layer, the most common version of Dolby Vision. You're right, in the dual layer you have the first layer as a normal H.265 3840x2160 4:2:0 HDR PQ 10bit planar track and the second layer as a fake H.265 1920x1080 4:2:0 10bit which is actually carrying metadata (it would look green if you tried to decode it on its own). Normal players ignore the second layer and just play the first one as a normal HDR10. Dolby Vision players can read the metadata track and combine it with the first to output 12bit HDR PQ with dynamically changing metadata.


Now, obviously making a LUT for such a thing wouldn't make sense, which is why I'm pretty sure that mojie126 is actually referring to dvhe0509. You see, dvhe0509 is actually a proprietary Dolby space (IPTc2) and the videos encoded with it are not supposed to be played back by normal devices. If you were to playback such a file, you would see the luma resemble a pseudo SDR curve and the chroma being with totally different values.

See here: https://forum.doom9.org/showthread.php?p=1964646

And here in this thread: https://forum.doom9.org/showthread.php?p=1964642#post1964642



I should actually give you a bit more context over how we made the dvhe0509 to BT709 LUT. Essentially, that dates back to February 2022 when algia (Livio Aloja, one of my colleagues, now retired) and I worked on it. Essentially, given that there was no documentation whatsoever, we couldn't really properly revert it and get an appropriate output in BT709. I mean, sure, we knew how things like the grass or the sky should roughly look like but it was really mostly guesswork and we were not really going anywhere. That changed when we got our hands on the Harmonic samples. You see, Harmonic published a few videos encoded in both dvhe0509 and BT709 so, with a lot of patience, armed with a lot of dedication and good will, we went through those one by one and we essentially mapped each and every value found in the dvhe0509 IPTc2 samples to the corresponding value in the BT709 SDR samples and from there we got the LUT. As to the luma, given that it resembled an SDR curve, we left it alone. The results were actually pretty decent even if we were ignoring the Dolby metadata alongside that.

LinearTransformation(Input="dvhe0509", Output="Linear_BT709")

Now, surely we could just reverse it so that we could potentially use the same logic to go from BT709 to a "fake" dvhe0509 IPTc2 and we could probably even get the values somewhat right but we would have no way of writing the correct metadata as we still wouldn't be able to generate the appropriate RPU. I'm pretty sure that no Dolby Vision decoder would accept a stream without the relevant metadata, so I guess we're stuck with this... :(

Some discs have full enhance layer and the track contain some kind of diff of I guess 12bit to the 10bit of main track.
Which probably look like some noise. The track has low bitrate still, so it will look terrible, but at least it still does something I guess...

Is that dvhe0506 conversion works as converting it to RGB using Rec.709 matrix then "correct" the wrong RGB?

FranceBB
19th May 2025, 07:52
Is that dvhe0506 conversion works as converting it to RGB using Rec.709 matrix then "correct" the wrong RGB?

Pretty much, yes. Far from ideal, but it gets the job done.

huhn
19th May 2025, 16:26
The one you're referring to is the dual layer, the most common version of Dolby Vision. You're right, in the dual layer you have the first layer as a normal H.265 3840x2160 4:2:0 HDR PQ 10bit planar track and the second layer as a fake H.265 1920x1080 4:2:0 10bit which is actually carrying metadata (it would look green if you tried to decode it on its own). Normal players ignore the second layer and just play the first one as a normal HDR10. Dolby Vision players can read the metadata track and combine it with the first to output 12bit HDR PQ with dynamically changing metadata.
glad to say we are past that and on the fly none LUT DV to HDR10 is a pretty default feature now. pretty much LLDV or what the name was DV handling at the source. this was done with a 3D LUT ion the past

the DV encoder got bt 2020 PQ to eat and the display is presenting bt 2020 PQ at the end.
itp instead of ycbcr a meta data lever or what ever does not change that. how many types of DV are there 12?

Some discs have full enhance layer and the track contain some kind of diff of I guess 12bit to the 10bit of main track.
Which probably look like some noise. The track has low bitrate still, so it will look terrible, but at least it still does something I guess...

Is that dvhe0506 conversion works as converting it to RGB using Rec.709 matrix then "correct" the wrong RGB?
there was a DV level that had highlights encoded in a 2 bit level that is 1080p it's so insanely useless it is still 10000 nits PQ bt 2020 just in a different container and yes a DV file can look quite different from a normal HDR10+ because it was mastered differently.

rwill
19th May 2025, 18:15
there was a DV level that had highlights encoded in a 2 bit level that is 1080p it's so insanely useless it is still 10000 nits PQ bt 2020 just in a different container and yes a DV file can look quite different from a normal HDR10+ because it was mastered differently.

So which TV's panel supports 10000nits so that you can watch that 10000 nits PQ10 without having to downmap it to the display guided by metadata?

huhn
19th May 2025, 18:34
So which TV's panel supports 10000nits so that you can watch that 10000 nits PQ10 without having to downmap it to the display guided by metadata?

none and you don't use meta data to do so. any device is measuring peak nit by a frame by frame basin meta data is really just there.

even SDR screen are doing that for decades.

rwill
20th May 2025, 04:19
none and you don't use meta data to do so.

The non Dolby Vision TVs maybe don't use metadata to do so... but there the content looks different from TV to TV which has its downsides too...

Z2697
20th May 2025, 17:00
there was a DV level that had highlights encoded in a 2 bit level that is 1080p it's so insanely useless it is still 10000 nits PQ bt 2020 just in a different container and yes a DV file can look quite different from a normal HDR10+ because it was mastered differently.

Do you mean the "disappeared" profile 4? There's no much info about it, but seems like it uses an SDR base layer, so you mean it's then using the EL to encode all the highlights?

Profile 7 is different, it looks like some sort of difference between bit depths. Being low resolution and low bitrate it should only improve "effective bit depth" marginally. And yeah being a "gradient enhancer" it technically shouldn't look much difference, if any.

LouieChuckyMerry
4th August 2025, 22:12
FranceBB: I hope you've been well :) . A couple years ago you were kind enough to help me come to a (barely, ha ha) functional enough understanding of your LUT Collection (v2.3 at the time) to tone map from BT2020 PQ to BT709 using your "PQ_to_BT709_v1.cube". Recently I attacked my 4K UHD Blu-ray of Stanley Kubrick's 1975 masterpiece Barry Lyndon and, upon comparing it to my 2K Blu-ray I discovered that the colors seem off a bit. Here's a frame of the 2K and the 4K to compare:

https://www.mediafire.com/file/peazbv8wgve8zez/BLyndon{2KVs4K]..7z/file (https://www.mediafire.com/file/ndez1is0otfwypx/BLyndon{2KVs4K]..7z/file)

To my eyes the original 2K looks better than the tone mapped 4K, so I'm wondering if you had a(ny) suggestion(s). I've downloaded your latest v3, but perhaps that's not the issue. Anyway, thanks for any input.

FranceBB
4th August 2025, 23:08
Hey Louie, I'm doing well, but I'm in the middle of changing flat as I'm moving closer to my workplace, namely at a 10 minutes walk from the office, which is always nice.
I took a quick look at the two versions, but if you could upload the same frame in PQ it would definitely help.

LouieChuckyMerry
5th August 2025, 00:07
I've updated the link to include your request and, please, I'm in absolutely zero hurry, so focus on your move (congratulations!).

FranceBB
24th August 2025, 01:19
I've updated the link to include your request and, please, I'm in absolutely zero hurry, so focus on your move (congratulations!).

I looked at the 4K screenshot, but that isn't really a BT2020 PQ screenshot. Some kind of conversion happened before it got captured, I'm afraid.

In other words, this isn't BT2020 PQ:
https://i.imgur.com/lG2hIap.png

If you manage to get the actual original BT2020 PQ screenshot out I can actually try to play with it and see what I'm dealing with. I can also use other tonemapping algorithms eventually like Reinhard and provide an actual script or indeed play with the sample and generate a different LUT. A way to do this would be to index via LWLibavVideoSource() in Avisynth and then grab the screenshot of the PQ source by right clicking and saving as PNG lossless via AVSPmod mod.

Given that 203 nits looks suspiciously familiar, what I think happened here is that *something* converted the screenshot from BT2020 to BT709 in terms of colormatrix, but it didn't deal with the transfer (i.e the color curve) at all.


Anyway, when it comes to the difference between the output from the LUT and the official BT709 version, the first thing that we can notice is the difference in luma:
https://i.imgur.com/wQjY469.png

The official version almost peaks at 0.70V which is 100 nits in BT709 SDR (it actually peaks at 90 nits a tiny bit below) while the output from the LUT peaks much lower at around 0.5V which is around 70 nits. If we take the sky as our reference to reach 100 nits and we assume that we're gonna stay consistently at that level through the movie (which is almost definitely the case given that it's a re-scan and grade of a 1975 film) I'm sure we can get something better out of it.

LouieChuckyMerry
27th August 2025, 02:50
FranceBB: Thank you so much for your reply :) . I won't be able to do as you request until the weekend but, off the top of my head, I'm wondering if taking a frame from the resultant UtVideo file, after using your LUT to tone map, is the issue. When I've the time this weekend I'll re(rere..., ha ha)read your above post and attack. Thanks again, and I hope your move went as well as possible.

LouieChuckyMerry
31st August 2025, 19:31
Thanks for your patience. Here's an(other) updated link:

https://www.mediafire.com/file/vi0qlbqunuue683/BarryLyndon%255B2KVs4K%255D.zip/file

The .png file is a screenshot from MPC-BE from the Criterion 4K video; I hope this is what you want.

FranceBB
9th September 2025, 15:20
It's still not PQ. At this point if you could just trim a tiny bit of that scene it will probably be better.
Either that, or if you wanna keep going with the image just index the file and then save the png using AVSPmod mod.

For reference, the picture you sent was:
https://i.imgur.com/aGSIKEo.png

while it should have been something like:
https://i.imgur.com/qv3ZBaW.png


In this second example I'm taking the BT709 SDR tonemapped version and I'm converting it back to BT2020 HDR PQ just to show you how a totally logarithmic curve is supposed to look like.
The waveform is mostly in the middle, the black is supposed to start high.

LouieChuckyMerry
11th September 2025, 01:04
Roger, Roger. Here's a link to a three-second clip from the Criterion UHD Blu-ray: TestClipBarryLyndon4K (https://www.mediafire.com/file/xw4i3ec91z7sy33/TestClipBarryLyndon4K.zip/file)

FranceBB
11th September 2025, 21:50
Alright, now we're talking.
The scene is perfect as it also has the clouds in the sky and from the official BT709 SDR version we know that those are supposed to almost peak at 0.7V or 100 nits, so we can adjust with that in mind. The reason why the whole thing is much dimmer than the official version is that the PQ is peaking at around 180 nits as we can see from here:

https://i.imgur.com/eTTmuz0.png


Using Reinhard tonemapping with appropriate tweaking the result isn't that bad and still maintains a rather soft look, but it is still relatively different from the official source:


LWLibavVideoSource("TestClipBarryLyndon4K.mkv")

ConvertBits(16)
ConvertYUVtoXYZ(Color=0, OutputMode=1, HDRMode=0, fullrange=false)
ConvertXYZ_Reinhard_HDRtoSDR(exposure_X=8.5, contrast_X=0.9, exposure_Y=8.5, contrast_Y=0.9, exposure_Z=8.5, contrast_Z=0.9)
ConvertXYZtoYUV(pColor=0)
Converttoyuv420()


https://i.imgur.com/jp3XwJo.png

The one that gets us much closer to the official version in this case is Mobius that allows us to make it peak exactly at the same point as the official SDR source. In this case, we can see that the highlights of the sky on the left have the same brightness, however there's a non insignificant deviation on the chroma which is visible on the grass on the right.



LWLibavVideoSource("TestClipBarryLyndon4K.mkv")

ConvertBits(16)
ConvertYUVtoXYZ(Color=0, OutputMode=1, HDRMode=0, fullrange=false)
ConvertXYZ_Mobius_HDRtoSDR(exposure_X=58.5, exposure_Y=58.5, exposure_Z=58.5)
ConvertXYZtoYUV(pColor=0)
Converttoyuv420()


https://i.imgur.com/i3KhArC.png


Lastly here's the same output using Hable:


LWLibavVideoSource("TestClipBarryLyndon4K.mkv")

ConvertBits(16)
ConvertYUVtoXYZ(Color=0, OutputMode=1, HDRMode=0, fullrange=false)
ConvertXYZ_Hable_HDRtoSDR(exposure_X=20, exposure_Y=20, exposure_Z=20)
ConvertXYZtoYUV(pColor=0)
Converttoyuv420()


https://i.imgur.com/fZ13MRr.png


Keep in mind that in my new location I still don't have a proper monitor, so I might even be talking rubbish here, but from the waveform and the tiny and relatively crappy laptop display on the Suckdows 11 Enterprise x64 computer I'm writing from I would suggest you to go with Mobius tonemapping and the 16bit planar precision XYZ roundtrip.

LouieChuckyMerry
14th September 2025, 22:05
"...I would suggest you to go with Mobius tonemapping and the 16bit planar precision XYZ roundtrip."
_____________

As an enthusiastic-if-somewhat-clueless, semi-dedicated video encoding hobbyist, I quote Professor Farnsworth: A wha...?

Seriously, I have a reasonable clue but want to make certain. The original script I used (that didn't look quite right; and, thanks again, you helped me sort this out several years ago) was:

LWLibavVideoSource("Path")
ConvertToPlanarRGB(Matrix="Rec2020",Interlaced=False)
ConvertBits(Bits=16)
Spline36Resize(1920,1080).Sharpen(0.2)
Cube("F:\InstalledPrograms\AviSynth+\LUTs\PQ_to_BT709_v1.cube",Fullrange=True)
ConvertToYUV420(Matrix="Rec709")

Now, you're suggesting I use:

LWLibavVideoSource("Path")
ConvertBits(16)
Spline36Resize(1920,1080).Sharpen(0.2)
ConvertYUVToXYZ(Color=0,OutputMode=1,HDRMode=0,Fullrange=False)
ConvertXYZ_Mobius_HDRToSDR(Exposure_X=58.5,Exposure_Y=58.5,Exposure_Z=58.5)
ConvertXYZtoYUV(pColor=0)
ConvertToYUV420()

instead?

A couple sub-questions:

1) Does the order of "ConvertToPlanar.../ConvertYUVToXYZ..." and "ConvertBits(Bits=16)" matter?

2) Do I need to change "ConvertToYUV420()" in your new suggestion to "ConvertToYUV420(Matrix="Rec709")" as in the original script?

EDIT: And I understand your monitor dilemma; I've a fifteen-year-old ThinkPad with a 1600x900 display that's rather lacking.

FranceBB
17th September 2025, 21:07
1) Does the order of "ConvertToPlanar.../ConvertYUVToXYZ..." and "ConvertBits(Bits=16)" matter?


Yep. There's nothing stopping you from doing the conversion with 8bit planar precision, obviously, but the result would be full of banding and other artifacts. Unlike YUV, when you're working in XYZ you're dealing with a very big space, which means that the effect of working with 8bit precision would be so high that the result would be terrible. It's not just subtle banding problems, you're gonna notice the rounding introduced artifacts, which is why I recommend working with at least 16bit planar precision, but you could totally go with 32bit float instead if you wanna play it safe. So, yes, the order matters 'cause we're first converting to high bit depth, so we have a bunch of zeroes (if you were to look at the values) and then we perform the conversion to XYZ by making use of this new precision all the way through the entire processing.


2) Do I need to change "ConvertToYUV420()" in your new suggestion to "ConvertToYUV420(Matrix="Rec709")" as in the original script?


Oh yes, absolutely. Also, if we wanna be pedantic, given that we're sadly living in a world in which frame properties are a thing, the final output would be:


#Indexing
LWLibavVideoSource("Path")

#Screw frame properties
PropClearAll()

#Bringing everything to 16bit planar
ConvertBits(16)

#Upscale with 16bit precision
Spline64ResizeMT(1920, 1080)

#Bringing everything to XYZ with 16bit precision
ConvertYUVToXYZ(Color=0, OutputMode=1, HDRMode=0, Fullrange=False)

#BT709 SDR Tonemapping with 16bit precision
ConvertXYZ_Mobius_HDRToSDR(Exposure_X=58.5, Exposure_Y=58.5, Exposure_Z=58.5)

#Bringing everything back to YUV with 16bit precision
ConvertXYZtoYUV(pColor=0)

#Converting to 4:2:0 with 16bit precision
ConvertToYUV420(Matrix="Rec709")

#Dithering with Floyd Steinberg Error Diffusion
ConvertBits(bits=8, dither=1)

#Populate frame properties
propSet("_ColorRange", 1) #Limited TV Range
propSet("_Matrix", 1) #BT709
propSet("_Transfer", 1) #BT709
propSet("_Primaries", 1) #BT709


In this case I used the "MT" version of the resizers rather than the internal Avisynth version as the multithreaded version not only is faster but it also if you use plugins_JPSDR.dll there will be a common threadpool between the resizers, the tonemapper and the conversion to and from XYZ. :)


By the way, you can still use my LUT to perform the conversion instead of a tonemapper, obviously. When it comes to performing this kind of conversion, there isn't a generic way to do it, you're gonna have to try and adjust on a content-by-content basis. Effectively, you can use my VideoTek() to check the source and then find a point in which there's something white, so something that "peaks" and then use that one as your reference to bring it to 100 nits in BT709 SDR, so 0.7V in the graph. Of course, when it comes to HDR PQ, it's not exactly this simple in the sense that you might have some scenes skyrocketing high while some others are relatively low, so you may not always wanna use the MaxCLL as your reference. A simple example is the remastered version of the Harry Potter movies. Harry Potter and the Prisoner of Azkaban peaks at 1838 nits while Harry Potter and the Goblet of Fire peaks at a whopping 2848, but this is only due to the special effects (i.e the magic spells) which are computer rendered anyway and were brought extremely high to enhance their visual effect. The rest of the scenes of the movies were much lower as those were shot in 2005 with SDR in mind, so by bringing 2848 nits down to 100 with a static LUT would make all the other scenes pitch dark and it would be very hard to distinguish what's going on. This is to say that there isn't something you can copy-paste and that it's gonna work all the time for every content. In my LUTs I tried to get the best compromise so that it's gonna be good enough for the vast majority of contents. Even in this peculiar case, for instance, the result was too dim but it wasn't bad and that's the whole point. :)
Ideally one would use a dynamically changing tonemapping like the one in MPV that automatically adapts on a scene by scene basis to bring the peak brightness back to 100 nits and you have thresholds that you can adjust based on the differences in blocks on what is considered a scene, however even that might lead to undesirable results like in Riviera when Georgina was walking and she was lit by the light and then in the dark and then lit and then in the dark and the dynamically adjusting tonemapper tried to bring everything up, then down, then up, then down, then up, then down etc and the result wasn't pleasant as it was more distracting than watching the output of a static LUT. Once again, there's no perfect solution.


I understand your monitor dilemma; I've a fifteen-year-old ThinkPad with a 1600x900 display that's rather lacking.

True, but I'm spoiled because at work in the old office I used to watch contents on the Sony BVM-HX310. It's quite old now, but it was amazing back in 2018.

LouieChuckyMerry
25th September 2025, 00:17
By the way, you can still use my LUT to perform the conversion instead of a tonemapper, obviously.

Thanks for your patience, FranceBB :) .

Since you helped me settle on it several years ago (https://forum.doom9.org/showpost.php?p=1988873&postcount=256), I've been happily using the script


LWLibavVideoSource("PATH")
ConvertToPlanarRGB(Matrix="Rec2020",Interlaced=False)
ConvertBits(Bits=16)
Spline36Resize(1920,1080)
Cube("F:\InstalledPrograms\AviSynth+\LUTs\PQ_to_BT709_v1.cube",Fullrange=True)
ConvertToYUV420(Matrix="Rec709")
PreFetch(8)

to downscale and convert my 4K sources to 2K before sprucing them up (with vsDeblockPP7, TemporalDegrain2, and CAS) then shrivelling them (with 10bit x264). The only reason I returned with more questions, is because I noticed that the sky in the upper-left of the duelling scene--one of my favo(no "u"!)rite shots ever; the clouds, the tree branches, the rock wall, the figures on a landscape, endless brilliance--after using your above LUT to convert the 4K Criterion to 2K, was lacking the slightly pinkish hue present in the 2K Criterion.

Anyway, your source-specific script

LWLibavVideoSource("Barry Lyndon PATH")
PropClearAll()
ConvertBits(Bits=16)
Spline64ResizeMT(1920,1080)
ConvertYUVToXYZ(Color=0,OutputMode=1,HDRMode=0,Fullrange=False)
ConvertXYZ_Mobius_HDRToSDR(Exposure_X=58.5,Exposure_Y=58.5,Exposure_Z=58.5)
ConvertXYZtoYUV(pColor=0)
ConvertToYUV420(Matrix="Rec709")
ConvertBits(Bits=8,Dither=1)
PropSet("_ColorRange",1)
PropSet("_Matrix",1)
PropSet("_Transfer",1)
PropSet("_Primaries",1)
PreFetch(8)

seems to have rectified this but, of course of course, it's also led to several (more) questions:

1) Why, exactly, would I want to start the script with "PropClearAll()"?

2) Is it always best to have "ConvertBits(16)" before converting, I think, the colorspace?

3) What's the difference, and is it important, between "ConvertToPlanarRGB(Matrix="Rec2020",Interlaced=False)" from the original script and "ConvertYUVToXYZ(Color=0,OutputMode=1,HDRMode=0,Fullrange=False)" from the "updated" script?

4) I'm pretty sure that "ConvertXYZ_Mobius_HDRToSDR(Exposure_X=58.5,Exposure_Y=58.5,Exposure_Z=58.5)" is specific to Barry Lyndon; but, what about "ConvertXYZtoYUV(pColor=0)"?

5) Do I really need "ConvertBits(Bits=8,Dither=1)" if I'm going to use the resultant lossless video file from downscaling-converting to then deblock-denoise-sharpen in 10bit x264?

6) Do I really need "PropSet("_ColorRange",1), PropSet("_Matrix",1), PropSet("_Transfer",1), PropSet("_Primaries",1)" at the end of the script if my 10bit x264 command line contains "--colorprim bt709 --transfer bt709 --colormatrix bt709"?

I reckon that what I'm truly wondering is how best to combine the two above scripts, given my usage, into a single, generally functional script that's an improvement on the original script you helped me settle on two-plus years ago. And, I realize that you're a busy person with a busy life, so no need for any chop-chop response. Some day I might actually be able to retire and devote myself to video encoding. A man can dream...

Z2697
26th September 2025, 05:58
Oh yes, absolutely. Also, if we wanna be pedantic, given that we're sadly living in a world in which frame properties are a thing, the final output would be:

Why is it sad?

roccomarco
26th September 2025, 21:54
Hi,

I try to apply a lut in avisynth into my Dji mini 4 drone file that produce HLG mp4 files

I wonder: is there a suitable lut for it? in avisynth, I think input is HLG and output is bt709
thanks

FranceBB
27th September 2025, 01:09
1) Why, exactly, would I want to start the script with "PropClearAll()"?


It really depends, but I personally use it in every encode I make because I'm used to the "old" way of managing things within Avisynth.

Let me explain.

Up until a few years ago there were only clip properties within the frameserver. This was the way things were and how people were used to do things. This basically meant that after indexing the clip properties were gonna be populated with some extremely basic information and then when you called a function if you actually specified the parameters then the function would use those, otherwise it would use the default ones. This basically meant that you knew exactly which parameter a function would be using if you didn't specify them as those were the default ones. This worked beautifully for years and years for the internal needs of this community as the overwhelming majority of people were literally indexing, filtering stuff out and then manually re-encoding with x262, xvid, x264, x265 and so on. In those cli encoders you can specify the various options to populate the metadata like --colorprim bt709 --transfer bt709 --colormatrix bt709 --range tv anyway so everything was fine, but what about automation? What about interacting seamlessly with other software? For that, you needed something better, which is why frame properties were introduced. Effectively frame properites are some "properties" that get populated for every frame and can change within those. This allowed lots of new possibilities like managing changing live streams in NDI as the transition from SDI with hardware routing to actual IP packets is happening or indeed it allowed automated filterchains to integrate with the users.

For instance, if you were to have a system perform some kind of operations but you also wanted to perform some modifications in Avisynth you would need to tell the system what exactly you've done to the stream. With frame properties the software getting the uncompressed A/V output from Avisynth (be it another frameserver and collection of tools like FFMpeg or a player like MPV etc) it would immediately know what it's dealing with and treat it correctly.

In FFAStrans (FFMpeg Avisnth Transcoder) (https://forum.doom9.org/showthread.php?t=176655) for instance, there's something called the filter_builder. Essentially, when you build an encoding workflow, the filter_builder is the one responsible to analyze the source (Mediainfo, ffprobe, exiftool, ffms2 etc), check the destination you want to create in terms of encoding, and then "build" the "filterchain" (hence the name) to create a path between the source and the destination so that the uncompressed A/V stream coming out is already ready to be encoded by the actual encoder (like x264) as it has already taken care of converting everything from the framerate to the chroma location to the sampling to the bit depth to the colormatrix, transfer and primaries etc. Now, if the user wanted to make some modifications using a custom avisynth script, he could, however back when only the clip properties were available, he couldn't tell the filter_builder what he had changed, so either he was gonna write a bunch of conditionals and take care of all the possible input combinations to then just tell the filter_builder "hey, don't do anything, just pass this through to the encoder, I got this" or he could just perform filtering like denoise, debanding etc but not change any of the properties of the stream to then allow the filter_builder to still build the filterchain. When the frame properties have been introduced, the user had - for the first time - the ability to actually tell the filter_builder what he has done and what it's gonna get, thus unlocking lots of different possibilities.


So... all is good and frame properties are good, right? Well, not quite. You see, unfortunately Avisynth has been without frame properties for quite some time which means that when they've been introduced there were lots of filters that didn't really support them. Some of them started interpreting them and changing them after they're called, some others kept using the same default parameters without changing the frame properties but preserved them as they basically passed them through to the next filter, while some other again just didn't support them at all and nuked them after they were invoked. This led to some unfortunate issues.

Take your example with my original script with Cube() for instance: the function "cube" is performing the conversion as it's applying my values to map BT2020 PQ to BT709 SDR, however such a function has no clue about what it's doing, all it knows is that it's taking some values in input and it's applying a transformation to map those in an output according to what is defined in the file using either trilinear or tetrahedral interpolation.
This means that it cannot and does not change the frame properties which means that if your source had smpte2084 with bt2020nc, it will still say that after the conversion which in turn means that every filter you call after that will still think that it's dealing with a PQ stream while actually it's dealing with a BT709 SDR one.

This is why I'm using PropClearAll() all the time in my manual encodes as I know what I'm doing given that this isn't an automatically generated script but I'm writing it and therefore I'm happy to pass the parameters I require while letting the other unspecified ones as default.

At work, however, basically all the 304 Avisynth workflows I made have frame properties correctly preserved / modified / populated to tell either the filter_builder or the other systems that come next what they're getting. :)

Why is it sad?
Basically everything I said above. ;)
The sad thing is that not everything supports it and with some plugins being abandoned they never will, so we're currently in a state in which we have to add a bunch of PropSet() in the scripts and be extra careful about what's going on.


2) Is it always best to have "ConvertBits(16)" before converting, I think, the colorspace?


Yep, it's always nice to have more precision and it shows.


3) What's the difference, and is it important, between "ConvertToPlanarRGB(Matrix="Rec2020",Interlaced=False)" from the original script and "ConvertYUVToXYZ(Color=0,OutputMode=1,HDRMode=0,Fullrange=False)" from the "updated" script?


In the first case you're going to 16bit planar RGB which is a relatively normal space and it's the one required to perform the transformation as the values described in the LUT are actually RGB values (either full pc range or limited tv range according to who created it, but in mine it's always full pc range and never limited / narrow range / studio rgb). I know that the guys at Warner Bros for instance have all their LUTs working in Studio RGB (i.e Narrow Range / Limited TV Range RGB). Anyway, we're digressing. The point is that it's what you require to perform the conversion. When it comes to tonemapping, instead, this could be done in RGB as well, however it's generally performed in a much wider space, namely XYZ. You see, XYZ is way bigger and allows for far better precision, especially if you use 32bit float. If you've ever watched a movie at the cinema, those are played from a DCP (Digital Cinema Package) (https://forum.doom9.org/showthread.php?p=1979437) which is in DCI P3 XYZ 4:4:4 12bit. Anyway in your case, with tonemapping, going through an XYZ roundtrip rather than an RGB roundtrip allows us to work in a much wider space with far better precision.


4) I'm pretty sure that "ConvertXYZ_Mobius_HDRToSDR(Exposure_X=58.5,Exposure_Y=58.5,Exposure_Z=58.5)" is specific to Barry Lyndon; but, what about "ConvertXYZtoYUV(pColor=0)"?


Correct, the tonemapping with Mobius is specific to Barry Lyndon and can't be applied generally to every content all the time without checking the actual waveform and where it peaks in terms of nits. On the other hand ConvertXYZtoYUV(pColor=0) is simply a conversion from XYZ to YUV that specifies BT709. By the way I first started experimenting with those back in 2018 (https://forum.doom9.org/showthread.php?t=175488&page=4) when Jean Philippe Scotto di Rinaldi (i.e JPSDR) implemented them in HDRTools. We're friends and back then we even shared test builds, a few experiments and several test files. We still are to this very day. :)


5) Do I really need "ConvertBits(Bits=8,Dither=1)" if I'm going to use the resultant lossless video file from downscaling-converting to then deblock-denoise-sharpen in 10bit x264?


Oh, no, feed x264 with the 16bit planar output from Avisynth and it will perform the dithering to 10bit at the very end in a very efficient way using the Sierra A2-4 Error Diffusion, which is the way it's intended. I do that all the time when I encode XAVC Intra Class 300 files in BT709 and BT2020 HLG at work :)


6) Do I really need "PropSet("_ColorRange",1), PropSet("_Matrix",1), PropSet("_Transfer",1), PropSet("_Primaries",1)" at the end of the script if my 10bit x264 command line contains "--colorprim bt709 --transfer bt709 --colormatrix bt709"?


You're fine, x264 doesn't care at all about frame properties, it only reads the good old clip properties, so you're fine. Oh, don't forget --range tv, though. ;)


I reckon that what I'm truly wondering is how best to combine the two above scripts, given my usage, into a single, generally functional script that's an improvement on the original script you helped me settle on two-plus years ago.


No need, you can keep using the original script. Consider this as a "one off", however if you have a bit of time and wanna experiment a bit, you can use my function VideoTek() (https://forum.doom9.org/showthread.php?t=175249) to check the waveform of the inputs and see where they peak and based on that use the appropriate values in the tonemapping function to get the best result. Other than that, if you want a fire-and-forget approach, you're gonna be more than fine with my initial script.

By the way, it uses trilinear by default and you should update to the latest version and use

Cube("test.cube", fullrange=1, interp=1)

instead. I wrote a paragraph on the wiki (http://avisynth.nl/index.php/AVSCube) about trilinear vs tetrahedral interpolation. Here's a snipped from it:


Trilinear interpolation is used by default only for historical reasons.
When Cube() was first developed it only had one interpolation method, namely trilinear.
Although this was far from ideal as it can produce ripples, plenty of people used it (as they were using this function), therefore when tetrahedral was introduced it was agreed to leave trilinear as default to allow people to still obtain the same results when upgrading the version.
However, in practice, tetrahedral should always be used and it's the suggested method.

Source (left) - Trilinear (center) - Tetrahedral (right):
https://i.imgur.com/qlu1T2X.png


Keep in mind that if you were to send a test pattern back to Sony after encoding and applying trilinear interpolation, they would refuse it as the plot in 3D space would show a deviation of the actual values from the expected ones.

https://i.imgur.com/IxxdXGT.pnghttps://i.imgur.com/2nKitu4.png

however they would pass the same file when encoded using the tetrahedral interpolation.


And, I realize that you're a busy person with a busy life, so no need for any chop-chop response.


No worries, I'm mostly busy with my job at the moment, but - and this is related to the reason why I moved - we're actually doing something amazing which is building a group platform under Comcast for all the Sky across the world, streamlining all the various processes. This led to several challenges but it's also particularly rewarding and the team even got a public award for this. Obviously Avisynth is (and will continue to be) an instrumental part of all of this. After all, it's the best frameserver in the world. :D


Some day I might actually be able to retire and devote myself to video encoding. A man can dream...

I honestly and seriously consider myself one of the luckiest guys alive as I'm literally doing this day in and day out at work. Nowadays it kinda shifted from manually encoding files, filtering them to get the perfect compositing etc for the production department to actually code and write supply chains, workflow integrations etc. Still, I've had my fun with a few Sky Original Productions in the past. :)

Ironically, the day I retire, I will miss the Avisynth servers I set up and the code I wrote to make it all work and glue together with the other systems. People at the office often joke that I'm "married to this company" but seriously those servers are the closest thing I have to "children" eheheheheheheheh

Jokes aside, we'll see what the future holds as I won't be able to retire for at least another 27 years anyway, so who knows what we're gonna be talking about in so many years and who knows if the servers are forever gonna be there. Time will tell.


is there a suitable lut for it? in avisynth, I think input is HLG and output is bt709

If the input is HLG and the output you want is BT709 SDR then you can totally use the HLG_BT2020_to_Linear_BT709.cube either directly using the Cube() function


ConvertBits(16)

ConvertToPlanarRGB()

Cube("C:\Programmi\AviSynth+\LUTs\HLG_to_BT709.cube", fullrange=1, interp=1)

Converttoyuv420()


or using my LinearTransformation (https://github.com/FranceBB/LinearTransformation) like this:

LinearTransformation(Input="BT2020_HLG", Output="Linear_BT709")

roccomarco
30th September 2025, 16:14
Hi for kindness this is my todays example footage from d*i o*mo 3 pocket

https://www.swisstransfer.com/d/3aab4bc1-fb45-4c63-b851-2336621fb68c

that contain the same scene using normal profile and hlg

(1Normal.MP4 / 1HLG.MP4 - 2Normal.MP4 / 2HLG.MP4 - 3Normal.MP4 - 3HLG.MP4)

I try to do a batch that process this camera that when retrieve the arib-std-b67 (if "%color_transferA%"=="arib-std-b67" ...) do the correct avisynth script that generate the rec.709 output

so I wonder: what can it be the correct script to process the source, for example 1HLG.MP4?

can I process all HLG sources in batch mode with an acceptable output result or or do I have to manually do some sort of color correction on parameters that can be evaluated by eye

I try something like this my maybe need correctios

Import("v:\avisynth\plugins\IResize.avsi")
Import("V:\AviSynth\plugins\smoothFPS2.avsi")
Import("V:\AviSynth\plugins\smoothFPS3.avsi")
Import("v:\AviSynth\plugins\CropResizedic2017.avsi")
Import("V:\AviSynth\FrameRateConverter2021\FrameRateConverter.avsi")
Import("V:\AviSynth\LinearTransformation-3.0\LinearTransformation.avsi")
LoadPlugin("V:\AviSynth\LinearTransformation-3.0\vscube.dll")
LoadPlugin("v:\AviSynth\FrameRateConverter2021\X64\FrameRateConverter.dll")
LoadPlugin("v:\AviSynth\FFMS2_23_64bit\ffms2-2.23.1-msvc\x64\ffms2.dll")
Import("V:\AviSynth\FFMS2_AVSI_2019\ffms2.avsi")
LoadPlugin("v:\AviSynth\LSMASH_AGO_2023\x64\LSMASHSource.dll")
LoadCPlugin("v:\avisynth\plugins64\yadif.dll")
LoadPlugin("V:\AviSynth\MaskTool2\X64\masktools2.dll")
LoadPlugin("V:\AviSynth\Santiag\ResampleMT.dll")
LoadPlugin("V:\AviSynth\Santiag\nnedi3.dll")
LoadPlugin("V:\AviSynth\Santiag\aWarpsharpMT.dll")
LoadPlugin("V:\AviSynth\Santiag\eedi3.dll")
LoadPlugin("V:\AviSynth\Santiag\EEDI2.dll")
LoadPlugin("V:\AviSynth\Santiag\GScript_26_64.dll")
LoadPlugin("V:\AviSynth\Santiag\SangNom2.dll")
Import("V:\AviSynth\Santiag\santiag.avsi")
Import("V:\AviSynth\QTGMC2025\QTGMC.avsi")
Import("V:\AviSynth\QTGMC2025\Zs_RF_Shared.avsi")
LoadPlugin("V:\AviSynth\QTGMC2025\mvtools2.dll")
LoadPlugin("V:\AviSynth\QTGMC2025\RgTools.dll")
LoadPlugin("v:\AviSynth\BestSource\BestSource.dll")
LoadPlugin("V:\AviSynth\RIFE\RIFE.dll")
LoadPlugin("V:\AviSynth\RIFE\avsresize.dll")
LoadPlugin("V:\AviSynth\RIFE\PlanarTools.dll")
LoadPlugin("V:\AviSynth\HDRTools\x64\Release_W7_AVX2\HDRTools.dll")
vid=BSVideoSource("C:\Users\Administrator\Desktop\Nuova cartella (37)\HLG DJI_20250928173444_0024_D.MP4")
aud=LWLibavAudioSource("C:\Users\Administrator\Desktop\Nuova cartella (37)\HLG DJI_20250928173444_0024_D.MP4",stream_index=1 )
left=GetChannel(aud, 1)
right=GetChannel(aud, 2)
both=mergechannels(left, right)
audiodub(vid, both)
ConvertAudioTo16Bit()
#TurnRight()
#Turn180()
#TurnLeft()

ConvertBits(16)
ConvertYUVtoXYZ(Color=0, HDRmode=2)
ConvertXYZ_Hable_HDRtoSDR(exposure_X=8.0, whitescale_X=8.0)
ConvertBits(bits=8, dither=1)
Convertbits(8)

assumeFPS(50)
Prefetch(8)

FranceBB
4th October 2025, 22:53
I try to do a batch that process this camera that when retrieve the arib-std-b67 (if "%color_transferA%"=="arib-std-b67" ...) do the correct avisynth script that generate the rec.709 output

For those things what you actually need is FFAStrans (FFMpeg Avisynth Transcoder) (https://forum.doom9.org/showthread.php?t=176655). There's no need to write conditionals, unless you want to apply a different conversion yourself with a Custom Avisynth Script. In other words, you can just use an "encoding" node and set either Auto (LUT) or Auto (Tonemapping) to get the BT709 output and the filter_builder will create the appropriate filterchain for you automatically based on the Mediainfo/FFProbe/ExifTool of the input source and the destination you picked. ;)


can I process all HLG sources in batch mode with an acceptable output result or or do I have to manually do some sort of color correction on parameters that can be evaluated by eye


It really depends on what you deem "acceptable".
Generally speaking, in HLG the output will be ok in most circumstances even if you were to just convert the colormatrix and primaries but not the transfer. Namely doing something like this:


ConvertYUVtoXYZ(Color=1)
ConvertXYZtoYUV(pColor=1)


thus pretending that the input is BT2020 SDR 100 nits instead and converting it to BT709 SDR. This would tecnically simulate what a user with a UHD BT2020 SDR TV would see if he was to play an HLG video as it cannot interpret the arib-std-b67 transfer.

If we did that with your samples, we would have got:

https://i.imgur.com/xiHaMTi.png
https://i.imgur.com/JkPQbD7.png

which is "acceptable" nonetheless. The problem arises from when you want to actually tonemap to bring the peak brightness of the HLG source to 100 nits in SDR because you gotta know the MaxCLL for this to happen. Sure, one could try to calculate it, but in compressed sources there might be outliers that need to be filtered out, so it's a whole different can of worms. Obviously, if you have a, let's say, 203 nits stream and you're not doing tonemapping, then the SDR output will look awful as it's way too dim.

In other words, there's no quick and easy way to always process something.


I try something like this my maybe need correctios


In your script you're missing the conversion back from XYZ to YUV.
After you performed tonemapping with Hable, you've now gone from BT2020 HLG to BT709 SDR, however you're still in XYZ, not in YUV.
Avisynth doesn't unfortunately support XYZ directly (even though I actually asked Ferenc about this a few times and it will, one day), so we're spoofing XYZ as YUV even though it's not.
If you were to look down at the bottom right in AVSPmod mod you would indeed see YUV but actually it's XYZ.

In other words, your script here:


ConvertBits(16)
ConvertYUVtoXYZ(Color=0, HDRmode=2)
ConvertXYZ_Hable_HDRtoSDR(exposure_X=8.0, whitescale_X=8.0)
ConvertBits(bits=8, dither=1)


should actually be:


ConvertBits(16)
ConvertYUVtoXYZ(Color=0, HDRmode=2)
ConvertXYZ_Hable_HDRtoSDR(exposure_X=8.0, whitescale_X=8.0)
ConvertXYZtoYUV(pColor=0)
ConvertBits(bits=8, dither=1)


note the "ConvertXYZtoYUV(pColor=0)" there.

Anyway, I've downloaded the package and I took a look at the source. It looks like it's peaking at 600 nits, which seems to be consistent through the entire video for the two shots:

https://i.imgur.com/LWdipNR.png
https://i.imgur.com/DtchbPv.png

The third one was different, though, probably due to some automatic compensation inside, but it's actually a rather terrible shot. It's going all the way through to 1000 nits but we can see white and clipped out portions of the sky, which is a bit of a problem.

https://i.imgur.com/LnXup1g.png


Using my LUTs either via LinearTransformation


LinearTransformation(Input="BT2020_HLG", Output="Linear_BT709")


or directly via


ConvertBits(16)
ConvertToPlanarRGB()
Cube("C:\Programmi\AviSynth+\LUTs\HLG_to_BT709.cube", fullrange=1, interp=1)
Converttoyuv420()


for the first shot achieves a slightly overexposed result:
https://i.imgur.com/LocZMKH.png

We can do much better via manual tonemapping using Reinhard:


ConvertYUVtoXYZ(Color=0, HDRmode=2)
ConvertXYZ_Reinhard_HDRtoSDR(exposure_X=1.6)
ConvertXYZtoYUV(pColor=0)


https://i.imgur.com/UI9tHI4.png

About the last picture, the one of the flowers, given that it's peaking at 1000 nits, I would go with:

ConvertYUVtoXYZ(Color=0, HDRmode=2)
ConvertXYZ_Reinhard_HDRtoSDR(exposure_X=1.0)
ConvertXYZtoYUV(pColor=0)

https://i.imgur.com/j8inJX6.png

p.s for those in the UK, if you can't see the images, it's because of Captain Hindsight's online safety bill as imgur is no longer available and yet another service caught in the crossfire. Say thanks to Labours and Sir Keir Starmer I guess.

LouieChuckyMerry
13th October 2025, 05:49
Thanks, again, for your patience, and my apologies for temporarily crashing Doom9 with my last batch of inane questions :p .

Seriously, thank you so so much for your reply :) ; it really helped me gain a better understanding of things (like the fact that "converting" and "tone mapping" are different beasts. D'oh! And --range tv is already sorted).

After re(rererererere)reading your reply, I think I can finally wrap my head around everything. I've updated to AVSCube 1.4, and added ",Interp=1" to my tone mapping, er, conversion script. Is this such a massive change that I'd actually notice a difference if I redid encodes I did earlier based on a trilinearly interpreted conversion? If the answer is yes, then please lie, ha ha.

I'll absolutely attempt to put your VideoTek function to use when I have a long enough stretch of free time (a man can dream...).

Congratulations on your team's award, and it makes me happy that you love your job because good people deserve happiness :) .

FranceBB
13th October 2025, 23:25
Thanks, again

No worries, happy to help, as always. :)


Is this such a massive change that I'd actually notice a difference if I redid encodes I did earlier based on a trilinearly interpreted conversion?

My personal position on this is no. I gotta be honest: I've never really seen any noticeable difference aside from synthetic benchmarks. Sure, the waveform is different and sure if you plot the deviation of the points in space you're gonna get the vectors, but when it comes to blind tests looking at a monitor, you're never gonna notice, like, there's no way.

Now that I've said that, I know two of my colleagues (one of which who is also a member of the SMPTE) who would take away my fictional "license to encode" and would absolutely say "yes" and the same goes for some of the folks at Warner and Sony, however, I've never really noticed a difference, so it's not like day and night. To be fair, I've been encoding some titles with trilinear interpolation ages ago and I'm not going to re-encode them.

In other words, you're gonna be fine. ;)



it makes me happy that you love your job because good people deserve happiness :) .

Awwww. :')

roccomarco
16th October 2025, 23:08
thank you very much for your help: I need to do some implementation in my batch routine.

Fracesco Be patient, for kindness, if possibile. or if you want: in this site sometimes I need some interesting thing I need - FOR example - to do a batch that process single photo files to adjust the frame with the fuzzy blur when is portrait

So I wonder: i need to buld a batch procedure that ingest my source photo files and the processing should do some movement in video at different speed inputted.

A sort of "AutoPhoto" (moves and pan,zoom on photo ---> preferable with avisynth ---> export on mxf 1920x1080@50i (interlaced)

can be done this? or can FFAStrans do some "autozoom" on static image photos? thank you very very much

and can FFAStrans transcode to 50P. If I need to transcode to xdcamhd422 25i I use ffmbc but is seem not longer developed so I need to use a similar

Tlen
16th November 2025, 10:15
Hi FranceBB,
thank you for your work.
I'm pretty new to the HDR environment and SDR downconversion (wich I'm interested in).
Atm I don't even try to understand all the math behind the color spaces and I dully play the end user role who see the results (eheh).

So, in my initial round test to manage conversion to SDR I encountered your luts and I promptly tested them.
The initial result of the first try i did, which purposely I did without seeing any reference comparison, to base it on purely reaction, appeared to me a little (not much) "dull". Good colors, but something off.
So first I went to capshaolic just to have an initial alternative source
and the impression of dullness of the lut was confirmed.
The capshaolic screen was more "correct" for me.
I did at this point another round with DGHDRtoSDR and although the default was a bit too bright playing with the white level I was able to obtain a result that I found pretty good and that incidentally matched with capshaolic conversion (I don't know wich method they used to do it).
So, going to data, here's the info from Mediainfo (it's Karate Kid Film)

Video #1
ID : 4113 (0x1011)
Menu ID : 1 (0x1)
Format : HEVC
Format/Info : High Efficiency Video Coding
Format profile : Main 10@L5.1@High
HDR format : SMPTE ST 2086, HDR10 compatible
Codec ID : 36
Duration : 2 h 6 min
Width : 3 840 pixels
Height : 2 160 pixels
Display aspect ratio : 16:9
Frame rate : 23.976 (24000/1001) FPS
Color space : YUV
Chroma subsampling : 4:2:0 (Type 2)
Bit depth : 10 bits
Color range : Limited
Color primaries : BT.2020
Transfer characteristics : PQ
Matrix coefficients : BT.2020 non-constant
Mastering display color primaries : BT.2020
Mastering display luminance : min: 0.0050 cd/m2, max: 4000 cd/m2
Maximum Content Light Level : 4010 cd/m2
Maximum Frame-Average Light Level : 268 cd/m2

This it the Raw image
https://thumbs4.imagebam.com/78/af/02/ME17SLFF_t.png (https://www.imagebam.com/view/ME17SLFF)

This it the Lut
ConvertBits(16)
ConvertToPlanarRGB()
Cube(eng,"PQ_to_BT709_v2.cube",interp=1,cpu=3)

https://thumbs4.imagebam.com/77/85/ad/ME17SLHN_t.png (https://www.imagebam.com/view/ME17SLHN)

This it DGHDRtoSDR
ConvertBits(16)
DGHDRtoSDR(mode="pq",impl="sw",white=1830)
https://thumbs4.imagebam.com/af/52/28/ME17SLIV_t.png (https://www.imagebam.com/view/ME17SLIV)

This is another frame adding capshaolic screen
Raw -> Lut -> DGHDRtoSDR -> caps
https://thumbs4.imagebam.com/09/6b/89/ME17SLLQ_t.png (https://www.imagebam.com/view/ME17SLLQ)https://thumbs4.imagebam.com/80/d5/5e/ME17SLLN_t.png (https://www.imagebam.com/view/ME17SLLN)https://thumbs4.imagebam.com/8e/cb/d3/ME17SLLM_t.png (https://www.imagebam.com/view/ME17SLLM)https://thumbs4.imagebam.com/8f/42/67/ME17SLLL_t.png (https://www.imagebam.com/view/ME17SLLL)

Why is your lut different?
Thank you :)

Emulgator
19th November 2025, 19:08
Safety ? If one source responds well to one or the other algo this does not mean that one is more correct than the other.
You would need a bouquet of reference material to judge any such conversion, there is no auto-improvement involved.
FranceBBs LUTs have passed his own, SKY practics and other testings well, provided that the source's grading/histogram population was somehow correct/useful in the first place.
Remember: MUST NOT CLIP, and this source needs to place 4000nits within SD.
Somebody's taste wants to keep highlight/shadow details and sacrifices some brightness/crunchyness and otherbody's taste may command his S-curve to be bent further up/down,
and later with availabilty of a brighter monitor regretting just that because these details/values are lost.
Looking at FranceBB LUT the girl/boy scene could use a bit more S-curve (but this scene came in with too flat exposure to begin with), the boy's shirt sat well.
Looking at DG/caps the girl/boy scene did well here, the boy's shirt came out blown out a bit.
Maybe this particular source should not have been mastered aiming for 4000nits, such negative stock would have been nicely bedded inside 1000 maybe.
Don't know that movie, are there huge explosions that need it ?
I would maybe regrade/re"time" (reexpose) that in source gamut, then downconvert.

DMD
30th November 2025, 09:40
Good morning.
I am trying to perform some shooting tests with the iPhone 17 Promax, generating a file in ProRes 422 HQ BT2020.
I would like to ask where I can find a conversion LUT from Apple Log2 HDR.
Thank you.

FranceBB
20th December 2025, 21:46
A sort of "AutoPhoto" (moves and pan,zoom on photo ---> preferable with avisynth ---> export on mxf 1920x1080@50i (interlaced)

can be done this? or can FFAStrans do some "autozoom" on static image photos?

I need to transcode to xdcamhd422 25i


Yes, of course it can be done, there's a reason why Avisynth is the best frameserver in the world.
What you need is the zoom function http://avisynth.nl/index.php/Zoom


#Indexing
ImageSource("PXL_20251118_190701454.jpg")

#Bringing everything to RGB
ConverttoRGB32()

#Pan from right to left
zoom(dstx="n", angle="0", width=1920, height=1080)

#Produce 25fps
AssumeFPS(25)

#Go back to YUV
Converttoyv16()


After which you can pass it to the encoder.

I use ffmbc but is seem not longer developed so I need to use a similar


There's no need, you can create a perfectly valid XDCAM-50 in the normal open source MPEG-2 encoder within FFMpeg. After all, what you need is just a normal FULL HD 1920x1080 MPEG-2 High 4:2:2 Profile, Level High 50 Mbit/s yv16 25i TFF BT709 SDR, so you can use


ffmpeg.exe -hide_banner -i "AVS Script.avs" -pix_fmt yuv422p -vcodec mpeg2video -s 1920:1080 -aspect 16:9 -vf setfield=tff -flags +ildct+ilme+cgop -b_strategy 0 -mpv_flags +strict_gop -sc_threshold 1000000000 -r 25 -b:v 50000k -minrate 50000k -maxrate 50000k -bufsize 36408333 -qmin 5 -qmax 28 -qsquish 99 -g 12 -bf 2 -profile:v 0 -level:v 2 -color_range 1 -color_primaries 1 -color_trc 1 -colorspace 1 -an -f mxf -y "raw_video.mxf"

bmxtranswrap.exe -p -y 00:00:00:00 -t op1a -o "final_output.mxf" "raw_video.mxf"

pause




In this example, here's the original picture (downscaled but it would be 4080x3072 so 1.328

https://i.postimg.cc/TPS0n528/image.png

and here's the output from Avisynth being a video that goes from left to right cropped in 1920x1080 to create the usual FULL HD 1.77 FF

https://i.postimg.cc/J4qQP27q/image.png

panning from right to left

https://i.postimg.cc/y88y5ykS/image.png


Once again the video is 25fps progressive flagged as interlaced and encoded as interlaced, however if you prefer the panning to be faster and the output to be truly interlaced 25i TFF then nothing stops you from using

AssumeFPS(50)

and then dividing in fields with

assumeTFF()
separatefields()
selectevery(4,0,3)
weave()

instead.

Obviously this entire thing can be easily inserted in a Custom AVS Script within FFAStrans and it can be automated, just make sure to use LoadPlugin() to load the appropriate zoom.dll if you're running it on hosts in which Avisynth isn't installed and make sure that the last thing you return is m_clip.

In other words, given that the indexing is already handled by the A/V Decoder node, in FFAStrans you would have the Custom AVS Script node set like


#Loading dependencies
LoadPlugin("%s_ffastrans_dir%\Processors\avs_plugins\zoom.dll")

#Bringing everything to RGB
ConverttoRGB32(m_clip)

#Pan from right to left
zoom(dstx="n", angle="0", width=1920, height=1080)

#Produce 25fps
AssumeFPS(25)

#Go back to YUV
Converttoyv16()

m_clip=last

return m_clip



followed by an XDCAM Encoding node and a delivery node. :)

https://i.postimg.cc/Twfw7C3z/image.png


thank you very very much


No problem at all. Once again, open source for the win. :D
Merry Christmas.





So, going to data, here's the info from Mediainfo (it's Karate Kid Film)

Video #1
ID : 4113 (0x1011)
Menu ID : 1 (0x1)
Format : HEVC
Format/Info : High Efficiency Video Coding
Format profile : Main 10@L5.1@High
HDR format : SMPTE ST 2086, HDR10 compatible
Codec ID : 36
Duration : 2 h 6 min
Width : 3 840 pixels
Height : 2 160 pixels
Display aspect ratio : 16:9
Frame rate : 23.976 (24000/1001) FPS
Color space : YUV
Chroma subsampling : 4:2:0 (Type 2)
Bit depth : 10 bits
Color range : Limited
Color primaries : BT.2020
Transfer characteristics : PQ
Matrix coefficients : BT.2020 non-constant
Mastering display color primaries : BT.2020
Mastering display luminance : min: 0.0050 cd/m2, max: 4000 cd/m2
Maximum Content Light Level : 4010 cd/m2
Maximum Frame-Average Light Level : 268 cd/m2


The Mediainfo puts the MaxCLL value to a whopping 4010 nits, however if we take a look at the MaxFALL we can see that the average is 268 nits. This means that we're gonna have a few very bright moments through the movie but overall it's not gonna be very bright outside of those. This also means that if we were to blindly trust the MaxCLL and put the 4010 nits peak back down to 100 nits of the BT709 SDR we would have a problem as the entire content would look dark.

This is unfortunately due to the nature of HDR and in particular in a totally logarithmic curve like PQ that can go all the way up to 10000 nits, potentially. In other words, while a dynamic tonemapping algorithm can be set to analyze the scenes and adjust itself based on the peak of the scene (this is what MPV does for instance when it performs this kind of conversion), when using a static LUT you're essentially mapping the points from an input curve to an output curve based on the values you defined in the 65x65x65 matrix. In other words, those are gonna be static and they won't adjust on a scene by scene basis. In this case, if you were to take the MaxCLL of 4010 nits and map it to 100 nits, you would be preserving the highlights in that particular scene, but everything else would be pushed down accordingly so the overwhelming majority of the scenes would be far too dark. In the same way, if you were to take 1000 nits as your reference and map it down to 100 nits of the BT709 SDR, you would have everything above 1000 completely clipped out which of course includes many highlights. In other words, the higher you set your reference, the more highlights you're going to preserve, but the "dimmer" it will look in an SDR display. The lower you set your reference, the brighter it will look on an SDR display, but the more details you're gonna lose as they can't be mapped to anything above 100 nits (i.e 235 in 8bit values or 0.7V).


Why is your lut different?

Well, let's take a look at the picture you just posted.

https://i.postimg.cc/kG7T6WsK/image.png


We can easily see that there aren't really many highlights and the sky on the right peaks at around 500 nits.

Using the PQ_to_BT709_v1.cube makes the BT709 SDR version peak at around 80 nits.

https://i.postimg.cc/zXKnZyFc/image.png

while using the PQ_to_BT709_v2.cube - which has a higher reference in terms of nits - makes it peak lower and the result is much dimmer at around 70 nits.

https://i.postimg.cc/8PS7vtLw/image.png

You used this second version of the LUT but for a content like yours in which the average is as low as barely 250 nits, I would suggest using v1 instead. Please note that the v1 v2 nomenclature doesn't make one better than the other and actually the reason why they're both there is exactly the one I explained above. In other words, there's no perfect way to perform this conversion, so according to the kind of content you have you may wanna use one rather than the other. If the content is very modern with lots of bright scenes and the average is also high, then use v2, while if the content is older or if it doesn't have many bright elements and you don't want it to look too dim, then use v1 instead.

Using Reinhard as a tonemapping algorithm the result is slightly different but we can get it to produce a decent output while still preserving the highlights for the scene:


ConvertYUVtoXYZ(Color=0, OutputMode=1, HDRMode=0, fullrange=false)

ConvertXYZ_Reinhard_HDRtoSDR(exposure_X=12.5, contrast_X=0.8, exposure_Y=12.5, contrast_Y=0.8, exposure_Z=12.5, contrast_Z=0.8)

ConvertXYZtoYUV(pColor=0)


https://i.postimg.cc/nhPxZhk3/image.png

Safety ?


Yep. Pretty much. I would generally always prefer not to clip. Outside of my personal taste, internally at Sky we had a long conversation with the various parties involved. For instance, even when we make the PQ to HLG conversions, we use the MaxCLL of the source to bring everything down to 1000 nits in HLG up until 4000 nits with literally 1 LUT per nit from 1000 to 4000, while anything above that will be clipped.


I would like to ask where I can find a conversion LUT from Apple Log2 HDR.

Oh...! I didn't know Apple started creating its own logs instead of choosing anything which has already been standardized. If you can record a few samples and share them here I'll try to take a look and create something to produce a BT709 SDR output, but mapping to PQ will take me a bit longer because when I google around to find official specs about Apple Log and Apple Log2 I can't find anything which means that I'll have to reverse it by looking at the waveform and it will take time.

If you can, try to make your samples as diverse as possible with clear peak highlights and dark elements so that I can use them as a reference for my white and black.

DMD
20th December 2025, 21:57
..
Oh...! I didn't know Apple started creating its own logs instead of choosing anything which has already been standardized. If you can record a few samples and share them here I'll try to take a look and create something to produce a BT709 SDR output, but mapping to PQ will take me a bit longer because when I google around to find official specs about Apple Log and Apple Log2 I can't find anything which means that I'll have to reverse it by looking at the waveform and it will take time.

If you can, try to make your samples as diverse as possible with clear peak highlights and dark elements so that I can use them as a reference for my white and black.
If you're interested, in the meantime I found this link with free LUTs for iPhone 17 Pro Max Apple Log2. I don't know if they're suitable for obtaining the BT2020 color space for HDR.
https://gamut.io/product/free-apple-conversion-luts-iphone/

FranceBB
20th December 2025, 22:18
I don't know if they're suitable for obtaining the BT2020

The files produced by the iPhone are in BT2020 in terms of colormatrix, but what you want is the smpte2084, so basically to go from the Apple Log / Apple Log2 transfer to the PQ transfer and this needs work. A friend of mine, Peter, has ordered an iPhone 17 Pro for Christmas (bad choice, but hey, the world is great 'cause it's various) so he will be able to provide some samples. In the meantime, if you have some samples recorded in BT2020 Apple Log and Apple Log2 please share them and I'll start working on it.

manolito
23rd December 2025, 20:32
Video #1
ID : 4113 (0x1011)
Menu ID : 1 (0x1)
Format : HEVC
Format/Info : High Efficiency Video Coding
Format profile : Main 10@L5.1@High
HDR format : SMPTE ST 2086, HDR10 compatible
Codec ID : 36
Duration : 2 h 6 min
Width : 3 840 pixels
Height : 2 160 pixels
Display aspect ratio : 16:9
Frame rate : 23.976 (24000/1001) FPS
Color space : YUV
Chroma subsampling : 4:2:0 (Type 2)
Bit depth : 10 bits
Color range : Limited
Color primaries : BT.2020
Transfer characteristics : PQ
Matrix coefficients : BT.2020 non-constant
Mastering display color primaries : BT.2020
Mastering display luminance : min: 0.0050 cd/m2, max: 4000 cd/m2
Maximum Content Light Level : 4010 cd/m2
Maximum Frame-Average Light Level : 268 cd/m2

Why is your lut different?
Thank you :)


Hi Tlen and FranceBB,

since my display hardware does not handle HDR sources I always have to convert such sources to SDR. I have old hardware which can handle DGHDRToSDR and Zimg, but not LibPlacebo. So my options to convert HDR to SDR are a little limited. But I never use LUTs because I do not like the concept... :devil:

You can find my current conversion scripts here:
https://forum.doom9.org/showthread.php?p=2014617#post2014617

My goal is to just convert HDR sources to SDR without any creative tweaks to correct source characteristics. I just want a conversion without any improvement of the source. Just like a basic PAL to NTSC (or vice versa) conversion without trying to improve the result from flaws of the source. No improvement of the source, just a dumb standards conversion.

To test my scripts with this source I need a short section of the original source clip which preserves all of the original source characteristics. Would it be possible for Tlen to upload such a clip?


Cheers
manolipo

FranceBB
24th December 2025, 15:26
I can see that you're using mobius tonemapping within FFMpeg


______________________________________________________________________________________

FFmpeg Fast (Slightly edited script by GeoffreyA)

Half HD target (1280x720):
--------------------------
PQ:
===
zscale=1280:-2,zscale=m=gbr:t=linear:dither=none:npl=225,format=gbrpf32le,tonemap=mobius:desat=3.0,zscale=m=709:t=709:p=709:r=limited:c=left:dither=error_diffusion,format=yuv420p,sidedata=delete

HLG:
====
zscale=1280:-2,zscale=m=gbr:t=linear:dither=none:npl=203,format=gbrpf32le,tonemap=mobius:desat=0,zscale=m=709:t=709:p=709:r=limited:c=left:dither=error_diffusion,format=yuv420p,sidedata=delete



I generally prefer Reinhard, so my FFMpeg equivalent of your command line would be:


#BT2020 PQ

ffmpeg -i "my_input.mxf" -map 0:v -c:v libx264 -profile:v high -level:v 4.1 -refs 4 -crf 25 -ignore_chapters 1 -ignore_unknown -write_tmcd 0 -movflags faststart -vf "sidedata=delete,metadata=delete,scale=w=1920:h=1080:flags=lanczos:sws_dither=ed,zscale=tin=smpte2084:min=bt2020nc:pin=bt2020:rin=tv:t=smpte2084:m=bt2020nc:p=bt2020:r=tv,zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=reinhard:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv422p10,scale=sws_dither=x_dither,format=yuv420p,setfield=prog,setdar=16/9,setsar=1,fps=25" -x264opts "opencl:keyint=50:force_cfr=1:deblock=-1,-1:aud=1:overscan=show:colorprim=bt709:fullrange=off:transfer=bt709:colormatrix=bt709" -color_primaries bt709 -color_trc bt709 -colorspace bt709 -color_range tv -field_order progressive -brand mp42 -max_muxing_queue_size 700 -map_metadata -1 -metadata creation_time=now -an -f mp4 -y "Video_Only.mp4"


#BT2020 HLG

ffmpeg -i "my_input.mxf" -map 0:v -c:v libx264 -profile:v high -level:v 4.1 -refs 4 -crf 25 -ignore_chapters 1 -ignore_unknown -write_tmcd 0 -movflags faststart -vf "sidedata=delete,metadata=delete,scale=w=1920:h=1080:flags=lanczos:sws_dither=ed,zscale=tin=arib-std-b67:min=bt2020nc:pin=bt2020:rin=tv:t=arib-std-b67:m=bt2020nc:p=bt2020:r=tv,zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=reinhard:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv422p10,scale=sws_dither=x_dither,format=yuv420p,setfield=prog,setdar=16/9,setsar=1,fps=25" -x264opts "opencl:keyint=25:force_cfr=1:deblock=-1,-1:aud=1:overscan=show:colorprim=bt709:fullrange=off:transfer=bt709:colormatrix=bt709" -color_primaries bt709 -color_trc bt709 -colorspace bt709 -color_range tv -field_order progressive -brand mp42 -max_muxing_queue_size 700 -map_metadata -1 -metadata creation_time=now -an -f mp4 -y "Video_Only.mp4"


I'm also going to FULL HD instead of HD.

That being said, it's very rare for me to use FFMpeg directly for those kind of things, in fact when I have to perform tonemapping I always use Avisynth.


The equivalent of your Avisynth part


AviSynth + (Script by Donald Graft. Separate parameters for PQ and HLG source files)

ConvertBits(16)

#PQ
DGHDRToSDR(mode="pq", white=1460, black=0, gamma=0.42, hue=0.00, r=1.00, g=1.00, b=1.15, tm=0.90, roll=0.70, fulldepth=false, impl="255")

#HLG
DGHDRtoSDR(mode="hlg", white=2820, gamma=0.50, r=1.10, g=1.07, b=1.12, roll=0.70)


for me it would be done via HDRTools made by Jean Philippe Scotto di Rinaldi, namely:


#PQ
ConvertBits(16)

ConvertYUVtoXYZ(Color=0, OutputMode=1, HDRMode=0, fullrange=false)

ConvertXYZ_Reinhard_HDRtoSDR(exposure_X=2.5, contrast_X=0.9, exposure_Y=2.5, contrast_Y=0.9, exposure_Z=2.5, contrast_Z=0.9)

ConvertXYZtoYUV(pColor=0)

Converttoyuv420()

ConvertBits(bits=8, dither=1)




#HLG
ConvertBits(16)

ConvertYUVtoXYZ(Color=0, HDRmode=2)

ConvertXYZ_Reinhard_HDRtoSDR(exposure_X=2.1, exposure_Y=2.1, exposure_Z=2.1)

ConvertXYZtoYUV(pColor=0)

Converttoyuv420()

ConvertBits(bits=8, dither=1)



I never use LUTs because I do not like the concept...

Predictability vs efficiency. In a perfect world with a perfect dynamic tonemapping algorithm that never fails and automatically adjusts on a scene by scene basis there would be no need for static matrices (i.e LUT). Unfortunately, we don't live in such a world, even though libplacebo (what MPV is using) is very close.

GeoffreyA
24th December 2025, 19:43
Here's my final version of the FFmpeg conversion. But I don't use it, libplacebo being much better.

-vf zscale=min=bt2020nc:tin=smpte2084:pin=2020:rin=limited:m=gbr:t=linear:p=2020:dither=none:npl=203:f=spline36,format=gbrpf32le,
tonemap=reinhard:desat=0,zscale=-1:1080:f=spline36,
crop=%crop%:exact=true,
zscale=m=709:t=709:p=709:r=limited:c=left:dither=error_diffusion:f=spline36,format=yuv420p,sidedata=delete

You can move the 1080p downscaling earlier so that tone mapping goes quicker, whilst keeping within the linear RGB stage.