shurik_pronkin
6th April 2026, 09:47
AutoLevelsEOD — adaptive per-channel auto-levels for AviSynth+
Port and extension of the old VirtualDub AutoLevels filter v1.2 by EOD, for AviSynth+. Same per-channel histogram-stretch math as the original, plus:
Automatic correction strength per frame — mild casts get fixed, artistic color (sunsets, tinted titles, dark scenes with bright accents) is left alone.
Scene-lock stabilization via a companion plugin, eliminating per-frame flicker on scenes where metrics drift across rule thresholds.
Measurement-only companion filter for calibration.
Developed against scanned 16mm Soviet animation ("Заколдованный мальчик", 1955) but not specific to that material.
The problem
Straight per-channel auto-levels (EOD's original) does the right thing on a frame with a clear cast, but applied blindly over an entire clip it:
Crushes dark scenes where there's no usable highlight to anchor against.
Over-corrects intentionally tinted frames (sunsets, night scenes).
Flickers on consecutive frames when their statistics drift slightly across any implicit threshold.
Running it manually with a different bar_bright per scene is tedious on a 60k-frame feature. And any naive "smooth the per-frame decision" approach (temporal median, sliding average) just shifts the jitter problem, it doesn't solve it.
The idea
Two independent pieces:
Rule set that picks bar_bright_eff per frame from a handful of full-histogram metrics: rawBmax, hiMass, spreadB, meanSpread, meanY. Each rule is a single threshold on one statistic. The effect is to say "this frame has a usable bright end AND a cast AND isn't artistic → correct with strength X; otherwise → don't touch".
Scene-lock using an external scene-change clip as signal. Within one scene, every frame's rule decision is computed independently, then the minimum (= most aggressive) is locked in for the whole scene. Frames that vote 255 ("don't touch") are naturally ignored because any corrective value is lower. Gives one stable decision per scene, locked until the next cut.
The pattern is: let the algorithm run freely per frame, then post-filter by taking the scene minimum — not "pre-select a representative frame and apply its decision". The rules already know which frames are reliable; taking the minimum across the scene means "if even one frame shows a correctable cast, correct the whole scene with that strength".
Scene detection
Scene-lock requires per-frame _scd_boundary props. The companion plugin https://github.com/schpuppa-art/SCDetectEOD-Avisynth- provides them — histogram-based, no motion estimation, stateless, works on material where MVTools SCDetect struggles (animation, scanned film). Both plugins are calibrated together on the same material.
Usage
LoadPlugin("SCDetectEOD.dll")
LoadPlugin("AutoLevelsEOD.dll")
src = LWLibavVideoSource("input.mkv").ConvertToRGB32()
sd = src.ConvertToYV12().SCDetectEOD()
src.AutoLevelsEOD(intensity=1, bar_width=16, bar_dark=55,
auto_bright=true, sd_clip=sd)
Without sd_clip, auto_bright runs per-frame without stabilization (fine for still-image calibration, not recommended for encoding). Without auto_bright, the filter behaves exactly as EOD's original with fixed bar_bright.
Parameters
mode 0 0=per-channel RGB (WB), 1=luma-only (contrast)
intensity 1 percentile cut strength, 1..99
bar_width 0 virtual anchor bar width in px (16 recommended)
bar_dark 16 dark anchor value (16..55 typical)
bar_bright 240 bright anchor (ignored when auto_bright=true)
auto_bright false enable per-frame adaptive bar_bright
sd_clip — optional SCDetectEOD output for scene-lock
debug false write metric frame props
Full parameter reference, rule set thresholds, calibration workflow and frame-property list are in the README on github.
Source and binaries
https://github.com/schpuppa-art/AutoLevelsEOD-Avisynth-
https://github.com/schpuppa-art/SCDetectEOD-Avisynth-
Build: cl /LD /EHsc /O2 /MD /DNDEBUG AutoLevelsEOD.cpp /link /OUT:AutoLevelsEOD.dll from an x64 Native Tools Command Prompt. MSVC only — MinGW is ABI-incompatible with AviSynth+ and will crash at load.
Credits
Original VirtualDub filter: EOD. Scene-detection companion and this port/extension: shurik_pronkin.
Feedback on other material welcome.
Port and extension of the old VirtualDub AutoLevels filter v1.2 by EOD, for AviSynth+. Same per-channel histogram-stretch math as the original, plus:
Automatic correction strength per frame — mild casts get fixed, artistic color (sunsets, tinted titles, dark scenes with bright accents) is left alone.
Scene-lock stabilization via a companion plugin, eliminating per-frame flicker on scenes where metrics drift across rule thresholds.
Measurement-only companion filter for calibration.
Developed against scanned 16mm Soviet animation ("Заколдованный мальчик", 1955) but not specific to that material.
The problem
Straight per-channel auto-levels (EOD's original) does the right thing on a frame with a clear cast, but applied blindly over an entire clip it:
Crushes dark scenes where there's no usable highlight to anchor against.
Over-corrects intentionally tinted frames (sunsets, night scenes).
Flickers on consecutive frames when their statistics drift slightly across any implicit threshold.
Running it manually with a different bar_bright per scene is tedious on a 60k-frame feature. And any naive "smooth the per-frame decision" approach (temporal median, sliding average) just shifts the jitter problem, it doesn't solve it.
The idea
Two independent pieces:
Rule set that picks bar_bright_eff per frame from a handful of full-histogram metrics: rawBmax, hiMass, spreadB, meanSpread, meanY. Each rule is a single threshold on one statistic. The effect is to say "this frame has a usable bright end AND a cast AND isn't artistic → correct with strength X; otherwise → don't touch".
Scene-lock using an external scene-change clip as signal. Within one scene, every frame's rule decision is computed independently, then the minimum (= most aggressive) is locked in for the whole scene. Frames that vote 255 ("don't touch") are naturally ignored because any corrective value is lower. Gives one stable decision per scene, locked until the next cut.
The pattern is: let the algorithm run freely per frame, then post-filter by taking the scene minimum — not "pre-select a representative frame and apply its decision". The rules already know which frames are reliable; taking the minimum across the scene means "if even one frame shows a correctable cast, correct the whole scene with that strength".
Scene detection
Scene-lock requires per-frame _scd_boundary props. The companion plugin https://github.com/schpuppa-art/SCDetectEOD-Avisynth- provides them — histogram-based, no motion estimation, stateless, works on material where MVTools SCDetect struggles (animation, scanned film). Both plugins are calibrated together on the same material.
Usage
LoadPlugin("SCDetectEOD.dll")
LoadPlugin("AutoLevelsEOD.dll")
src = LWLibavVideoSource("input.mkv").ConvertToRGB32()
sd = src.ConvertToYV12().SCDetectEOD()
src.AutoLevelsEOD(intensity=1, bar_width=16, bar_dark=55,
auto_bright=true, sd_clip=sd)
Without sd_clip, auto_bright runs per-frame without stabilization (fine for still-image calibration, not recommended for encoding). Without auto_bright, the filter behaves exactly as EOD's original with fixed bar_bright.
Parameters
mode 0 0=per-channel RGB (WB), 1=luma-only (contrast)
intensity 1 percentile cut strength, 1..99
bar_width 0 virtual anchor bar width in px (16 recommended)
bar_dark 16 dark anchor value (16..55 typical)
bar_bright 240 bright anchor (ignored when auto_bright=true)
auto_bright false enable per-frame adaptive bar_bright
sd_clip — optional SCDetectEOD output for scene-lock
debug false write metric frame props
Full parameter reference, rule set thresholds, calibration workflow and frame-property list are in the README on github.
Source and binaries
https://github.com/schpuppa-art/AutoLevelsEOD-Avisynth-
https://github.com/schpuppa-art/SCDetectEOD-Avisynth-
Build: cl /LD /EHsc /O2 /MD /DNDEBUG AutoLevelsEOD.cpp /link /OUT:AutoLevelsEOD.dll from an x64 Native Tools Command Prompt. MSVC only — MinGW is ABI-incompatible with AviSynth+ and will crash at load.
Credits
Original VirtualDub filter: EOD. Scene-detection companion and this port/extension: shurik_pronkin.
Feedback on other material welcome.