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)
def inverse_scale(source: vs.VideoNode, width: int = None, height: int = 0, kernel: str = 'bilinear', taps: int = 4, b: float = 1 / 3, c: float = 1 / 3, mask_detail: bool = False, descale_mask_zones: str = '', denoise: bool = False, bm3d_sigma: float = 1, knl_strength: float = 0.4, use_gpu: bool = True) \ -> vs.VideoNode: """ Use descale to reverse the scaling on a given input clip. width, height, kernel, taps, a1, a2 are parameters for resizing. descale_mask_zones can be used to only mask certain zones to improve performance; uses rfs syntax. denoise, bm3d_sigma, knl_strength, use_gpu are parameters for denoising; denoise = False to disable use_gpu = True -> chroma will be denoised with KNLMeansCL (faster) """ if not height: raise ValueError( 'inverse_scale: you need to specify a value for the output height') only_luma = source.format.num_planes == 1 if get_depth(source) != 32: source = source.resize.Point(format=source.format.replace( bits_per_sample=32, sample_type=vs.FLOAT)) width = fallback(width, getw(height, source.width / source.height)) # if we denoise luma and chroma separately, do the chroma here while it’s still 540p if denoise and use_gpu and not only_luma: source = core.knlm.KNLMeansCL(source, a=2, h=knl_strength, d=3, device_type='gpu', device_id=0, channels='UV') planes = split(source) planes[0] = _descale_luma(planes[0], width, height, kernel, taps, b, c) if only_luma: return planes[0] planes = _descale_chroma(planes, width, height) if mask_detail: upscaled = fvf.Resize(planes[0], source.width, source.height, kernel=kernel, taps=taps, a1=b, a2=c) planes[0] = mask_descale(get_y(source), planes[0], upscaled, zones=descale_mask_zones) scaled = join(planes) return mvf.BM3D( scaled, radius1=1, sigma=[bm3d_sigma, 0] if use_gpu else bm3d_sigma) if denoise else scaled
def test_descale(clip: vs.VideoNode, height: int, kernel: str = 'bicubic', b: float = 1 / 3, c: float = 1 / 3, taps: int = 3, show_error: bool = True) -> vs.VideoNode: funcname = "test_descale" """ 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', as that will make comparison a lot easier. Some of this code was leveraged from DescaleAA, and it also uses functions available in fvsfunc. :param height: int: Target descaled height. :param kernel: str: Descale kernel - 'bicubic'(default), 'bilinear', 'lanczos', 'spline16', or 'spline36' :param b: float: B-param for bicubic kernel. (Default value = 1 / 3) :param c: float: C-param for bicubic kernel. (Default value = 1 / 3) :param taps: int: Taps param for lanczos kernel. (Default value = 43) :param show_error: bool: Show diff between the original clip and the reupscaled clip """ clip_y = get_y(clip) desc = fvf.Resize(clip_y, get_w(height), height, kernel=kernel, a1=b, a2=c, taps=taps, invks=True) upsc = fvf.Resize(desc, clip.width, clip.height, kernel=kernel, a1=b, a2=c, taps=taps) 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], planes=[0, 1, 2], colorfamily=vs.YUV) return core.text.FrameProps(merge, "PlaneStatsDiff") if show_error else merge
def generate_detail_mask(source, downscaled, kernel='bicubic', taps=4, a1=1 / 3, a2=1 / 3, threshold=0.05): upscaled = fvf.Resize(downscaled, source.width, source.height, kernel=kernel, taps=taps, a1=a1, a2=a2) mask = core.std.Expr([source, upscaled], 'x y - abs') \ .resize.Bicubic(downscaled.width, downscaled.height).std.Binarize(threshold) mask = iterate(mask, core.std.Maximum, 2) return iterate(mask, core.std.Inflate, 2)
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)
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)
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 Descale444ToTarget(clip: vs.VideoNode, descale_masked: bool = True, nnedi3_rpow2: bool = False, *, native_kernel: str = 'bicubic', native_width: int, native_height: int, target_kernel: str = 'spline36', target_width: int, target_height: int, **kwargs): y = clip.std.ShufflePlanes(planes=0, colorfamily=vs.GRAY) u = clip.std.ShufflePlanes(planes=1, colorfamily=vs.GRAY) v = clip.std.ShufflePlanes(planes=2, colorfamily=vs.GRAY) if descale_masked: y = fvf.DescaleM(y, descale_kernel=native_kernel, w=native_width, h=native_height, **kwargs) else: y = fvf.Resize(y, kernel=native_kernel, w=native_width, h=native_height, invks=True, **kwargs) if y.height < target_height and y.width < target_width and y.height / y.width == target_height / target_width and nnedi3_rpow2: y = rpow2.nnedi3_rpow2(y, correct_shift=True, kernel=target_kernel, width=target_width, height=target_height) elif (native_height, native_width) != (target_height, target_width) or not nnedi3_rpow2: y = fvf.Resize(y, w=target_width, h=target_height, kernel=target_kernel) if u.height < target_height and u.width < target_width and u.height / u.width == target_height / target_width: u = rpow2.nnedi3_rpow2(u, correct_shift=True, kernel=target_kernel, width=y.width, height=y.height) v = rpow2.nnedi3_rpow2(v, correct_shift=True, kernel=target_kernel, width=y.width, height=y.height) elif (u.height, u.width) != (target_height, target_width): u = fvf.Resize(u, w=y.width, h=y.height, kernel=target_kernel, sx=0.25) v = fvf.Resize(v, w=y.width, h=y.height, kernel=target_kernel, sx=0.25) clip = core.std.ShufflePlanes(clips=[y, u, v], planes=[0, 0, 0], colorfamily=vs.YUV) return clip
def DescaleAAMod(src: vs.VideoNode, w: Optional[int] = None, h: int = 720, thr: int = 10, kernel: str = 'bicubic', b: Union[float, Fraction] = Fraction(0), c: Union[float, Fraction] = Fraction(1, 2), taps: int = 4, expand: int = 3, inflate: int = 3, showmask: bool = False) -> vs.VideoNode: """ Mod of DescaleAA to use nnedi3_resample, which produces sharper results than nnedi3 rpow2. Original script by Frechdachs Original Summary: Downscale only lineart with an inverted kernel and interpolate it back to its original resolution with NNEDI3. Parts of higher resolution like credits are protected by a mask. Basic idea stolen from a script made by Daiz. :param src: Source clip :type src: VideoNode :param w: Downscale resolution width, defaults to 1280 :type w: int, optional :param h: Downscale resolution height, defaults to 720 :type h: int :param thr: Threshhold used in masking, defaults to 10 :type thr: int :param kernel: Downscaling kernel, defaults to 'bilinear' :type kernel: str :param b: Downscaling parameter used in fvf.Resize, defaults to 0 :type b: var :param c: Downscaling parameter used in fvf.Resize, defaults to 1/2 :type c: var :param taps: Downscaling parameter used in fvf.Resize, defaults to 4 :type taps: int :param expand: Number of times to expand the difference mask, defaults to 3 :type expand: int :param inflate: Number of times to inflate the difference mask, defaults to 3 :type inflate: int :param showmask: Return mask created, defaults to False :type showmask: bool :return: The filtered video :rtype: VideoNode """ import fvsfunc as fvf from nnedi3_resample import nnedi3_resample if kernel.lower().startswith('de'): kernel = kernel[2:] ow = src.width oh = src.height if w is None: w = get_w(h, src.width / src.height) bits = src.format.bits_per_sample sample_type = src.format.sample_type if sample_type == vs.INTEGER: maxvalue = (1 << bits) - 1 thr = thr * maxvalue // 0xFF else: maxvalue = 1 thr /= (235 - 16) # Fix lineart src_y = core.std.ShufflePlanes(src, planes=0, colorfamily=vs.GRAY) deb = fvf.Resize(src_y, w, h, kernel=kernel, a1=b, a2=c, taps=taps, invks=True) sharp = nnedi3_resample(deb, ow, oh, invks=True, invkstaps=2, kernel="bicubic", a1=0.70, a2=0, nns=4, qual=2, pscrn=4) edgemask = core.std.Prewitt(sharp, planes=0) if kernel == "bicubic" and c >= 0.7: edgemask = core.std.Maximum(edgemask, planes=0) sharp = core.resize.Point(sharp, format=src.format.id) # Restore true 1080p deb_upscale = fvf.Resize(deb, ow, oh, kernel=kernel, a1=b, a2=c, taps=taps) diffmask = core.std.Expr([src_y, deb_upscale], 'x y - abs') for _ in range(expand): diffmask = core.std.Maximum(diffmask, planes=0) for _ in range(inflate): diffmask = core.std.Inflate(diffmask, planes=0) mask = core.std.Expr([diffmask, edgemask], 'x {thr} >= 0 y ?'.format(thr=thr)) mask = mask.std.Inflate().std.Deflate() out_y = core.std.MaskedMerge(src, sharp, mask, planes=0) #scale chroma new_uv = nnedi3_resample(src, ow, oh, invks=True, invkstaps=2, kernel="gauss", a1=30, nns=4, qual=2, pscrn=4, chromak_down="gauss", chromak_down_invks=True, chromak_down_invkstaps=2, chromak_down_taps=1, chromak_down_a1=16) edgemask = core.std.Prewitt(new_uv, planes=0) edgemask_uv = core.std.Invert(edgemask, planes=[0]) # Restore true 1080p deb_upscale = fvf.Resize(src, ow, oh, kernel=kernel, a1=b, a2=c, taps=taps) diffmask = core.std.Expr([src, deb_upscale], 'x y - abs') for _ in range(expand): diffmask = core.std.Maximum(diffmask, planes=0) for _ in range(inflate): diffmask = core.std.Inflate(diffmask, planes=0) mask_uv = core.std.Expr([diffmask, edgemask_uv], 'x {thr} >= 0 y ?'.format(thr=thr)) mask_uv = mask_uv.std.Inflate().std.Deflate() out_uv = core.std.MaskedMerge(src, new_uv, mask_uv, planes=[1, 2]) out = core.std.ShufflePlanes([out_y, out_uv, out_uv], planes=[0, 1, 2], colorfamily=vs.YUV) if showmask: out = mask return out