Пример #1
0
def simple_native_mask(
    clip: vs.VideoNode,
    descale_w: IntegerFloat,
    descale_h: IntegerFloat,
    blurh: IntegerFloat = 1.5,
    blurv: IntegerFloat = 1.5,
    iter_max: int = 3,
    no_resize: bool = False,
) -> vs.VideoNode:
    """
    Create a native mask to make sure native content does not get descaled.

    Parameters
    ----------
    clip: :class:`VideoNode`
        The video source.
    descale_w: :class:`Union[int, float]`
        Target descale width resolution for checking.
    descale_h: :class:`Union[int, float]`
        Target descale height resolution for checking.
    blurh: :class:`Union[int, float]`
        Horizontal blur strength.
    blurv: :class:`Union[int, float]`
        Vertical blur strength.
    iter_max: :class:`int`
        Iteration count that will expand the mask size.
    no_resize: :class:`bool`
        Don't resize to the descaled resolution (keep it at original resolution)

    Returns
    -------
    :class:`VideoNode`
        The native mask.
    """
    has_plugin_or_raise(["fmtc", "descale"])
    clip_32 = fvf.Depth(clip, 32)
    y_32 = get_y(clip_32)
    clip_bits = clip.format.bits_per_sample

    target_w = clip.width
    target_h = clip.height
    descale_w = int(round(descale_w))
    descale_h = int(round(descale_h))

    down = core.descale.Debicubic(y_32, descale_w, descale_h)
    up = core.resize.Bicubic(down, target_w, target_h)
    dmask = core.std.Expr([y_32, up], "x y - abs 0.025 > 1 0 ?")
    dmask = iterate(dmask, core.std.Maximum, iter_max)
    if blurh > 0 and blurv > 0:
        dmask = core.std.BoxBlur(dmask,
                                 hradius=cast(int, blurh),
                                 vradius=cast(int, blurv))
    if not no_resize:
        dmask = core.resize.Bicubic(dmask, descale_w, descale_h)
    return fvf.Depth(dmask, clip_bits)
Пример #2
0
    def _select(n, y, debic_list, sraa_upscale, rfactor, f):
        # This simply descales to each of those and selects the most appropriate for each frame.
        errors = [x.props.PlaneStatsAverage for x in f]
        y_deb = debic_list[errors.index(min(errors))]
        dmask = core.std.Expr([y, y_deb.resize.Bicubic(clip.width, clip.height)], 'x y - abs 0.025 > 1 0 ?').std.Maximum().std.SetFrameProp("_descaled_resolution", intval=y_deb.height)

        if sraa_upscale:
            up = upscaled_sraa(y_deb, rfactor, h=clip.height).resize.Bicubic(clip.width, clip.height)
        else:
            up = fvf.Depth(nnedi3_rpow2(fvf.Depth(y_deb, 16), nns=4, correct_shift=True, width=clip.width, height=clip.height), 32)
        return core.std.ClipToProp(up, dmask)
Пример #3
0
    def select_scale(n: int, f: List[vs.VideoFrame],
                     descale_list: List[vs.VideoNode]):
        errors = [x.props["PlaneStatsAverage"] for x in f]
        y_deb = descale_list[errors.index(min(errors))]  # type: ignore
        dmask = core.std.Expr(
            [y, global_clip_resizer(y_deb, target_w, target_h)],
            "x y - abs 0.025 > 1 0 ?").std.Maximum()
        y_deb16 = fvf.Depth(y_deb, 16)

        if rescale:
            y_scaled = upscale_nnedi3(
                y_deb16,
                nns=4,
                correct_shift=True,
                width=target_w,
                height=target_h,
                use_gpu=use_gpu  # type: ignore
            ).fmtc.bitdepth(bits=32)
        else:
            y_scaled = global_clip_resizer(y_deb16, target_w,
                                           target_h).fmtc.bitdepth(bits=32)
        dmask = global_clip_resizer(dmask, target_w, target_h)
        if show_native_res and not show_mask:
            y_scaled = y_scaled.text.Text(
                f"Native resolution for this frame: {y_deb.height}")
        return core.std.ClipToProp(y_scaled, dmask)
Пример #4
0
 def skip(n, f):
     if f.props.PlaneStatsMaximum == 0:
         return core.std.BlankClip(clip, format=vs.GRAYS, color=[0])
     else:
         subedge = core.std.Expr(c444, 'x y z min min')
         diff = core.std.Expr([getY(clip).std.Convolution([1]*9), getY(ref).std.Convolution([1]*9)], 'x 0.8 > x 0.2 < or x y - abs 0.1 > and 1 0 ?').std.Maximum().std.Maximum()
         mask = core.misc.Hysteresis(subedge, diff)
         mask = iterate(mask, core.std.Maximum, expandN)
         return mask.std.Inflate().std.Inflate().std.Convolution([1]*9)
             
     clip = fvf.Depth(clip, 32)
     ref = fvf.Depth(ref, 32)
     right = core.resize.Point(clip, src_left=4)    # right shift by 4 pixels
     subedge = core.std.Expr([clip, right], ['x y - abs 0.7 > 1 0 ?', 'x abs 0.1 < y abs 0.1 < and 1 0 ?'])
     c444 = split(subedge.resize.Bilinear(format=vs.YUV444PS))
     luma = c444[0].std.PlaneStats()
     mask = core.std.FrameEval()
Пример #5
0
def conditional_descale(clip: vs.VideoNode, height: int,
                        kernel: str = 'bicubic',
                        b: float = 1 / 3, c: float = 1 / 3,
                        taps: int = 4,
                        threshold: float = 0.003,
                        upscaler: str = None) -> vs.VideoNode:
    funcname = "conditional_descale"
    """
    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 either nnedi3_rpow2 or waifu2x-caffe.

    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.

    :param height: int:                   Target descale height
    :param threshold: float:              Threshold for deciding to descale or leave the original frame
    :param upscaler: str:                 What scaler is used to upscale (options: nnedi3_rpow2 (default), upscaled_sraa, waifu2x)
    :param replacement_clip: videoNode    A clip to replace frames that were not descaled with.
    """
    def _get_error(clip, height, kernel, b, c, taps):
        descale = fvf.Resize(clip, get_w(height), height,  kernel=kernel, a1=b, a2=c, taps=taps, invks=True)
        upscale = fvf.Resize(clip, clip.width, clip.height,  kernel=kernel, a1=b, a2=c, taps=taps)
        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 = fvf.Depth(clip, 32, dither_type='none')

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

    upscaler = upscaler or "nnedi3_rpow2"
    if upscaler in ['nnedi3_rpow2', 'nnedi3', 'nn3_rp2']:
        planes[0] = nnedi3_rpow2(descaled).resize.Spline36(clip.width, clip.height)
    elif upscaler in ['nnedi3_resample', 'nn3_res']:
        planes[0] = nnedi3_resample(descaled, clip.width, clip.height, kernel='gauss', invks=True, invkstaps=2, taps=1, a1=32, nns=4, qual=2, pscrn=4)
    elif upscaler in ['upscaled_sraa', 'up_sraa', 'sraa']:
        planes[0] = upscaled_sraa(descaled, h=clip.height, sharp_downscale=False).resize.Spline36(clip.width)
    elif upscaler in ['waifu2x', 'w2x']:
        planes[0] = core.caffe.Waifu2x(descaled, noise=-1, scale=2, model=6, cudnn=True, processor=0,  tta=False).resize.Spline36(clip.width, clip.height)
    else:
        return error(funcname, f'"{upscaler}" is not a valid option for "upscaler". Please pick either "nnedi3_rpow2", "nnedi3_resample", "upscaled_sraa", or "waifu2x"')

    descaled = kgf.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)
