Last active
November 2, 2023 08:06
-
-
Save noizuy/fa8274cc1c9475b82cbe59edd4761bd3 to your computer and use it in GitHub Desktop.
automated dirty line fix - basically just bbmod without the limiting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import vapoursynth as vs | |
core = vs. core | |
from vstools import join, plane, depth | |
# b-spline like in bbmod | |
def bblur(c, w=1, h=None): | |
if h is None: | |
h = w | |
return c.resize.Bicubic(width=w, height=h, filter_param_a=1, filter_param_b=0).resize.Bicubic(width=c.width, height=c.height, filter_param_a=1, filter_param_b=0) | |
def autolvls(clip, rows=[], columns=[], blur=bblur, prefilter=None, planes=[0]): | |
""" | |
Fix dirty lines using a blurred reference. | |
More or less a rewrite of bbmod. Key differences are allowing stronger blurring, always using only the next row/column as reference, no limiting of the adjustment, and even worse performance. | |
Probably not too hard to optimize. | |
For weak dirty lines, e.g. just a letterboxed resize with no post-processing, the default should work great. | |
For stronger dirty lines, it'd make more sense to run a prefilter blur and significantly weaken the blur, maybe even disable it. | |
The default is basically the exact same thing as rektlvls without prot_val except the adjustment value is automated. | |
""" | |
if isinstance(rows, int): | |
rows = [rows] | |
else: | |
rows = sorted(rows)[::-1] | |
if isinstance(columns, int): | |
columns = [columns] | |
else: | |
columns = sorted(columns)[::-1] | |
if isinstance(planes, int): | |
planes = [planes] | |
def _fix(current, is_row, num): | |
""" | |
The actual processing function. | |
""" | |
# use next/previous row/column as reference | |
if is_row: | |
if num < 0: | |
num += clip.height | |
num_clip = current.std.CropAbs(width=current.width, height=1, top=num) | |
ref_clip = current.std.CropAbs(width=current.width, height=1, top=num + 2 * (int(num < current.height / 2) - 0.5)) | |
else: | |
if num < 0: | |
num += clip.width | |
num_clip = current.std.CropAbs(height=current.height, width=1, left=num) | |
ref_clip = current.std.CropAbs(height=current.height, width=1, left=num + 2 * (int(num < current.width / 2) - 0.5)) | |
# run prefilter if desired | |
# this could e.g. be a blur | |
num_clip_pf = num_clip | |
if prefilter: | |
num_clip_pf = prefilter(num_clip_pf) | |
ref_clip = prefilter(ref_clip) | |
# reference / current, limit to maximum luma change in limited range | |
# this sounds like it'd need to be different for full range but if that's the case you got garbage | |
# this part tells us how much each pixel needs to be adjusted by to match the reference | |
adjustment = core.std.Expr([num_clip_pf, ref_clip], f"y x / 16 235 / max 235 16 / min") | |
# blur the adjustment to account for differences between rows/columns and apply it | |
adjustment = blur(adjustment) | |
adjusted = core.std.Expr([num_clip, adjustment], f"x y *") | |
if is_row: | |
adjusted = adjusted.std.AddBorders(top=num, bottom=current.height - num - 1) | |
else: | |
adjusted = adjusted.std.AddBorders(left=num, right=current.width - num - 1) | |
if is_row: | |
return core.akarin.Expr([adjusted, current], f"Y {num} = x y ?") | |
return core.akarin.Expr([adjusted, current], f"X {num} = x y ?") | |
return_planes = [] | |
for p in range(clip.format.num_planes): | |
current = plane(clip, p) | |
if p in planes: | |
current = depth(current, 32) | |
# chroma | |
if p > 0: | |
current = current.std.Expr("x 0.5 +") | |
for row in rows: | |
current = _fix(current, True, row) | |
for column in columns: | |
current = _fix(current, False, column) | |
# chroma | |
if p > 0: | |
current = current.std.Expr("x 0.5 -") | |
return_planes.append(current) | |
return join(return_planes) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment