Ejemplo n.º 1
0
def DebandReader(clip, csvfile, range=30, delimiter=' ', mask=None, luma_scaling=15):
    """
    DebandReader, read a csv file to apply a f3kdb filter for given strengths and frames. From awsmfunc.
    > Usage: DebandReader(clip, csvfile, grain, range)
      * csvfile is the path to a csv file containing in each row: <startframe> <endframe> <<strength_y>,**<strength_b>,**<strength_r>> <grain strength> <mask>
      * mask is the mask list you want to apply. it should be in a list
      * range is passed as range in the f3kdb filter
    """
    import csv

    filtered = clip if get_depth(clip) <= 16 else Depth(clip, 16)
    depth = get_depth(clip)

    with open(csvfile) as debandcsv:
        csvzones = csv.reader(debandcsv, delimiter=delimiter)
        for row in csvzones:
            clip_mask = int(row[4])
            strength = row[2].split(',')
            while len(strength) < 3:
                strength.append(strength[-1])
            grain_strength = float(row[3])
            db = core.f3kdb.Deband(clip, y=strength[0], cb=strength[1], cr=strength[2], grainy=0, grainc=0,
                                   range=range, output_depth=depth)
            db = agm.adptvgrnMod(db, luma_scaling=luma_scaling, strength=grain_strength)
            filtered = awf.ReplaceFrames(filtered, db, mappings="[" + row[0] + " " + row[1] + "]")
            if mask:
                filtered = core.std.MaskedMerge(filtered, clip, mask[clip_mask])

    return filtered
Ejemplo n.º 2
0
def lfdeband(clip: vs.VideoNode) -> vs.VideoNode:
    """A simple debander ported from AviSynth by Zastin from debandshit

    Args:
        clip (vs.VideoNode): Source clip

    Returns:
        vs.VideoNode: Debanded clip.
    """
    if clip.format is None:
        raise ValueError("lfdeband: 'Variable-format clips not supported'")

    bits = get_depth(clip)
    wss, hss = 1 << clip.format.subsampling_w, 1 << clip.format.subsampling_h
    w, h = clip.width, clip.height
    dw, dh = round(w / 2), round(h / 2)

    clip = depth(clip, 16)
    dsc = core.resize.Spline64(clip, dw - dw % wss, dh - dh % hss)

    d3kdb = dumb3kdb(dsc, radius=30, threshold=80, grain=0)

    ddif = core.std.MakeDiff(d3kdb, dsc)

    dif = core.resize.Spline64(ddif, w, h)
    out = core.std.MergeDiff(clip, dif)
    return depth(out, bits)
Ejemplo n.º 3
0
def scaled_grain(clip: vs.VideoNode,
                 var: float = 0.25,
                 uvar: float = 0,
                 grain_h: Optional[int] = None,
                 grain_w: Optional[int] = None,
                 static: bool = True,
                 adaptive: bool = False,
                 luma_scaling: int = 12,
                 kernel: str = 'bicubic',
                 b: float = 0,
                 c: float = 1 / 2,
                 taps: int = 3) -> vs.VideoNode:
    """Grains a clip in the given dimensions and merges it with the source clip.

    This is useful for making larger grain patterns when
    using a grain resolution smaller than the source clip's resolution.
    It supports static and dynamic grain with optional adaptive brightness
    masking to grain darker areas more than brighter areas.

    Args:
        clip: The source clip. Assumes YUV format.
        var: Luma grain variance (strength).
        uvar: Chroma grain variance (strength).
        grain_h: Height of the grained clip.
        grain_w: Width of the grained clip.
        static: Determines whether static (constant) or dynamic grain is used.
        adaptive: Determines whether adaptive brightness masking is used.
        luma_scaling: The scaling factor for adaptive brightness masking.
            Lower values increase the graining of brighter areas.
        kernel: The scaling kernel used to scale the grain clip to
            the source clip's dimensions.
        b, c, taps: Parameters for tweaking the kernel.
    """
    grain_h = fallback(grain_h, clip.height / 2)
    grain_w = fallback(grain_w, get_w(grain_h, clip.width / clip.height))

    blank_value = (1 << get_depth(clip)) / 2 if is_integer(clip) else 0
    blank_clip = core.std.BlankClip(
        clip,
        width=grain_w,
        height=grain_h,
        color=[blank_value, blank_value, blank_value])

    grained = core.grain.Add(blank_clip, var=var, uvar=uvar, constant=static)
    grained = fvf.Resize(grained,
                         clip.width,
                         clip.height,
                         kernel=kernel,
                         a1=b,
                         a2=c,
                         taps=taps)

    if adaptive:
        src_res_blank = core.resize.Point(blank_clip, clip.width, clip.height)
        adaptive_mask = core.adg.Mask(core.std.PlaneStats(clip), luma_scaling)
        grained = core.std.MaskedMerge(src_res_blank, grained, adaptive_mask)

    merged = core.std.MergeDiff(clip, grained)

    return clamp(merged)
Ejemplo n.º 4
0
def luma_mask(clip: vs.VideoNode, thr_lo: float, thr_hi: float, invert: bool = True) -> vs.VideoNode:
    """Mask each pixel according to its luma value.
       From debandshit.

    Args:
        clip (vs.VideoNode):
            Source clip.

        thr_lo (float):
            All pixels below this threshold will be binary

        thr_hi (float):
            All pixels above this threshold will be binary

        All pixels in-between will be scaled from black to white

        invert (bool, optional):
            When true, masks dark areas (pixels below lo will be white, and vice versa).
            Defaults to True.

    Returns:
        vs.VideoNode: Luma mask.
    """
    bits = get_depth(clip)
    is_float = get_sample_type(clip) == vs.FLOAT
    peak = 1.0 if is_float else (1 << bits) - 1

    mask = pick_px_op(
        is_float,
        (f'x {thr_lo} < 0 x {thr_hi} > {peak} x {thr_lo} - {thr_lo} {thr_hi} - / {peak} * ? ?',
         lambda x: round(0 if x < thr_lo else peak if x > thr_hi else (x - thr_lo) / (thr_hi - thr_lo) * peak)))(get_y(clip))

    return mask.std.Invert() if invert else mask
Ejemplo n.º 5
0
def _get_bits(clip: vs.VideoNode,
              expected_depth: int = 16) -> Tuple[int, vs.VideoNode]:
    from vsutil import get_depth

    bits = get_depth(clip)
    return bits, depth(clip,
                       expected_depth) if bits != expected_depth else clip