Пример #6
0
def adaptive_grain(clip: vs.VideoNode, strength=0.25, static=True, luma_scaling=12, mask_bits=8, show_mask=False) -> vs.VideoNode:
    """
    generates grain based on frame and pixel brightness.
    details can be found here: https://kageru.moe/article.php?p=adaptivegrain
    strength is the strength of the grain generated by AddGrain, static=True for static grain
    luma_scaling manipulates the grain alpha curve. Higher values will generate less grain (especially in brighter scenes)
    while lower values will generate more grain, even in brighter scenes
    Please note that 8 bit should be enough for the mask; 10, if you want to do everything in 10 bit.
    It is technically possible to set it to up to 16 (float does not work), but you won't gain anything.
    An 8 bit mask uses 1 MB of RAM, 10 bit need 4 MB, and 16 bit use 256 MB.
    Lookup times might also increase (they shouldn't, but you never know), as well as the initial generation time.
    """
    import numpy as np

    def fill_lut(y):
        """
        Using horner's method to compute this polynomial:
        (1 - (1.124 * x - 9.466 * x ** 2 + 36.624 * x ** 3 - 45.47 * x ** 4 + 18.188 * x ** 5)) ** ((y ** 2) * luma_scaling) * 255
        Using the normal polynomial is about 2.5x slower during the initial generation.
        I know it doesn't matter as it only saves a few ms (or seconds at most), but god damn, just let me have some fun here, will ya?
        Just truncating (rather than rounding) the array would also half the processing time,
        but that would decrease the precision and is also just unnecessary.
        """
        x = np.arange(0, 1, 1 / (1 << mask_bits))
        z = (1 - (x * (1.124 + x * (-9.466 + x * (36.624 + x * (-45.47 + x * 18.188)))))) ** ((y ** 2) * luma_scaling)
        if clip.format.sample_type == vs.INTEGER:
            z = z * ((1 << mask_bits) - 1)
            z = np.rint(z).astype(int)
        return z.tolist()

    def generate_mask(n, f, clip):
        frameluma = round(f.props.PlaneStatsAverage * 999)
        table = lut[int(frameluma)]
        return core.std.Lut(clip, lut=table)

    clip8 = fvf.Depth(clip, mask_bits)
    bits = clip.format.bits_per_sample

    lut = [None] * 1000
    for y in np.arange(0, 1, 0.001):
        lut[int(round(y * 1000))] = fill_lut(y)

    luma = core.std.ShufflePlanes(clip8, 0, vs.GRAY)
    luma = core.std.PlaneStats(luma)
    grained = core.grain.Add(clip, var=strength, constant=static)

    mask = core.std.FrameEval(luma, partial(generate_mask, clip=luma), prop_src=luma)
    mask = core.resize.Spline36(mask, clip.width, clip.height)

    if bits != mask_bits:
        mask = core.fmtc.bitdepth(mask, bits=bits, dmode=1)

    if show_mask:
        return mask

    return core.std.MaskedMerge(clip, grained, mask)
Пример #7
0
def fix_cr_tint(clip: vs.VideoNode, value: int = 128) -> vs.VideoNode:
    funcname = "fix_cr_tint"
    """
    Tries to forcibly fix Crunchyroll's green tint by adding pixel values

    :param value: int:  Values added to every pixel
    """
    if get_depth(clip) != 16:
        clip = fvf.Depth(clip, 16)
    return core.std.Expr(clip, f'x {value} +')
Пример #8
0
def luma_histogram(src, plane=0):
    """ Helper function to grab a clip's plane and dither to 8-bit to use hist.Luma """
    name = 'luma_histogram'

    if plane < 0 or src.format.num_planes <= plane:
        raise TypeError(
            name +
            ": 'plane' must be between 0 and the number of src clip planes")

    src = fvf.Depth(src, 8) if src.format.bits_per_sample != 8 else src
    plane = core.std.ShufflePlanes(src, planes=plane, colorfamily=vs.GRAY)

    return core.hist.Luma(plane)
Пример #9
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
Пример #10
0
def NnEedi3(src: vs.VideoNode, strength=1, alpha=0.25, beta=0.5, gamma=40, nrad=2, mdis=20, nsize=3, nns=3, qual=1):
    """
    Script written by Zastin. What it does is clamp the "change" done by eedi3 to the "change" of nnedi3. This should
    fix every issue created by eedi3. For example: https://i.imgur.com/hYVhetS.jpg
    """
    clip = get_y(src)
    if clip.format.bits_per_sample != 16:
        clip = fvf.Depth(clip, 16)
    thr = strength * 256

    strong = taa.TAAmbk(clip, aatype='Eedi3', alpha=alpha, beta=beta, gamma=gamma, nrad=nrad, mdis=mdis, mtype=0)
    weak = taa.TAAmbk(clip, aatype='Nnedi3', nsize=nsize, nns=nns, qual=qual, mtype=0)
    expr = 'x z - y z - * 0 < y x y {l} + min y {l} - max ?'.format(l=thr)
    aa = core.std.Expr([strong, weak, clip], expr)
    mask = clip.std.Prewitt().std.Binarize(50 >> 8).std.Maximum().std.Convolution([1] * 9)
    merged = core.std.MaskedMerge(clip, aa, mask)
    return clip if src.format.color_family == vs.GRAY else core.std.ShufflePlanes([clip, src], [0, 1, 2], vs.YUV)
Пример #11
0
def source(file: str, resample=False) -> vs.VideoNode:
    """
    Just a stupid import script. There really is no reason to use this, but hey, it was fun to write.
    """
    if file.startswith("file:///"):
        file = file[8::]

    if file.endswith(".d2v"):
        clip = core.d2v.Source(file)

    if is_image(file):
        clip = core.imwri.Read(file)
    else:
        if file.endswith(".m2ts"):
            clip = core.lsmas.LWLibavSource(file)
        else:
            clip = core.ffms2.Source(file)

    if resample:
        clip = fvf.Depth(clip, 16)
    return clip
Пример #12
0
def quick_denoise(clip: vs.VideoNode, mode='knlm', bm3d=True, sigma=3, h=1.0, refine_motion=True, sbsize=16, resample=True):
    """
    Wrapper for generic denoising. Denoising is done by BM3D with a given denoisers being used for ref. Returns the denoised clip used
    as ref if BM3D=False.

    Mode 1 = KNLMeansCL
    Mode 2 = SMDegrain
    Mode 3 = DFTTest

    Will be removed eventuallyTM.
    """
    if resample:
        if clip.format.bits_per_sample != 16:
            clip = fvf.Depth(clip, 16)
    clipY = core.std.ShufflePlanes(clip, 0, vs.GRAY)

    if mode in [1, 'knlm']:
        denoiseY = clipY.knlm.KNLMeansCL(d=3, a=2, h=h)
    elif mode in [2, 'SMD', 'SMDegrain']:
        denoiseY = haf.SMDegrain(clipY, prefilter=3, RefineMotion=refine_motion)
    elif mode in [3, 'DFT', 'dfttest']:
        denoiseY = clipY.dfttest.DFTTest(sigma=4.0, tbsize=1, sbsize=sbsize, sosize=sbsize*0.75)
    else:
        raise ValueError('denoise: unknown mode')

    if bm3d:
        denoisedY = mvf.BM3D(clipY, sigma=sigma, psample=0, radius1=1, ref=denoiseY)
    elif bm3d is False:
        denoisedY = denoiseY

    if clip.format.color_family is vs.GRAY:
        return denoisedY
    else:
        srcU = clip.std.ShufflePlanes(1, vs.GRAY)
        srcV = clip.std.ShufflePlanes(2, vs.GRAY)
        merged = core.std.ShufflePlanes([denoisedY, srcU, srcV], 0, vs.YUV)
        return merged
