import vapoursynth as vs def is420(clip): return clip.format.color_family == vs.YUV and clip.format.subsampling_w == 1 and clip.format.subsampling_h == 1 def is422(clip): return clip.format.color_family == vs.YUV and clip.format.subsampling_w == 1 and clip.format.subsampling_h == 0 def is444(clip): return clip.format.color_family == vs.YUV and clip.format.subsampling_w == 0 and clip.format.subsampling_h == 0 def isGray(clip): return clip.format.color_family == vs.GRAY def Blur(clip, amount=0, planes=None): lower_limit = 0 upper_limit = 1.5849625 if amount < lower_limit or amount > upper_limit: raise ValueError("Blur: 'amount' must be between {} and {}.".format(lower_limit, upper_limit)) center_weight = 1 / pow(2, amount) side_weight = (1 - center_weight) / 2 corner = int(side_weight * side_weight * 1000 + 0.5) side = int(side_weight * center_weight * 1000 + 0.5) center = int(center_weight * center_weight * 1000 + 0.5) blur_matrix = [corner, side, corner, side, center, side, corner, side, corner] return clip.std.Convolution(matrix=blur_matrix, planes=planes) # Recursive helper functions for repeated edi image doubling # When calling these functions, the f and t parameters should always be left at their default values. # They're used only to control behavior during recursion and changing them will cause a malfunction. def edi_rpow2_znedi3(clip, rfactorX, rfactorY, alignc=False, nsize=None, nns=None, qual=None, etype=None, pscrn=None, opt=None, int16_prescreener=None, int16_predictor=None, exp=None, f=True, turned=False): core = vs.core # If alignc=true, always use field=1 for doubling the width # to maintain alignment of horizontally subsampled chroma. field2 = int(f) if alignc: field1 = 1 else: field1 = field2 # Only turn right if doubling the width and the input isn't already turned dbl = clip if not turned and rfactorX > 1: dbl = dbl.std.Transpose() turned = True if rfactorX > 1: dbl = dbl.znedi3.nnedi3(field=field1, dh=True, nsize=nsize, nns=nns, qual=qual, etype=etype, pscrn=pscrn, opt=opt, int16_prescreener=int16_prescreener, int16_predictor=int16_predictor, exp=exp) # Only turn left if the height is going to be doubled or after the last iteration of doubling the width. # This avoids unnecessary turning when only the width is doubled repeatedly. if turned and (rfactorY > 1 or rfactorX == 2): dbl = dbl.std.Transpose() turned = False if rfactorY > 1: dbl = dbl.znedi3.nnedi3(field=field2, dh=True, nsize=nsize, nns=nns, qual=qual, etype=etype, pscrn=pscrn, opt=opt, int16_prescreener=int16_prescreener, int16_predictor=int16_predictor, exp=exp) if rfactorX > 1 or rfactorY > 1: return edi_rpow2_znedi3(clip=dbl, rfactorX=max(1, rfactorX // 2), rfactorY=max(1, rfactorY // 2), alignc=alignc, nsize=nsize, nns=nns, qual=qual, etype=etype, pscrn=pscrn, opt=opt, int16_prescreener=int16_prescreener, int16_predictor=int16_predictor, exp=exp, f=False, turned=turned) else: return clip def edi_rpow2_nnedi3cl(clip, rfactorX, rfactorY, alignc=False, nsize=None, nns=None, qual=None, etype=None, pscrn=None, f=True, turned=False): core = vs.core # FIXME Not using the dw parameter because it behaves differently from the Avisynth version. # If alignc=true, always use field=1 for doubling the width # to maintain alignment of horizontally subsampled chroma. field2 = int(f) if alignc: field1 = 1 else: field1 = field2 # Only turn right if doubling the width and the input isn't already turned dbl = clip if not turned and rfactorX > 1: dbl = dbl.std.Transpose() turned = True if rfactorX > 1: dbl = dbl.nnedi3cl.NNEDI3CL(field=field1, dh=True, nsize=nsize, nns=nns, qual=qual, etype=etype, pscrn=pscrn) # Only turn left if the height is going to be doubled or after the last iteration of doubling the width. # This avoids unnecessary turning when only the width is doubled repeatedly. if turned and (rfactorY > 1 or rfactorX == 2): dbl = dbl.std.Transpose() turned = False if rfactorY > 1: dbl = dbl.nnedi3cl.NNEDI3CL(field=field2, dh=True, nsize=nsize, nns=nns, qual=qual, etype=etype, pscrn=pscrn) if rfactorX > 1 or rfactorY > 1: return edi_rpow2_nnedi3cl(clip=dbl, rfactorX=max(1, rfactorX // 2), rfactorY=max(1, rfactorY // 2), alignc=alignc, nsize=nsize, nns=nns, qual=qual, etype=etype, pscrn=pscrn, f=False, turned=turned) else: return clip def edi_rpow2_eedi2(clip, rfactorX, rfactorY, alignc=False, mthresh=None, lthresh=None, vthresh=None, estr=None, dstr=None, maxd=None, map=None, nt=None, pp=None, f=True, turned=False): core = vs.core field2 = int(f) if alignc: field1 = 1 else: field1 = field2 dbl = clip if not turned and rfactorX > 1: dbl = dbl.std.Transpose() turned = True if rfactorX > 1: dbl = dbl.eedi2.EEDI2(field=field1, mthresh=mthresh, lthresh=lthresh, vthresh=vthresh, estr=estr, dstr=dstr, maxd=maxd, map=map, nt=nt, pp=pp) if turned and (rfactorY > 1 or rfactorX == 2): dbl = dbl.std.Transpose() turned = False if rfactorY > 1: dbl = dbl.eedi2.EEDI2(field=field2, mthresh=mthresh, lthresh=lthresh, vthresh=vthresh, estr=estr, dstr=dstr, maxd=maxd, map=map, nt=nt, pp=pp) if rfactorX > 1 or rfactorY > 1: return edi_rpow2_eedi2(clip=dbl, rfactorX=max(1, rfactorX // 2), rfactorY=max(1, rfactorY // 2), alignc=alignc, mthresh=mthresh, lthresh=lthresh, vthresh=vthresh, estr=estr, dstr=dstr, maxd=maxd, map=map, nt=nt, pp=pp, f=False, turned=turned) else: return clip def edi_rpow2_eedi3(clip, rfactorX, rfactorY, alignc=False, alpha=None, beta=None, gamma=None, nrad=None, mdis=None, hp=None, ucubic=None, cost3=None, vcheck=None, vthresh0=None, vthresh1=None, vthresh2=None, sclip=None, sclip_params=dict(), mclip=None, opt=None, f=True, turned=False, nnrep=False): core = vs.core field2 = int(f) if alignc: field1 = 1 else: field1 = field2 # Use a src_left or src_top shift dependent on the field when resizing # for the sclip or mclip to keep it in alignment with the eedi3 clip. # Additionally, the vertical shift for YV12 chroma is doubled, # so the chroma must be resized separately when doubling the height. if field1 == 1: rshift1 = 0.25 else: rshift1 = -0.25 if field2 == 1: rshift2 = 0.25 else: rshift2 = -0.25 dbl = clip if not turned and rfactorX > 1: dbl = dbl.std.Transpose() turned = True if rfactorX > 1: if sclip == "": sclip2 = None elif sclip == "znedi3": sclip2 = dbl.znedi3.nnedi3(field=field1, dh=True, **sclip_params) elif sclip == "nnedi3cl": sclip2 = dbl.nnedi3cl.NNEDI3CL(field=field1, dh=True, **sclip_params) elif sclip == "eedi2": sclip2 = dbl.eedi2.EEDI2(field=field1, **sclip_params) else: sclip2 = eval("core.resize." + sclip)(clip=dbl, width=dbl.width, height=dbl.height * 2, src_left=0, src_top=rshift1, **sclip_params) mclip1 = mclip if mclip1 is not None: mclip1 = mclip1.std.Transpose() dbl = dbl.eedi3m.EEDI3(field=field1, dh=True, alpha=alpha, beta=beta, gamma=gamma, nrad=nrad, mdis=mdis, hp=hp, ucubic=ucubic, cost3=cost3, vcheck=vcheck, vthresh0=vthresh0, vthresh1=vthresh1, vthresh2=vthresh2, sclip=sclip2, mclip=mclip1, opt=opt) if nnrep and sclip == "znedi3": dbl = core.rgvs.Repair(clip=dbl, repairclip=sclip2, mode=9) if mclip is not None: mclip = core.resize.Spline36(clip=mclip, width=mclip.width * 2, height=mclip.height, src_left=rshift1, src_top=0) mclip = core.std.Binarize(clip=mclip) if turned and (rfactorY > 1 or rfactorX == 2): dbl = dbl.std.Transpose() turned = False if rfactorY > 1: if sclip == "": sclip2 = None elif sclip == "znedi3": sclip2 = dbl.znedi3.nnedi3(field=field2, dh=True, **sclip_params) elif sclip == "nnedi3cl": sclip2 = dbl.nnedi3cl.NNEDI3CL(field=field2, dh=True, **sclip_params) elif sclip == "eedi2": sclip2 = dbl.eedi2.EEDI2(field=field2, **sclip_params) else: sclip2 = eval("core.resize." + sclip)(clip=dbl, width=dbl.width, height=dbl.height * 2, src_left=0, src_top=rshift2, **sclip_params) dbl = dbl.eedi3m.EEDI3(field=field2, dh=True, alpha=alpha, beta=beta, gamma=gamma, nrad=nrad, mdis=mdis, hp=hp, ucubic=ucubic, cost3=cost3, vcheck=vcheck, vthresh0=vthresh0, vthresh1=vthresh1, vthresh2=vthresh2, sclip=sclip2, mclip=mclip, opt=opt) if nnrep and sclip == "znedi3": dbl = core.rgvs.Repair(clip=dbl, repairclip=sclip2, mode=9) if mclip is not None: mclip = core.resize.Spline36(clip=mclip, width=mclip.width, height=mclip.height * 2, src_left=0, src_top=rshift2) mclip = core.std.Binarize(clip=mclip) if rfactorX > 1 or rfactorY > 1: return edi_rpow2_eedi3(clip=dbl, rfactorX=max(1, rfactorX // 2), rfactorY=max(1, rfactorY // 2), alignc=alignc, alpha=alpha, beta=beta, gamma=gamma, nrad=nrad, mdis=mdis, hp=hp, ucubic=ucubic, cost3=cost3, vcheck=vcheck, vthresh0=vthresh0, vthresh1=vthresh1, vthresh2=vthresh2, sclip=sclip, sclip_params=sclip_params, mclip=mclip, opt=opt, f=False, turned=turned, nnrep=nnrep) else: return clip def edi_rpow2(clip, rfactorX=2, rfactorY=None, edi="znedi3", cshift="", fwidth=None, fheight=None, cplace="MPEG2", planes=None, bordfix=None, YV12cfix=True, nsize=0, nns=3, qual=None, etype=None, pscrn=None, opt=None, int16_prescreener=None, int16_predictor=None, exp=None, alpha=None, beta=None, gamma=None, nrad=None, mdis=None, hp=None, ucubic=None, cost3=None, vcheck=None, vthresh0=None, vthresh1=None, vthresh2=None, sclip="", sclip_params=dict(), mclip=None, mthresh=None, lthresh=None, vthresh=None, estr=None, dstr=None, maxd=None, map=None, nt=None, pp=None, nnrep=False): core = vs.core def Default(param, value): if param is None: return value else: return param if clip.width == 0 or clip.height == 0: raise ValueError("edi_rpow2: 'clip' must have constant dimensions.") if clip.format is None: raise ValueError("edi_rpow2: 'clip' must have constant format.") iw = clip.width ih = clip.height rfactorY = Default(rfactorY, rfactorX) fwidth = Default(fwidth, iw * rfactorX) fheight = Default(fheight, ih * rfactorY) planes = Default(planes, list(range(clip.format.num_planes))) bordfix = Default(bordfix, edi != "eedi3") if rfactorX <= 0 or (rfactorX & (rfactorX - 1) != 0): raise ValueError("edi_rpow2: 'rfactorX' must be a power of 2.") if rfactorY <= 0 or (rfactorY & (rfactorY - 1) != 0): raise ValueError("edi_rpow2: 'rfactorY' must be a power of 2.") acceptable_edi = ["znedi3", "nnedi3cl", "eedi3", "eedi2"] if edi not in acceptable_edi: raise ValueError("edi_rpow2: 'edi' must be one of {}.".format(acceptable_edi)) acceptable_cshift = ["", "Bilinear", "Bicubic", "Point", "Lanczos", "Spline16", "Spline36"] if cshift not in acceptable_cshift: raise ValueError("edi_rpow2: 'cshift' must be one of {}.".format(acceptable_cshift)) acceptable_cplace = ["MPEG1", "MPEG2"] if cplace not in acceptable_cplace: raise ValueError("edi_rpow2: 'cplace' must be one of {}.".format(acceptable_cplace)) acceptable_sclip = acceptable_cshift + ["znedi3", "nnedi3cl", "eedi2"] if sclip not in acceptable_sclip: raise ValueError("edi_rpow2: 'sclip' must be one of {}.".format(acceptable_sclip)) if not isinstance(sclip_params, dict): raise ValueError("edi_rpow2: 'sclip_params' must be a dict.") # Override the fwidth and fheight parameters if not correcting the center shift if cshift == "": fwidth = iw * rfactorX fheight = ih * rfactorY csp = clip.format.id hssc12 = clip.format.subsampling_w vssc12 = clip.format.subsampling_h alignc = hssc12 if hssc12 and fwidth % 2: raise ValueError("edi_rpow2: 'fwidth' of {} must be a multiple of 2.".format(clip.format.name)) if vssc12 and fheight % 2: raise ValueError("edi_rpow2: 'fheight' of {} must be a multiple of 2.".format(clip.format.name)) # Input and output chroma width and height determined by subsampling iw_c = iw >> hssc12 ih_c = ih >> vssc12 fwidth_c = fwidth >> hssc12 fheight_c = fheight >> vssc12 # Center shift correction values if rfactorX == 1: cshiftH = 0 elif hssc12: cshiftH = -0.5 * (rfactorX - 1) else: cshiftH = -0.5 if rfactorY == 1: cshiftV = 0 else: cshiftV = -0.5 if hssc12: cshiftH_c = cshiftH / 2.0 else: cshiftH_c = cshiftH if vssc12 and rfactorY > 1: cshiftV_c = cshiftV / 2.0 - 0.25 else: cshiftV_c = cshiftV # Add the MPEG1 or MPEG2 chroma shift correction to the cshiftH_c value if hssc12: MPEG1shift = -0.25 * (rfactorX - 1) MPEG2shift = 0.25 * (1.0 - iw * rfactorX / fwidth) if cplace == "MPEG1": cshiftH_c = cshiftH_c + MPEG1shift elif cplace == "MPEG2" and cshift != "Point": cshiftH_c = cshiftH_c + MPEG2shift # Pad at least four rows if bordfix=true. if bordfix and rfactorX > 1: padR = padL = 4 else: padR = padL = 0 if bordfix and rfactorY > 1: padT = padB = 4 else: padT = padB = 0 padL_c = padL >> hssc12 padR_c = padR >> hssc12 padT_c = padT >> vssc12 padB_c = padB >> vssc12 einput = clip emclip = mclip if bordfix: einput = core.resize.Point(clip=einput, width=iw + padL + padR, height=ih + padT + padB, src_left=-padL, src_top=-padT, src_width=iw + padL + padR, src_height=ih + padT + padB) if emclip is not None: emclip = core.resize.Point(clip=emclip, width=iw + padL + padR, height=ih + padT + padB, src_left=-padL, src_top=-padT, src_width=iw + padL + padR, src_height=ih + padT + padB) padL = [padL, padL_c, padL_c] padR = [padR, padR_c, padR_c] padT = [padT, padT_c, padT_c] padB = [padB, padB_c, padB_c] fwidth = [fwidth, fwidth_c, fwidth_c] fheight = [fheight, fheight_c, fheight_c] cshiftH = [cshiftH, cshiftH_c, cshiftH_c] cshiftV = [cshiftV, cshiftV_c, cshiftV_c] pow2 = [] blank = core.std.BlankClip(clip=clip, width=fwidth[0], height=fheight[0]) for plane in range(einput.format.num_planes): if not plane in planes: pow2.append(core.std.ShufflePlanes(clips=blank, planes=plane, colorfamily=vs.GRAY)) continue p = core.std.ShufflePlanes(clips=einput, planes=plane, colorfamily=vs.GRAY) # Image enlargement if edi == "znedi3": p = edi_rpow2_znedi3(clip=p, rfactorX=rfactorX, rfactorY=rfactorY, alignc=alignc, nsize=nsize, nns=nns, qual=qual, etype=etype, pscrn=pscrn, opt=opt, int16_prescreener=int16_prescreener, int16_predictor=int16_predictor, exp=exp) elif edi == "nnedi3cl": p = edi_rpow2_nnedi3cl(clip=p, rfactorX=rfactorX, rfactorY=rfactorY, alignc=alignc, nsize=nsize, nns=nns, qual=qual, etype=etype, pscrn=pscrn) elif edi == "eedi3": emclip_p = emclip if emclip_p is not None: emclip_p = core.std.ShufflePlanes(clips=emclip_p, planes=plane, colorfamily=vs.GRAY) p = edi_rpow2_eedi3(clip=p, rfactorX=rfactorX, rfactorY=rfactorY, alignc=alignc, alpha=alpha, beta=beta, gamma=gamma, nrad=nrad, mdis=mdis, hp=hp, ucubic=ucubic, cost3=cost3, vcheck=vcheck, vthresh0=vthresh0, vthresh1=vthresh1, vthresh2=vthresh2, sclip=sclip, sclip_params=sclip_params, mclip=emclip_p, opt=opt, nnrep=nnrep) elif edi == "eedi2": p = edi_rpow2_eedi2(clip=p, rfactorX=rfactorX, rfactorY=rfactorY, alignc=alignc, mthresh=mthresh, lthresh=lthresh, vthresh=vthresh, estr=estr, dstr=dstr, maxd=maxd, map=map, nt=nt, pp=pp) # Crop off the padding p = core.std.Crop(clip=p, left=padL[plane] * rfactorX, top=padT[plane] * rfactorY, right=padR[plane] * rfactorX, bottom=padB[plane] * rfactorY) # Center shift correction if cshift != "": p = eval("core.resize." + cshift)(clip=p, width=fwidth[plane], height=fheight[plane], src_left=cshiftH[plane], src_top=cshiftV[plane]) pow2.append(p) # Even if the center shift isn't corrected, # doubling the height of YV12 causes a vertical chroma shift that needs to be corrected if cshift == "" and is420(clip) and YV12cfix and rfactorY > 1: for plane in range(1, einput.format.num_planes): if plane in planes: pow2[plane] = core.resize.Spline36(clip=pow2[plane], width=fwidth[plane], height=fheight[plane], src_left=0, src_top=-0.25) if len(pow2) == 1: return pow2[0] else: return core.std.ShufflePlanes(clips=pow2, planes=[0, 0, 0], colorfamily=clip.format.color_family) def xaa(clip, ow=None, oh=None, ss=None, ssw=None, ssh=None, mode="sr SangNom", uscl=None, dscl="Spline36", csharp=None, cstr=None, mask=None, mtype=None, mthr=None, chroma=None, cplace="MPEG2", nns=1, eedimthr=0.0, eediA=0.2, eediB=0.25, eediG=20.0): core = vs.core def Default(param, value): if param is None: return value else: return param # Avisynth's Round function doesn't do quite the same thing as Python's round function. def AvisynthRound(value): if value < 0: return -int(-value + 0.5) else: return int(value + 0.5) # Resize the luma and the chroma separately in order to use different subpixel shifts. def ResizeSeparately(clip, width, height, src_left, src_top, kernel="Spline36", cplace="MPEG2"): core = vs.core if not isinstance(clip, vs.VideoNode): raise ValueError("ResizeSeparately: 'clip' must be a clip.") if clip.format is None: raise ValueError("ResizeSeparately: 'clip' must have constant format.") if clip.width == 0 or clip.height == 0: raise ValueError("ResizeSeparately: 'clip' must have constant dimensions.") ratio = 1 << clip.format.subsampling_w if width % ratio != 0: raise ValueError("ResizeSeparately: 'width' must be a multiple of {}. Instead it is {}.".format(ratio, width)) ratio = 1 << clip.format.subsampling_h if height % ratio != 0: raise ValueError("ResizeSeparately: 'height' must be a multiple of {}. Instead it is {}.".format(ratio, height)) if not isinstance(src_left, list) or len(src_left) != 2: raise ValueError("ResizeSeparately: 'src_left' must be a list of two numbers.") if not isinstance(src_top, list) or len(src_top) != 2: raise ValueError("ResizeSeparately: 'src_top' must be a list of two numbers.") if src_left[0] == src_left[1] and src_top[0] == src_top[1]: raise ValueError("ResizeSeparately: 'src_left'/'src_top' are the same for the luma and chroma. If the luma and chroma can be resized with the same subpixel shifts, use the resizer filters directly.") if isGray(clip): raise ValueError("ResizeSeparately: 'clip' is GRAY. Use the resizer filters directly.") if kernel not in ["Bilinear", "Bicubic", "Lanczos", "Spline16", "Spline36"]: raise ValueError("ResizeSeparately: 'kernel' must be 'Bilinear', 'Bicubic', 'Lanczos', 'Spline16', 'Spline36'.") if cplace not in ["MPEG1", "MPEG2"]: raise ValueError("ResizeSeparately: 'cplace' must be 'MPEG1' or 'MPEG2'.") width = [width, width >> clip.format.subsampling_w, width >> clip.format.subsampling_w] height = [height, height >> clip.format.subsampling_h, height >> clip.format.subsampling_h] src_left[1] = src_left[1] / (1 << clip.format.subsampling_w) if clip.format.subsampling_w == 1 and cplace == "MPEG2": MPEG2shift = 0.25 * (1.0 - clip.width / width[0]) src_left[1] += MPEG2shift src_top[1] = src_top[1] / (1 << clip.format.subsampling_h) src_left.append(src_left[1]) src_top.append(src_top[1]) planes = [None, None, None] for plane in range(3): p = core.std.ShufflePlanes(clips=clip, planes=plane, colorfamily=vs.GRAY) p = eval("core.resize." + kernel)(clip=p, width=width[plane], height=height[plane], src_left=src_left[plane], src_top=src_top[plane]) planes[plane] = p return core.std.ShufflePlanes(clips=planes, planes=[0, 0, 0], colorfamily=clip.format.color_family) if not isinstance(clip, vs.VideoNode): raise ValueError("xaa: 'clip' must be a clip.") iw = clip.width ih = clip.height if clip.width == 0 or clip.height == 0: raise RuntimeError("xaa: 'clip' must have constant dimensions.") ow = Default(ow, iw) oh = Default(oh, ih) if mode[0:2] in ["dr", "di"]: ss = Default(ss, 1.0) elif mode == "maa2": mode = "sr SangNom" mtype = Default(mtype, "Sobel") mthr = Default(mthr, 7.0) elif mode == "daa": mode = "drv znedi3" ss = Default(ss, 1.0) csharp = Default(csharp, 1) mask = Default(mask, 0) chroma = Default(chroma, 1) elif mode in ["Mrdaa", "MrdaaLame"]: mode = "null" uscl = Default(uscl, "znedi3") csharp = Default(csharp, 2) cstr = Default(cstr, 1.0) mask = Default(mask, 0) chroma = Default(chroma, 1) elif mode == "santiag": mode = "di2 znedi3" ss = Default(ss, 1.0) mask = Default(mask, 0) chroma = Default(chroma, 1) ss = Default(ss, 2.0) ssw = Default(ssw, ss) ssh = Default(ssh, ss) uscl = Default(uscl, "Spline36") csharp = Default(csharp, 0) cstr = Default(cstr, -1.0) mask = Default(mask, 1) mtype = Default(mtype, "TEdgeMask") mthr = Default(mthr, 8.0) chroma = Default(chroma, 0) if ow == 0 or oh == 0: raise ValueError("xaa: 'ow' and 'oh' cannot be 0.") if ss <= 0 or ssw <= 0 or ssh <= 0: raise ValueError("xaa: 'ss', 'ssw', and 'ssh' must be greater than 0.") acceptable_dscl = ["Bilinear", "Bicubic", "Point", "Lanczos", "Spline16", "Spline36"] if dscl not in acceptable_dscl: raise ValueError("xaa: 'dscl' must be one of {}.".format(acceptable_dscl)) acceptable_uscl = acceptable_dscl + ["znedi3", "nnedi3cl", "eedi3", "eedi2", "eedi3 nnedi3cl", "eedi3 znedi3", "eedi3 eedi2"] if uscl not in acceptable_uscl: raise ValueError("xaa: 'uscl' must be one of {}.".format(acceptable_uscl)) if mask > 2: raise ValueError("xaa: 'mask' must be 0, 1, 2, or a negative number.") if csharp not in [0, 1, 2]: raise ValueError("xaa: 'csharp' must be 0, 1, or 2.") if cplace not in ["MPEG1", "MPEG2"]: raise ValueError("xaa: 'cplace' must be 'MPEG1' or 'MPEG2'.") if clip.format is None: raise RuntimeError("xaa: 'clip' must have constant format.") ##### Set variable values based on parameter settings ##### # Translate the cplace parameter into values understood by the VapourSynth resizers. if cplace == "MPEG1": chromaloc = "center" elif cplace == "MPEG2": chromaloc = "left" if clip.format.sample_type != vs.INTEGER or clip.format.bits_per_sample > 16: raise RuntimeError("xaa: 'clip' must have 8..16 bit integer format.") if not (isGray(clip) or is420(clip) or is422(clip) or is444(clip)): raise RuntimeError("xaa: 'clip' must be GRAY, 420, 422, or 444.") hssc12 = clip.format.subsampling_w > 0 vssc12 = clip.format.subsampling_h > 0 if ow > 0 and hssc12 and ow % 2: raise ValueError("xaa: output width of {} must be a multiple of 2.".format(clip.format.name)) if oh > 0 and vssc12 and oh % 2: raise ValueError("xaa: output height of {} must be a multiple of 2.".format(clip.format.name)) if isGray(clip): chroma = 0 if chroma not in [0, 1, 2]: raise ValueError("xaa: 'chroma' must be 0, 1, or 2.") if chroma == 0: planes = [0] else: planes = [0, 1, 2] # If ssw or ssh is a float, use it as a multiplier to determine the supersampled resolution if isinstance(ssw, float): if ssw == 1.0: ssw = iw else: ssw = int(AvisynthRound(iw * ssw / 4.0) * 4) elif not isinstance(ssw, int): raise TypeError("xaa: 'ssw' must be a float or int.") if isinstance(ssh, float): if ssh == 1.0: ssh = ih else: ssh = int(AvisynthRound(ih * ssh / 4.0) * 4) elif not isinstance(ssh, int): raise TypeError("xaa: 'ssh' must be a float or int.") # Output at the supersampled resolution if the output width or height is negative if ow < 0 or oh < 0: ow = ssw oh = ssh # Make a copy of the mode parameter to use in error messages. original_mode = mode if mode == "null": aa_mode = "null" elif mode[0:2] in ["sr", "dr", "di"]: aa_mode = mode[0:2] mode = mode[2:] else: raise ValueError("xaa: invalid mode string '{}': the antialiasing mode must be 'sr', 'dr', or 'di'.".format(original_mode)) # Remove spacers from the mode string mode_separators = [" ", "_", "+", "-"] if mode[0] in mode_separators: mode = mode[1:] aa_h = True aa_v = True # Get the aa direction from the mode string if mode[0] == "b": mode = mode[1:] elif mode[0] == "h": aa_v = False mode = mode[1:] elif mode[0] == "v": aa_h = False mode = mode[1:] if mode[0] in mode_separators: mode = mode[1:] digits = "0123456789" # Get the number of aa passes from the mode string if mode[0] in digits: aa_pass = int(mode[0]) mode = mode[1:] if aa_pass == 0: raise ValueError("xaa: the number of aa passes must be greater than 0.") else: aa_pass = 1 if mode[0] in mode_separators: mode = mode[1:] # Get the aa type from the mode string if mode == "null": aa_type = "null" elif mode.startswith("SangNom"): aa_type = "SangNom" elif mode.startswith("nnedi3cl"): aa_type = "nnedi3cl" elif mode.startswith("znedi3"): aa_type = "znedi3" elif mode.startswith("eedi3"): aa_type = "eedi3" elif mode.startswith("eedi2"): aa_type = "eedi2" else: raise ValueError("xaa: invalid mode string '{}': the antialiasing type must be 'SangNom', 'nnedi3cl', 'znedi3', 'eedi3', or 'eedi2'.".format(original_mode)) # Get the aa sclip from the mode string if aa_type == "eedi3" and len(mode) > 5: mode = mode[5:] if mode[0] in mode_separators: mode = mode[1:] aa_sclip = mode acceptable_aa_sclip = acceptable_dscl + ["SangNom", "znedi3", "nnedi3cl", "eedi2"] if aa_sclip not in acceptable_aa_sclip: raise ValueError("xaa: invalid mode string '{}': the antialiasing sclip type must be one of {}.".format(original_mode, acceptable_aa_sclip)) else: aa_sclip = "" # Get the sclip setting for eedi3 upscaling from the uscl string if uscl.startswith("eedi3") and len(uscl) > 5: rs_sclip = uscl[5:] uscl = "eedi3" if rs_sclip[0] in mode_separators: rs_sclip = rs_sclip[1:] else: rs_sclip = "" # Define these before the resize sections so they can be used as conditionals # TODO this comment might have been necessary only in Avisynth if aa_mode == "di" and csharp != 1 and not aa_v: aa_ow = ssw * 2 else: aa_ow = ssw if aa_mode == "di" and csharp != 1 and aa_v: aa_oh = ssh * 2 else: aa_oh = ssh if ssw > iw or ssh > ih: rs1_type = uscl else: rs1_type = dscl rs1_isedi = "edi" in rs1_type if ow > aa_ow or oh > aa_oh: rsaa_type = uscl else: rsaa_type = dscl rsaa_isedi = "edi" in rsaa_type if ow > iw or oh > ih: rs2_type = uscl else: rs2_type = dscl rs2_isedi = "edi" in rs2_type # Override the csharp setting if cstr=0, or if csharp=1 and mode="null" since it won't have any effect in that case. if cstr == 0 or (csharp == 1 and aa_mode == "null"): csharp = 0 # Override the cstr setting if csharp=0 since it won't be used. if csharp == 0: cstr = 0 # Get TEdgeMask's type parameter from the mtype string if present, or otherwise default to type 4 if mtype.startswith("TEdgeMask") and len(mtype) > len("TEdgeMask") + 1: raise ValueError("xaa: TEdgeMask type must be a single character.") if mtype.startswith("TEdgeMask") and len(mtype) > len("TEdgeMask"): temtype = int(mtype[len("TEdgeMask")]) mtype = "TEdgeMask" if temtype < 1 or temtype > 5: raise ValueError("xaa: TEdgeMask type must be between 1 and 5.") else: temtype = 4 acceptable_mtype = ["TEdgeMask", "TCanny", "Prewitt", "Sobel"] if mtype not in acceptable_mtype: raise ValueError("xaa: 'mtype' must be one of {}.".format(acceptable_mtype)) if mthr <= 0.0: raise ValueError("xaa: 'mthr' must be greater than 0.") # Round the mthr values for Prewitt and Sobel mask types if mtype not in ["TEdgeMask", "TCanny"]: mthr = AvisynthRound(mthr) eedimthr = AvisynthRound(eedimthr) # Remove frame properties that could confuse nnedi3 etc or the resizer. clip = core.std.RemoveFrameProps(clip=clip, props=["_FieldBased", "_Field"]) ##### Scale the input clip to the supersampled resolution ##### # rs1_type and rs1_isedi are defined earlier if ssw > iw * 6 - 4: rs1_rfacX = 8 elif ssw > iw * 3 - 4: rs1_rfacX = 4 elif ssw > iw: rs1_rfacX = 2 else: rs1_rfacX = 1 if ssh > ih * 6 - 4: rs1_rfacY = 8 elif ssh > ih * 3 - 4: rs1_rfacY = 4 elif ssh > ih: rs1_rfacY = 2 else: rs1_rfacY = 1 if ssw >= iw * rs1_rfacX or ssh >= ih * rs1_rfacY: rs1_cshift = "Spline36" else: rs1_cshift = dscl # To avoid resizing twice, don't correct the edi_rpow2 center shift until # resizing to the output resolution if no other processing will happen until then. # This isn't done all the time because the center shift messes with antialiasing. delay_cshift = aa_mode == "null" and rs1_isedi and ssw == iw * rs1_rfacX and ssh == ih * rs1_rfacY if delay_cshift and rs1_isedi and rs1_rfacX > 1: if not hssc12 or chroma == 0: rs1_hshift = -0.5 else: rs1_hshift = -0.5 * (rs1_rfacX - 1) else: rs1_hshift = 0 if delay_cshift and rs1_isedi and rs1_rfacY > 1: rs1_vshift = -0.5 else: rs1_vshift = 0 if delay_cshift and rs1_isedi and rs1_rfacX > 1 and cplace == "MPEG1" and hssc12 and chroma: rs1_hshift_c = -0.5 * (rs1_rfacX - 1) + rs1_hshift else: rs1_hshift_c = rs1_hshift if delay_cshift and rs1_isedi and rs1_rfacY > 1 and vssc12 and chroma: rs1_vshift_c = rs1_vshift * 2.0 else: rs1_vshift_c = rs1_vshift # both as in both horizontal (3x1) and vertical (1x3) mt_expand_mode_both = [0, 1, 0, 1, 1, 0, 1, 0] # Edge mask for eedi3's mclip parameter to use for the rs1 and rs2 resizes if uscl == "eedi3" and eedimthr > 0: if mtype == "TEdgeMask": rs12_mclip = clip.tedgemask.TEdgeMask(threshold=eedimthr, type=temtype, link=0, planes=planes) elif mtype == "TCanny": rs12_mclip = clip.tcanny.TCanny(t_h=eedimthr, t_l=eedimthr, planes=planes, op=0) rs12_mclip = rs12_mclip.std.Maximum(planes=planes, coordinates=mt_expand_mode_both).std.Inflate(planes=planes) rs12_mclip = rs12_mclip.std.Minimum(planes=planes, coordinates=mt_expand_mode_both) elif mtype == "Prewitt": # Add 1 to obtain the behaviour of mt_edge's thX1/thX2 parameters: # mt_edge does pixel <= mthr ? 0 : 255 # But we use std.Binarize which does pixel < mthr ? 0 : 255 rs12_mclip = clip.std.Prewitt(planes=planes).std.Binarize(threshold=(eedimthr + 1) << (clip.format.bits_per_sample - 8), planes=planes) elif mtype == "Sobel": rs12_mclip = clip.std.Sobel(planes=planes).std.Binarize(threshold=(eedimthr + 1) << (clip.format.bits_per_sample - 8), planes=planes) rs12_mclip = rs12_mclip.std.Inflate(planes=planes) else: rs12_mclip = None if chroma == 0: clip_y8 = core.std.ShufflePlanes(clips=clip, planes=0, colorfamily=vs.GRAY) else: clip_y8 = clip if ssw == iw and ssh == ih: rs1 = clip_y8 elif rs1_isedi: edi_params = dict(clip=clip_y8, rfactorX=rs1_rfacX, rfactorY=rs1_rfacY, edi=rs1_type, cplace=cplace, alpha=eediA, beta=eediB, gamma=eediG, sclip=rs_sclip, mclip=rs12_mclip) if delay_cshift: edi_params.update(dict(YV12cfix=False)) else: edi_params.update(dict(cshift=rs1_cshift, fwidth=ssw, fheight=ssh)) rs1 = edi_rpow2(**edi_params) else: rs1 = eval("core.resize." + rs1_type)(clip=clip_y8, width=ssw, height=ssh, chromaloc_s=chromaloc, chromaloc_in_s=chromaloc) ##### Apply antialiasing to the supersampled clip ##### # To avoid resizing twice, don't downscale the aaclip after di antialiasing unless needed for csharp=1. aa_delayresize_h = aa_mode == "di" and csharp != 1 and not aa_v aa_delayresize_v = aa_mode == "di" and csharp != 1 and aa_v if aa_delayresize_h: aa_hshift = -0.5 else: aa_hshift = 0 if aa_delayresize_v: aa_vshift = -0.5 else: aa_vshift = 0 if aa_delayresize_h and hssc12 and chroma and cplace == "MPEG1": aa_hshift_c = -0.5 + aa_hshift else: aa_hshift_c = aa_hshift if aa_delayresize_v and vssc12 and chroma: aa_vshift_c = aa_vshift * 2.0 else: aa_vshift_c = aa_vshift # Pad the frame with rows of duplicate pixels to avoid errors from # mod1 resolutions and edge distortion caused by deinterlacing. rs1_ismod4 = ssw % 4 == 0 and ssh % 4 == 0 rs1_ismod8 = ssw % 8 == 0 and ssh % 8 == 0 rs1_pad8 = (not rs1_ismod8 and aa_type == "eedi2" and hssc12 and not is420(clip) and chroma) or \ (not rs1_ismod8 and aa_type == "eedi3" and hssc12 and not is420(clip) and chroma and aa_sclip not in ["", "SangNom", "znedi3", "nnedi3cl"]) # Don't add padding when using di eedi3 antialiasing unless it's # needed for mod4/mod8 compatibility because it doesn't distort edges. rs1_addpad = not (aa_mode == "null" or (aa_mode == "di" and aa_type == "eedi3" and rs1_ismod4 and not rs1_pad8)) if rs1_pad8: rs1_padR = 8 - ssw % 8 rs1_padT = 8 - ssh % 8 else: rs1_padR = 4 - ssw % 4 rs1_padT = 4 - ssh % 4 if rs1_padR < 4: rs1_padR += 4 if rs1_padT < 4: rs1_padT += 4 if rs1_pad8 and (ssw + rs1_padR + 4) % 8 == 0: rs1_padL = 4 elif rs1_pad8: rs1_padL = 8 else: rs1_padL = 4 if rs1_pad8 and (ssh + rs1_padT + 4) % 8 == 0: rs1_padB = 4 elif rs1_pad8: rs1_padB = 8 else: rs1_padB = 4 ssw_pad = ssw ssh_pad = ssh if rs1_addpad: ssw_pad += rs1_padR + rs1_padL ssh_pad += rs1_padT + rs1_padB if hssc12: ssw_pad_c = ssw_pad // 2 else: ssw_pad_c = ssw_pad if vssc12: ssh_pad_c = ssh_pad // 2 else: ssh_pad_c = ssh_pad # Edge mask for eedi3's mclip parameter to use for antialiasing aa_mclip = None aa_mclipv = None aa_mcliph = None if eedimthr > 0: if mtype == "TEdgeMask": aa_mclip = rs1.tedgemask.TEdgeMask(threshold=eedimthr, type=temtype, link=0, planes=planes) elif mtype == "TCanny": aa_mclip = rs1.tcanny.TCanny(t_h=eedimthr, t_l=eedimthr, planes=planes, op=0) aa_mclip = aa_mclip.std.Maximum(planes=planes, coordinates=mt_expand_mode_both).std.Inflate(planes=planes) aa_mclip = aa_mclip.std.Minimum(planes=planes, coordinates=mt_expand_mode_both) elif mtype == "Prewitt": aa_mclip = rs1.std.Prewitt(planes=planes).std.Binarize(threshold=(eedimthr + 1) << (rs1.format.bits_per_sample - 8), planes=planes) elif mtype == "Sobel": aa_mclip = rs1.std.Sobel(planes=planes).std.Binarize(threshold=(eedimthr + 1) << (rs1.format.bits_per_sample - 8), planes=planes) aa_mclip = aa_mclip.std.Inflate(planes=planes) if rs1_addpad: aa_mclip_pad = aa_mclip.resize.Point(width=ssw + rs1_padL + rs1_padR, height=ssh + rs1_padT + rs1_padB, src_left=-rs1_padL, src_top=-rs1_padT, src_width=ssw + rs1_padL + rs1_padR, src_height=ssh + rs1_padT + rs1_padB) else: aa_mclip_pad = aa_mclip aa_mclipv = aa_mclip_pad aa_mcliph = aa_mclipv.std.Transpose() if rs1_addpad: rs1_pad = rs1.resize.Point(width=ssw + rs1_padL + rs1_padR, height=ssh + rs1_padT + rs1_padB, src_left=-rs1_padL, src_top=-rs1_padT, src_width=ssw + rs1_padL + rs1_padR, src_height=ssh + rs1_padT + rs1_padB) else: rs1_pad = rs1 # Perform antialiasing if aa_mode == "null": aaclip = rs1 elif (aa_type != "SangNom" and not is422(clip)) or (aa_type != "SangNom" and is420(clip) and chroma): # When using SangNom2, it's faster to split the planes into Y8 clips and process them separately # TODO Is it? Why? aaclip = rs1_pad if aa_h: aaclip = aaclip.std.Transpose() if aa_mode == "sr": aaclip = xaa_sr(aaclip, aa_type, aa_pass, cplace, 48, nns, eediA, eediB, eediG, aa_sclip, aa_mcliph) elif aa_mode == "dr": aaclip = xaa_dr(aaclip, aa_type, aa_pass, cplace, 48, nns, eediA, eediB, eediG, aa_sclip, aa_mcliph) elif aa_mode == "di": aaclip = xaa_di(aaclip, aa_type, aa_pass, cplace, 48, nns, eediA, eediB, eediG, aa_sclip, aa_mcliph) if not aa_delayresize_h: if vssc12 and chroma: aaclip = ResizeSeparately(clip=aaclip, width=ssh_pad, height=ssw_pad, src_left=[0, 0], src_top=[-0.5, -1.0], kernel=dscl, cplace=cplace) else: aaclip = eval("core.resize." + dscl)(clip=aaclip, width=ssh_pad, height=ssw_pad, src_left=0, src_top=-0.5, chromaloc_s=chromaloc, chromaloc_in_s=chromaloc) aaclip = aaclip.std.Transpose() if aa_v: if aa_mode == "sr": aaclip = xaa_sr(aaclip, aa_type, aa_pass, cplace, 48, nns, eediA, eediB, eediG, aa_sclip, aa_mclipv) elif aa_mode == "dr": aaclip = xaa_dr(aaclip, aa_type, aa_pass, cplace, 48, nns, eediA, eediB, eediG, aa_sclip, aa_mclipv) elif aa_mode == "di": aaclip = xaa_di(aaclip, aa_type, aa_pass, cplace, 48, nns, eediA, eediB, eediG, aa_sclip, aa_mclipv) if not aa_delayresize_v: if vssc12 and chroma: aaclip = ResizeSeparately(clip=aaclip, width=ssw_pad, height=ssh_pad, src_left=[0, 0], src_top=[-0.5, -1.0], kernel=dscl, cplace=cplace) else: aaclip = eval("core.resize." + dscl)(clip=aaclip, width=ssw_pad, height=ssh_pad, src_left=0, src_top=-0.5, chromaloc_s=chromaloc, chromaloc_in_s=chromaloc) else: snaa_plane = [48, 0, 0] ssw_pad_plane = [ssw_pad, ssw_pad_c, ssw_pad_c] ssh_pad_plane = [ssh_pad, ssh_pad_c, ssh_pad_c] aaclip_plane = [None, None, None] for plane in range(rs1_pad.format.num_planes): aaclip_plane[plane] = core.std.ShufflePlanes(clips=rs1_pad, planes=plane, colorfamily=vs.GRAY) if aa_mcliph is not None: aa_mcliph_plane = core.std.ShufflePlanes(clips=aa_mcliph, planes=plane, colorfamily=vs.GRAY) else: aa_mcliph_plane = None if aa_mclipv is not None: aa_mclipv_plane = core.std.ShufflePlanes(clips=aa_mclipv, planes=plane, colorfamily=vs.GRAY) else: aa_mclipv_plane = None if aa_h: aaclip_plane[plane] = aaclip_plane[plane].std.Transpose() if aa_mode == "sr": aaclip_plane[plane] = xaa_sr(aaclip_plane[plane], aa_type, aa_pass, cplace, snaa_plane[plane], nns, eediA, eediB, eediG, aa_sclip, aa_mcliph_plane) elif aa_mode == "dr": aaclip_plane[plane] = xaa_dr(aaclip_plane[plane], aa_type, aa_pass, cplace, snaa_plane[plane], nns, eediA, eediB, eediG, aa_sclip, aa_mcliph_plane) elif aa_mode == "di": aaclip_plane[plane] = xaa_di(aaclip_plane[plane], aa_type, aa_pass, cplace, snaa_plane[plane], nns, eediA, eediB, eediG, aa_sclip, aa_mcliph_plane) if not aa_delayresize_h: aaclip_plane[plane] = eval("core.resize." + dscl)(clip=aaclip_plane[plane], width=ssh_pad_plane[plane], height=ssw_pad_plane[plane], src_left=0, src_top=-0.5) aaclip_plane[plane] = aaclip_plane[plane].std.Transpose() if aa_v: if aa_mode == "sr": aaclip_plane[plane] = xaa_sr(aaclip_plane[plane], aa_type, aa_pass, cplace, snaa_plane[plane], nns, eediA, eediB, eediG, aa_sclip, aa_mclipv_plane) elif aa_mode == "dr": aaclip_plane[plane] = xaa_dr(aaclip_plane[plane], aa_type, aa_pass, cplace, snaa_plane[plane], nns, eediA, eediB, eediG, aa_sclip, aa_mclipv_plane) elif aa_mode == "di": aaclip_plane[plane] = xaa_di(aaclip_plane[plane], aa_type, aa_pass, cplace, snaa_plane[plane], nns, eediA, eediB, eediG, aa_sclip, aa_mclipv_plane) if not aa_delayresize_v: aaclip_plane[plane] = eval("core.resize." + dscl)(clip=aaclip_plane[plane], width=ssw_pad_plane[plane], height=ssh_pad_plane[plane], src_left=0, src_top=-0.5) if rs1_pad.format.num_planes == 1: aaclip = aaclip_plane[0] else: aaclip = core.std.ShufflePlanes(clips=aaclip_plane, planes=[0, 0, 0], colorfamily=rs1_pad.format.color_family) # Crop the padding off if rs1_addpad: if aa_delayresize_h: aaclip = aaclip.std.Crop(left=rs1_padL * 2, top=rs1_padT, right=rs1_padR * 2, bottom=rs1_padB) elif aa_delayresize_v: aaclip = aaclip.std.Crop(left=rs1_padL, top=rs1_padT * 2, right=rs1_padR, bottom=rs1_padB * 2) else: aaclip = aaclip.std.Crop(left=rs1_padL, top=rs1_padT, right=rs1_padR, bottom=rs1_padB) ##### Apply contra-sharpening before scaling to the output resolution if csharp=1 ##### if csharp == 1: aadiff = core.std.MakeDiff(clipa=rs1, clipb=aaclip, planes=planes) if cstr < 0: aablur = core.rgvs.RemoveGrain(clip=aaclip, mode=11) else: aablur = Blur(clip=aaclip, amount=cstr * 0.2) sharpdiff = core.std.MakeDiff(clipa=aaclip, clipb=aablur, planes=planes) repaired = core.rgvs.Repair(clip=sharpdiff, repairclip=aadiff, mode=13) aaclip = core.std.MergeDiff(clipa=aaclip, clipb=repaired, planes=planes) ##### Scale the antialiased clip to the output resolution ##### # rsaa_type and rsaa_isedi are defined earlier if ow > aa_ow * 6 - 4: rsaa_rfacX = 8 elif ow > aa_ow * 3 - 4: rsaa_rfacX = 4 elif ow > aa_ow: rsaa_rfacX = 2 else: rsaa_rfacX = 1 if oh > aa_oh * 6 - 4: rsaa_rfacY = 8 elif oh > aa_oh * 3 - 4: rsaa_rfacY = 4 elif oh > aa_oh: rsaa_rfacY = 2 else: rsaa_rfacY = 1 if ow >= aa_ow * rsaa_rfacX or oh >= aa_oh * rsaa_rfacY: rsaa_cshift = "Spline36" else: rsaa_cshift = dscl # Center shift values for the rsaa resize if edi_rpow2 is used if rsaa_isedi and rsaa_rfacX > 1: if not hssc12 or chroma == 0: rsaa_hshift = -0.5 else: rsaa_hshift = -0.5 * (rsaa_rfacX - 1) else: rsaa_hshift = 0 if rsaa_isedi and rsaa_rfacY > 1: rsaa_vshift = -0.5 else: rsaa_vshift = 0 if rsaa_isedi and rsaa_rfacX > 1 and cplace == "MPEG1" and hssc12 and chroma: rsaa_hshift_c = -0.5 * (rsaa_rfacX - 1) + rsaa_hshift else: rsaa_hshift_c = rsaa_hshift if rsaa_isedi and rsaa_rfacY > 1 and vssc12 and chroma: rsaa_vshift_c = rsaa_vshift * 2.0 else: rsaa_vshift_c = rsaa_vshift # Add center shift corrections from rs1 delay_cshift and aa_delayresize to the rsaa corrections if rsaa_isedi: rsaa_hshift = rsaa_hshift + rs1_hshift * rsaa_rfacX + aa_hshift * rsaa_rfacX rsaa_vshift = rsaa_vshift + rs1_vshift * rsaa_rfacY + aa_vshift * rsaa_rfacY rsaa_hshift_c = rsaa_hshift_c + rs1_hshift_c * rsaa_rfacX + aa_hshift_c * rsaa_rfacX rsaa_vshift_c = rsaa_vshift_c + rs1_vshift_c * rsaa_rfacY + aa_vshift_c * rsaa_rfacY else: rsaa_hshift = rs1_hshift + aa_hshift rsaa_vshift = rs1_vshift + aa_vshift rsaa_hshift_c = rs1_hshift_c + aa_hshift_c rsaa_vshift_c = rs1_vshift_c + aa_vshift_c rsaa_UVshift = rsaa_hshift != rsaa_hshift_c or rsaa_vshift != rsaa_vshift_c # Reuse the eedi3 mclip mask from antialiasing if uscl == "eedi3" and eedimthr > 0: rsaa_mclip = aa_mclip else: rsaa_mclip = None aaclip_y8 = aaclip if ow == aa_ow and oh == aa_oh and not delay_cshift and not aa_delayresize_h and not aa_delayresize_v: rsaa = aaclip_y8 elif rsaa_isedi and (delay_cshift or aa_delayresize_h or aa_delayresize_v): rsaa = aaclip_y8 rsaa = edi_rpow2(clip=rsaa, rfactorX=rsaa_rfacX, rfactorY=rsaa_rfacY, edi=rsaa_type, cplace=cplace, YV12cfix=False, alpha=eediA, beta=eediB, gamma=eediG, sclip=rs_sclip, mclip=rsaa_mclip) if rsaa_UVshift: rsaa = ResizeSeparately(clip=rsaa, width=ow, height=oh, src_left=[rsaa_hshift, rsaa_hshift_c], src_top=[rsaa_vshift, rsaa_vshift_c], kernel=rsaa_cshift, cplace=cplace) else: rsaa = eval("core.resize." + rsaa_cshift)(clip=rsaa, width=ow, height=oh, src_left=rsaa_hshift, src_top=rsaa_vshift, chromaloc_in_s=chromaloc, chromaloc_s=chromaloc) elif rsaa_isedi: rsaa = aaclip_y8 rsaa = edi_rpow2(clip=rsaa, rfactorX=rsaa_rfacX, rfactorY=rsaa_rfacY, edi=rsaa_type, cshift=rsaa_cshift, fwidth=ow, fheight=oh, cplace=cplace, alpha=eediA, beta=eediB, gamma=eediG, sclip=rs_sclip, mclip=rsaa_mclip) else: if rsaa_UVshift: rsaa = ResizeSeparately(clip=aaclip_y8, width=ow, height=oh, src_left=[rsaa_hshift, rsaa_hshift_c], src_top=[rsaa_vshift, rsaa_vshift_c], kernel=rsaa_cshift, cplace=cplace) else: rsaa = eval("core.resize." + rsaa_cshift)(clip=aaclip_y8, width=ow, height=oh, src_left=rsaa_hshift, src_top=rsaa_vshift, chromaloc_in_s=chromaloc, chromaloc_s=chromaloc) # Make sure the rsaa clip has the same chroma sampling as the input # rsaa is either the same format as the input clip (when chroma is not 0), or it is GRAY (when chroma is 0) if rsaa.format.id != clip.format.id: rsaa = core.resize.Bicubic(clip=rsaa, format=clip.format.id) ##### Scale the input clip to the output resolution without antialiasing for masking and chroma ##### if ow > iw * 6 - 4: rs2_rfacX = 8 elif ow > iw * 3 - 4: rs2_rfacX = 4 elif ow > iw: rs2_rfacX = 2 else: rs2_rfacX = 1 if oh > ih * 6 - 4: rs2_rfacY = 8 elif oh > ih * 3 - 4: rs2_rfacY = 4 elif oh > ih: rs2_rfacY = 2 else: rs2_rfacY = 1 if ow >= iw * rs2_rfacX and oh >= ih * rs2_rfacY: rs2_cshift = "Spline36" else: rs2_cshift = dscl if ow == iw and oh == ih: rs2 = clip elif rs2_isedi: rs2 = clip # The Avisynth version had rfactorY=rs1_rfacY and rs2_rfacY was never used. rs2 = edi_rpow2(clip=rs2, rfactorX=rs2_rfacX, rfactorY=rs2_rfacY, edi=rs2_type, cshift=rs2_cshift, fwidth=ow, fheight=oh, cplace=cplace, alpha=eediA, beta=eediB, gamma=eediG, sclip=rs_sclip, mclip=rs12_mclip) else: rs2 = eval("core.resize." + rs2_type)(clip=clip, width=ow, height=oh, chromaloc_in_s=chromaloc, chromaloc_s=chromaloc) ##### Apply contra-sharpening after scaling to the output resolution if csharp=2 ##### if csharp == 2: UVrg = 11 if chroma else -1 UVrp = 13 if chroma else -1 aadiff = core.std.MakeDiff(clipa=rs2, clipb=rsaa, planes=planes) if cstr < 0: aablur = core.rgvs.RemoveGrain(clip=rsaa, mode=[11, UVrg, UVrg]) else: aablur = Blur(clip=rsaa, amount=cstr * 0.2) sharpdiff = core.std.MakeDiff(clipa=rsaa, clipb=aablur, planes=planes) repaired = core.rgvs.Repair(clip=sharpdiff, repairclip=aadiff, mode=[13, UVrp, UVrp]) rsaa = core.std.MergeDiff(clipa=rsaa, clipb=repaired, planes=planes) ##### Masking, chroma merging, and output ##### # Reuse the eedi3 mclip mask from the rs1 and rs2 resizes if it's the same if rs12_mclip is not None and mthr == eedimthr and iw == ow and ih == oh: emask = rs12_mclip else: if mtype == "TEdgeMask": emask = rs2.tedgemask.TEdgeMask(threshold=mthr, type=temtype, link=0, planes=planes) elif mtype == "TCanny": emask = rs2.tcanny.TCanny(t_h=mthr, t_l=mthr, planes=planes, op=0) emask = emask.std.Maximum(planes=planes, coordinates=mt_expand_mode_both).std.Inflate(planes=planes) emask = emask.std.Minimum(planes=planes, coordinates=mt_expand_mode_both) elif mtype == "Prewitt": emask = rs2.std.Prewitt(planes=planes).std.Binarize(threshold=(mthr + 1) << (rs2.format.bits_per_sample - 8), planes=planes) elif mtype == "Sobel": emask = rs2.std.Sobel(planes=planes).std.Binarize(threshold=(mthr + 1) << (rs2.format.bits_per_sample - 8), planes=planes) emask = emask.std.Inflate(planes=planes) # 8-bit version of the rs2 clip for masking # TODO why can't you make the overlay in the same format as the input clip? # TODO okay, you can't do it in GRAY because it can't be green, but what's the reason to convert 422? rs2_yv24 = rs2.resize.Bicubic(format=core.query_video_format(color_family=vs.YUV, sample_type=rs2.format.sample_type, bits_per_sample=rs2.format.bits_per_sample, subsampling_w=0, subsampling_h=0)) if isGray(clip) or is422(clip): # TODO why doesn't YUV420P8 need this? eover = rs2_yv24 overlay = rs2_yv24 else: eover = rs2 overlay = rs2 eover = core.std.Expr(clips=eover, expr=["", "x 2 /"]) overlay = core.std.MaskedMerge(clipa=overlay, clipb=eover, mask=core.std.ShufflePlanes(clips=emask, planes=0, colorfamily=vs.GRAY), first_plane=True) if is422(clip): overlay = overlay.resize.Bicubic(format=core.query_video_format(color_family=overlay.format.color_family, sample_type=overlay.format.sample_type, bits_per_sample=overlay.format.bits_per_sample, subsampling_w=1, subsampling_h=0)) if chroma == 0: merge_planes = [0] elif chroma == 1: merge_planes = [0, 1, 2] elif chroma == 2: merge_planes = [1, 2] if mask == 0: if isGray(clip) or chroma == 1: merged = rsaa elif chroma == 0: merged = core.std.ShufflePlanes(clips=[rsaa, rs2, rs2], planes=[0, 1, 2], colorfamily=rsaa.format.color_family) elif chroma == 2: merged = core.std.ShufflePlanes(clips=[rs2, rsaa, rsaa], planes=[0, 1, 2], colorfamily=rsaa.format.color_family) elif mask == 1: merged = core.std.MaskedMerge(clipa=rs2, clipb=rsaa, mask=emask, planes=merge_planes) elif mask == 2: merged = core.std.MaskedMerge(clipa=rsaa, clipb=rs2, mask=emask, planes=merge_planes) if chroma == 0: merged = core.std.ShufflePlanes(clips=[merged, rs2, rs2], planes=[0, 1, 2], colorfamily=merged.format.color_family) elif chroma == 2: merged = core.std.ShufflePlanes(clips=[rs2, merged, merged], planes=[0, 1, 2], colorfamily=merged.format.color_family) if mask < 0: output = overlay else: output = merged return output ##### Recursive functions for multipass antialiasing ##### # TODO move these functions inside xaa # maybe? def xaa_sr(clip, type="SangNom", passes=1, cplace="MPEG2", snaa=48, nns=1, alpha=0.2, beta=0.25, gamma=20.0, sclip="", mclip=None, f=1): core = vs.core iw = clip.width ih = clip.height # Alternate the field every pass, starting with 1 by default field = f % 2 if field == 1: snfield = 1 else: snfield = 2 if clip.format.num_planes > 1: snaa = [snaa, 0, 0] # SeparateFields will throw an error from mod2 heights if the input is YV12, # so don't define sf unless it's needed, in which case the resolution will have been padded to mod4. pad8 = type == "eedi2" or (type == "eedi3" and sclip not in ["", "SangNom", "znedi3", "nnedi3cl"]) if pad8: sf = clip.std.SeparateFields(tff=True).std.RemoveFrameProps(props=["_Field"]) if field == 1: sf = sf.std.SelectEvery(cycle=2, offsets=0) else: sf = sf.std.SelectEvery(cycle=2, offsets=1) else: sf = None # Use a src_top shift dependent on the field when resizing for the sclip to keep it in alignment with the eedi3 clip. # Additionally, the vertical shift for YV12 chroma is doubled, so the chroma must be resized separately. if field == 1: rshift = 0.25 else: rshift = -0.25 if sclip == "": sclip2 = None elif sclip == "SangNom": sclip2 = clip.sangnom.SangNom(order=snfield, aa=snaa) elif sclip == "znedi3": sclip2 = clip.znedi3.nnedi3(field=field, nns=nns) elif sclip == "nnedi3cl": sclip2 = clip.nnedi3cl.NNEDI3CL(field=field, nns=nns) elif sclip == "eedi2": sclip2 = sf.eedi2.EEDI2(field=field) elif is420(clip): sclip2 = ResizeSeparately(clip=sf, width=iw, height=ih, src_left=[0, 0], src_top=[rshift, rshift * 2], kernel=sclip, cplace=cplace) else: sclip2 = eval("core.resize." + sclip)(clip=sf, width=iw, height=ih, src_left=0, src_top=rshift, chromaloc_in_s=chromaloc, chromaloc_s=chromaloc) if type == "SangNom": aa = clip.sangnom.SangNom(order=snfield, aa=snaa) elif type == "znedi3": aa = clip.znedi3.nnedi3(field=field, nns=nns) elif type == "nnedi3cl": aa = clip.nnedi3cl.NNEDI3CL(field=field, nns=nns) elif type == "eedi3": aa = clip.eedi3m.EEDI3(field=field, alpha=alpha, beta=beta, gamma=gamma, sclip=sclip2, mclip=mclip) elif type == "eedi2": aa = sf.eedi2.EEDI2(field=field) else: raise ValueError("xaa_sr: invalid antialiasing type '{}'.".format(type)) if passes > 0: return xaa_sr(clip=aa, type=type, passes=passes - 1, cplace=cplace, snaa=snaa, nns=nns, alpha=alpha, beta=beta, gamma=gamma, sclip=sclip, mclip=mclip, f=f + 1) else: return clip def xaa_dr(clip, type="znedi3", passes=1, cplace="MPEG2", snaa=48, nns=1, alpha=0.2, beta=0.25, gamma=20.0, sclip="", mclip=None): core = vs.core iw = clip.width ih = clip.height if clip.format.num_planes > 1: snaa = [snaa, 0, 0] pad8 = type == "eedi2" or (type == "eedi3" and sclip not in ["", "SangNom", "znedi3", "nnedi3cl"]) if pad8: sf = clip.std.SeparateFields(tff=True).std.RemoveFrameProps(props=["_Field"]) tf = sf.std.SelectEvery(cycle=2, offsets=0) bf = sf.std.SelectEvery(cycle=2, offsets=1) else: sf = None tf = None bf = None if sclip == "": sclip2 = None elif sclip == "SangNom": sclip2 = core.std.Interleave(clips=[core.sangnom.SangNom(clip=clip, order=1, aa=snaa), core.sangnom.SangNom(clip=clip, order=2, aa=snaa)]) elif sclip == "znedi3": sclip2 = clip.znedi3.nnedi3(field=3, nns=nns) elif sclip == "nnedi3cl": sclip2 = clip.nnedi3cl.NNEDI3CL(field=3, nns=nns) elif sclip == "eedi2": sclip2 = sf.eedi2.EEDI2(field=3) elif is420(clip): sclip2 = core.std.Interleave(clips=[ResizeSeparately(clip=tf, width=iw, height=ih, src_left=[0, 0], src_top=[0.25, 0.5], kernel=sclip, cplace=cplace), ResizeSeparately(clip=bf, width=iw, height=ih, src_left=[0, 0], src_top=[-0.25, -0.5], kernel=sclip, cplace=cplace)]) else: sclip2 = core.std.Interleave(clips=[eval("core.resize." + sclip)(clip=tf, width=iw, height=ih, src_left=0, src_top=0.25, chromaloc_in_s=chromaloc, chromaloc_s=chromaloc), eval("core.resize." + sclip)(clip=bf, width=iw, height=ih, src_left=0, src_top=-0.25, chromaloc_in_s=chromaloc, chromaloc_s=chromaloc)]) if type == "SangNom": aa = core.std.Interleave(clips=[core.sangnom.SangNom(clip=clip, order=1, aa=snaa), core.sangnom.SangNom(clip=clip, order=2, aa=snaa)]) elif type == "znedi3": aa = clip.znedi3.nnedi3(field=3, nns=nns) elif type == "nnedi3cl": aa = clip.nnedi3cl.NNEDI3CL(field=3, nns=nns) elif type == "eedi3": aa = clip.eedi3m.EEDI3(field=3, alpha=alpha, beta=beta, gamma=gamma, sclip=sclip2, mclip=mclip) elif type == "eedi2": aa = sf.eedi2.EEDI2(field=3) else: raise ValueError("xaa_dr: invalid antialiasing type '{}'.".format(type)) aa = core.std.Merge(clipa=aa.std.SelectEvery(cycle=2, offsets=0), clipb=aa.std.SelectEvery(cycle=2, offsets=1), weight=0.5) if passes > 0: return xaa_dr(clip=aa, type=type, passes=passes - 1, cplace=cplace, snaa=snaa, nns=nns, alpha=alpha, beta=beta, gamma=gamma, sclip=sclip, mclip=mclip) else: return clip def xaa_di(clip, type="znedi3", passes=1, cplace="MPEG2", snaa=48, nns=1, alpha=0.2, beta=0.25, gamma=20.0, sclip="", mclip=None, f=1, dh=True): core = vs.core # Translate the cplace parameter into values understood by the VapourSynth resizers. if cplace == "MPEG1": chromaloc = "center" elif cplace == "MPEG2": chromaloc = "left" iw = clip.width ih = clip.height # Alternate the field every pass, starting with 1. # Don't change the f value from the default or else the center shift correction will be wrong. field = f % 2 if field == 1: snfield = 1 else: snfield = 2 if clip.format.num_planes > 1: snaa = [snaa, 0, 0] pad8 = type == "eedi2" or (type == "eedi3" and sclip not in ["", "SangNom", "znedi3", "nnedi3cl"]) if pad8: sf = clip.std.SeparateFields(tff=True).std.RemoveFrameProps(props=["_Field"]) if field == 1: sf = sf.std.SelectEvery(cycle=2, offsets=0) else: sf = sf.std.SelectEvery(cycle=2, offsets=1) else: sf = None if field == 1: rshift = 0.25 else: rshift = -0.25 if sclip == "": sclip2 = None elif sclip == "SangNom": sclip2 = clip if dh: sclip2 = core.resize.Point(clip=sclip2, width=iw, height=ih * 2, chromaloc_in_s=chromaloc, chromaloc_s=chromaloc) sclip2 = sclip2.sangnom.SangNom(order=snfield, aa=snaa) elif sclip == "znedi3": sclip2 = clip.znedi3.nnedi3(field=field, dh=dh, nns=nns) elif sclip == "nnedi3cl": sclip2 = clip.nnedi3cl.NNEDI3CL(field=field, dh=dh, nns=nns) elif sclip == "eedi2": if dh: sclip2 = clip.eedi2.EEDI2(field=field) else: sclip2 = sf.eedi2.EEDI2(field=field) elif is420(clip): if dh: sclip2 = ResizeSeparately(clip=clip, width=iw, height=ih * 2, src_left=[0, 0], src_top=[rshift, rshift * 2], kernel=sclip, cplace=cplace) else: sclip2 = ResizeSeparately(clip=sf, width=iw, height=ih * 2, src_left=[0, 0], src_top=[rshift, rshift * 2], kernel=sclip, cplace=cplace) else: if dh: sclip2 = eval("core.resize." + sclip)(clip=clip, width=iw, height=ih * 2, src_left=0, src_top=rshift, chromaloc_in_s=chromaloc, chromaloc_s=chromaloc) else: sclip2 = eval("core.resize." + sclip)(clip=sf, width=iw, height=ih, src_left=0, src_top=rshift, chromaloc_in_s=chromaloc, chromaloc_s=chromaloc) # The height is only doubled on the first pass if type == "SangNom": aa = clip if dh: aa = core.resize.Point(clip=aa, width=iw, height=ih * 2, chromaloc_in_s=chromaloc, chromaloc_s=chromaloc) aa = aa.sangnom.SangNom(order=snfield, aa=snaa) elif type == "znedi3": aa = clip.znedi3.nnedi3(field=field, dh=dh, nns=nns) elif type == "nnedi3cl": aa = clip.nnedi3cl.NNEDI3CL(field=field, dh=dh, nns=nns) elif type == "eedi3": aa = clip.eedi3m.EEDI3(field=field, dh=dh, alpha=alpha, beta=beta, gamma=gamma, sclip=sclip2, mclip=mclip) elif type == "eedi2": if dh: aa = clip.eedi2.EEDI2(field=field) else: aa = sf.eedi2.EEDI2(field=field) else: raise ValueError("xaa_di: invalid antialiasing type '{}'.".format(type)) # Double the height of the mclip for passes after the first. if mclip is not None and dh: if is420(clip): mclip = ResizeSeparately(clip=mclip, width=iw, height=ih * 2, src_left=[0, 0], src_top=[0.25, 0.5], kernel="Spline36", cplace=cplace) else: mclip = core.resize.Spline36(clip=mclip, width=iw, height=ih * 2, src_left=0, src_top=0.25, chromaloc_in_s=chromaloc, chromaloc_s=chromaloc) mclip = mclip.std.Binarize(planes=planes) if passes > 0: return xaa_di(clip=aa, type=type, passes=passes - 1, cplace=cplace, snaa=snaa, nns=nns, alpha=alpha, beta=beta, gamma=gamma, sclip=sclip, mclip=mclip, f=f + 1, dh=False) else: return clip