diff --git a/vsdeinterlace/combing.py b/vsdeinterlace/combing.py index 6097216..2be5c1a 100644 --- a/vsdeinterlace/combing.py +++ b/vsdeinterlace/combing.py @@ -1,78 +1,100 @@ from __future__ import annotations -from typing import cast +from typing import cast, overload from vsexprtools import ExprVars, complexpr_available, norm_expr from vsrgtools import sbr from vstools import ( - ConvMode, CustomEnum, CustomIntEnum, FuncExceptT, FunctionUtil, PlanesT, + MISSING, ConvMode, CustomEnum, FieldBasedT, FuncExceptT, FunctionUtil, MissingT, PlanesT, core, depth, expect_bits, get_neutral_values, scale_8bit, vs ) __all__ = [ - 'fix_interlaced_fades', + 'fix_telecined_fades', + 'vinverse' ] -class fix_interlaced_fades(CustomIntEnum): - Average = 0 - Darken = 1 - Brighten = 2 - - def __call__( # type: ignore[misc] - self, clip: vs.VideoNode, colors: float | list[float] | PlanesT = 0.0, - planes: PlanesT | FuncExceptT = None, func: FuncExceptT | None = None - ) -> vs.VideoNode: - """ - Give a mathematically perfect solution to decombing fades made *after* telecine - (which made perfect IVTC impossible) that start or end in a solid color. - - Steps between the frames are not adjusted, so they will remain uneven depending on the telecine pattern, - but the decombing is blur-free, ensuring minimum information loss. However, this may cause small amounts - of combing to remain due to error amplification, especially near the solid-color end of the fade. - - This is an improved version of the Fix-Telecined-Fades plugin. - - Make sure to run this *after* IVTC! - - :param clip: Clip to process. - :param colors: Fade source/target color (floating-point plane averages). - - :return: Clip with fades to/from `colors` accurately deinterlaced. - Frames that don't contain such fades may be damaged. - """ - func = func or self.__class__ - - if not complexpr_available: - raise ExprVars._get_akarin_err()(func=func) - - f = FunctionUtil(clip, func, planes, (vs.GRAY, vs.YUV), 32) - - fields = f.work_clip.std.Limiter().std.SeparateFields(tff=True) - - for i in f.norm_planes: - fields = fields.std.PlaneStats(None, i, f'P{i}') - - props_clip = core.akarin.PropExpr( - [f.work_clip, fields[::2], fields[1::2]], lambda: { # type: ignore[misc] - f'f{t}Avg{i}': f'{c}.P{i}Average {color} -' # type: ignore[has-type] - for t, c in ['ty', 'bz'] - for i, color in zip(f.norm_planes, f.norm_seq(colors)) - } - ) - - expr_mode = ['+ 2 /', 'min', 'max'] - - fix = norm_expr( - props_clip, 'Y 2 % x.fbAvg{i} x.ftAvg{i} ? AVG! ' - 'AVG@ 0 = x x {color} - x.ftAvg{i} x.fbAvg{i} ' - '{expr_mode} AVG@ / * ? {color} +', - planes, i=f.norm_planes, expr_mode=expr_mode[self], - color=colors, force_akarin=func, - ) - - return f.return_clip(fix) +@overload +def fix_telecined_fades( + clip: vs.VideoNode, tff: bool | FieldBasedT | None, colors: float | list[float] = 0.0, + planes: PlanesT = None, func: FuncExceptT | None = None +) -> vs.VideoNode: + ... + +@overload +def fix_telecined_fades( + clip: vs.VideoNode, colors: float | list[float] = 0.0, + planes: PlanesT = None, func: FuncExceptT | None = None +) -> vs.VideoNode: + ... + +def fix_telecined_fades( # type: ignore[misc] + clip: vs.VideoNode, tff: bool | FieldBasedT | None | float | list[float] | MissingT = MISSING, + colors: float | list[float] | PlanesT = 0.0, + planes: PlanesT | FuncExceptT = None, func: FuncExceptT | None = None +) -> vs.VideoNode: + """ + Give a mathematically perfect solution to decombing fades made *after* telecining + (which made perfect IVTC impossible) that start or end in a solid color. + + Steps between the frames are not adjusted, so they will remain uneven depending on the telecining pattern, + but the decombing is blur-free, ensuring minimum information loss. However, this may cause small amounts + of combing to remain due to error amplification, especially near the solid-color end of the fade. + + This is an improved version of the Fix-Telecined-Fades plugin. + + Make sure to run this *after* IVTC/deinterlacing! + + :param clip: Clip to process. + :param tff: This parameter is deprecated and unused. It will be removed in the future. + :param colors: Fade source/target color (floating-point plane averages). + + :return: Clip with fades to/from `colors` accurately deinterlaced. + Frames that don't contain such fades may be damaged. + """ + # Gracefully handle positional arguments that either include or + # exclude tff, hopefully without interfering with keyword arguments. + # Remove this block when tff is fully dropped from the parameter list. + if isinstance(tff, (float, list)): + if colors == 0.0: + tff, colors = MISSING, tff + elif planes is None: + tff, colors, planes = MISSING, tff, colors + else: + tff, colors, planes, func = MISSING, tff, colors, planes + + func = func or fix_telecined_fades + + if not complexpr_available: + raise ExprVars._get_akarin_err()(func=func) + + if tff is not MISSING: + print(DeprecationWarning('fix_telecined_fades: The tff parameter is unnecessary and therefore deprecated!')) + + f = FunctionUtil(clip, func, planes, (vs.GRAY, vs.YUV), 32) + + fields = f.work_clip.std.Limiter().std.SeparateFields(tff=True) + + for i in f.norm_planes: + fields = fields.std.PlaneStats(None, i, f'P{i}') + + props_clip = core.akarin.PropExpr( + [f.work_clip, fields[::2], fields[1::2]], lambda: { # type: ignore[misc] + f'f{t}Avg{i}': f'{c}.P{i}Average {color} -' # type: ignore[has-type] + for t, c in ['ty', 'bz'] + for i, color in zip(f.norm_planes, f.norm_seq(colors)) + } + ) + + fix = norm_expr( + props_clip, 'Y 2 % x.fbAvg{i} x.ftAvg{i} ? AVG! ' + 'AVG@ 0 = x x {color} - x.ftAvg{i} x.fbAvg{i} + 2 / AVG@ / * ? {color} +', + planes, i=f.norm_planes, color=colors, force_akarin=func, + ) + + return f.return_clip(fix) class Vinverse(CustomEnum):