From a8e81735c1ca8c0bdd28debf410a6bc483081c1c Mon Sep 17 00:00:00 2001 From: Francesco Bucciantini Date: Sun, 23 Nov 2025 19:57:23 +0000 Subject: [PATCH 1/3] Updated Documentation with LogIntermediateStats Updated Documentation with LogIntermediateStats --- README.md | 101 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 542e7ed..41661cd 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ ## 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. - +
+
+The created textfile's name is "MaxCLLFind_Results0.txt". +
+If it already exists, a new file called MaxCLLFind_Results1.txt will be created and the integer will keep increasing at each iteration. +
+
**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 @@ -11,11 +14,19 @@ The created textfile's name is "MaxCLLFind_Results0.txt". It will be overwritten ``` 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. +
+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 +``` +
+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. +
This plugin only accepts RGB inputs. If your HDR clip isn't RGB, convert it first. - +
For example, let's say you are loading a HDR HEVC YUV file, do this: + ``` clip = clip.ConvertToRGB64(matrix="Rec2020") clip.MaxCLLFind() @@ -28,25 +39,80 @@ 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. +
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: - +
``` 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. +
+``` +MaxCLLFind(maxFallAlgorithm=0, LogIntermediateStats=true) +``` +
+For instance: +
+Stats at frame 1: +
+MaxCLL: 0, raw value: 0 0 at X 0 Y 0 at frame 0 +
+MinCLL: 0, raw value: 0 0 at X 0 Y 2159 at frame 0 +
+MaxFALL: 0 at frame 0 +
+FALL Average: 0 across 1 frames. +
+
+Stats at frame 101: +
+MaxCLL: 400.011, raw value: 42767 0.652583 at X 0 Y 1101 at frame 7 +
+MinCLL: 0, raw value: 0 0 at X 0 Y 2159 at frame 0 +
+MaxFALL: 8.55332 at frame 41 +
+FALL Average: 4.07527 across 101 frames. +
+
+Stats at frame 201: +
+MaxCLL: 400.011, raw value: 42767 0.652583 at X 0 Y 1101 at frame 7 +
+MinCLL: 0, raw value: 0 0 at X 0 Y 2159 at frame 0 +
+MaxFALL: 8.55332 at frame 41 +
+FALL Average: 2.84126 across 201 frames. +
+
+If you're only interested in the final values, set LogIntermediateStats to false when calling the function. +
-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. +
+and only the last value will be reported once the processing ends. +
+Stats at frame 201: +
+MaxCLL: 400.011, raw value: 42767 0.652583 at X 0 Y 1101 at frame 7 +
+MinCLL: 0, raw value: 0 0 at X 0 Y 2159 at frame 0 +
+MaxFALL: 8.55332 at frame 41 +
+FALL Average: 2.84126 across 201 frames. +
### 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 @@ -54,5 +120,6 @@ If you feel like improving the code, refactoring or cleaning up, feel free. I mi - 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* \ No newline at end of file +*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* From 35f0cea97156a7921d3608aba2cec25136233c71 Mon Sep 17 00:00:00 2001 From: Francesco Bucciantini Date: Sun, 23 Nov 2025 19:59:17 +0000 Subject: [PATCH 2/3] Updated maxcllfind.h to include logIntermediateStats Added the new logIntermediateStats member variable and updated the constructor signature in the header. --- Average/maxcllfind.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Average/maxcllfind.h b/Average/maxcllfind.h index ea3c99d..14a4be1 100644 --- a/Average/maxcllfind.h +++ b/Average/maxcllfind.h @@ -13,7 +13,7 @@ template 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(); @@ -56,5 +56,6 @@ class MaxCLLFind : public GenericVideoFilter { int fileWriteCounter; std::string statsFileName; + bool logIntermediateStats; }; From 7a915ea6c172a2312d274f8b69065460ed7836ec Mon Sep 17 00:00:00 2001 From: Francesco Bucciantini Date: Sun, 23 Nov 2025 20:02:20 +0000 Subject: [PATCH 3/3] Updated maxcllfind.cpp to output results based on logIntermediateStats Updated the constructor definition, the parameter parsing in the function, and added a conditional check to GetFrame() to decide whether to output the results every 100 frames or just the end value calculated based on the logIntermediateStats (by default is true for backwards compatibility with the current behavior). --- Average/maxcllfind.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Average/maxcllfind.cpp b/Average/maxcllfind.cpp index f697290..c7c5304 100644 --- a/Average/maxcllfind.cpp +++ b/Average/maxcllfind.cpp @@ -255,7 +255,7 @@ void MaxCLLFind::dofindmaxcll_packed_c(c } template -MaxCLLFind::MaxCLLFind(PClip clip, IScriptEnvironment* env, float* nitArray) +MaxCLLFind::MaxCLLFind(PClip clip, IScriptEnvironment* env, float* nitArray, bool logIntermediateStats) : GenericVideoFilter(clip) , nitArray(nitArray) , highestrawvalue(0) @@ -275,6 +275,7 @@ MaxCLLFind::MaxCLLFind(PClip clip, IScri , MaxFALL(0) , MaxFALLFrame(0) , fileWriteCounter(0) + , logIntermediateStats(logIntermediateStats) , statsFileName("") { int pixelsize = vi.ComponentSize(); @@ -438,7 +439,7 @@ PVideoFrame MaxCLLFind::GetFrame(int n, (*this.*processor_)(src, n); /* } }*/ - if (framesCounted++ % 100 == 0) { + if (logIntermediateStats && framesCounted++ % 100 == 0) { writeCLLStats(); } @@ -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(); @@ -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(clip, env, nitArray); + return new MaxCLLFind(clip, env, nitArray, logIntermediateStats); } if (maxFallAlgorithm == MAXFALL_NONE && hasAlpha) { - return new MaxCLLFind(clip, env, nitArray); + return new MaxCLLFind(clip, env, nitArray, logIntermediateStats); } if (maxFallAlgorithm == MAXFALL_OFFICIAL && !hasAlpha) { - return new MaxCLLFind(clip, env, nitArray); + return new MaxCLLFind(clip, env, nitArray, logIntermediateStats); } if (maxFallAlgorithm == MAXFALL_OFFICIAL && hasAlpha) { - return new MaxCLLFind(clip, env, nitArray); + return new MaxCLLFind(clip, env, nitArray, logIntermediateStats); } if (maxFallAlgorithm == MAXFALL_ALLCHANNELS && !hasAlpha) { - return new MaxCLLFind(clip, env, nitArray); + return new MaxCLLFind(clip, env, nitArray, logIntermediateStats); } if (maxFallAlgorithm == MAXFALL_ALLCHANNELS && hasAlpha) { - return new MaxCLLFind(clip, env, nitArray); + return new MaxCLLFind(clip, env, nitArray, logIntermediateStats); } } @@ -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"; -} \ No newline at end of file +}