Skip to content

Instantly share code, notes, and snippets.

@noizuy
Last active November 2, 2023 08:06
Show Gist options
  • Save noizuy/fa8274cc1c9475b82cbe59edd4761bd3 to your computer and use it in GitHub Desktop.
Save noizuy/fa8274cc1c9475b82cbe59edd4761bd3 to your computer and use it in GitHub Desktop.
automated dirty line fix - basically just bbmod without the limiting
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