Ejemplo n.º 6
0
def Deband(clip: vs.VideoNode,
           radius: int = 17,
           threshold: float = 4,
           iterations: int = 1,
           grain: float = 4,
           chroma: bool = True) -> vs.VideoNode:
    """Wrapper for placebo.Deband because at the moment, processing one plane is faster.

    Args:
        clip (vs.VideoNode):
        radius (int, optional): Defaults to 17.
        threshold (float, optional): Defaults to 4.
        iterations (int, optional): Defaults to 1.
        grain (float, optional): Defaults to 4.
        chroma (bool, optional): Defaults to True.

    Returns:
        vs.VideoNode
    """
    if get_depth(clip) != 16:
        clip = depth(clip, 16)
    if chroma is True:
        clip = join([
            core.placebo.Deband(x, 1, iterations, threshold, radius, grain)
            for x in split(clip)
        ])
    else:
        clip = core.placebo.Deband(clip, 1, iterations, threshold, radius,
                                   grain)
    return clip
Ejemplo n.º 7
0
def _get_bits(clip: vs.VideoNode,
              expected_depth: int = 16) -> Tuple[int, vs.VideoNode]:
    """Checks bitdepth, set bitdepth if necessary, and sends original clip's bitdepth back with the clip"""
    from vsutil import depth, get_depth

    bits = get_depth(clip)
    return bits, depth(clip,
                       expected_depth) if bits != expected_depth else clip
Ejemplo n.º 8
0
def test_descale(clip: vs.VideoNode,
                 height: int,
                 kernel: str = 'bicubic',
                 b: Union[float, Fraction] = Fraction(1, 3),
                 c: Union[float, Fraction] = Fraction(1, 3),
                 taps: int = 3,
                 show_error: bool = True) -> vs.VideoNode:
    """
    Generic function to test descales with;
    descales and reupscales a given clip, allowing you to compare the two easily.

    When comparing, it is recommended to do atleast a 4x zoom using Nearest Neighbor.
    I also suggest using 'compare' (py:func:lvsfunc.comparison.compare),
    as that will make comparing the output with the source clip a lot easier.

    Some of this code was leveraged from DescaleAA found in fvsfunc.

    Dependencies: vapoursynth-descale

    :param clip:           Input clip
    :param height:         Target descaled height.
    :param kernel:         Kernel used to descale (see :py:func:`lvsfunc.util.get_scale_filter`)
    :param b:              B-param for bicubic kernel (Default: 1 / 3)
    :param c:              C-param for bicubic kernel (Default: 1 / 3)
    :param taps:           Taps param for lanczos kernel (Default: 3)
    :param show_error:     Show diff between the original clip and the reupscaled clip (Default: True)

    :return: A clip re-upscaled with the same kernel
    """
    try:
        from descale import get_filter
    except ModuleNotFoundError:
        raise ModuleNotFoundError("test_descale: missing dependency 'descale'")

    b = float(b)
    c = float(c)

    if get_depth(clip) != 32:
        clip = util.resampler(clip, 32)

    clip_y = get_y(clip)

    desc = get_filter(b, c, taps, kernel)(clip_y,
                                          get_w(height,
                                                clip.width / clip.height),
                                          height)
    upsc = util.get_scale_filter(kernel, b=b, c=c, taps=taps)(desc, clip.width,
                                                              clip.height)
    upsc = core.std.PlaneStats(clip_y, upsc)

    if clip is vs.GRAY:
        return core.text.FrameProps(upsc,
                                    "PlaneStatsDiff") if show_error else upsc
    merge = core.std.ShufflePlanes([upsc, clip], [0, 1, 2], vs.YUV)
    return core.text.FrameProps(merge,
                                "PlaneStatsDiff") if show_error else merge
Ejemplo n.º 9
0
def linemask(clip: vs.VideoNode,
             strength: int = 200,
             protection: int = 2,
             luma_cap: int = 224,
             threshold: float = 3) -> Tuple[vs.VideoNode, vs.VideoNode]:
    """
    Lineart mask from havsfunc.FastLineDarkenMod, using the very same syntax.

    Furthermore, it checks the overall planestatsaverage of the frame
    to determine if it's a super grainy scene or not.
    """
    import math
    from functools import partial
    from typing import List

    from lvsfunc.misc import get_prop
    from vsutil import depth, get_depth, get_y

    def _reduce_grain(n: int, f: vs.VideoFrame,
                      clips: List[vs.VideoNode]) -> vs.VideoNode:
        return clips[1] if get_prop(f, 'PlaneStatsAverage',
                                    float) > 0.032 else clips[0]

    def _cround(x: float) -> float:
        return math.floor(x + 0.5) if x > 0 else math.ceil(x - 0.5)

    assert clip.format

    clip_y = get_y(depth(clip, 8))
    bits = clip.format.bits_per_sample

    peak = (1 << get_depth(clip_y)) - 1

    strngth = strength / 128
    lum = _cround(luma_cap * peak / 255) if peak != 1 else luma_cap / 255
    thr = _cround(threshold * peak / 255) if peak != 1 else threshold / 255

    maxed = clip_y.std.Maximum(threshold=peak / (protection + 1)).std.Minimum()
    dark = core.std.Expr(
        [clip_y, maxed],
        expr=
        f'y {lum} < y {lum} ? x {thr} + > x y {lum} < y {lum} ? - 0 ? {strngth} * x +'
    )
    extr = core.std.Lut2(clip_y,
                         dark,
                         function=lambda x, y: 255 if abs(x - y) else 0)

    dedot = extr.rgvs.RemoveGrain(6)
    blur = dedot.std.Convolution(matrix=[1, 2, 1, 2, 0, 2, 1, 2, 1])
    degrain = core.std.FrameEval(dedot,
                                 partial(_reduce_grain, clips=[dedot, blur]),
                                 dedot.std.PlaneStats())

    return dark, depth(degrain, bits)
Ejemplo n.º 10
0
    def rescale(self, clip: vs.VideoNode, height: int = 720,
                kernel: lvsfunc.kernels.Kernel = lvsfunc.kernels.Catrom(),
                thr: Union[int, float] = 55, expand: int = 2) -> vs.VideoNode:
        """Makes a mask based on rescaled difference.
           Modified version of Atomchtools.

        Args:
            clip (vs.VideoNode):
                Source clip. Can be Gray, YUV or RGB.
                Keep in mind that descale plugin will descale all planes
                after conversion to GRAYS, YUV444PS and RGBS respectively.

            height (int, optional):
                Height to descale to. Defaults to 720.

            kernel (lvsfunc.kernels.Kernel, optional):
                Kernel used to descale. Defaults to lvsfunc.kernels.Bicubic(b=0, c=0.5).

            thr (Union[int, float], optional):
                Binarization threshold. Defaults to 55.

            expand (int, optional):
                Growing/shrinking shape. 0 is allowed. Defaults to 2.

        Returns:
            vs.VideoNode: Rescaled mask.
        """
        if clip.format is None:
            raise FormatError('diff_rescale_mask: Variable format not allowed!')

        bits = get_depth(clip)
        gray_only = clip.format.num_planes == 1
        thr = scale_value(thr, bits, 32, scale_offsets=True)

        pre = core.resize.Bicubic(
            clip, format=clip.format.replace(
                bits_per_sample=32, sample_type=vs.FLOAT, subsampling_w=0, subsampling_h=0
            ).id
        )
        descale = kernel.descale(pre, get_w(height), height)
        rescale = kernel.scale(descale, clip.width, clip.height)

        diff = core.std.Expr(split(pre) + split(rescale), mae_expr(gray_only))

        mask = iterate(diff, lambda x: core.rgsf.RemoveGrain(x, 2), 2)
        mask = core.std.Expr(mask, f'x 2 4 pow * {thr} < 0 1 ?')

        mask = self._minmax(mask, 2 + expand, True)
        mask = mask.std.Deflate()

        return mask.resize.Point(
            format=clip.format.replace(color_family=vs.GRAY, subsampling_w=0, subsampling_h=0).id,
            dither_type='none'
        )
