Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions Average/maxcllfind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ void MaxCLLFind<maxFallAlgorithm, components_per_pixel>::dofindmaxcll_packed_c(c
}

template<MaxFallAlgorithm maxFallAlgorithm, int components_per_pixel>
MaxCLLFind<maxFallAlgorithm, components_per_pixel>::MaxCLLFind(PClip clip, IScriptEnvironment* env, float* nitArray)
MaxCLLFind<maxFallAlgorithm, components_per_pixel>::MaxCLLFind(PClip clip, IScriptEnvironment* env, float* nitArray, bool logIntermediateStats)
: GenericVideoFilter(clip)
, nitArray(nitArray)
, highestrawvalue(0)
Expand All @@ -275,6 +275,7 @@ MaxCLLFind<maxFallAlgorithm, components_per_pixel>::MaxCLLFind(PClip clip, IScri
, MaxFALL(0)
, MaxFALLFrame(0)
, fileWriteCounter(0)
, logIntermediateStats(logIntermediateStats)
, statsFileName("") {

int pixelsize = vi.ComponentSize();
Expand Down Expand Up @@ -438,7 +439,7 @@ PVideoFrame MaxCLLFind<maxFallAlgorithm, components_per_pixel>::GetFrame(int n,
(*this.*processor_)(src, n);
/* }
}*/
if (framesCounted++ % 100 == 0) {
if (logIntermediateStats && framesCounted++ % 100 == 0) {
writeCLLStats();
}

Expand All @@ -453,6 +454,9 @@ AVSValue __cdecl create_maxcllfind(AVSValue args, void* user_data, IScriptEnviro
// 1 = Average of all channels of all pixels
MaxFallAlgorithm maxFallAlgorithm = (MaxFallAlgorithm)args[1].AsInt(MaxFallAlgorithm::MAXFALL_OFFICIAL);

//reads the parameter (by default is true for backwards compatibility and shows results every 100 frames)
bool logIntermediateStats = args[2].AsBool(true);

auto clip = args[0].AsClip();
auto vi = clip->GetVideoInfo();

Expand All @@ -469,24 +473,24 @@ AVSValue __cdecl create_maxcllfind(AVSValue args, void* user_data, IScriptEnviro

bool hasAlpha = vi.IsRGB64() || vi.IsRGB32() || vi.IsPlanarRGBA();
if (maxFallAlgorithm == MAXFALL_NONE && !hasAlpha) {
return new MaxCLLFind<MAXFALL_NONE, 3>(clip, env, nitArray);
return new MaxCLLFind<MAXFALL_NONE, 3>(clip, env, nitArray, logIntermediateStats);
}
if (maxFallAlgorithm == MAXFALL_NONE && hasAlpha) {
return new MaxCLLFind<MAXFALL_NONE, 4>(clip, env, nitArray);
return new MaxCLLFind<MAXFALL_NONE, 4>(clip, env, nitArray, logIntermediateStats);
}

if (maxFallAlgorithm == MAXFALL_OFFICIAL && !hasAlpha) {
return new MaxCLLFind<MAXFALL_OFFICIAL, 3>(clip, env, nitArray);
return new MaxCLLFind<MAXFALL_OFFICIAL, 3>(clip, env, nitArray, logIntermediateStats);
}
if (maxFallAlgorithm == MAXFALL_OFFICIAL && hasAlpha) {
return new MaxCLLFind<MAXFALL_OFFICIAL, 4>(clip, env, nitArray);
return new MaxCLLFind<MAXFALL_OFFICIAL, 4>(clip, env, nitArray, logIntermediateStats);
}

if (maxFallAlgorithm == MAXFALL_ALLCHANNELS && !hasAlpha) {
return new MaxCLLFind<MAXFALL_ALLCHANNELS, 3>(clip, env, nitArray);
return new MaxCLLFind<MAXFALL_ALLCHANNELS, 3>(clip, env, nitArray, logIntermediateStats);
}
if (maxFallAlgorithm == MAXFALL_ALLCHANNELS && hasAlpha) {
return new MaxCLLFind<MAXFALL_ALLCHANNELS, 4>(clip, env, nitArray);
return new MaxCLLFind<MAXFALL_ALLCHANNELS, 4>(clip, env, nitArray, logIntermediateStats);
}
}

Expand All @@ -499,6 +503,6 @@ extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit3(IScri
// 1 - reference clip
// 2 - testclip (for example a downsized version of the clip to be regraded. this one will be actually used for the regrading and comparing to reference.
// 3+ - additional reference clips (not supported atm, will be ignored)
env->AddFunction("maxcllfind", "c[maxFallAlgorithm]i", create_maxcllfind, 0);
env->AddFunction("maxcllfind", "c[maxFallAlgorithm]i[LogIntermediateStats]b", create_maxcllfind, 0);
return "Mind your sugar level";
}
}
3 changes: 2 additions & 1 deletion Average/maxcllfind.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ template<MaxFallAlgorithm maxFallAlgorithm, int components_per_pixel>
class MaxCLLFind : public GenericVideoFilter {
public:

MaxCLLFind(PClip clip, IScriptEnvironment* env, float* nitArray);
MaxCLLFind(PClip clip, IScriptEnvironment* env, float* nitArray, bool logIntermediateStats);
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
~MaxCLLFind();

Expand Down Expand Up @@ -56,5 +56,6 @@ class MaxCLLFind : public GenericVideoFilter {

int fileWriteCounter;
std::string statsFileName;
bool logIntermediateStats;
};

101 changes: 84 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
## MaxCLLFind ##

PQ HDR Analyzer plugin for AVISynth, analyzes MaxCLL and MaxFALL and writes that to a text file after closing the application that is calling AVISynth.

The created textfile's name is "MaxCLLFind_Results0.txt". It will be overwritten if it already exists.

<br>
<br>
The created textfile's name is "MaxCLLFind_Results0.txt".
<br>
If it already exists, a new file called MaxCLLFind_Results1.txt will be created and the integer will keep increasing at each iteration.
<br>
<br>
**Caution:** This may not be the correct way to calculate MaxFALL and MaxCLL. For (Max)FALL I averaged the nit intensities of every single channel of every pixel in the frame. For MaxCLL I simply took the brightest channel of any pixel in any of the frames. There may be some weighting necessary akin to what was done here: https://github.com/HDRWCG/HDRStaticMetadata


### Usage
```
clip.MaxCLLFind()
```
Load in VirtualDub and click Play. After video is finished playing, close VirtualDub. The plugin also writes the Average FALL (frame average light level) into the text file. If you want this result to be accurate, make sure to not load any frame more than once.

Load in VirtualDub and click Play. After video finished playing, close VirtualDub.
<br>
Alternatively, you can use FFMpeg to seek through the AVS Script without actually processing anything.
```
ffmpeg.exe -hide_banner -benchmark -i "AVS Script.avs" -map 0:0? -an -f null out.null
```
<br>
The plugin also writes the Average FALL (frame average light level) into the text file. If you want this result to be accurate, make sure to not load any frame more than once.
<br>
This plugin only accepts RGB inputs. If your HDR clip isn't RGB, convert it first.

<br>
For example, let's say you are loading a HDR HEVC YUV file, do this:

```
clip = clip.ConvertToRGB64(matrix="Rec2020")
clip.MaxCLLFind()
Expand All @@ -28,31 +39,87 @@ The default MaxFALL algorithm uses the SMPTE recommendation of averaging max(R,G
```
clip.MaxCLLFind(maxFallAlgorithm=1)
```
This is more for your own curiosity and might lead to playback problems like flickering if used as actual HDR metadata, since it typically leads to typically slightly lower average intensity readings and if the TV bases its own dimming on the official recommendation, it might dim the image when it reaches a higher FALL than your calculated MaxFALL, which will almost certainly happen.

This is more for your own curiosity and might lead to playback problems like flickering if used as actual HDR metadata, since it typically leads to slightly lower average intensity readings and if the TV bases its own dimming on the official recommendation, it might dim the image when it reaches a higher FALL than your calculated MaxFALL, which will almost certainly happen.
<br>
The official maxFall algorithm is not supported by the planar formats. If your input is planar either disable the maxFall calculation or use the unofficial algorithm or convert your clip to a packed format like RGB48. The maxFall calculation is disabled by:

<br>
```
clip.MaxCLLFind(maxFallAlgorithm=-1)
```

### Word of caution

This filter is really badly written. Since I'm a C++ noob, I took the Average filter and ditched everything I didn't need and commented out some other things, so there are lots of remnants of the Average filter still in this code. It will have to be cleaned up eventually.
### Results Output
By default, the output values are written in the MaxCLLFind_Results0.txt every 100 frames, which means that those are gonna be reported from the beginning to the end of the clip so that you can see how they're updated as the clip progresses.
<br>
```
MaxCLLFind(maxFallAlgorithm=0, LogIntermediateStats=true)
```
<br>
For instance:
<br>
Stats at frame 1:
<br>
MaxCLL: 0, raw value: 0 0 at X 0 Y 0 at frame 0
<br>
MinCLL: 0, raw value: 0 0 at X 0 Y 2159 at frame 0
<br>
MaxFALL: 0 at frame 0
<br>
FALL Average: 0 across 1 frames.
<br>
<br>
Stats at frame 101:
<br>
MaxCLL: 400.011, raw value: 42767 0.652583 at X 0 Y 1101 at frame 7
<br>
MinCLL: 0, raw value: 0 0 at X 0 Y 2159 at frame 0
<br>
MaxFALL: 8.55332 at frame 41
<br>
FALL Average: 4.07527 across 101 frames.
<br>
<br>
Stats at frame 201:
<br>
MaxCLL: 400.011, raw value: 42767 0.652583 at X 0 Y 1101 at frame 7
<br>
MinCLL: 0, raw value: 0 0 at X 0 Y 2159 at frame 0
<br>
MaxFALL: 8.55332 at frame 41
<br>
FALL Average: 2.84126 across 201 frames.
<br>
<br>
If you're only interested in the final values, set LogIntermediateStats to false when calling the function.
<br>

It might also have unexpected bugs and glitches and I cannot guarantee the correctness of the results, since I'm not a colour scientist.
```
MaxCLLFind(maxFallAlgorithm=0, LogIntermediateStats=false)
```

The RGB value to nits conversion algorithm was lifted from the colour-science package of python.
<br>
and only the last value will be reported once the processing ends.
<br>
Stats at frame 201:
<br>
MaxCLL: 400.011, raw value: 42767 0.652583 at X 0 Y 1101 at frame 7
<br>
MinCLL: 0, raw value: 0 0 at X 0 Y 2159 at frame 0
<br>
MaxFALL: 8.55332 at frame 41
<br>
FALL Average: 2.84126 across 201 frames.
<br>

### Contribute

If you feel like improving the code, refactoring or cleaning up, feel free. I might do it someday myself or I might not, I don't know.
If you feel like improving the code, refactoring or cleaning up, feel free.


## TODO

- Add functionality to import cutlist for dynamic scene-based metadata. I'm not sure how this would be implemented in an encode, but I read that this possibility exists, so it would be nice to have.

## Changelog
*2025-11-24 - Added option to return just the last output without any intermediate values.*

*2019-12-30 - Fixed MaxFALL Algorithm to be based on the official SMPTE recommendation. This algorithm computes the frame average brightness based on the average of the brightest channel of each pixel, or in other words, max(R,G,B). The old algorithm was using the average of all channels of all pixels, leading to slightly lower resulting values in the tests I did. The old algorithm can be still optionally used via the parameter maxFallAlgorithm=1*
*2019-12-30 - Fixed MaxFALL Algorithm to be based on the official SMPTE recommendation. This algorithm computes the frame average brightness based on the average of the brightest channel of each pixel, or in other words, max(R,G,B). The old algorithm was using the average of all channels of all pixels, leading to slightly lower resulting values in the tests I did. The old algorithm can be still optionally used via the parameter maxFallAlgorithm=1*