Пример #13
0
def edi_resample(src,
                 w,
                 h,
                 edi=None,
                 kernel='spline16',
                 a1=None,
                 a2=None,
                 sx=None,
                 sy=None,
                 invks=False,
                 taps=4,
                 invkstaps=4,
                 **kwargs):
    """
    Edge-directed interpolation resampler

    Doubles the height with the given edge-directed interpolation filter as many times
    as needed and downsamples to the given w and h, fixing the chroma shift if necessary.

    Supports:
    - eedi2
    - eedi3
    - eedi3cl
    - nnedi3 (znedi3)
    - nnedi3cl

    Currently, it only correctly supports maintaining a similar aspect ratio
    because it always doubles both the width and height.
    """
    name = 'edi_resample'

    valid_edis = {
        'eedi2': [
            'mthresh', 'lthresh', 'vthresh', 'estr', 'dstr', 'maxd', 'map',
            'nt', 'pp'
        ],
        'eedi3': [
            'alpha', 'beta', 'gamma', 'nrad', 'mdis', 'hp', 'ucubic', 'cost3',
            'vcheck', 'vthresh0', 'vthresh1', 'vthresh2', 'sclip', 'opt'
        ],
        'eedi3cl': [
            'alpha', 'beta', 'gamma', 'nrad', 'mdis', 'hp', 'ucubic', 'cost3',
            'vcheck', 'vthresh0', 'vthresh1', 'vthresh2', 'sclip', 'opt',
            'device'
        ],
        'nnedi3': [
            'nsize', 'nns', 'qual', 'etype', 'pscrn', 'opt',
            'int16_prescreener', 'int16_predictor', 'exp'
        ],
        'nnedi3cl': ['nsize', 'nns', 'qual', 'etype', 'pscrn', 'device'],
    }

    if not isinstance(src, vs.VideoNode):
        raise TypeError(name + ": 'src' must be a clip")
    if not isinstance(edi, str):
        raise TypeError(
            name +
            ": Must use a supported edge-directed interpolation filter string")

    edi = edi.lower()

    if edi not in valid_edis:
        raise TypeError(
            name + ": '" + edi +
            "' is not a supported edge-directed interpolation filter")

    # Check if kwargs are valid for given edi
    for arg in kwargs:
        if arg not in valid_edis[edi]:
            raise TypeError(name + ": '" + arg +
                            "' is not a valid argument for " + edi)

    edifuncs = {
        'eedi2':
        (lambda src: core.eedi2.EEDI2(src, field=1, **kwargs).std.Transpose()),
        'eedi3': (lambda src: core.eedi3m.EEDI3(
            src, field=1, dh=True, **kwargs).std.Transpose()),
        'eedi3cl': (lambda src: core.eedi3m.EEDI3CL(
            src, field=1, dh=True, **kwargs).std.Transpose()),
        'nnedi3': (lambda src: core.znedi3.nnedi3(
            src, field=1, dh=True, **kwargs).std.Transpose()),
        'nnedi3cl': (lambda src: core.nnedi3cl.NNEDI3CL(
            src, field=1, dh=True, dw=True, **kwargs)),
    }

    scale = h / src.height

    if scale == 1:
        return src

    double_count = ceil(log(scale, 2))
    double_count = double_count * 2 if edi != 'nnedi3cl' else double_count

    doubled = src

    for _ in range(double_count):
        doubled = edifuncs[edi](doubled)

    if sx is None:
        sx = [-0.5, -0.5 *
              src.format.subsampling_w] if double_count >= 1 else 0
    if sy is None:
        sy = [-0.5, -0.5 *
              src.format.subsampling_h] if double_count >= 1 else 0

    down = core.fmtc.resample(doubled,
                              w=w,
                              h=h,
                              sx=sx,
                              sy=sy,
                              kernel=kernel,
                              a1=a1,
                              a2=a2,
                              taps=taps,
                              invks=invks,
                              invkstaps=invkstaps)

    return fvf.Depth(down, src.format.bits_per_sample)
Пример #14
0
def masked_f3kdb(clip: vs.VideoNode,
                 mask: Optional[vs.VideoNode] = None,
                 range: int = 15,
                 y: int = 40,
                 cb: Optional[int] = None,
                 cr: Optional[int] = None,
                 grainy: int = 0,
                 grainc: int = 0,
                 agrain: int = 0,
                 luma_scaling: int = 12,
                 sample_mode: int = 2,
                 keep_tv_range: bool = True,
                 output_depth: Optional[int] = None) -> vs.VideoNode:
    """Wrapper function for neo_f3kdb.

    Additional changes include sane defaults, the ability to merge with
    an external mask clip, and additionally add kagefunc's
    adaptive_grain to the final, debanded clip.

    grainy, grainc, and adaptive_grain are applied
    to the final, post-merged clip.

    Differing default behavior:
    - y defaults to 40 and cr/cb default to y//2.
    - output_depth defaults to the source bit depth.
    - grainy and grainc default to 0.
    - grain is always static.
    - keep_tv_range defaults to True (since typical sources are TV range).

    Args:
        Most f3kdb arguments: https://f3kdb.readthedocs.io/en/latest/index.html
        mask: Mask clip to use for merging with debanded clip.
        agrain: The strength arg for adaptive_grain.
        luma_scaling: The luma_scaling arg for adaptive_grain.
    """
    name = 'masked_f3kdb'

    if not isinstance(clip, vs.VideoNode):
        raise TypeError(name + ": 'clip' must be a clip")
    if mask is not None and not isinstance(mask, vs.VideoNode):
        raise TypeError(name + ": 'mask' must be a clip")

    src_bits = clip.format.bits_per_sample

    if cb is None:
        cb = y // 2
    if cr is None:
        cr = y // 2
    if output_depth is None:
        output_depth = src_bits
    if mask is not None and mask.format.bits_per_sample != src_bits:
        mask = fvf.Depth(mask, src_bits, dither_type='none')

    debanded = core.neo_f3kdb.Deband(clip,
                                     range=range,
                                     y=y,
                                     cb=cb,
                                     cr=cr,
                                     grainy=0,
                                     grainc=0,
                                     sample_mode=sample_mode,
                                     keep_tv_range=keep_tv_range,
                                     output_depth=src_bits)

    if mask is not None:
        debanded = core.std.MaskedMerge(debanded, clip, mask)
    if grainy > 0 or grainc > 0:
        debanded = core.neo_f3kdb.Deband(debanded,
                                         range=0,
                                         y=0,
                                         cb=0,
                                         cr=0,
                                         grainy=grainy,
                                         grainc=grainc,
                                         sample_mode=sample_mode,
                                         keep_tv_range=keep_tv_range,
                                         output_depth=src_bits)
    if agrain > 0:
        debanded = kgf.adaptive_grain(debanded,
                                      strength=agrain,
                                      luma_scaling=luma_scaling)

    return fvf.Depth(debanded, output_depth)