Ejemplo n.º 11
0
def fix_cr_tint(clip: vs.VideoNode, value: int = 128) -> vs.VideoNode:
    """
    Tries to forcibly fix Crunchyroll's green tint by adding pixel values.

    :param clip:   Input clip
    :param value:  Value added to every pixel (Default: 128)

    :return:       Clip with CR tint fixed
    """
    if get_depth(clip) != 16:
        clip = depth(clip, 16)
    return core.std.Expr(clip, f'x {value} +')
Ejemplo n.º 12
0
    def clamp_integer(c):
        depth = get_depth(c)
        depth_max = (1 << depth) - 1
        is_limited = is_limited_range(clip)

        max_luma = 235 * (1 << (depth - 8))
        max_chroma = 240 * (1 << (depth - 8))

        minimum = 16 * (1 << (depth - 8)) if is_limited else 0
        maximum = [max_luma, max_chroma] if is_limited else depth_max

        return core.std.Limiter(c, min=minimum, max=maximum)
Ejemplo n.º 13
0
def chroma_reconstruct(clip: vs.VideoNode,
                       radius: int = 2,
                       i444: bool = False) -> vs.VideoNode:
    """
    A function to demangle messed-up chroma, like for example chroma
    that was downscaled using Nearest Neighbour, or the chroma found on DVDs.
    This function should be used with care, and not blindly applied to anything.

    This function can also return a 4:4:4 clip. This is not recommended
    except for very specific cases, like for example where you're
    dealing with a razor-sharp 1080p source with a lot of bright colours.
    Otherwise, have it return the 4:2:0 clip instead.

    Original function by shane, modified by Ichunjo and LightArrowsEXE.

    Aliases for this function are `lvsfunc.demangle` and `lvsfunc.crecon`.

    :param clip:    Input clip
    :param radius:  Boxblur radius
    :param i444:    Return a 4:4:4 clip

    :return:        Clip with demangled chroma in either 4:2:0 or 4:4:4
    """
    if clip.format is None:
        raise ValueError("recon: 'Variable-format clips not supported'")

    def dmgl(clip: vs.VideoNode) -> vs.VideoNode:
        return core.resize.Bicubic(clip, w, h, src_left=0.25)

    w, h = clip.width, clip.height

    clipb = depth(clip, 32)
    planes = split(clipb)
    clip_y = planes[0]
    planes[0] = planes[0].resize.Bicubic(planes[1].width,
                                         planes[1].height,
                                         src_left=-.5,
                                         filter_param_a=1 / 3,
                                         filter_param_b=1 / 3)
    planes[0], planes[1], planes[2] = map(dmgl,
                                          (planes[0], planes[1], planes[2]))
    y_fix = core.std.MakeDiff(clip_y, planes[0])
    yu, yv = _Regress(planes[0], planes[1], planes[2], radius=radius)

    u_fix = _ReconstructMulti(y_fix, yu, radius=radius)
    planes[1] = core.std.MergeDiff(planes[1], u_fix)
    v_fix = _ReconstructMulti(y_fix, yv, radius=radius)
    planes[2] = core.std.MergeDiff(planes[2], v_fix)

    merged = join([clip_y, planes[1], planes[2]])
    return core.resize.Bicubic(merged, format=clip.format.id) if not i444 \
        else depth(merged, get_depth(clip))
Ejemplo n.º 14
0
def test_descale(clip: vs.VideoNode,
                 width: Optional[int] = None,
                 height: int = 720,
                 kernel: kernels.Kernel = kernels.Bicubic(b=0, c=1 / 2),
                 show_error: bool = True) -> Tuple[vs.VideoNode, ScaleAttempt]:
    """
    Generic function to test descales with;
    descales and reupscales a given clip, allowing you to compare the two easily.
    Also returns a :py:class:`lvsfunc.scale.ScaleAttempt` with additional
    information.

    When comparing, it is recommended to do atleast a 4x zoom using Nearest Neighbor.
    I also suggest using 'compare' (:py:func:`lvsfunc.comparison.compare`),
    as that will make comparing the output with the source clip a lot easier.

    Some of this code was leveraged from DescaleAA found in fvsfunc.

    Dependencies:
    * vapoursynth-descale

    :param clip:           Input clip
    :param width:          Target descale width. If None, determine from `height`
    :param height:         Target descale height (Default: 720)
    :param kernel:         Kernel used to descale (see :py:class:`lvsfunc.kernels.Kernel`,
                           Default: kernels.Bicubic(b=0, c=1/2))
    :param show_error:     Render PlaneStatsDiff on the reupscaled frame (Default: True)

    :return: A tuple containing a clip re-upscaled with the same kernel and
             a ScaleAttempt tuple.
    """
    if clip.format is None:
        raise ValueError("test_descale: 'Variable-format clips not supported'")

    width = width or get_w(height, clip.width / clip.height)

    clip_y = depth(get_y(clip), 32)

    descale = _perform_descale(Resolution(width, height), clip_y, kernel)
    rescaled = depth(core.std.PlaneStats(descale.rescaled, clip_y),
                     get_depth(clip))

    if clip.format.num_planes == 1:
        rescaled = core.text.FrameProps(
            rescaled, "PlaneStatsDiff") if show_error else rescaled
    else:
        merge = core.std.ShufflePlanes([rescaled, clip], [0, 1, 2], vs.YUV)
        rescaled = core.text.FrameProps(
            merge, "PlaneStatsDiff") if show_error else merge

    return rescaled, descale
Ejemplo n.º 15
0
def _retinex_edgemask(src: vs.VideoNode, sigma: int = 1) -> vs.VideoNode:
    """
    Use retinex to greatly improve the accuracy of the edge detection in dark scenes.
    sigma is the sigma of tcanny

    From kagefunc.py, moved here so I don't need to import it.
    """
    luma = get_y(src)
    max_value = 1 if src.format.sample_type == vs.FLOAT else (
        1 << get_depth(src)) - 1
    ret = core.retinex.MSRCP(luma, sigma=[50, 200, 350], upper_thr=0.005)
    tcanny = ret.tcanny.TCanny(
        mode=1, sigma=sigma).std.Minimum(coordinates=[1, 0, 1, 0, 0, 1, 0, 1])
    return core.std.Expr([_kirsch(luma), tcanny], f"x y + {max_value} min")
Ejemplo n.º 16
0
    def get_mask(self, clip: vs.VideoNode, lthr: float = 0.0, hthr: Optional[float] = None, multi: float = 1.0) -> vs.VideoNode:
        """Makes edge mask based on convolution kernel.
           The resulting mask can be thresholded with lthr, hthr and multiplied with multi.

        Args:
            clip (vs.VideoNode):
                Source clip.

            lthr (float, optional):
                Low threshold. Anything below lthr will be set to 0. Defaults to 0.

            hthr (Optional[float], optional):
                High threshold. Anything above hthr will be set to the range max. Defaults to None.

            multi (float, optional):
                Multiply all pixels by this before thresholding. Defaults to 1.0.

        Returns:
            vs.VideoNode: Mask clip.
        """
        assert clip.format is not None

        bits = get_depth(clip)
        is_float = get_sample_type(clip) == vs.FLOAT
        peak = 1.0 if is_float else (1 << bits) - 1
        hthr = peak if hthr is None else hthr


        clip_p = self._preprocess(clip)
        mask = self._compute_mask(clip_p)

        mask = depth(mask, bits, range=Range.FULL, range_in=Range.FULL)


        if multi != 1:
            mask = pick_px_op(
                is_float, (f'x {multi} *', lambda x: round(max(min(x * multi, peak), 0)))
            )(mask)


        if lthr > 0 or hthr < peak:
            mask = pick_px_op(
                is_float, (f'x {hthr} > {peak} x {lthr} <= 0 x ? ?',
                           lambda x: peak if x > hthr else 0 if x <= lthr else x)
            )(mask)


        return mask
Ejemplo n.º 17
0
def quick_import(file: str, force_lsmas=False, resample=True):
    """
        A function to quickly import and resample a file.

        If the file does not have a chroma subsampling of 4:2:0, it'll automatically be converted to such.
    """
    src = lvf.src(file, force_lsmas=force_lsmas)
    depth = vsutil.get_depth(src)

    if vsutil.get_subsampling != '420':
        src = multi_resample(src, depth=depth)

    if resample:
        return fvf.Depth(src, 16)
    else:
        return src
Ejemplo n.º 18
0
def shader(clip: vs.VideoNode,
           width: int,
           height: int,
           shader_file: str,
           luma_only: bool = True,
           **kwargs) -> vs.VideoNode:
    """Wrapper for placebo.Resample
       https://github.com/Lypheo/vs-placebo#vs-placebo

    Args:
        clip (vs.VideoNode): Source clip/

        width (int): Destination width.

        height (int): Destination height.

        shader_file (str):
            Path to shader file used into placebo.Shader.

        luma_only (bool, optional):
            If process the luma only. Defaults to True.

    Returns:
        vs.VideoNode: Shader'd clip.
    """
    if get_depth(clip) != 16:
        clip = depth(clip, 16)
    if luma_only is True:
        filter_shader = 'box'
        if clip.format.num_planes == 1:
            if width > clip.width or height > clip.height:
                clip = clip.resize.Point(format=vs.YUV444P16)
            else:
                blank = core.std.BlankClip(clip, clip.width / 4,
                                           clip.height / 4, vs.GRAY16)
                clip = join([clip, blank, blank])
    else:
        filter_shader = 'ewa_lanczos'

    clip = core.placebo.Shader(clip,
                               shader_file,
                               width,
                               height,
                               filter=filter_shader,
                               **kwargs)

    return get_y(clip) if luma_only is True else clip
Ejemplo n.º 19
0
def rescaler(clip: vs.VideoNode, height: int,
             shader_file: Optional[str] = None, **kwargs: Any
             ) -> Tuple[vs.VideoNode, vs.VideoNode]:
    """
    Multi-descaling + reupscaling function.
    Compares multiple descales and takes darkest/brightest pixels from clips as necessary
    """
    import lvsfunc as lvf
    import muvsfunc as muf
    from vardefunc.mask import FDOG
    from vardefunc.scale import fsrcnnx_upscale, nnedi3_upscale

    bits = get_depth(clip)
    clip = depth(clip, 32)

    clip_y = get_y(clip)
    scalers: List[Callable[[vs.VideoNode, int, int], vs.VideoNode]] = [
        lvf.kernels.Spline36().descale,
        lvf.kernels.Catrom().descale,
        lvf.kernels.BicubicSharp().descale,
        lvf.kernels.Catrom().scale
    ]

    descale_clips = [scaler(clip_y, get_w(height), height) for scaler in scalers]

    descale_clip = core.std.Expr(descale_clips, 'x y z a min max min y z a max min max z a min max')
    if shader_file:
        rescale = fsrcnnx_upscale(descale_clip, shader_file=shader_file, downscaler=None)
    else:
        rescale = nnedi3_upscale(descale_clip)

    rescale = muf.SSIM_downsample(rescale, clip.width, clip.height, smooth=((3 ** 2 - 1) / 12) ** 0.5,
                                  sigmoid=True, filter_param_a=0, filter_param_b=0)

    l_mask = FDOG().get_mask(clip_y, lthr=0.065, hthr=0.065).std.Maximum().std.Minimum()
    l_mask = l_mask.std.Median().std.Convolution([1] * 9)  # stolen from varde xd
    masked_rescale = core.std.MaskedMerge(clip_y, rescale, l_mask)

    scaled = join([masked_rescale, plane(clip, 1), plane(clip, 2)])

    upscale = lvf.kernels.Spline36().scale(descale_clips[0], clip.width, clip.height)
    detail_mask = lvf.scale.descale_detail_mask(clip_y, upscale, threshold=0.04)

    scaled_down = scaled if bits == 32 else depth(scaled, bits)
    mask_down = detail_mask if bits == 32 else depth(detail_mask, 16, range_in=Range.FULL, range=Range.LIMITED)
    return scaled_down, mask_down
Ejemplo n.º 20
0
def get_neutral_value(clip: vs.VideoNode, chroma: bool = False) -> float:
    """
    Taken from vsutil. This isn't in any new versions yet, so mypy complains.
    Will remove once vsutil does another version bump.

    Returns the neutral value for the combination
    of the plane type and bit depth/type of the clip as float.

    :param clip:        Input clip.
    :param chroma:      Whether to get luma or chroma plane value

    :return:            Neutral value.
    """
    check_variable(clip, "get_neutral_value")
    assert clip.format

    is_float = clip.format.sample_type == vs.FLOAT

    return (0. if chroma else
            0.5) if is_float else float(1 << (get_depth(clip) - 1))