Пример #15
0
def rescale(src,
            w=None,
            h=None,
            mask_detail=False,
            mask=None,
            thr=10,
            expand=2,
            inflate=2,
            descale_kernel='bicubic',
            b=1 / 3,
            c=1 / 3,
            descale_taps=3,
            kernel='spline16',
            taps=None,
            invks=False,
            invkstaps=3,
            a1=None,
            a2=None,
            nsize=4,
            nns=4,
            f=None,
            show_mask=False):
    """
    Descale and re-upscale a clip

    This descales a clip's luma, nnedi3_resamples it back to its original resolution,
    and merges back in the original chroma if applicable. It can also mask detail that is
    greater than the 'native' resolution and merge it back into the final rescaled clip.

    Parameters:
    -----------
    w:                           source clip's native width to descale to
    h:                           source clip's native height to descale to
    mask_detail (False):         mask higher-than-native-resolution detail
    mask:                        external mask clip to use instead of built-in masking
    thr (10):                    threshold of detail to include in built-in mask
    expand (2):                  number of times to expand built-in mask
    inflate (2):                 number of times to inflate built-in mask
    descale_kernel ('bicubic'):  kernel for descale
    b (1/3):                     b value for descale
    c (1/3):                     c value for descale
    descale_taps (3):            taps value for descale
    kernel ('spline16'):         kernel for nnedi3_resample rescale
    taps:                        taps value for nnedi3_resample rescale
    invks (False):               invks for nnedi3_resample rescale
    invkstaps (3):               invkstaps for nnedi3_resample
    a1:                          a1 for nnedi3_resample
    a2:                          a2 for nnedi3_resample
    nsize (4):                   nsize for nnedi3_resample
    nns (4):                     nns for nnedi3_resample
    f:                           function to perform on descaled luma before upscaling
    show_mask (False):           output detail mask

    """
    name = 'rescale'

    if not isinstance(src, vs.VideoNode):
        raise TypeError(name + ": 'src' must be a clip")
    if mask is not None and not isinstance(mask, vs.VideoNode):
        raise TypeError(name + ": 'mask' must be a clip")
    if h is None:
        raise TypeError(name + ": native height 'h' must be given")
    if show_mask and not mask_detail:
        raise TypeError(name +
                        ": 'show_mask' can only be used with mask_detail=True")

    sw = src.width
    sh = src.height
    src_bits = src.format.bits_per_sample
    is_gray = src.format.color_family == vs.GRAY

    if w is None:
        w = h / sh * sw
    if mask is not None and mask.format.bits_per_sample != src_bits:
        mask = fvf.Depth(mask, src_bits, dither_type='none')

    y = src if is_gray else get_y(src)
    descaled = fvf.Resize(y,
                          w,
                          h,
                          kernel=descale_kernel,
                          a1=b,
                          a2=c,
                          taps=descale_taps,
                          invks=True)

    # Built-in diff mask generation (from fvsfunc's DescaleM)
    if mask_detail and mask is None:
        peak = (1 << src_bits) - 1
        thr = hvf.scale(thr, peak)

        up = fvf.Resize(descaled,
                        sw,
                        sh,
                        kernel=descale_kernel,
                        a1=b,
                        a2=c,
                        taps=descale_taps)

        diff_mask = core.std.Expr([y, up], 'x y - abs')
        diff_mask = fvf.Resize(diff_mask, w, h, kernel='bilinear')
        diff_mask = core.std.Binarize(diff_mask, threshold=thr)

        diff_mask = kgf.iterate(diff_mask, core.std.Maximum, expand)
        diff_mask = kgf.iterate(diff_mask, core.std.Inflate, inflate)

        diff_mask = core.resize.Spline36(diff_mask, sw, sh)
    elif mask_detail:
        diff_mask = mask

    if show_mask:
        return diff_mask

    if f is not None:
        descaled = f(descaled)

    rescaled = nnedi3_resample(descaled,
                               sw,
                               sh,
                               nsize=nsize,
                               nns=nns,
                               kernel=kernel,
                               a1=a1,
                               a2=a2,
                               taps=taps,
                               invks=invks,
                               invkstaps=invkstaps)

    if mask_detail:
        rescaled = core.std.MaskedMerge(rescaled, y, diff_mask)

    if is_gray:
        return rescaled

    return merge_chroma(rescaled, src)
Пример #16
0
def smarter_descale(src: vs.VideoNode,
                    resolutions: List[int],
                    descaler: Optional[Callable[[vs.VideoNode, int, int], vs.VideoNode]] = None,
                    rescaler: Optional[Callable[[vs.VideoNode, int, int], vs.VideoNode]] = None,
                    upscaler: Optional[Callable[[vs.VideoNode, int, int], vs.VideoNode]] = None,
                    thr: float = 0.05,
                    rescale: bool = True, to_src: bool = False) -> vs.VideoNode:
    """
    An updated version of smart_descale, hence smart*er*_descale.
    Still in its experimental stage, and this is just a wrapper to also handle
    format conversions afterwards because x264 throws a fit otherwise.

    Example use:
        import functools
        import lvsfunc as lvf

        scaled = lvf.smarter_descale(src, range(840,849), functools.partial(core.descale.Debicubic, b=0, c=1/2), functools.partial(core.resize.Spline36))
        scaled.set_output()

    :param resolutions: List[int]:     A list of the resolutions to attempt to descale to. For example: "resolutions=[720, 810]"
                                        Range can help with easily getting a range of resolutions
    :param descaler:                   Descaler used. Default is descale.Bicubic
    :param rescaler:                   Rescaler used. Default is resize.Bicubic
    :param upscaler:                   Upscaler used. Default is resize.Bicubi.
    :param thr: float:                 Threshold for the descaling. Corrosponds directly to the same error rate as getscaler
    :param rescale: bool:              To rescale to the highest-given height and handle conversion for x264 fuckery. Default is True
    :param to_src: bool:               To upscale back to the src resolution with nnedi3_resample (inverse gauss)
    """

    descaler = descaler or core.descale.Debicubic
    rescaler = rescaler or core.resize.Bicubic
    upscaler = upscaler or core.resize.Spline36

    ScaleAttempt = namedtuple('ScaleAttempt', ['descaled', 'rescaled', 'resolution', 'diff'])
    src = fvf.Depth((get_y(src) if src.format.num_planes != 1 else src), 32) \
        .std.SetFrameProp('descaleResolution', intval=src.height)

    def perform_descale(height: int) -> ScaleAttempt:
        resolution = Resolution(get_w(height, src.width / src.height), height)
        descaled = descaler(src, resolution.width, resolution.height) \
            .std.SetFrameProp('descaleResolution', intval=height)
        rescaled = rescaler(descaled, src.width, src.height)
        diff = core.std.Expr([rescaled, src], 'x y - abs').std.PlaneStats()
        return ScaleAttempt(descaled, rescaled, resolution, diff)

    clips_by_resolution = {c.resolution.height: c for c in map(perform_descale, resolutions)}
    # If we pass a variable res clip as first argument to FrameEval, we’re also allowed to return one.
    variable_res_clip = core.std.Splice([
        core.std.BlankClip(src, length=len(src)-1), core.std.BlankClip(src, length=1, width=src.width + 1)
    ], mismatch=True)

    def select_descale(n: int, f: List[vs.VideoFrame]):
        # TODO: higher resolutions tend to be lower. compensate for that
        print(f)
        print()
        print([fr.props.PlaneStatsAverage for fr in f])
        best_res = max(f, key=lambda frame: math.log(src.height - frame.props.descaleResolution, 2) * round(1/frame.props.PlaneStatsAverage) ** 0.2)
        print('selected')
        print(clips_by_resolution)
        print(list(clips_by_resolution.keys()))
        print(best_res.props.descaleResolution)
        best_attempt = clips_by_resolution.get(best_res.props.descaleResolution)
        print('found')
        if threshold == 0:
            return best_attempt.descaled
        # No blending here because src and descaled have different resolutions.
        # The caller can use the frameProps to deal with that if they so desire.
        if best_res.props.PlaneStatsAverage > threshold:
            return src
        return best_attempt.descaled

    props = [c.diff for c in clips_by_resolution.values()]
    print(props)
    scaled = core.std.FrameEval(variable_res_clip, select_descale,
                                prop_src=props)

    if rescale:
        # You MUST set the output format again, or else x264/x265 will throw a hissy fit
        scaled = upscaler(scaled, get_w(resolutions.sort()[-1]), resolutions.sort()[-1], format=src.format)
        if to_src:
            # This is done after scaling up because else nn3_rs returns an error
            scaled = nnedi3_resample(scaled, src.width, src.height, kernel='gauss', invks=True, invkstaps=2, taps=1, a1=32, nns=4, qual=2, pscrn=4)
    return scaled
Пример #17
0
from vapoursynth import core
import vsTAAmbk as taa
import fvsfunc as fvf

apple = core.ffms2.Source('apple.mkv')

apple = core.std.Crop(apple, 2, 2, 2, 2)
apple = fvf.Depth(apple, 16)
apple = core.resize.Spline36(apple, 720, 540)

apple_d = core.placebo.Deband(apple, dither=False)
apple_ds = core.placebo.Deband(apple,
                               iterations=2,
                               threshold=10,
                               radius=20,
                               dither=False)
apple_dv = core.placebo.Deband(apple,
                               iterations=4,
                               threshold=23,
                               radius=20,
                               grain=0.7,
                               dither=False)