Ejemplo n.º 21
0
def IVTC(clip: vs.VideoNode) -> vs.VideoNode:
    """"
        Experimental script for inverse telecining and deinterlacing.
        Forked from <https://github.com/LightArrowsEXE/dotfiles/blob/master/mpv/.config/mpv/vs/ivtc.vpy>

        Requires VapourSynth <http://www.vapoursynth.com/doc/about.html>

        Additional dependencies:
            * vsutil <https://pypi.org/project/vsutil/>
            * TIVTC <https://github.com/dubhater/vapoursynth-tivtc>		
				TIVTC's documentation <http://avisynth.nl/index.php/TIVTC/TFM>

        :param clip:         Input clip

        :return:             IVTC'd clip
    """

    down = depth(clip, 8)
    tfm = core.tivtc.TFM(down)
    return depth(tfm, get_depth(clip))
Ejemplo n.º 22
0
def gamma2linear(clip: vs.VideoNode, curve: CURVES, gcor: float = 1.0,
                 sigmoid: bool = False, thr: float = 0.5, cont: float = 6.5,
                 epsilon: float = 1e-6) -> vs.VideoNode:
    check_variable(clip, "gamma2linear")
    assert clip.format

    if get_depth(clip) != 32 and clip.format.sample_type != vs.FLOAT:
        raise ValueError("gamma2linear: 'Only 32 bits float is allowed'")

    c = get_coefs(curve)

    expr = f'x {c.k0} <= x {c.phi} / x {c.alpha} + 1 {c.alpha} + / {c.gamma} pow ? {gcor} pow'
    if sigmoid:
        x0 = f'1 1 {cont} {thr} * exp + /'
        x1 = f'1 1 {cont} {thr} 1 - * exp + /'
        expr = f'{thr} 1 {expr} {x1} {x0} - * {x0} + {epsilon} max / 1 - {epsilon} max log {cont} / -'

    expr = f'{expr} 0.0 max 1.0 min'

    return core.std.Expr(clip, expr).std.SetFrameProps(_Transfer=8)
Ejemplo n.º 23
0
def linear2gamma(clip: vs.VideoNode, curve: CURVES, gcor: float = 1.0,
                 sigmoid: bool = False, thr: float = 0.5, cont: float = 6.5,
                 ) -> vs.VideoNode:
    check_variable(clip, "linear2gamma")
    assert clip.format

    if get_depth(clip) != 32 and clip.format.sample_type != vs.FLOAT:
        raise ValueError("linear2gamma: 'Only 32 bits float is allowed'")

    c = get_coefs(curve)

    expr = 'x'
    if sigmoid:
        x0 = f'1 1 {cont} {thr} * exp + /'
        x1 = f'1 1 {cont} {thr} 1 - * exp + /'
        expr = f'1 1 {cont} {thr} {expr} - * exp + / {x0} - {x1} {x0} - /'

    expr += f' {gcor} pow'
    expr = f'{expr} {c.k0} {c.phi} / <= {expr} {c.phi} * {expr} 1 {c.gamma} / pow {c.alpha} 1 + * {c.alpha} - ?'
    expr = f'{expr} 0.0 max 1.0 min'

    return core.std.Expr(clip, expr).std.SetFrameProps(_Transfer=curve)
Ejemplo n.º 24
0
def shift_tint(clip: vs.VideoNode, values: Union[int, Sequence[int]] = 16) -> vs.VideoNode:
    """
    A function for forcibly adding pixel values to a clip.
    Can be used to fix green tints in Crunchyroll sources, for example.
    Only use this if you know what you're doing!

    This function accepts a single integer or a list of integers.
    Values passed should mimic those of an 8bit clip.
    If your clip is not 8bit, they will be scaled accordingly.

    If you only pass 1 value, it will copied to every plane.
    If you pass 2, the 2nd one will be copied over to the 3rd.
    Don't pass more than three.

    :param clip:    Input clip
    :param values:  Value added to every pixel, scales accordingly to your clip's depth (Default: 16)

    :return:        Clip with pixel values added
    """
    val: Tuple[float, float, float]

    if isinstance(values, int):
        val = (values, values, values)
    elif len(values) == 2:
        val = (values[0], values[1], values[1])
    elif len(values) == 3:
        val = (values[0], values[1], values[2])
    else:
        raise ValueError("shift_tint: 'Too many values supplied'")

    if any(v > 255 or v < -255 for v in val):
        raise ValueError("shift_tint: 'Every value in \"values\" must be below 255'")

    cdepth = get_depth(clip)
    cv: List[float] = [scale_value(v, 8, cdepth) for v in val] if cdepth != 8 else list(val)

    return core.std.Expr(clip, expr=[f'x {cv[0]} +', f'x {cv[1]} +', f'x {cv[2]} +'])
Ejemplo n.º 25
0
def shift_tint(clip: vs.VideoNode,
               values: Union[int, List[int]] = 16) -> vs.VideoNode:
    """
    A function for forcibly adding pixel values to a clip.
    Can be used to fix green tints in CrunchyRoll sources, for example.
    Only use this if you know what you're doing!

    Values passed should mimic those of an 8bit clip.
    If your clip is not 8bit, they will be scaled accordingly.

    If you only pass 1 value, it will copied to every plane.
    If you pass 2, the 2nd one will be copied over to the 3rd.

    Alias for this function is `lvsfunc.misc.fix_cr_tint`.

    :param clip:   Input clip
    :param value:  Value added to every pixel, scales accordingly to your clip's depth (Default: 16)

    :return:       Clip with pixel values added
    """
    if isinstance(values, int):
        values = [values, values, values]
    elif len(values) == 2:
        values = [values[0], values[1], values[1]]

    if any(v > 255 or v < -255 for v in values):
        raise ValueError(
            "shift_tint: 'Every value in \"values\" must be below 255'")

    cdepth = get_depth(clip)
    if cdepth != 8:
        values: List[float, int] = [scale_value(v, 8, cdepth) for v in values]

    return core.std.Expr(
        clip,
        expr=[f'x {values[0]} +', f'x {values[1]} +', f'x {values[2]} +'])
Ejemplo n.º 26
0
def descale(clip: vs.VideoNode,
            upscaler: Optional[Callable[[vs.VideoNode, int, int],
                                        vs.VideoNode]] = reupscale,
            width: Union[int, List[int], None] = None,
            height: Union[int, List[int]] = 720,
            kernel: kernels.Kernel = kernels.Bicubic(b=0, c=1 / 2),
            threshold: float = 0.0,
            mask: Optional[Callable[[vs.VideoNode, vs.VideoNode],
                                    vs.VideoNode]] = descale_detail_mask,
            src_left: float = 0.0,
            src_top: float = 0.0,
            show_mask: bool = False) -> vs.VideoNode:
    """
    A unified descaling function.
    Includes support for handling fractional resolutions (experimental),
    multiple resolutions, detail masking, and conditional scaling.

    If you want to descale to a fractional resolution,
    set src_left and src_top and round up the target height.

    If the source has multiple native resolutions, specify ``height``
    as a list.

    If you want to conditionally descale, specify a non-zero threshold.

    Dependencies: vapoursynth-descale, znedi3

    :param clip:                    Clip to descale
    :param upscaler:                Callable function with signature upscaler(clip, width, height)
                                    -> vs.VideoNode to be used for reupscaling.
                                    Must be capable of handling variable res clips
                                    for multiple heights and conditional scaling.
                                    If a single height is given and upscaler is None,
                                    a constant resolution GRAY clip will be returned instead.
                                    Note that if upscaler is None, no upscaling will be performed
                                    and neither detail masking nor proper fractional descaling can be preformed.
                                    (Default: :py:func:`lvsfunc.scale.reupscale`)
    :param width:                   Width to descale to (if None, auto-calculated)
    :param height:                  Height(s) to descale to. List indicates multiple resolutions,
                                    the function will determine the best. (Default: 720)
    :param kernel:                  Kernel used to descale (see :py:class:`lvsfunc.kernels.Kernel`,
                                    (Default: kernels.Bicubic(b=0, c=1/2))
    :param threshold:               Error threshold for conditional descaling (Default: 0.0, always descale)
    :param mask:                    Function used to mask detail. If ``None``, no masking.
                                    Function must accept a clip and a reupscaled clip and return a mask.
                                    (Default: :py:func:`lvsfunc.scale.descale_detail_mask`)
    :param src_left:                Horizontal shifting for fractional resolutions (Default: 0.0)
    :param src_top:                 Vertical shifting for fractional resolutions (Default: 0.0)
    :param show_mask:               Return detail mask

    :return:                       Descaled and re-upscaled clip with float bitdepth
    """
    if clip.format is None:
        raise ValueError("descale: 'Variable-format clips not supported'")

    if type(height) is int:
        height = [cast(int, height)]

    height = cast(List[int], height)

    if type(width) is int:
        width = [cast(int, width)]
    elif width is None:
        width = [
            get_w(h, aspect_ratio=clip.width / clip.height) for h in height
        ]

    width = cast(List[int], width)

    if len(width) != len(height):
        raise ValueError(
            "descale: Asymmetric number of heights and widths specified")

    resolutions = [Resolution(*r) for r in zip(width, height)]

    clip = depth(clip, 32)
    assert clip.format is not None  # clip was modified by depth, but that wont make it variable
    clip_y = get_y(clip) \
        .std.SetFrameProp('descaleResolution', intval=clip.height)

    variable_res_clip = core.std.Splice([
        core.std.BlankClip(clip_y, length=len(clip) - 1),
        core.std.BlankClip(clip_y, length=1, width=clip.width + 1)
    ],
                                        mismatch=True)

    descale_partial = partial(_perform_descale, clip=clip_y, kernel=kernel)
    clips_by_resolution = {
        c.resolution.height: c
        for c in map(descale_partial, resolutions)
    }

    props = [c.diff for c in clips_by_resolution.values()]
    select_partial = partial(_select_descale,
                             threshold=threshold,
                             clip=clip_y,
                             clips_by_resolution=clips_by_resolution)

    descaled = core.std.FrameEval(variable_res_clip,
                                  select_partial,
                                  prop_src=props)

    if src_left != 0 or src_top != 0:
        descaled = core.resize.Bicubic(descaled,
                                       src_left=src_left,
                                       src_top=src_top)

    if upscaler is None:
        upscaled = descaled
        if len(height) == 1:
            upscaled = core.resize.Point(upscaled, width[0], height[0])
        else:
            return upscaled
    else:
        upscaled = upscaler(descaled, clip.width, clip.height)

    if src_left != 0 or src_top != 0:
        upscaled = core.resize.Bicubic(descaled,
                                       src_left=-src_left,
                                       src_top=-src_top)

    if upscaled.format is None:
        raise RuntimeError(
            "descale: 'Upscaler cannot return variable-format clips'")

    if mask:
        clip_y = clip_y.resize.Point(format=upscaled.format.id)
        rescaled = kernel.scale(descaled, clip.width, clip.height,
                                (src_left, src_top))
        rescaled = rescaled.resize.Point(format=clip.format.id)
        dmask = mask(clip_y, rescaled)

        if upscaler is None:
            dmask = core.resize.Spline36(dmask, upscaled.width,
                                         upscaled.height)
            clip_y = core.resize.Spline36(clip_y, upscaled.width,
                                          upscaled.height)

        if show_mask:
            return dmask

        upscaled = core.std.MaskedMerge(upscaled, clip_y, dmask)

    upscaled = depth(upscaled, get_depth(clip))

    if clip.format.num_planes == 1 or upscaler is None:
        return upscaled
    return join([upscaled, plane(clip, 1), plane(clip, 2)])