apple = taa.TAAmbk(
    apple_d[:814] + apple_ds[814:821] + apple_d[821:1709] +
    apple_dv[1709:1812] + apple_d[1812:2950] + apple_ds[2950:3320] +
    apple_d[3320:3786], 2) + taa.TAAmbk(apple_d[3786:3793], 1) + taa.TAAmbk(
        apple_d[3793:4250],
        2) + taa.TAAmbk(apple_d[4250:4316], 1) + taa.TAAmbk(
            apple_d[4316:4447],
            2) + taa.TAAmbk(apple_d[4447:4559], 1) + taa.TAAmbk(
Пример #18
0
 def resample(clip):
     # Resampling to 8bit and RGB to properly display how it appears on your screen
     return fvf.Depth(mvf.ToRGB(clip), 8)
Пример #19
0
def smart_descale(clip: vs.VideoNode,
                  res: List[int],
                  b: float = 1/3, c: float = 1/3,
                  thresh1: float = 0.03, thresh2: float = 0.7,
                  no_mask: float = False,
                  show_mask: bool = False, show_dmask: bool = False,
                  sraa_upscale: bool = False, rfactor: float = 1.5,
                  sraa_sharp: bool = False) -> vs.VideoNode:
    funcname = "smart_descale"
    """
    Original function written by kageru and modified into a general function by me.
    For more information and comments I suggest you check out the original script:
        https://git.kageru.moe/kageru/vs-scripts/src/branch/master/abyss1.py

    A descaling function that compares relative errors between multiple resolutions and descales accordingly.
    Most of this code was leveraged from kageru's Made in Abyss script.
    As this is an incredibly complex function, I will offer only minimal support.

    :param res: List[int]:             A list of resolutions to descale to. For example: [900, 871, 872, 877]
    :param thresh1: float:             Threshold for when a frame will be descaled.
    :param thresh2: float:             Threshold for when a frame will not be descaled.
    :param sraa_upscale: bool:         Use upscaled_sraa to upscale the frames (warning: very slow!)
    :param rfactor: float:             Image enlargement factor for upscaled_sraa
    """
    def _descaling(clip: vs.VideoNode, h: int, b: float, c: float):
        # Descale and return a tuple of descaled clip and diff mask between that and the original.
        down = clip.descale.Debicubic(get_w(h), h, b, c)
        up = down.resize.Bicubic(clip.width, clip.height, filter_param_a=b, filter_param_b=c)
        diff = core.std.Expr([clip, up], 'x y - abs').std.PlaneStats()
        return down, diff

    def _select(n, y, debic_list, sraa_upscale, rfactor, f):
        # This simply descales to each of those and selects the most appropriate for each frame.
        errors = [x.props.PlaneStatsAverage for x in f]
        y_deb = debic_list[errors.index(min(errors))]
        dmask = core.std.Expr([y, y_deb.resize.Bicubic(clip.width, clip.height)], 'x y - abs 0.025 > 1 0 ?').std.Maximum().std.SetFrameProp("_descaled_resolution", intval=y_deb.height)

        if sraa_upscale:
            up = upscaled_sraa(y_deb, rfactor, h=clip.height).resize.Bicubic(clip.width, clip.height)
        else:
            up = fvf.Depth(nnedi3_rpow2(fvf.Depth(y_deb, 16), nns=4, correct_shift=True, width=clip.width, height=clip.height), 32)
        return core.std.ClipToProp(up, dmask)

    def _square():
        top = core.std.BlankClip(length=len(y), format=vs.GRAYS, height=4, width=10, color=[1])
        side = core.std.BlankClip(length=len(y), format=vs.GRAYS, height=2, width=4, color=[1])
        center = core.std.BlankClip(length=len(y), format=vs.GRAYS, height=2, width=2, color=[0])
        t1 = core.std.StackHorizontal([side, center, side])
        return core.std.StackVertical([top, t1, top])

    def _restore_original(n, f, clip: vs.VideoNode, orig: vs.VideoNode, thresh_a: float, thresh_b: float):
        # Just revert the entire scaling if the difference is too big. This should catch the 1080p scenes.
        if f.props.PlaneStatsAverage < thresh_a:
            return clip.std.SetFrameProp("_descaled", intval=1)
        elif f.props.PlaneStatsAverage > thresh_b:
            return orig.std.SetFrameProp("_descaled", intval=0)
        return core.std.Merge(clip, orig, (f.props.PlaneStatsAverage - thresh_a) * 20).std.SetFrameProp("_descaled", intval=2)

    # Error handling
    if len(res) < 2:
        return error(funcname, 'This function requires more than two resolutions to descale to')


    og = clip
    clip32 = fvf.Depth(clip, 32)
    if one_plane(clip32):
        y = get_y(clip32)
    else:
        y, u, v = split(clip32)

    debic_listp = [_descaling(y, h, b, c) for h in res]
    debic_list = [a[0] for a in debic_listp]
    debic_props = [a[1] for a in debic_listp]

    y_deb = core.std.FrameEval(y, partial(_select, y=y, debic_list=debic_list,
                                                  sraa_upscale=sraa_upscale, rfactor=rfactor), prop_src=debic_props)
    # TO-DO: It returns a frame size error here for whatever reason. Need to figure out what causes it and fix it
    dmask = core.std.PropToClip(y_deb)
    if show_dmask:
        return dmask
    # TO-DO: Figure out how to make it properly round depending on resolution (although this should usually be 1080p anyway)
    line = core.std.StackHorizontal([_square()]*192)
    full_squares = core.std.StackVertical([line]*108)

    artifacts = core.misc.Hysteresis(dmask.resize.Bicubic(clip32.width, clip32.height, _format=vs.GRAYS),
                                     core.std.Expr([get_y(clip32).tcanny.TCanny(sigma=3), full_squares], 'x y min'))

    ret_raw = kgf.retinex_edgemask(fvf.Depth(clip, 16))
    ret = ret_raw.std.Binarize(30).rgvs.RemoveGrain(3)

    mask = core.std.Expr([ret.resize.Point(_format=vs.GRAYS), kgf.iterate(artifacts, core.std.Maximum, 3)], 'x y -').std.Binarize(0.4)

    mask = mask.std.Inflate().std.Convolution(matrix=[1]*9).std.Convolution(matrix=[1]*9)
    if show_mask:
        return mask

    y = core.std.MaskedMerge(y, y_deb, mask)
    merged = join([y, u, v]) if not one_plane(og) else y
    merged = fvf.Depth(merged, get_depth(og))
    if no_mask:
        return merged

    dmask = dmask.std.PlaneStats() # TO-DO: It returns a frame size error here for whatever reason. Need to figure out what causes it and fix it
    return merged.std.FrameEval(partial(_restore_original, clip=merged, orig=og, thresh_a=thresh1, thresh_b=thresh2), prop_src=dmask)#.std.SetFrameProp("_descaled_resolution", intval=y_deb.height)
Пример #20
0
from vapoursynth import core
import awsmfunc as awf
import havsfunc as haf
import fvsfunc as fvf
import lvsfunc as lvf
import G41Fun

ep = 2

source = core.lsmas.LWLibavSource(
    f"Code.Lyoko.S01E{ep:02}.DVD.Remux.Multi-Odd_HD.mkv")[1130:]
source = source.std.AssumeFPS(fpsnum=25)
source = source.std.Crop(top=6, bottom=2, left=10, right=8)
lyoko = awf.fb(source, left=1, right=1)
lyoko = lyoko.vinverse.Vinverse()
lyoko = fvf.Depth(lyoko, 16)
no_filter = lyoko
lyoko = awf.bbmod(lyoko, top=4, bottom=4, thresh=4, blur=4)
lyoko = fvf.AutoDeblock(lyoko, adb1=0.4, adb2=0.1, adb1d=0.3, adb2d=1.4)
aa2 = lvf.aa.upscaled_sraa(lyoko, rfactor=2.0, rep=True)
lyoko = lvf.aa.upscaled_sraa(lyoko, rfactor=3.0, rep=True)
lyoko = G41Fun.MaskedDHA(lyoko, rx=1.4, ry=1.4, darkstr=0.0, lowsens=70)
lyoko = awf.ReplaceFrames(
    lyoko, aa2,
    "[4189 4238] [4931 5386] [6796 6845] [9084 9133] [14670 14876] [16507 16556] [19215 19512] [19560 19662] [21924 22287] [22348 22486] [22577 22751] [23495 23843] [24354 25444] [25498 26806] [26950 27304] [27947 28648] [28751 29253] [29421 29768] [29824 29905] [30008 30104] [30250 30304] [30391 30419] [30506 30590] [30659 30699] [31071 31323]"
)
lyoko = lyoko.f3kdb.Deband(range=2, grainy=16, grainc=16)
lyoko = fvf.ReplaceFrames(haf.Deblock_QED(no_filter), lyoko,
                          "[0 33399] [33650 33655]")

lyoko = fvf.Depth(lyoko, 10)
Пример #21
0
def hardsubmask(clip: vs.VideoNode,
                ref: vs.VideoNode,
                mode='default',
                expandN=None,
                highpass=25) -> vs.VideoNode:
    """
    Uses multiple techniques to mask the hardsubs in video streams like Anime on Demand or Wakanim.
    Might (should) work for other hardsubs, too, as long as the subs are somewhat close to black/white.
    It's kinda experimental, but I wanted to try something like this.
    It works by finding the edge of the subtitle (where the black border and the white fill color touch),
    and it grows these areas into a regular brightness + difference mask via hysteresis.
    This should (in theory) reliably find all hardsubs in the image with barely any false positives (or none at all).
    Output depth and processing precision are the same as the input
    It is not necessary for 'clip' and 'ref' to have the same bit depth, as 'ref' will be dithered to match 'clip'
    Most of this code was written by Zastin (https://github.com/Z4ST1N)
    Clean code soon(tm)
    """

    clp_f = clip.format
    bits = clp_f.bits_per_sample
    st = clp_f.sample_type
    peak = 1 if st == vs.FLOAT else (1 << bits) - 1

    if expandN is None:
        expandN = clip.width // 200
    # if mode in ['default', None]:

    out_fmt = core.register_format(vs.GRAY, st, bits, 0, 0)
    YUV_fmt = core.register_format(clp_f.color_family, vs.INTEGER, 8,
                                   clp_f.subsampling_w, clp_f.subsampling_h)

    y_range = 219 << (bits - 8) if st == vs.INTEGER else 1
    uv_range = 224 << (bits - 8) if st == vs.INTEGER else 1
    offset = 16 << (bits - 8) if st == vs.INTEGER else 0

    uv_abs = ' abs ' if st == vs.FLOAT else ' {} - abs '.format(
        (1 << bits) // 2)
    yexpr = 'x y - abs {thr} > 255 0 ?'.format(thr=y_range * 0.7)
    uvexpr = 'x {uv_abs} {thr} < y {uv_abs} {thr} < and 255 0 ?'.format(
        uv_abs=uv_abs, thr=uv_range * 0.1)

    difexpr = 'x {upper} > x {lower} < or x y - abs {mindiff} > and 255 0 ?'.format(
        upper=y_range * 0.8 + offset,
        lower=y_range * 0.2 + offset,
        mindiff=y_range * 0.1)

    # right shift by 4 pixels.
    # fmtc uses at least 16 bit internally, so it's slower for 8 bit,
    # but its behaviour when shifting/replicating edge pixels makes it faster otherwise
    if bits < 16:
        right = core.resize.Point(clip, src_left=4)
    else:
        right = core.fmtc.resample(clip, sx=4, flt=False)
    subedge = core.std.Expr([clip, right], [yexpr, uvexpr], YUV_fmt.id)
    c444 = split(
        subedge.resize.Bicubic(format=vs.YUV444P8,
                               filter_param_a=0,
                               filter_param_b=0.5))
    subedge = core.std.Expr(c444, 'x y z min min')

    clip, ref = getY(clip), getY(ref)
    ref = ref if clip.format == ref.format else fvf.Depth(ref, bits)

    clips = [clip.std.Convolution([1] * 9), ref.std.Convolution([1] * 9)]
    diff = core.std.Expr(clips, difexpr, vs.GRAY8).std.Maximum().std.Maximum()

    mask = core.misc.Hysteresis(subedge, diff)
    mask = iterate(mask, core.std.Maximum, expandN)
    mask = mask.std.Inflate().std.Inflate().std.Convolution([1] * 9)
    mask = fvf.Depth(mask, bits, range=1, range_in=1)
    """
    # needs some more testing
    elif mode == 'fast':
        highpass = highpass << (bits - 8) if st == vs.INTEGER else highpass / 255
        clip, ref = getY(clip), getY(ref)
        ref = ref if clip.format == ref.format else fvf.Depth(ref, bits)
        edge = clip.std.Sobel()
        diff = core.std.Expr([clip, ref], 'x y - abs {:d} < 0 {:d} ?'.format(highpass, peak))
        mask = core.misc.Hysteresis(edge, diff)
        mask = iterate(mask, core.std.Maximum, expandN)
        mask = mask.std.Convolution([1] * 9)
    else:
        raise ValueError('hardsubmask: Unknown mode')
    """
    return mask
Пример #22
0
def faggotdb_mod(clip: vs.VideoNode,
                 thrY=40,
                 thrC=None,
                 radiusY=15,
                 radiusC=15,
                 CbY=44,
                 CrY=44,
                 CbC=44,
                 CrC=44,
                 grainY=32,
                 grainC=None,
                 grainCC=0,
                 sample_mode=2,
                 neo=True,
                 dynamic_grainY=False,
                 dynamic_grainC=False,
                 tv_range=True,
                 mask="retinex",
                 binarize=False,
                 binarize_thr=70,
                 grayscale=True,
                 bitresamp=False,
                 outbits=None,
                 blurmask=True,
                 horizblur=2,
                 vertblur=2) -> vs.VideoNode:

    funcName = "faggotdb_mod"  # name kept to be fallback compatible

    # Original Idea: Author who created Fag3kdb. Edited by AlucardSama04; additional modifications by l00t

    if not isinstance(clip, vs.VideoNode):
        raise TypeError(f"{funcName}: This is not a clip")

    if outbits is None:
        outbits = clip.format.bits_per_sample

    if bitresamp:
        if clip.format.bits_per_sample != outbits:
            clip = fvf.Depth(
                clip,
                bits=outbits)  # instead of error, auto convert to 16 bits
    elif clip.format.bits_per_sample != outbits:
        raise TypeError(f"{funcName}: Input-output bitdepth mismatch")

    # if not isinstance(mask, vs.VideoNode):
    # raise vs.Error(f"{funcName}: mask' only clip inputs")

    if mask in [-1]:  # more user friendly if we do the masking intentionally
        mask = clip
        blurmask = False
    elif mask in [0, "retinex"]:
        mask = kgf.retinex_edgemask(clip)
    elif mask in [1, "kirsch"]:
        mask = kgf.kirsch(clip)
    elif mask in [2, "Sobel"]:
        mask = core.std.Sobel(clip, scale=1)
    elif mask in [3, "Prewitt"]:
        mask = core.std.Prewitt(clip)
    elif mask in [4, "GF"]:
        mask = fvf.GradFun3(clip, mask=2, debug=1)
    else:
        raise ValueError(f"{funcName}: Unknown Mask Mode")

    if grayscale:
        mask = core.std.ShufflePlanes(mask, planes=0, colorfamily=vs.GRAY)

    if binarize:  # binarize threshold should be adjusted according to bitdepth
        mask = core.std.Binarize(mask, threshold=binarize_thr)

    if blurmask:
        mask = core.std.BoxBlur(mask, hradius=horizblur, vradius=vertblur)

    if thrC is None:
        thrC = int(round(thrY / 2))

    if grainC is None:
        grainC = int(round(grainY / 2))

    if grainCC is None:
        grainCC = 0

    f3kdb = core.neo_f3kdb.Deband if neo else core.f3kdb.Deband

    U = plane(clip, 1)
    V = plane(clip, 2)

    U = f3kdb(U,
              range=radiusC,
              y=thrC,
              cb=CbC,
              cr=CrC,
              grainy=grainC,
              grainc=0,
              sample_mode=sample_mode,
              dynamic_grain=dynamic_grainC,
              keep_tv_range=tv_range,
              output_depth=outbits)

    V = f3kdb(V,
              range=radiusC,
              y=thrC,
              cb=CbC,
              cr=CrC,
              grainy=grainC,
              grainc=0,
              sample_mode=sample_mode,
              dynamic_grain=dynamic_grainC,
              keep_tv_range=tv_range,
              output_depth=outbits)

    filtered = core.std.ShufflePlanes([clip, U, V], [0, 0, 0], vs.YUV)

    filtered = f3kdb(filtered,
                     range=radiusY,
                     y=thrY,
                     cb=CbY,
                     cr=CrY,
                     grainy=grainY,
                     grainc=grainCC,
                     sample_mode=sample_mode,
                     dynamic_grain=dynamic_grainY,
                     keep_tv_range=tv_range,
                     output_depth=outbits
                     )  # if grainCC > 0 UV planes will be debanded once again

    return core.std.MaskedMerge(filtered, clip, mask)
Пример #23
0
def quick_output(clip: vs.VideoNode, bitdepth=8):
    """
        A function for quickly preparing the clip for the output node.
    """
    if vsutil.get_depth != bitdepth:
        return fvf.Depth(clip, bitdepth)
Пример #24
0
def simple_aa(src,
              aatype='nnedi3',
              aatypeuv=None,
              mask=None,
              kernel='spline36',
              nsize=3,
              nns=4,
              qual=2,
              alpha=0.5,
              beta=0.2,
              nrad=3,
              mdis=30):
    """
    Basic nnedi3/eedi3 anti-aliasing with optional use of external mask

    Default values should be good for most anti-aliasing.
    By default it will use ocl for eedi3 and cpu for nnedi3 (znedi3).

    Parameters:
    -----------
    aatype ('nnedi3'):   type of anti-aliasing to use ('nnedi3', 'eedi3', 'combo')
    aatypeuv:            type of anti-aliasing to use on chroma (disabled by default)
    mask:                optional, external mask to use
    kernel ('spline36'): kernel to downsample interpolated clip
    nnedi3/eedi3 specific parameters

    """
    def perform_aa(src, aatype):
        sw = src.width
        sh = src.height

        if aatype == 'nnedi3':
            aa = core.znedi3.nnedi3(src,
                                    field=1,
                                    dh=True,
                                    nsize=nsize,
                                    nns=nns,
                                    qual=qual).std.Transpose()
            aa = core.znedi3.nnedi3(aa,
                                    field=1,
                                    dh=True,
                                    nsize=nsize,
                                    nns=nns,
                                    qual=qual).std.Transpose()
        elif aatype == 'eedi3':
            aa = core.eedi3m.EEDI3CL(src,
                                     field=1,
                                     dh=True,
                                     alpha=alpha,
                                     beta=beta,
                                     nrad=nrad,
                                     mdis=mdis).std.Transpose()
            aa = core.eedi3m.EEDI3CL(aa,
                                     field=1,
                                     dh=True,
                                     alpha=alpha,
                                     beta=beta,
                                     nrad=nrad,
                                     mdis=mdis).std.Transpose()
        elif aatype == 'combo':
            aa = core.eedi3m.EEDI3CL(src,
                                     field=1,
                                     dh=True,
                                     alpha=alpha,
                                     beta=beta,
                                     nrad=nrad,
                                     mdis=mdis)
            aa = core.znedi3.nnedi3(aa,
                                    field=0,
                                    dh=True,
                                    nsize=nsize,
                                    nns=nns,
                                    qual=qual).std.Transpose()
            aa = core.eedi3m.EEDI3CL(aa,
                                     field=1,
                                     dh=True,
                                     alpha=alpha,
                                     beta=beta,
                                     nrad=nrad,
                                     mdis=mdis)
            aa = core.znedi3.nnedi3(aa,
                                    field=0,
                                    dh=True,
                                    nsize=nsize,
                                    nns=nns,
                                    qual=qual).std.Transpose()

        return fvf.Resize(aa, w=sw, h=sh, sx=-0.5, sy=-0.5, kernel=kernel)

    name = 'simple_aa'

    valid_aatypes = ['nnedi3', 'eedi3', 'combo']

    if isinstance(aatype, str):
        aatype = aatype.lower()
    if isinstance(aatypeuv, str):
        aatypeuv = aatypeuv.lower()
    if aatype is not None and aatype not in valid_aatypes:
        raise TypeError(name +
                        ": 'aatype' must be 'nnedi3', 'eedi3', or 'combo'")
    if aatypeuv is not None and aatypeuv not in valid_aatypes:
        raise TypeError(name +
                        ": 'aatypeuv' must be 'nnedi3', 'eedi3', or 'combo'")
    if src.format.color_family not in [vs.GRAY, vs.YUV]:
        raise TypeError(name + ": src clip must be GRAY or YUV")

    src_bits = src.format.bits_per_sample
    is_gray = src.format.color_family == vs.GRAY

    if mask is not None and mask.format.bits_per_sample != src_bits:
        mask = fvf.Depth(mask, src_bits, dither_type='none')

    planes = [src] if is_gray else get_yuv(src)

    if aatype is not None:
        planes[0] = perform_aa(planes[0], aatype)

    if aatypeuv is not None:
        planes[1:] = [perform_aa(plane, aatypeuv) for plane in planes[1:]]

    aa = to_yuv(planes)

    if mask is not None:
        aa = core.std.MaskedMerge(src, aa, mask)

    return aa
Пример #25
0
def recursive_apply_mask(
    src_a: vs.VideoNode,
    src_b: vs.VideoNode,
    mask_folder: Union[str, Path],
    iter: int = 1,
) -> vs.VideoNode:
    """
    Recursively check `mask_folder` for a .png or .ass file
    After it found all of them, it will use the mask
    to merge together the two clips.

    Acceptable filename format:
    - frameNum.png
    - frameStart-frameEnd.png
    - itsUpToYou.ass
    Example:
    - 2500.png
    - 2000-2004.png
    - maskep1.ass

    Parameters
    ----------
    src_a: :class:`VideoNode`
        The first clip.
    src_b: :class:`VideoNode`
        The second clip.
    mask_folder: :class:`Union[str, Path]`
        The folder that contains the masks.
    iter: :class:`int`
        How many times we will need to iterate the mask.

    Returns
    -------
    :class:`VideoNode`
        The merged clip.
    """

    if isinstance(mask_folder, str):
        mask_folder = Path(mask_folder)
    has_plugin_or_raise(["fmtc", "imwri"])

    imwri = core.imwri
    masks_png = mask_folder.glob("*.png")
    for mask in masks_png:
        frame = os_path.basename(mask).rsplit(".", 1)[0]
        frame = [int(i) for i in frame.split("-")][:2]
        if len(frame) < 2:
            frame = [frame[0], frame[0]]
        first_f, last_f = frame

        image = fvf.Depth(
            imwri.Read(str(mask), ).resize.Point(format=vs.GRAYS,
                                                 matrix_s="709"),
            src_a.format.bits_per_sample,
        ).std.AssumeFPS(
            fpsnum=src_a.fps.numerator,
            fpsden=src_a.fps.denominator,
        )

        src_a_n, src_b_n = src_a[first_f:last_f + 1], src_b[first_f:last_f + 1]

        image = image * ((last_f + 1) - first_f)
        image = get_y(image)
        src_masked = core.std.MaskedMerge(src_a_n, src_b_n, image)
        for _ in range(iter - 1):
            src_masked = src_masked.std.MaskedMerge(src_b_n, image)
        src_a = insert_clip(src_a, insert=src_masked, start_frame=first_f)

    masks_ass = mask_folder.glob("*.ass")
    for mask in masks_ass:
        blank_mask = src_a.std.BlankClip()
        ass_mask = get_y(blank_mask.sub.TextFile(str(mask)))

        src_a = core.std.MaskedMerge(src_a, src_b, ass_mask)
    return src_a
Пример #26
0
def adaptive_scaling(
    src: vs.VideoNode,
    target_w: Optional[IntegerFloat] = None,
    target_h: Optional[IntegerFloat] = None,
    descale_range: List[int] = [],
    kernel: DescaleKernel = "bicubic",
    b: IntegerFloat = 1 / 3,
    c: IntegerFloat = 1 / 3,
    taps: int = 3,
    iter_max: int = 3,
    rescale: bool = True,
    use_gpu: bool = False,
    show_native_res: bool = False,
    show_mask: bool = False,
):
    """
    Descale a video within the range and upscale it back
    to the target_w and target_h.

    If target is not defined, it will use original resolution.

    Written originally by kageru, modified by N4O.

    Valid kernel:
    - bicubic
    - bilinear
    - lanczos
    - spline16
    - spline36
    - spline64

    Default to ``bicubic`` kernel.

    Parameters
    ----------
    src: :class:`VideoNode`
        The video source.
    target_w: :class:`Union[int, float]`
        Target final video width.
    target_h: :class:`Union[int, float]`
        Target final video height.
    descale_range: :class:`List[Union[int, float]]`
        List of number of descale target height.
    kernel: :class:`DescaleKernel`
        Kernel used for descaling.
    b: :class:`Union[int, float]`
        B-parameter for the kernel.
    c: :class:`Union[int, float]`
        C-parameter for the kernel.
    taps: :class:`int`
        Lanczos taps for the kernel.
    iter_max: :class:`int`
        Iteration count that will expand the mask size.
    rescale: :class:`bool`
        Rescale the video if ``True``.
    use_gpu: :class:`bool`
        Use GPU for rescaling (need nnedi3cl).
    show_native_res: :class:`bool`
        Show a text notifying what the native resolution are.
    show_mask: :class:`bool`
        Do you want to show the mask or not.
    """
    target_w = src.width if target_w is None else target_w
    target_h = src.height if target_h is None else target_h
    kernel = kernel.lower()  # type: ignore
    if kernel not in VALID_KERNELS:
        raise ValueError(f"adaptive_scaling: Invalid kernel: {kernel}")
    if not isinstance(src, vs.VideoNode):
        raise TypeError("adaptive_scaling: The source must be a clip.")
    if not isinstance(descale_range, (list, tuple)):
        raise TypeError("adaptive_scaling: descale_range must be a list.")

    descale_range = list(descale_range)
    if len(descale_range) != 2:
        raise ValueError(
            "adaptive_scaling: descale_range must have only 2 elements.")
    if descale_range[0] > descale_range[1]:
        raise ValueError(
            "adaptive_scaling: descale_range first value cannot be larger than second value"
        )

    if rescale:
        if descale_range[0] > target_h or descale_range[1] > target_h:
            raise ValueError(
                "adaptive_scaling: One of the descale_range value cannot be larger than target_h"
            )
    has_plugin_or_raise(
        ["retinex", "tcanny", "descale", "fmtc", "rgvs", "nnedi3"])
    target_w = int(round(target_w))
    target_h = int(round(target_h))

    if target_w % 2 != 0:
        raise ValueError("adaptive_scaling: target_w must be even.")
    if target_h % 2 != 0:
        raise ValueError("adaptive_scaling: target_h must be even.")

    ref = src
    ref_d = ref.format.bits_per_sample
    clip32 = fvf.Depth(ref, 32)
    y = get_y(clip32)
    global_clip_resizer = _get_resizer(b, c, taps, kernel)
    global_clip_descaler = partial(core.descale.Descale,
                                   kernel=kernel,
                                   b=b,
                                   c=c,
                                   taps=taps)

    def simple_descale(y_clip: vs.VideoNode,
                       h: int) -> Tuple[vs.VideoNode, vs.VideoNode]:
        ar = y_clip.width / y_clip.height
        down = global_clip_descaler(y_clip, get_w(h, ar), h)
        if rescale:
            up = global_clip_resizer(down, target_w, target_h)
        else:
            up = global_clip_resizer(down, y_clip.width, y_clip.height)
        diff = core.std.Expr([y, up], "x y - abs").std.PlaneStats()
        return down, diff

    descaled_clips_list = [
        simple_descale(y, h) for h in range(descale_range[0], descale_range[1])
    ]
    descaled_clips = [clip[0] for clip in descaled_clips_list]
    descaled_props = [clip[1] for clip in descaled_clips_list]

    if not rescale:
        y = global_clip_resizer(y, target_w, target_h)
        clip32 = global_clip_resizer(clip32, target_w, target_h)

    def select_scale(n: int, f: List[vs.VideoFrame],
                     descale_list: List[vs.VideoNode]):
        errors = [x.props["PlaneStatsAverage"] for x in f]
        y_deb = descale_list[errors.index(min(errors))]  # type: ignore
        dmask = core.std.Expr(
            [y, global_clip_resizer(y_deb, target_w, target_h)],
            "x y - abs 0.025 > 1 0 ?").std.Maximum()
        y_deb16 = fvf.Depth(y_deb, 16)

        if rescale:
            y_scaled = upscale_nnedi3(
                y_deb16,
                nns=4,
                correct_shift=True,
                width=target_w,
                height=target_h,
                use_gpu=use_gpu  # type: ignore
            ).fmtc.bitdepth(bits=32)
        else:
            y_scaled = global_clip_resizer(y_deb16, target_w,
                                           target_h).fmtc.bitdepth(bits=32)
        dmask = global_clip_resizer(dmask, target_w, target_h)
        if show_native_res and not show_mask:
            y_scaled = y_scaled.text.Text(
                f"Native resolution for this frame: {y_deb.height}")
        return core.std.ClipToProp(y_scaled, dmask)

    y_deb = core.std.FrameEval(y,
                               partial(select_scale,
                                       descale_list=descaled_clips),
                               prop_src=descaled_props)
    dmask = core.std.PropToClip(y_deb)

    line = core.std.StackHorizontal([_square_clip(y)] * (target_w // 10))
    full = core.std.StackVertical([line] * (target_h // 10))

    line_mask = global_clip_resizer(full, target_w, target_h)

    artifacts = core.misc.Hysteresis(
        global_clip_resizer(dmask, target_w, target_h, format=vs.GRAYS),
        core.std.Expr([get_y(clip32).tcanny.TCanny(sigma=3), line_mask],
                      "x y min"),
    )

    ret_raw = _retinex_edgemask(ref)
    if not rescale:
        ret_raw = global_clip_resizer(ret_raw, target_w, target_h)

    ret = ret_raw.std.Binarize(30).rgvs.RemoveGrain(3)
    mask = core.std.Expr([
        iterate(artifacts, core.std.Maximum, iter_max),
        ret.resize.Point(format=vs.GRAYS)
    ], "y x -").std.Binarize(0.4)
    mask = mask.std.Inflate().std.Convolution([1] * 9).std.Convolution([1] * 9)

    if show_mask:
        return mask

    merged = core.std.MaskedMerge(y, y_deb, mask)
    merged = core.std.ShufflePlanes([merged, clip32], [0, 1, 2], vs.YUV)
    return fvf.Depth(merged, ref_d)
Пример #27
0
import fvsfunc as fvf
import lvsfunc as lvf
import G41Fun

lyokos = []

for ep in [3, 11, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 24, 25, 26]:
    src = core.lsmas.LWLibavSource(
        f"Code.Lyoko.S01E{ep:02}.DVD.Remux.Multi-Odd_HD.mkv")[:1130]
    src = src.std.AssumeFPS(fpsnum=25)
    lyokos.append(
        fvf.Depth(
            awf.bbmod(fvf.AutoDeblock(src.vinverse.Vinverse(),
                                      adb1=0.2,
                                      adb2=0.8,
                                      adb1d=0.1,
                                      adb2d=1.4),
                      top=6,
                      bottom=4,
                      thresh=4,
                      blur=6), 16))

average = core.average.Mean(lyokos)
average = average.std.Crop(top=2, bottom=2, left=2, right=2)

lyoko = lvf.aa.upscaled_sraa(average, rfactor=2.0, rep=True)

deband = lyoko.f3kdb.Deband(range=2, grainy=16, grainc=16)

deband = awf.ReplaceFrames(deband,
                           lyoko.f3kdb.Deband(range=15, grainy=32, grainc=32),
                           "[417 425]")