Ejemplo n.º 27
0
def fsrcnnx_upscale(
    clip: vs.VideoNode,
    width: int = None,
    height: int = 1080,
    shader_file: str = None,  # noqa: PLR0912
    downscaler: Callable[[vs.VideoNode, int, int],
                         vs.VideoNode] = core.resize.Bicubic,
    upscaled_smooth: Optional[vs.VideoNode] = None,
    strength: float = 100.0,
    profile: str = 'slow',
    lmode: int = 1,
    overshoot: float = None,
    undershoot: float = None,
    sharpener: Callable[[vs.VideoNode], vs.VideoNode] = partial(z4usm,
                                                                radius=2,
                                                                strength=65)
) -> vs.VideoNode:
    """
    Upscale the given luma source clip with FSRCNNX to a given width / height
    while preventing FSRCNNX artifacts by limiting them.

    Args:
        source (vs.VideoNode):
            Source clip, assuming this one is perfectly descaled.

        width (int):
            Target resolution width (if None, auto-calculated). Defaults to None.

        height (int):
            Target resolution height. Defaults to 1080.

        shader_file (str):
            Path to the FSRCNNX shader file. Defaults to None.

        downscaler (Callable[[vs.VideoNode, int, int], vs.VideoNode], optional):
            Resizer used to downscale the upscaled clip. Defaults to core.resize.Bicubic.

        upscaled_smooth (Optional[vs.VideoNode]):
            Smooth doubled clip. If not provided, will use nnedi3_upscale(source).

        strength (float):
            Only for profile='slow'.
            Strength between the smooth upscale and the fsrcnnx upscale where 0.0 means the full smooth clip
            and 100.0 means the full fsrcnnx clip. Negative and positive values are possible, but not recommended.

        profile (str): Profile settings. Possible strings: "fast", "old", "slow" or "zastin".
                       – "fast" is the old draft mode (the plain fsrcnnx clip returned).
                       – "old" is the old mode to deal with the bright pixels.
                       – "slow" is the new mode, more efficient, using clamping.
                       – "zastin" is a combination between a sharpened nnedi3 upscale and a fsrcnnx upscale.
                         The sharpener prevents the interior of lines from being brightened and fsrnncx
                         (as a clamping clip without nnedi3) prevents artifacting (halos) from the sharpening.

        lmode (int): Only for profile='slow':
                     – (< 0): Limit with rgvs.Repair (ex: lmode=-1 --> rgvs.Repair(1), lmode=-5 --> rgvs.Repair(5) ...)
                     – (= 0): No limit.
                     – (= 1): Limit to over/undershoot.

        overshoot (int):
            Only for profile='slow'.
            Limit for pixels that get brighter during upscaling.

        undershoot (int):
            Only for profile='slow'.
            Limit for pixels that get darker during upscaling.

        sharpener (Callable[[vs.VideoNode, Any], vs.VideoNode], optional):
            Only for profile='zastin'.
            Sharpening function used to replace the sharped smoother nnedi3 upscale.
            Defaults to partial(z4USM, radius=2, strength=65)

    Returns:
        vs.VideoNode: Upscaled luma clip.
    """
    bits = get_depth(clip)

    clip = get_y(clip)
    clip = depth(clip, 16)

    if width is None:
        width = get_w(height, clip.width / clip.height)
    if overshoot is None:
        overshoot = strength / 100
    if undershoot is None:
        undershoot = overshoot

    profiles = ['fast', 'old', 'slow', 'zastin']
    if profile not in profiles:
        raise ValueError(
            'fsrcnnx_upscale: "profile" must be "fast", "old", "slow" or "zastin"'
        )
    num = profiles.index(profile.lower())

    if not shader_file:
        raise ValueError(
            'fsrcnnx_upscale: You must set a string path for "shader_file"')

    fsrcnnx = shader(clip, clip.width * 2, clip.height * 2, shader_file)

    if num >= 1:
        # old or slow profile
        smooth = depth(get_y(upscaled_smooth),
                       bits) if upscaled_smooth else nnedi3_upscale(clip)
        if num == 1:
            # old profile
            limit = core.std.Expr([fsrcnnx, smooth], 'x y min')
        elif num == 2:
            # slow profile
            upscaled = core.std.Expr(
                [fsrcnnx, smooth],
                'x {strength} * y 1 {strength} - * +'.format(
                    strength=strength / 100))
            if lmode < 0:
                limit = core.rgvs.Repair(upscaled, smooth, abs(lmode))
            elif lmode == 0:
                limit = upscaled
            elif lmode == 1:
                dark_limit = core.std.Minimum(smooth)
                bright_limit = core.std.Maximum(smooth)

                overshoot = scale_value(overshoot,
                                        8,
                                        16,
                                        range_in=Range.FULL,
                                        range=Range.FULL)
                undershoot = scale_value(undershoot,
                                         8,
                                         16,
                                         range_in=Range.FULL,
                                         range=Range.FULL)
                limit = core.std.Expr([
                    upscaled, bright_limit, dark_limit
                ], f'x y {overshoot} + > y {overshoot} + x ? z {undershoot} - < z {undershoot} - x y {overshoot} + > y {overshoot} + x ? ?'
                                      )
            else:
                raise ValueError(
                    'fsrcnnx_upscale: "lmode" must be < 0, 0 or 1')
        else:
            # zastin profile
            smooth_sharp = sharpener(smooth)
            limit = core.std.Expr([smooth, fsrcnnx, smooth_sharp],
                                  'x y z min max y z max min')
    else:
        limit = fsrcnnx

    if downscaler:
        scaled = downscaler(limit, width, height)
    else:
        scaled = limit

    return depth(scaled, bits)
Ejemplo n.º 28
0
def conditional_descale(clip: vs.VideoNode,
                        height: int,
                        upscaler: Callable[[vs.VideoNode, int, int],
                                           vs.VideoNode],
                        kernel: str = 'bicubic',
                        b: Union[float, Fraction] = Fraction(1, 3),
                        c: Union[float, Fraction] = Fraction(1, 3),
                        taps: int = 4,
                        threshold: float = 0.003) -> vs.VideoNode:
    """
    Descales and reupscales a clip; if the difference exceeds the threshold, the frame will not be descaled.
    If it does not exceed the threshold, the frame will upscaled using the caller-supplied upscaler function.

    Useful for bad BDs that have additional post-processing done on some scenes, rather than all of them.

    Currently only works with bicubic, and has no native 1080p masking.
    Consider scenefiltering OP/EDs with a different descale function instead.

    The code for _get_error was mostly taken from kageru's Made in Abyss script.
    Special thanks to Lypheo for holding my hand as this was written.

    Dependencies: vapoursynth-descale

    :param clip:                   Input clip
    :param upscaler:               Callable function with signature upscaler(clip, width, height) -> vs.VideoNode to be used for reupscaling.
                                   Example for nnedi3_rpow2: `lambda clip, width, height: nnedi3_rpow2(clip).resize.Spline36(width, height)`
    :param height:                 Target descale height
    :param kernel:                 Kernel used to descale (see :py:func:`lvsfunc.util.get_scale_filter`, Default: bicubic)
    :param b:                      B-param for bicubic kernel (Default: 1 / 3)
    :param c:                      C-param for bicubic kernel (Default: 1 / 3)
    :param taps:                   Taps param for lanczos kernel (Default: 4)
    :param threshold:              Threshold for deciding to descale or leave the original frame (Default: 0.003)

    :return:                       Constant-resolution rescaled clip
    """
    try:
        from descale import get_filter
    except ModuleNotFoundError:
        raise ModuleNotFoundError(
            "conditional_descale: missing dependency 'descale'")

    b = float(b)
    c = float(c)

    def _get_error(clip, height, kernel, b, c, taps):
        descale = get_filter(b, c, taps,
                             kernel)(clip,
                                     get_w(height, clip.width / clip.height),
                                     height)
        upscale = util.get_scale_filter(kernel, b=b, c=c,
                                        taps=taps)(clip, clip.width,
                                                   clip.height)
        diff = core.std.PlaneStats(upscale, clip)
        return descale, diff

    def _diff(n, f, clip_a, clip_b, threshold):
        return clip_a if f.props.PlaneStatsDiff > threshold else clip_b

    if get_depth(clip) != 32:
        clip = util.resampler(clip, 32)

    planes = split(clip)
    descaled, diff = _get_error(planes[0],
                                height=height,
                                kernel=kernel,
                                b=b,
                                c=c,
                                taps=taps)

    try:
        planes[0] = upscaler(descaled, clip.width, clip.height)
    except:
        raise Exception("conditional_descale: upscale function misbehaved")

    descaled = join(planes).resize.Spline36(format=clip.format)
    descaled = descaled.std.SetFrameProp("_descaled", intval=1)
    clip = clip.std.SetFrameProp("_descaled", intval=0)

    return core.std.FrameEval(
        clip, partial(_diff, clip_a=clip, clip_b=descaled,
                      threshold=threshold), diff)
Ejemplo n.º 29
0
def prep(*clips: vs.VideoNode, w: int = 1280, h: int = 720, dith: bool = True, yuv444: bool = True, static: bool = True) \
        -> Union[vs.VideoNode, List[vs.VideoNode]]:
    """Prepares multiple clips of diff sizes/bit-depths to be compared.

    Can optionally be used as a simplified resize/ftmc wrapper for one
    clip. Clips MUST be either YUV420 or YUV444.

    Transforms all planes to w x h using Bicubic:
        Hermite 0,0 for downscale / Mitchell 1/3,1/3 for upscale.

    :param clips: clip(s) to process
        :bit depth: ANY
        :color family: YUV
        :float precision: ANY
        :sample type: ANY
        :subsampling: 420, 444

    :param w: target width in px

    :param h: target height in px

    :param dith: whether or not to dither clips down to 8-bit (Default value = True)

    :param yuv444: whether or not to convert all clips to 444 chroma subsampling (Default value = True)

    :param static: changes dither mode based on clip usage (Default value = True)
        True will use Floyd-Steinberg error diffusion (good for static screenshots)
        False will use Sierra's Filter Lite error diffusion (faster)

    :returns: processed clip(s)
    """
    outclips = []
    for clip in clips:
        if get_subsampling(clip) == '444':
            if clip.height > h:
                clip_scaled = core.resize.Bicubic(clip,
                                                  w,
                                                  h,
                                                  filter_param_a=0,
                                                  filter_param_b=0)
            elif clip.height < h:
                clip_scaled = core.resize.Bicubic(clip,
                                                  w,
                                                  h,
                                                  filter_param_a=0.33,
                                                  filter_param_b=0.33)
            else:
                clip_scaled = clip

        elif get_subsampling(clip) == '420' and yuv444:
            if clip.height > h:
                if clip.height >= (2 * h):
                    # this downscales chroma with Hermite instead of Mitchell
                    clip_scaled = core.resize.Bicubic(
                        clip,
                        w,
                        h,
                        filter_param_a=0,
                        filter_param_b=0,
                        filter_param_a_uv=0,
                        filter_param_b_uv=0,
                        format=clip.format.replace(subsampling_w=0,
                                                   subsampling_h=0))
                else:
                    clip_scaled = core.resize.Bicubic(
                        clip,
                        w,
                        h,
                        filter_param_a=0,
                        filter_param_b=0,
                        filter_param_a_uv=0.33,
                        filter_param_b_uv=0.33,
                        format=clip.format.replace(subsampling_w=0,
                                                   subsampling_h=0))
            elif clip.height < h:
                clip_scaled = core.resize.Bicubic(clip,
                                                  w,
                                                  h,
                                                  filter_param_a=0.33,
                                                  filter_param_b=0.33,
                                                  format=clip.format.replace(
                                                      subsampling_w=0,
                                                      subsampling_h=0))
            else:
                clip_scaled = core.resize.Bicubic(clip,
                                                  filter_param_a=0.33,
                                                  filter_param_b=0.33,
                                                  format=clip.format.replace(
                                                      subsampling_w=0,
                                                      subsampling_h=0))

        else:
            if clip.height > h:
                clip_scaled = core.resize.Bicubic(clip,
                                                  w,
                                                  h,
                                                  filter_param_a=0,
                                                  filter_param_b=0)
            elif clip.height < h:
                clip_scaled = core.resize.Bicubic(clip,
                                                  w,
                                                  h,
                                                  filter_param_a=0.33,
                                                  filter_param_b=0.33)
            else:
                clip_scaled = clip

        if get_depth(clip_scaled) > 8:
            if dith:
                if static:
                    # Floyd-Steinberg error diffusion
                    clip_dith = core.fmtc.bitdepth(clip_scaled,
                                                   bits=8,
                                                   dmode=6)
                else:
                    # Sierra-2-4A "Filter Lite" error diffusion
                    clip_dith = core.fmtc.bitdepth(clip_scaled,
                                                   bits=8,
                                                   dmode=3)
            else:
                # No dither, round to the closest value
                clip_dith = core.fmtc.bitdepth(clip_scaled, bits=8, dmode=1)
        else:
            clip_dith = clip_scaled

        outclips.append(clip_dith)

    if len(outclips) == 1:
        return clip_dith

    return outclips
Ejemplo n.º 30
0
def f3kbilateral(clip: vs.VideoNode,
                 radius: int = 16,
                 threshold: Union[int, List[int]] = 65,
                 grain: Union[int, List[int]] = 0,
                 f3kdb_args: Dict[str, Any] = {},
                 lf_args: Dict[str, Any] = {}) -> vs.VideoNode:
    """f3kdb multistage bilateral-esque filter from debandshit.
       This function is more of a last resort for extreme banding.

    Args:
        clip (vs.VideoNode): Source clip.

        radius (int, optional):
            Same as F3kdb constructor. Defaults to 16.

        threshold (Union[int, List[int]], optional):
            Same as F3kdb constructor. Defaults to 65.

        grain (Union[int, List[int]], optional):
            Same as F3kdb constructor.
            It happens after mvsfunc.LimitFilter and call another instance of F3kdb if != 0.
            Defaults to 0.

        f3kdb_args (Dict[str, Any], optional):
            Same as F3kdb constructor. Defaults to {}.

        lf_args (Dict[str, Any], optional):
            Arguments passed to mvsfunc.LimitFilter. Defaults to {}.

    Returns:
        vs.VideoNode: Debanded clip.
    """
    try:
        from mvsfunc import LimitFilter
    except ModuleNotFoundError as mod_err:
        raise ModuleNotFoundError(
            "f3kbilateral: missing dependency 'mvsfunc'") from mod_err

    if clip.format is None:
        raise FormatError(
            "f3kbilateral: 'Variable-format clips not supported'")

    bits = get_depth(clip)

    limflt_args: Dict[str, Any] = dict(thr=0.6, elast=3.0, thrc=None)
    limflt_args.update(lf_args)

    # Radius
    rad1 = round(radius * 4 / 3)
    rad2 = round(radius * 2 / 3)
    rad3 = round(radius / 3)

    # F3kdb objects
    db1 = F3kdb(rad1, threshold, 0, **f3kdb_args)
    db2 = F3kdb(rad2, threshold, 0, **f3kdb_args)
    db3 = F3kdb(rad3, threshold, 0, **f3kdb_args)

    # Edit the thr of first f3kdb object
    db1.thy, db1.thcb, db1.thcr = db1.thy // 2, db1.thcb // 2, db1.thcr // 2

    # Perform deband
    clip = depth(clip, 16)

    flt1 = db1.deband(clip)
    flt2 = db2.deband(flt1)
    flt3 = db3.deband(flt2)

    # Limit
    limit = LimitFilter(flt3, flt2, ref=clip, **lf_args)

    # Grain
    if grain != 0 or grain is not None:
        grained = F3kdb(grain=grain, **f3kdb_args).grain(limit)
    else:
        grained = limit

    return depth(grained, bits)