├── README.md └── fvsfunc.py /README.md: -------------------------------------------------------------------------------- 1 | # fvsfunc 2 | Small collection of VapourSynth functions I used at least once. 3 | Most are simple wrappers or ports of AviSynth functions. 4 | -------------------------------------------------------------------------------- /fvsfunc.py: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | import re 3 | from functools import partial 4 | 5 | # Small collection of VapourSynth functions I used at least once. 6 | # Most are simple wrappers or ports of AviSynth functions. 7 | 8 | # Included functions: 9 | # 10 | # GradFun3mod 11 | # DescaleM (DebilinearM, DebicubicM etc.) 12 | # Downscale444 13 | # JIVTC 14 | # OverlayInter 15 | # AutoDeblock 16 | # ReplaceFrames (ReplaceFramesSimple) 17 | # maa 18 | # TemporalDegrain 19 | # DescaleAA 20 | # InsertSign 21 | 22 | # To use all included functions, you need to have 23 | # the following Python scripts installed: 24 | # 25 | # havsfunc: https://github.com/HomeOfVapourSynthEvolution/havsfunc 26 | # mvsfunc: https://github.com/HomeOfVapourSynthEvolution/mvsfunc 27 | # muvsfunc: https://github.com/WolframRhodium/muvsfunc 28 | # nnedi3_rpow2: https://gist.github.com/4re/342624c9e1a144a696c6 29 | 30 | 31 | core = vs.core 32 | 33 | 34 | """ 35 | VapourSynth port of Gebbi's GradFun3mod 36 | 37 | Based on Muonium's GradFun3 port: 38 | https://github.com/WolframRhodium/muvsfunc 39 | 40 | If you don't use any of the newly added arguments 41 | it will behave just like unmodified GradFun3. 42 | 43 | Differences: 44 | 45 | - added smode=5 that uses a bilateral filter on the GPU (CUDA) 46 | output should be very similar to smode=2 47 | - fixed the strength of the bilateral filter when using 48 | smode=2 to match the AviSynth version 49 | - changed argument lsb to bits (default is input bitdepth) 50 | - case of the resizer doesn't matter anymore 51 | - every resizer supported by fmtconv.resample can be specified 52 | - yuv444 can now be used with any output resolution 53 | - removed fh and fv arguments for all resizers 54 | 55 | Requirements: 56 | 57 | - muvsfunc https://github.com/WolframRhodium/muvsfunc 58 | - havsfunc https://github.com/HomeOfVapourSynthEvolution/havsfunc 59 | - mvsfunc https://github.com/HomeOfVapourSynthEvolution/mvsfunc 60 | - Bilateral https://github.com/HomeOfVapourSynthEvolution/VapourSynth-Bilateral 61 | - BilateralGPU (optional, needs OpenCV 3.2 with CUDA module) https://github.com/WolframRhodium/VapourSynth-BilateralGPU 62 | - fmtconv https://github.com/EleonoreMizo/fmtconv 63 | - Descale (optional) https://github.com/Frechdachs/vapoursynth-descale 64 | - dfttest https://github.com/HomeOfVapourSynthEvolution/VapourSynth-DFTTest 65 | - nnedi3 https://github.com/dubhater/vapoursynth-nnedi3 66 | - nnedi3_rpow2 https://gist.github.com/4re/342624c9e1a144a696c6 67 | 68 | Original header: 69 | 70 | ################################################################################################################## 71 | # 72 | # High bitdepth tools for Avisynth - GradFun3mod r6 73 | # based on Dither v1.27.2 74 | # Author: Firesledge, slightly modified by Gebbi 75 | # 76 | # What? 77 | # - This is a slightly modified version of the original GradFun3. 78 | # - It combines the usual color banding removal stuff with resizers during the process 79 | # for sexier results (less detail loss, especially for downscales of cartoons). 80 | # - This is a starter script, not everything is covered through parameters. Modify it to your needs. 81 | # 82 | # Requirements (in addition to the Dither requirements): 83 | # - AviSynth 2.6.x 84 | # - Debilinear, Debicubic, DebilinearM 85 | # - NNEDI3 + nnedi3_resize16 86 | # 87 | # Changes from the original GradFun3: 88 | # - yuv444 = true 89 | # (4:2:0 -> 4:4:4 colorspace conversion, needs 1920x1080 input) 90 | # - resizer = [ "none", "Debilinear", "DebilinearM", "Debicubic", "DebicubicM", "Spline16", 91 | # "Spline36", "Spline64", "lineart_rpow2", "lineart_rpow2_bicubic" ] 92 | # (use it only for downscales) 93 | # NOTE: As of r2 Debicubic doesn't have 16-bit precision, so a Y (luma) plane fix by torch is used here, 94 | # more info: https://mechaweaponsvidya.wordpress.com/2015/07/07/a-precise-debicubic/ 95 | # Without yuv444=true Dither_resize16 is used with an inverse bicubic kernel. 96 | # - w = 1280, h = 720 97 | # (output width & height for the resizers; or production resolution for resizer="lineart_rpow2") 98 | # - smode = 4 99 | # (the old GradFun3mod behaviour for legacy reasons; based on smode = 1 (dfttest); 100 | # not useful anymore in most cases, use smode = 2 instead (less detail loss)) 101 | # - deb = true 102 | # (legacy parameter; same as resizer = "DebilinearM") 103 | # 104 | # Usage examples: 105 | # - Source is bilinear 720p->1080p upscale (BD) with 1080p credits overlayed, 106 | # revert the upscale without fucking up the credits: 107 | # lwlibavvideosource("lol.m2ts") 108 | # GradFun3mod(smode=1, yuv444=true, resizer="DebilinearM") 109 | # 110 | # - same as above, but bicubic Catmull-Rom upscale (outlines are kind of "blocky" and oversharped): 111 | # GradFun3mod(smode=1, yuv444=true, resizer="DebicubicM", b=0, c=1) 112 | # (you may try any value between 0 and 0.2 for b, and between 0.7 and 1 for c) 113 | # 114 | # - You just want to get rid off the banding without changing the resolution: 115 | # GradFun3(smode=2) 116 | # 117 | # - Source is 1080p production (BD), downscale to 720p: 118 | # GradFun3mod(smode=2, yuv444=true, resizer="Spline36") 119 | # 120 | # - Source is a HDTV transportstream (or CR or whatever), downscale to 720p: 121 | # GradFun3mod(smode=2, resizer="Spline36") 122 | # 123 | # - Source is anime, 720p->1080p upscale, keep the resolution 124 | # but with smoother lineart instead of bilinear upscaled shit: 125 | # GradFun3mod(smode=2, resizer="lineart_rpow2") 126 | # This won't actually resize the video but instead mask the lineart and re-upscale it using 127 | # nnedi3_rpow2 which often results in much better looking lineart (script mostly by Daiz). 128 | # 129 | # Note: Those examples don't include parameters like thr, radius, elast, mode, ampo, ampn, staticnoise. 130 | # You probably don't want to use the default values. 131 | # For 16-bit output use: 132 | # GradFun3mod(lsb=true).Dither_out() 133 | # 134 | # What's the production resolution of my korean cartoon? 135 | # - Use your eyes combined with Debilinear(1280,720) - if it looks like oversharped shit, 136 | # it was probably produced in a higher resolution. 137 | # - Use Debilinear(1280,720).BilinearResize(1920,1080) for detail loss search. 138 | # - Alternatively you can lookup the (estimated) production resolution at 139 | # http://anibin.blogspot.com (but don't blindly trust those results) 140 | # 141 | # This program is free software. It comes without any warranty, to 142 | # the extent permitted by applicable law. You can redistribute it 143 | # and/or modify it under the terms of the Do What The Fuck You Want 144 | # To Public License, Version 2, as published by Sam Hocevar. See 145 | # http://sam.zoy.org/wtfpl/COPYING for more details. 146 | # 147 | ################################################################################################################## 148 | 149 | """ 150 | def GradFun3(src, thr=None, radius=None, elast=None, mask=None, mode=None, ampo=None, 151 | ampn=None, pat=None, dyn=None, staticnoise=None, smode=None, thr_det=None, 152 | debug=None, thrc=None, radiusc=None, elastc=None, planes=None, ref=None, 153 | yuv444=None, w=None, h=None, resizer=None, b=None, c=None, bits=None): 154 | 155 | try: 156 | import mvsfunc as mvf 157 | except ImportError: 158 | raise ImportError('GradFun3: mvsfunc not found. Download it here: https://github.com/HomeOfVapourSynthEvolution/mvsfunc') 159 | try: 160 | import muvsfunc as muf 161 | except ImportError: 162 | raise ImportError('GradFun3: muvsfunc not found. Download it here: https://github.com/WolframRhodium/muvsfunc') 163 | 164 | def smooth_mod(src_16, ref_16, smode, radius, thr, elast, planes): 165 | if smode == 0: 166 | return muf._GF3_smoothgrad_multistage(src_16, ref_16, radius, thr, elast, planes) 167 | elif smode == 1: 168 | return muf._GF3_dfttest(src_16, ref_16, radius, thr, elast, planes) 169 | elif smode == 2: 170 | return bilateral(src_16, ref_16, radius, thr, elast, planes) 171 | elif smode == 3: 172 | return muf._GF3_smoothgrad_multistage_3(src_16, radius, thr, elast, planes) 173 | elif smode == 4: 174 | return dfttest_mod(src_16, ref_16, radius, thr, elast, planes) 175 | elif smode == 5: 176 | return bilateral_gpu(src_16, ref_16, radius, thr, elast, planes) 177 | else: 178 | raise ValueError(funcname + ': wrong smode value!') 179 | 180 | def dfttest_mod(src, ref, radius, thr, elast, planes): 181 | hrad = max(radius * 3 // 4, 1) 182 | last = core.dfttest.DFTTest(src, sigma=thr * 12, sbsize=hrad * 4, 183 | sosize=hrad * 3, tbsize=1, planes=planes) 184 | last = mvf.LimitFilter(last, ref, thr=thr, elast=elast, planes=planes) 185 | return last 186 | 187 | def bilateral(src, ref, radius, thr, elast, planes): 188 | thr_1 = max(thr * 4.5, 1.25) 189 | thr_2 = max(thr * 9, 5.0) 190 | r4 = max(radius * 4 / 3, 4.0) 191 | r2 = max(radius * 2 / 3, 3.0) 192 | r1 = max(radius * 1 / 3, 2.0) 193 | last = src 194 | last = core.bilateral.Bilateral(last, ref=ref, sigmaS=r4 / 2, sigmaR=thr_1 / 255, 195 | planes=planes, algorithm=0) 196 | # NOTE: I get much better results if I just call Bilateral once 197 | #last = core.bilateral.Bilateral(last, ref=ref, sigmaS=r2 / 2, sigmaR=thr_2 / 255, 198 | # planes=planes, algorithm=0) 199 | #last = core.bilateral.Bilateral(last, ref=ref, sigmaS=r1 / 2, sigmaR=thr_2 / 255, 200 | # planes=planes, algorithm=0) 201 | last = mvf.LimitFilter(last, src, thr=thr, elast=elast, planes=planes) 202 | return last 203 | 204 | def bilateral_gpu(src, ref, radius, thr, elast, planes): 205 | t = max(thr * 4.5, 1.25) 206 | r = max(radius * 4 / 3, 4.0) 207 | last = core.bilateralgpu.Bilateral(src, sigma_spatial=r / 2, sigma_color=t) 208 | last = mvf.LimitFilter(last, ref, thr=thr, elast=elast, planes=planes) 209 | return last 210 | 211 | funcname = 'GradFun3' 212 | 213 | # Type checking 214 | kwargsdict = {'src': [src, (vs.VideoNode,)], 'thr': [thr, (int, float)], 'radius': [radius, (int,)], 215 | 'elast': [elast, (int, float)], 'mask': [mask, (int,)], 'mode': [mode, (int,)], 216 | 'ampo': [ampo, (int, float)], 'ampn': [ampn, (int, float)], 'pat': [pat, (int,)], 217 | 'dyn': [dyn, (bool,)], 'staticnoise': [staticnoise, (bool,)], 'smode': [smode, (int,)], 218 | 'thr_det': [thr_det, (int, float)], 'debug': [debug, (bool, int)], 'thrc': [thrc, (int, float)], 219 | 'radiusc': [radiusc, (int,)], 'elastc': [elastc, (int, float)], 'planes': [planes, (int, list)], 220 | 'ref': [ref, (vs.VideoNode,)], 'yuv444': [yuv444, (bool,)], 'w': [w, (int,)], 'h': [h, (int,)], 221 | 'resizer': [resizer, (str,)], 'b': [b, (int, float)], 'c': [c, (int, float)], 'bits': [bits, (int,)]} 222 | 223 | for k, v in kwargsdict.items(): 224 | if v[0] is not None and not isinstance(v[0], v[1]): 225 | raise TypeError('{funcname}: "{variable}" must be {types}!' 226 | .format(funcname=funcname, variable=k, types=' or '.join([TYPEDICT[t] for t in v[1]]))) 227 | 228 | # Set defaults 229 | if smode is None: 230 | smode = 2 231 | if thr is None: 232 | thr = 0.35 233 | if radius is None: 234 | radius = 12 if smode not in [0, 3] else 9 235 | if elast is None: 236 | elast = 3.0 237 | if mask is None: 238 | mask = 2 239 | if thr_det is None: 240 | thr_det = 2 + round(max(thr - 0.35, 0) / 0.3) 241 | if debug is None: 242 | debug = False 243 | if thrc is None: 244 | thrc = thr 245 | if radiusc is None: 246 | radiusc = radius 247 | if elastc is None: 248 | elastc = elast 249 | if planes is None: 250 | planes = list(range(src.format.num_planes)) 251 | if ref is None: 252 | ref = src 253 | if yuv444 is None: 254 | yuv444 = False 255 | if w is None: 256 | w = 1280 257 | if h is None: 258 | h = 720 259 | if resizer is None: 260 | resizer = '' 261 | if yuv444 and not resizer: 262 | resizer = 'spline36' 263 | if b is None: 264 | b = 1/3 265 | if c is None: 266 | c = 1/3 267 | if bits is None: 268 | bits = src.format.bits_per_sample 269 | 270 | # Value checking 271 | if src.format.color_family not in [vs.YUV, vs.GRAY]: 272 | raise TypeError(funcname + ': "src" must be YUV or GRAY color family!') 273 | if ref.format.color_family not in [vs.YUV, vs.GRAY]: 274 | raise TypeError(funcname + ': "ref" must be YUV or GRAY color family!') 275 | if thr < 0.1 or thr > 10.0: 276 | raise ValueError(funcname + ': "thr" must be in [0.1, 10.0]!') 277 | if thrc < 0.1 or thrc > 10.0: 278 | raise ValueError(funcname + ': "thrc" must be in [0.1, 10.0]!') 279 | if radius <= 0: 280 | raise ValueError(funcname + ': "radius" must be positive.') 281 | if radiusc <= 0: 282 | raise ValueError(funcname + ': "radiusc" must be positive.') 283 | if elast < 1: 284 | raise ValueError(funcname + ': Valid range of "elast" is [1, +inf)!') 285 | if elastc < 1: 286 | raise ValueError(funcname + ': Valid range of "elastc" is [1, +inf)!') 287 | if smode not in [0, 1, 2, 3, 4, 5]: 288 | raise ValueError(funcname + ': "smode" must be in [0, 1, 2, 3, 4, 5]!') 289 | if smode in [0, 3]: 290 | if radius not in list(range(2, 10)): 291 | raise ValueError(funcname + ': "radius" must be in 2-9 for smode=0 or 3 !') 292 | if radiusc not in list(range(2, 10)): 293 | raise ValueError(funcname + ': "radiusc" must be in 2-9 for smode=0 or 3 !') 294 | elif smode in [1, 4]: 295 | if radius not in list(range(1, 129)): 296 | raise ValueError(funcname + ': "radius" must be in 1-128 for smode=1 or smode=4 !') 297 | if radiusc not in list(range(1, 129)): 298 | raise ValueError(funcname + ': "radiusc" must be in 1-128 for smode=1 or smode=4 !') 299 | if thr_det <= 0.0: 300 | raise ValueError(funcname + ': "thr_det" must be positive!') 301 | 302 | ow = src.width 303 | oh = src.height 304 | 305 | src_16 = core.fmtc.bitdepth(src, bits=16, planes=planes) if src.format.bits_per_sample < 16 else src 306 | src_8 = core.fmtc.bitdepth(src, bits=8, dmode=1, planes=[0]) if src.format.bits_per_sample != 8 else src 307 | ref_16 = core.fmtc.bitdepth(ref, bits=16, planes=planes) if ref.format.bits_per_sample < 16 else ref 308 | 309 | # Do lineart smoothing first for sharper results 310 | if resizer.lower() == 'lineart_rpow2': 311 | src_16 = ProtectedDebiXAA(src_16, w, h, bicubic=False) 312 | elif resizer.lower() == 'lineart_rpow2_bicubic': 313 | src_16 = ProtectedDebiXAA(src_16, w, h, bicubic=True, b=b, c=c) 314 | 315 | # Main debanding 316 | chroma_flag = (thrc != thr or radiusc != radius or 317 | elastc != elast) and 0 in planes and (1 in planes or 2 in planes) 318 | 319 | if chroma_flag: 320 | planes2 = [0] if 0 in planes else [] 321 | else: 322 | planes2 = planes 323 | 324 | if not planes2: 325 | raise ValueError(funcname + ': no plane is processed') 326 | 327 | flt_y = smooth_mod(src_16, ref_16, smode, radius, thr, elast, planes2) 328 | if chroma_flag: 329 | flt_c = smooth_mod(src_16, ref_16, smode, radiusc, thrc, elastc, [x for x in planes if x != 0]) 330 | flt = core.std.ShufflePlanes([flt_y,flt_c], [0,1,2], src.format.color_family) 331 | else: 332 | flt = flt_y 333 | 334 | # Edge/detail mask 335 | td_lo = max(thr_det * 0.75, 1.0) 336 | td_hi = max(thr_det, 1.0) 337 | mexpr = 'x {tl} - {th} {tl} - / 255 *'.format(tl=td_lo - 0.0001, th=td_hi + 0.0001) 338 | 339 | if mask > 0: 340 | dmask = mvf.GetPlane(src_8, 0) 341 | dmask = muf._Build_gf3_range_mask(dmask, mask) 342 | dmask = core.std.Expr([dmask], [mexpr]) 343 | dmask = core.rgvs.RemoveGrain(dmask, [22]) 344 | if mask > 1: 345 | dmask = core.std.Convolution(dmask, matrix=[1,2,1,2,4,2,1,2,1]) 346 | if mask > 2: 347 | dmask = core.std.Convolution(dmask, matrix=[1,1,1,1,1,1,1,1,1]) 348 | dmask = core.fmtc.bitdepth(dmask, bits=16) 349 | res_16 = core.std.MaskedMerge(flt, src_16, dmask, planes=planes, first_plane=True) 350 | else: 351 | res_16 = flt 352 | 353 | # Resizing / colorspace conversion (GradFun3mod) 354 | res_16_y = core.std.ShufflePlanes(res_16, planes=0, colorfamily=vs.GRAY) 355 | if resizer.lower() == 'debilinear': 356 | rkernel = Resize(res_16_y if yuv444 else res_16, w, h, kernel='bilinear', invks=True) 357 | elif resizer.lower() == 'debicubic': 358 | rkernel = Resize(res_16_y if yuv444 else res_16, w, h, kernel='bicubic', a1=b, a2=c, invks=True) 359 | elif resizer.lower() == 'debilinearm': 360 | rkernel = DebilinearM(res_16_y if yuv444 else res_16, w, h, chroma=not yuv444) 361 | elif resizer.lower() == 'debicubicm': 362 | rkernel = DebicubicM(res_16_y if yuv444 else res_16, w, h, b=b, c=c, chroma=not yuv444) 363 | elif resizer.lower() in ('lineart_rpow2', 'lineart_rpow2_bicubic'): 364 | if yuv444: 365 | rkernel = Resize(res_16_y, w, h, kernel='spline36') 366 | else: 367 | rkernel = res_16 368 | elif not resizer: 369 | rkernel = res_16 370 | else: 371 | rkernel = Resize(res_16_y if yuv444 else res_16, w, h, kernel=resizer.lower()) 372 | 373 | if yuv444: 374 | ly = rkernel 375 | lu = core.std.ShufflePlanes(res_16, planes=1, colorfamily=vs.GRAY) 376 | lv = core.std.ShufflePlanes(res_16, planes=2, colorfamily=vs.GRAY) 377 | lu = Resize(lu, w, h, kernel='spline16', sx=0.25) 378 | lv = Resize(lv, w, h, kernel='spline16', sx=0.25) 379 | rkernel = core.std.ShufflePlanes([ly,lu,lv], planes=[0,0,0], colorfamily=vs.YUV) 380 | res_16 = rkernel 381 | 382 | # Dithering 383 | result = res_16 if bits == 16 else core.fmtc.bitdepth(res_16, bits=bits, planes=planes, dmode=mode, ampo=ampo, 384 | ampn=ampn, dyn=dyn, staticnoise=staticnoise, patsize=pat) 385 | 386 | if debug: 387 | last = dmask 388 | if bits != 16: 389 | last = core.fmtc.bitdepth(last, bits=bits) 390 | else: 391 | last = result 392 | return last 393 | 394 | 395 | # GradFun3 alias 396 | GradFun3mod = GradFun3 397 | 398 | # GradFun3 alias 399 | gf3 = GradFun3 400 | 401 | 402 | """ 403 | VapourSynth port of DebilinearM 404 | 405 | Currently only YUV420 and YUV422 input makes sense 406 | 407 | Differences: 408 | 409 | - changed the cubic argument to descale_kernel, 410 | so that this function is not limited to bilinear or bicubic 411 | - chroma is never scaled with an inverted kernel 412 | - added yuv444 argument to convert to yuv444 413 | - added arguments to fine tune the resizers 414 | 415 | Usage: 416 | 417 | It is recommended to use the function alias for the desired kernel: 418 | 419 | DebilinearM(clip, 1280, 720) 420 | DebicubicM(clip, 1280, 720, b=0, c=0.5) 421 | DelanczosM(clip, 1280, 720, taps=3) 422 | Despline16M(clip, 1280, 720) 423 | Despline36M(clip, 1280, 720) 424 | 425 | Original header: 426 | 427 | DebilinearM is a wrapper function for the Debilinear and Debicubic plugins that masks parts of the frame that aren't upscaled, 428 | such as text overlays, and uses a regular resize kernel to downscale those areas. It works by downscaling the input 429 | clip to the target resolution with Debilinear or Debicubic, upscaling it again, comparing it to the original clip, 430 | and masking pixels that have a difference greater than the specified threshold. 431 | 432 | """ 433 | def DescaleM(src, w, h, thr=None, expand=None, inflate=None, descale_kernel=None, kernel=None, kernely=None, kerneluv=None, 434 | taps=None, tapsy=None, tapsuv=None, a1=None, a2=None, a1y=None, a2y=None, a1uv=None, a2uv=None, b=None, c=None, 435 | chroma=None, yuv444=None, showmask=None, ow=None, oh=None): 436 | 437 | # Type checking 438 | kwargsdict = {'src': [src, (vs.VideoNode,)], 'w': [w, (int,)], 'h': [h, (int,)], 'thr': [thr, (int,)], 439 | 'expand': [expand, (int,)], 'inflate': [inflate, (int,)],'descale_kernel': [descale_kernel, (str,)], 440 | 'kernel': [kernel, (str,)], 'kernely': [kernely, (str,)], 'kerneluv': [kerneluv, (str,)], 441 | 'taps': [taps, (int,)], 'tapsy': [tapsy, (int,)], 'tapsuv': [tapsuv, (int,)], 'a1': [a1, (int, float)], 442 | 'a2': [a2, (int, float)], 'a1y': [a1y, (int, float)], 'a2y': [a2y, (int, float)], 443 | 'a1uv': [a1uv, (int, float)], 'a2uv': [a2uv, (int, float)], 'b': [b, (int, float)], 444 | 'c': [c, (int, float)], 'chroma': [chroma, (bool,)], 'yuv444': [yuv444, (bool,)], 445 | 'showmask': [showmask, (int,)], 'ow': [ow, (int,)], 'oh': [oh, (int,)]} 446 | 447 | for k, v in kwargsdict.items(): 448 | if v[0] is not None and not isinstance(v[0], v[1]): 449 | raise TypeError('DescaleM: "{variable}" must be {types}!' 450 | .format(variable=k, types=' or '.join([TYPEDICT[t] for t in v[1]]))) 451 | 452 | # Set defaults 453 | if thr is None: 454 | thr = 10 455 | if expand is None: 456 | expand = 1 457 | if inflate is None: 458 | inflate = 2 459 | if chroma is None: 460 | chroma = True 461 | if src.format.num_planes == 1: 462 | chroma = False 463 | if yuv444 is None: 464 | yuv444 = False 465 | if showmask is None: 466 | showmask = 0 467 | if descale_kernel is None: 468 | descale_kernel = 'bilinear' 469 | elif descale_kernel.lower().startswith('de'): 470 | descale_kernel = descale_kernel[2:] 471 | if kernely is None: 472 | kernely = kernel 473 | if kerneluv is None: 474 | kerneluv = kernel 475 | if tapsy is None: 476 | tapsy = taps 477 | if tapsuv is None: 478 | tapsuv = taps 479 | if a1y is None: 480 | a1y = a1 481 | if a2y is None: 482 | a2y = a2 483 | if a1uv is None: 484 | a1uv = a1 485 | if a2uv is None: 486 | a2uv = a2 487 | if ow is None: 488 | ow = w 489 | if oh is None: 490 | oh = h 491 | 492 | # Value checking 493 | if thr < 0 or thr > 0xFF: 494 | raise ValueError('DebilinearM: "thr" must be in the range of 0 and 255!') 495 | if showmask < 0 or showmask > 2: 496 | raise ValueError('DebilinearM: "showmask" must be 0, 1 or 2!') 497 | if yuv444 and not chroma: 498 | raise ValueError('DebilinearM: "yuv444=True" and "chroma=False" cannot be used at the same time!') 499 | 500 | src_w = src.width 501 | src_h = src.height 502 | 503 | bits = src.format.bits_per_sample 504 | sample_type = src.format.sample_type 505 | 506 | if sample_type == vs.INTEGER: 507 | maxvalue = (1 << bits) - 1 508 | thr = thr * maxvalue // 0xFF 509 | else: 510 | thr /= (235 - 16) 511 | 512 | # Resizing 513 | src_y = core.std.ShufflePlanes(src, planes=0, colorfamily=vs.GRAY) 514 | if chroma: 515 | src_u = core.std.ShufflePlanes(src, planes=1, colorfamily=vs.GRAY) 516 | src_v = core.std.ShufflePlanes(src, planes=2, colorfamily=vs.GRAY) 517 | 518 | dbi = Resize(src_y, w, h, kernel=descale_kernel, a1=b, a2=c, taps=taps, invks=True) 519 | dbi2 = Resize(dbi, src_w, src_h, kernel=descale_kernel, a1=b, a2=c, taps=taps) 520 | if (w, h) != (ow, oh): 521 | dbi = Resize(dbi, ow, oh, kernel=kernely, taps=tapsy, a1=a1y, a2=a2y) 522 | 523 | if chroma and yuv444: 524 | rs = Resize(src_y, ow, oh, kernel=kernely, taps=tapsy, a1=a1y, a2=a2y) 525 | rs_u = Resize(src_u, ow, oh, kernel=kerneluv, taps=tapsuv, a1=a1uv, a2=a2uv, sx=0.25) 526 | rs_v = Resize(src_v, ow, oh, kernel=kerneluv, taps=tapsuv, a1=a1uv, a2=a2uv, sx=0.25) 527 | else: 528 | rs = Resize(src if chroma else src_y, ow, oh, kernel=kernely, taps=tapsy, a1=a1y, a2=a2y) 529 | 530 | # Masking 531 | diffmask = core.std.Expr([src_y, dbi2], 'x y - abs') 532 | if showmask != 2: 533 | diffmask = Resize(diffmask, ow, oh, kernel='bilinear') 534 | diffmask = core.std.Binarize(diffmask, threshold=thr) 535 | for _ in range(expand): 536 | diffmask = core.std.Maximum(diffmask, planes=0) 537 | for _ in range(inflate): 538 | diffmask = core.std.Inflate(diffmask, planes=0) 539 | 540 | if chroma: 541 | merged = core.std.ShufflePlanes([dbi,rs_u,rs_v] if yuv444 else [dbi,rs], planes=[0,0,0] if yuv444 else [0,1,2], colorfamily=vs.YUV) 542 | else: 543 | merged = dbi 544 | 545 | if showmask > 0: 546 | out = diffmask 547 | else: 548 | if yuv444: 549 | rs = core.std.ShufflePlanes([rs,merged], planes=[0,1,2], colorfamily=vs.YUV) 550 | out = core.std.MaskedMerge(merged, rs, diffmask, planes=0) 551 | 552 | return out 553 | 554 | 555 | # DescaleM alias 556 | DebilinearM = partial(DescaleM, descale_kernel='bilinear') 557 | 558 | # DescaleM alias 559 | DebicubicM = partial(DescaleM, descale_kernel='bicubic') 560 | 561 | # DescaleM alias 562 | DelanczosM = partial(DescaleM, descale_kernel='lanczos') 563 | 564 | # DescaleM alias 565 | Despline16M = partial(DescaleM, descale_kernel='spline16') 566 | 567 | # DescaleM alias 568 | Despline36M = partial(DescaleM, descale_kernel='spline36') 569 | 570 | 571 | """ 572 | Wrapper for fmtconv to scale each plane individually to the same size and fix chroma shift 573 | 574 | Will only produce correct results if input is YUV420 or YUV422 with left aligned chroma 575 | 576 | """ 577 | def Downscale444(clip, w=1280, h=720, kernely="spline36", kerneluv="spline16", tapsy=3, tapsuv=3, a1y=None, 578 | a1uv=None, a2y=None, a2uv=None, a3y=None, a3uv=None, invks=False, invkstaps=None): 579 | y = core.std.ShufflePlanes(clip, planes=0, colorfamily=vs.GRAY) 580 | u = core.std.ShufflePlanes(clip, planes=1, colorfamily=vs.GRAY) 581 | v = core.std.ShufflePlanes(clip, planes=2, colorfamily=vs.GRAY) 582 | y = Resize(y, w=w, h=h, kernel=kernely, taps=tapsy, a1=a1y, a2=a2y, a3=a3y, invks=invks, invkstaps=invkstaps) 583 | u = Resize(u, w=w, h=h, kernel=kerneluv, taps=tapsuv, a1=a1uv, a2=a2uv, a3=a3uv, sx=0.25) 584 | v = Resize(v, w=w, h=h, kernel=kerneluv, taps=tapsuv, a1=a1uv, a2=a2uv, a3=a3uv, sx=0.25) 585 | out = core.std.ShufflePlanes(clips=[y,u,v], planes=[0,0,0], colorfamily=vs.YUV) 586 | return out 587 | 588 | 589 | """ 590 | VapourSynth port of JIVTC. 591 | Original script by lovesyk (https://github.com/lovesyk/avisynth-scripts/blob/master/JIVTC.avsi) 592 | 593 | JIVTC applies inverse telecine in a way to minimize artifacts often seen on Japanese 594 | TV broadcasts followed by recalculating the fields that might still contain some. 595 | 596 | Dependencies: yadifmod, nnedi3 597 | 598 | clip src: Source clip. Has to be 60i (30000/1001). 599 | int pattern: First frame of any clean-combed-combed-clean-clean sequence. 600 | int threshold (10): This setting controls with how much probability one field has to 601 | look better than the other to recalculate the other one using it. 602 | Since there is no point dropping a field on a still (detail loss) 603 | or an action (both results will look bad) scene, keep this above 0. 604 | bool draft (false): If set to true, skip recalculate step (which means keep 50% of bad fields). 605 | clip ivtced: Can be used to supply a custom IVTCed clip. 606 | Keep in mind that the default IVTC process gets rid of 50% of 607 | bad fields which might be "restored" depending on your supplied clip. 608 | string bobber: Can be used to supply a custom bobber. 609 | The less information the bobber uses from the other field, 610 | the better the result will be. 611 | bool show (false): If set to true, mark those frames that were recalculated. 612 | 613 | """ 614 | def JIVTC(src, pattern, thr=10, draft=False, ivtced=None, bobber=None, show=False, tff=None): 615 | 616 | def calculate(n, f, ivtced, bobbed): 617 | diffprev = f[0].props.EvenDiff 618 | diffnext = f[1].props.OddDiff 619 | if diffnext > diffprev: 620 | prerecalc = core.std.SelectEvery(bobbed, 2, 0) 621 | else: 622 | prerecalc = core.std.SelectEvery(bobbed, 2, 1) 623 | if abs(diffprev - diffnext) * 0xFF < thr: 624 | return ivtced 625 | if show: 626 | prerecalc = core.text.Text(prerecalc, 'Recalculated') 627 | return prerecalc 628 | 629 | pattern = pattern % 5 630 | 631 | defivtc = core.std.SeparateFields(src, tff=tff).std.DoubleWeave() 632 | selectlist = [[0,3,6,8], [0,2,5,8], [0,2,4,7], [2,4,6,9], [1,4,6,8]] 633 | defivtc = core.std.SelectEvery(defivtc, 10, selectlist[pattern]) 634 | 635 | ivtced = defivtc if ivtced is None else ivtced 636 | if bobber is None: 637 | bobbed = core.yadifmod.Yadifmod(ivtced, edeint=core.nnedi3.nnedi3(ivtced, 2), order=0, mode=1) 638 | else: 639 | bobbed = bobber(ivtced) 640 | 641 | if src.fps_num != 30000 or src.fps_den != 1001: 642 | raise ValueError('JIVTC: This filter can only be used with 60i clips.') 643 | if bobbed.fps_num != 48000 or bobbed.fps_den != 1001: 644 | raise ValueError('JIVTC: The bobber you specified does not double the frame rate.') 645 | 646 | sep = core.std.SeparateFields(ivtced) 647 | even = core.std.SelectEvery(sep, 2, 0) 648 | odd = core.std.SelectEvery(sep, 2, 1) 649 | diffeven = core.std.PlaneStats(even, even.std.DuplicateFrames([0]), prop='Even') 650 | diffodd = core.std.PlaneStats(odd, odd.std.DeleteFrames([0]), prop='Odd') 651 | recalc = core.std.FrameEval(ivtced, partial(calculate, ivtced=ivtced, bobbed=bobbed), 652 | prop_src=[diffeven,diffodd]) 653 | 654 | inter = core.std.Interleave([ivtced, recalc]) 655 | selectlist = [[0,3,4,6], [0,2,5,6], [0,2,4,7], [0,2,4,7], [1,2,4,6]] 656 | final = core.std.SelectEvery(inter, 8, selectlist[pattern]) 657 | 658 | out = ivtced if draft else final 659 | out = core.std.SetFrameProp(out, prop='_FieldBased', intval=0) 660 | return out 661 | 662 | 663 | """ 664 | VapourSynth port of OverlayInter 665 | 666 | Based on the AviSynth script by Majin3 and the already ported 667 | ivtc_txt60mc by Firesledge that can be found inside havsfunc 668 | 669 | It's much faster than ivtc_txt60mc because you can limit processing 670 | to a small part of the clip. 671 | 672 | Original Header: 673 | # OverlayInter 0.1 by Majin3 (06.09.2012) 674 | # Converts 60i overlays (like scrolling credits) on top of telecined 24p video to 24p using motion interpolation. 675 | # Required: MVTools2, QTGMC (if not using a custom bobber) 676 | # int pattern: First frame of a clean-combed-combed-clean-clean sequence 677 | # int pos (0): Overlay position: 0: whole screen - 1: left - 2: top - 3: right - 4: bottom 678 | # int size (0): Overlay size in px from the corresponding position 679 | # bool show (false): Enable this to show the area selected by "pos" and "size" 680 | # bool draft (false): Enable this to speed up processing by using low-quality bobbing and motion interpolation 681 | # string bobber: A custom bobber if you do not wish to use "QTGMC(Preset="Very Slow", SourceMatch=2, Lossless=2)" 682 | # string ivtc: A custom IVTC if you do not wish to use simple IVTC based on "pattern" 683 | # Based on ivtc_txt60mc 1.1 by Firesledge 684 | 685 | """ 686 | def OverlayInter(src, pattern, pos=0, size=0, show=False, draft=False, bobber=None, ivtc=None, tff=None): 687 | 688 | try: 689 | import havsfunc as haf 690 | except ImportError: 691 | raise ImportError('OverlayInter: havsfunc not found. Download it here: https://github.com/HomeOfVapourSynthEvolution/havsfunc') 692 | 693 | if bobber is None and not isinstance(tff, bool): 694 | raise TypeError('OverlayInter: "tff" must be set. Setting tff to True means top field first. False means bottom field first') 695 | 696 | field_ref = (pattern * 2) % 5 697 | invpos = (5 - field_ref) % 5 698 | pattern %= 5 699 | 700 | croplist = [[0,0,0,0], [size,0,0,0], [0,0,size,0], [0,size,0,0], [0,0,0,size]] 701 | keep = core.std.CropRel(src, *croplist[pos]) 702 | 703 | croplist = [[0,0,0,0], [0,src.width-size-4,0,0], [0,0,0,src.height-size-4], 704 | [src.width-size-4,0,0,0], [0,0,src.height-size-4,0]] 705 | bobbed = core.std.CropRel(src, *croplist[pos]) 706 | if draft: 707 | bobbed = haf.Bob(bobbed, tff=tff) 708 | elif bobber is None: 709 | bobbed = haf.QTGMC(bobbed, Preset='very slow', SourceMatch=3, Lossless=2, TFF=tff) 710 | else: 711 | bobbed = bobber(bobbed) 712 | 713 | if ivtc is None: 714 | ivtclist = [[0,3,6,8], [0,2,5,8], [0,2,4,7], [2,4,6,9], [1,4,6,8]] 715 | ivtc = keep.std.SeparateFields(tff=tff).std.DoubleWeave().std.SelectEvery(10, ivtclist[pattern]) 716 | else: 717 | ivtc = ivtc(keep) 718 | 719 | if invpos > 1: 720 | clean = core.std.AssumeFPS(bobbed[0] + core.std.SelectEvery(bobbed, 5, [6 - invpos]), 721 | fpsnum=12000, fpsden=1001) 722 | else: 723 | clean = core.std.SelectEvery(bobbed, 5, [1 - invpos]) 724 | if invpos > 3: 725 | jitter = core.std.AssumeFPS(bobbed[0] + core.std.SelectEvery(bobbed, 5, [4 - invpos, 8 - invpos]), 726 | fpsnum=24000, fpsden=1001) 727 | else: 728 | jitter = core.std.SelectEvery(bobbed, 5, [3 - invpos, 4 - invpos]) 729 | 730 | jsup = core.mv.Super(jitter) 731 | vecsup = haf.DitherLumaRebuild(jitter, s0=1).mv.Super(rfilter=4) 732 | vectb = core.mv.Analyse(jsup if draft else vecsup, overlap=0 if draft else 4, blksize=16, isb=True) 733 | if not draft: 734 | vectb = core.mv.Recalculate(vecsup, vectb, blksize=8, overlap=2) 735 | vectf = core.mv.Analyse(jsup if draft else vecsup, overlap=0 if draft else 4, blksize=16, isb=False) 736 | if not draft: 737 | vectf = core.mv.Recalculate(vecsup, vectf, blksize=8, overlap=2) 738 | comp = core.mv.FlowInter(jitter, jsup, vectb, vectf) 739 | fixed = core.std.SelectEvery(comp, 2, 0) 740 | fixed = core.std.Interleave([clean,fixed])[invpos // 2:] 741 | 742 | croplist = [[0,0,0,0], [0,4,0,0], [0,0,0,4], [4,0,0,0], [0,0,4,0]] 743 | fixed = core.std.CropRel(fixed, *croplist[pos]) 744 | 745 | if show: 746 | maxvalue = (1 << src.format.bits_per_sample) - 1 747 | offset = 32 * maxvalue // 0xFF if fixed.format.sample_type == vs.INTEGER else 32 / 0xFF 748 | fixed = core.std.Expr(fixed, ['','x {} +'.format(offset),'']) 749 | 750 | if pos == 1: 751 | out = core.std.StackHorizontal([fixed,ivtc]) 752 | elif pos == 2: 753 | out = core.std.StackVertical([fixed,ivtc]) 754 | elif pos == 3: 755 | out = core.std.StackHorizontal([ivtc,fixed]) 756 | elif pos == 4: 757 | out = core.std.StackVertical([ivtc,fixed]) 758 | else: 759 | out = fixed 760 | out = core.std.SetFrameProp(out, prop='_FieldBased', intval=0) 761 | 762 | return out 763 | 764 | 765 | """ 766 | VapourSynth port of AutoDeblock2. Original script by joletb, vinylfreak89, eXmendiC and Gebbi. 767 | 768 | The purpose of this script is to automatically remove MPEG2 artifacts. 769 | 770 | Supports 8..16 bit integer YUV formats 771 | 772 | """ 773 | def AutoDeblock(src, edgevalue=24, db1=1, db2=6, db3=15, deblocky=True, deblockuv=True, debug=False, redfix=False, 774 | fastdeblock=False, adb1=3, adb2=4, adb3=8, adb1d=2, adb2d=7, adb3d=11, planes=None): 775 | 776 | try: 777 | import havsfunc as haf 778 | except ImportError: 779 | raise ImportError('AutoDeblock: havsfunc not found. Download it here: https://github.com/HomeOfVapourSynthEvolution/havsfunc') 780 | 781 | if src.format.color_family not in [vs.YUV]: 782 | raise TypeError("AutoDeblock: src must be YUV color family!") 783 | 784 | if src.format.bits_per_sample < 8 or src.format.bits_per_sample > 16 or src.format.sample_type != vs.INTEGER: 785 | raise TypeError("AutoDeblock: src must be between 8 and 16 bit integer format") 786 | 787 | # Scale values to handle high bit depths 788 | shift = src.format.bits_per_sample - 8 789 | edgevalue = edgevalue << shift 790 | maxvalue = (1 << src.format.bits_per_sample) - 1 791 | 792 | # Scales the output of PlaneStats (which is a float, 0-1) to 8 bit values. 793 | # We scale to 8 bit because all thresholds/parameters for this function are 794 | # specified in an 8-bit scale. 795 | # All processing still happens in the native bit depth of the input format. 796 | def to8bit(f): 797 | return f * 255 798 | 799 | def sub_props(src, f, name): 800 | 801 | OrigDiff_str = str(to8bit(f[0].props.OrigDiff)) 802 | YNextDiff_str = str(to8bit(f[1].props.YNextDiff)) 803 | return core.sub.Subtitle(src, name + f"\nOrigDiff: {OrigDiff_str}\nYNextDiff: {YNextDiff_str}") 804 | 805 | def eval_deblock_strength(n, f, fastdeblock, debug, unfiltered, fast, weakdeblock, 806 | mediumdeblock, strongdeblock): 807 | unfiltered = sub_props(unfiltered, f, "unfiltered") if debug else unfiltered 808 | out = unfiltered 809 | if fastdeblock: 810 | if to8bit(f[0].props.OrigDiff) > adb1 and to8bit(f[1].props.YNextDiff) > adb1d: 811 | return sub_props(fast, f, "deblock") if debug else fast 812 | else: 813 | return unfiltered 814 | if to8bit(f[0].props.OrigDiff) > adb1 and to8bit(f[1].props.YNextDiff) > adb1d: 815 | out = sub_props(weakdeblock, f, "weakdeblock") if debug else weakdeblock 816 | if to8bit(f[0].props.OrigDiff) > adb2 and to8bit(f[1].props.YNextDiff) > adb2d: 817 | out = sub_props(mediumdeblock, f, "mediumdeblock") if debug else mediumdeblock 818 | if to8bit(f[0].props.OrigDiff) > adb3 and to8bit(f[1].props.YNextDiff) > adb3d: 819 | out = sub_props(strongdeblock, f, "strongdeblock") if debug else strongdeblock 820 | return out 821 | 822 | def fix_red(n, f, unfiltered, autodeblock): 823 | if (to8bit(f[0].props.YAverage) > 50 and to8bit(f[0].props.YAverage) < 130 824 | and to8bit(f[1].props.UAverage) > 95 and to8bit(f[1].props.UAverage) < 130 825 | and to8bit(f[2].props.VAverage) > 130 and to8bit(f[2].props.VAverage) < 155): 826 | return unfiltered 827 | return autodeblock 828 | 829 | if redfix and fastdeblock: 830 | raise ValueError('AutoDeblock: You cannot set both "redfix" and "fastdeblock" to True!') 831 | 832 | if planes is None: 833 | planes = [] 834 | if deblocky: planes.append(0) 835 | if deblockuv: planes.extend([1,2]) 836 | 837 | orig = core.std.Prewitt(src) 838 | orig = core.std.Expr(orig, f"x {edgevalue} >= {maxvalue} x ?") 839 | orig_d = orig.rgvs.RemoveGrain(4).rgvs.RemoveGrain(4) 840 | 841 | predeblock = haf.Deblock_QED(src.rgvs.RemoveGrain(2).rgvs.RemoveGrain(2)) 842 | fast = core.dfttest.DFTTest(predeblock, tbsize=1) 843 | 844 | unfiltered = src 845 | weakdeblock = core.dfttest.DFTTest(predeblock, sigma=db1, tbsize=1, planes=planes) 846 | mediumdeblock = core.dfttest.DFTTest(predeblock, sigma=db2, tbsize=1, planes=planes) 847 | strongdeblock = core.dfttest.DFTTest(predeblock, sigma=db3, tbsize=1, planes=planes) 848 | 849 | difforig = core.std.PlaneStats(orig, orig_d, prop='Orig') 850 | diffnext = core.std.PlaneStats(src, src.std.DeleteFrames([0]), prop='YNext') 851 | autodeblock = core.std.FrameEval(unfiltered, partial(eval_deblock_strength, fastdeblock=fastdeblock, 852 | debug=debug, unfiltered=unfiltered, fast=fast, weakdeblock=weakdeblock, 853 | mediumdeblock=mediumdeblock, strongdeblock=strongdeblock), 854 | prop_src=[difforig,diffnext]) 855 | 856 | if redfix: 857 | src = core.std.PlaneStats(src, prop='Y') 858 | src_u = core.std.PlaneStats(src, plane=1, prop='U') 859 | src_v = core.std.PlaneStats(src, plane=2, prop='V') 860 | autodeblock = core.std.FrameEval(unfiltered, partial(fix_red, unfiltered=unfiltered, 861 | autodeblock=autodeblock), prop_src=[src,src_u,src_v]) 862 | 863 | return autodeblock 864 | 865 | 866 | """ 867 | Basically a wrapper for std.Trim and std.Splice that recreates the functionality of 868 | AviSynth's ReplaceFramesSimple (http://avisynth.nl/index.php/RemapFrames) 869 | that was part of the plugin RemapFrames by James D. Lin 870 | 871 | Usage: ReplaceFrames(clipa, clipb, mappings="[200 300] [1100 1150] 400 1234") 872 | 873 | This will replace frames 200..300, 1100..1150, 400 and 1234 from clipa with 874 | the corresponding frames from clipb. 875 | 876 | """ 877 | def ReplaceFrames(clipa, clipb, mappings=None, filename=None): 878 | 879 | if not isinstance(clipa, vs.VideoNode): 880 | raise TypeError('ReplaceFrames: "clipa" must be a clip!') 881 | if not isinstance(clipb, vs.VideoNode): 882 | raise TypeError('ReplaceFrames: "clipb" must be a clip!') 883 | if clipa.format.id != clipb.format.id: 884 | raise TypeError('ReplaceFrames: "clipa" and "clipb" must have the same format!') 885 | if filename is not None and not isinstance(filename, str): 886 | raise TypeError('ReplaceFrames: "filename" must be a string!') 887 | if mappings is not None and not isinstance(mappings, str): 888 | raise TypeError('ReplaceFrames: "mappings" must be a string!') 889 | if mappings is None: 890 | mappings = '' 891 | 892 | if filename: 893 | with open(filename, 'r') as mf: 894 | mappings += '\n{}'.format(mf.read()) 895 | # Some people used this as separators and wondered why it wasn't working 896 | mappings = mappings.replace(',', ' ').replace(':', ' ') 897 | 898 | frames = re.findall('\d+(?!\d*\s*\d*\s*\d*\])', mappings) 899 | ranges = re.findall('\[\s*\d+\s+\d+\s*\]', mappings) 900 | maps = [] 901 | for range_ in ranges: 902 | maps.append([int(x) for x in range_.strip('[ ]').split()]) 903 | for frame in frames: 904 | maps.append([int(frame), int(frame)]) 905 | 906 | for start, end in maps: 907 | if start > end: 908 | raise ValueError('ReplaceFrames: Start frame is bigger than end frame: [{} {}]'.format(start, end)) 909 | if end >= clipa.num_frames or end >= clipb.num_frames: 910 | raise ValueError('ReplaceFrames: End frame too big, one of the clips has less frames: {}'.format(end)) 911 | 912 | out = clipa 913 | for start, end in maps: 914 | temp = clipb[start:end+1] 915 | if start != 0: 916 | temp = out[:start] + temp 917 | if end < out.num_frames - 1: 918 | temp = temp + out[end+1:] 919 | out = temp 920 | return out 921 | 922 | 923 | # ReplaceFrames alias 924 | ReplaceFramesSimple = ReplaceFrames 925 | 926 | # ReplaceFrames alias 927 | rfs = ReplaceFrames 928 | 929 | 930 | """ 931 | This overlays a clip onto another. 932 | Default matrix for RGB -> YUV conversion is "709" 933 | Use "601" if you want to mimic AviSynth's Overlay() 934 | overlay should be a list of [video, mask] or a path string to an RGBA file 935 | If you specifiy a clip instead then a mask with max value will be generated 936 | (RGBA videos opened by ffms2 with alpha=True are already such a list) 937 | """ 938 | def InsertSign(clip, overlay, start, end=None, matrix='709'): 939 | 940 | if start < 0: 941 | raise ValueError('InsertSign: "start" must not be lower than 0!') 942 | if isinstance(overlay, str): 943 | overlay = core.ffms2.Source(overlay, alpha=True) 944 | if not isinstance(overlay, list): 945 | overlay = [overlay, None] 946 | if end is None: 947 | end = start + overlay[0].num_frames 948 | else: 949 | end += 1 950 | if end > clip.num_frames: 951 | end = clip.num_frames 952 | if start >= end: 953 | raise ValueError('InsertSign: "start" must be smaller than or equal to "end"!') 954 | if matrix == '601': 955 | matrix = '470bg' 956 | clip_cf = clip.format.color_family 957 | overlay_cf = overlay[0].format.color_family 958 | 959 | before = clip[:start] if start != 0 else None 960 | middle = clip[start:end] 961 | after = clip[end:] if end != clip.num_frames else None 962 | 963 | matrix_s = None 964 | matrix_in_s = None 965 | if clip_cf == vs.YUV and overlay_cf == vs.RGB: 966 | matrix_s = matrix 967 | if overlay_cf == vs.YUV and clip_cf == vs.RGB: 968 | matrix_in_s = matrix 969 | sign = core.resize.Spline36(overlay[0], clip.width, clip.height, format=clip.format.id, 970 | matrix_s=matrix_s, matrix_in_s=matrix_in_s, 971 | dither_type='error_diffusion') 972 | 973 | if overlay[1] is None: 974 | overlay[1] = core.std.BlankClip(sign, format=vs.GRAY8, color=255) 975 | mask = core.resize.Bicubic(overlay[1], clip.width, clip.height) 976 | mask = Depth(mask, bits=clip.format.bits_per_sample, range='full', range_in='full') 977 | 978 | middle = core.std.MaskedMerge(middle, sign, mask) 979 | 980 | out = middle 981 | if before is not None: 982 | out = before + out 983 | if after is not None: 984 | out = out + after 985 | return out 986 | 987 | 988 | """ 989 | Downscale only lineart with an inverted kernel and interpolate 990 | it back to its original resolution with NNEDI3. 991 | 992 | Parts of higher resolution like credits are protected by a mask. 993 | 994 | Basic idea stolen from a script made by Daiz. 995 | 996 | """ 997 | def DescaleAA(src, w=1280, h=720, thr=10, kernel='bilinear', b=1/3, c=1/3, taps=3, 998 | expand=3, inflate=3, showmask=False): 999 | 1000 | try: 1001 | import nnedi3_rpow2 as nnp2 1002 | except ImportError: 1003 | raise ImportError('DescaleAA: nnedi3_rpow2 not found. Download it here: https://gist.github.com/4re/342624c9e1a144a696c6') 1004 | 1005 | if kernel.lower().startswith('de'): 1006 | kernel = kernel[2:] 1007 | 1008 | ow = src.width 1009 | oh = src.height 1010 | 1011 | bits = src.format.bits_per_sample 1012 | sample_type = src.format.sample_type 1013 | 1014 | if sample_type == vs.INTEGER: 1015 | maxvalue = (1 << bits) - 1 1016 | thr = thr * maxvalue // 0xFF 1017 | else: 1018 | maxvalue = 1 1019 | thr /= (235 - 16) 1020 | 1021 | # Fix lineart 1022 | src_y = core.std.ShufflePlanes(src, planes=0, colorfamily=vs.GRAY) 1023 | deb = Resize(src_y, w, h, kernel=kernel, a1=b, a2=c, taps=taps, invks=True) 1024 | sharp = nnp2.nnedi3_rpow2(deb, 2, ow, oh) 1025 | thrlow = 4 * maxvalue // 0xFF if sample_type == vs.INTEGER else 4 / 0xFF 1026 | thrhigh = 24 * maxvalue // 0xFF if sample_type == vs.INTEGER else 24 / 0xFF 1027 | edgemask = core.std.Prewitt(sharp, planes=0) 1028 | edgemask = core.std.Expr(edgemask, "x {thrhigh} >= {maxvalue} x {thrlow} <= 0 x ? ?" 1029 | .format(thrhigh=thrhigh, maxvalue=maxvalue, thrlow=thrlow)) 1030 | if kernel == "bicubic" and c >= 0.7: 1031 | edgemask = core.std.Maximum(edgemask, planes=0) 1032 | sharp = core.resize.Point(sharp, format=src.format.id) 1033 | 1034 | # Restore true 1080p 1035 | deb_upscale = Resize(deb, ow, oh, kernel=kernel, a1=b, a2=c, taps=taps) 1036 | diffmask = core.std.Expr([src_y, deb_upscale], 'x y - abs') 1037 | for _ in range(expand): 1038 | diffmask = core.std.Maximum(diffmask, planes=0) 1039 | for _ in range(inflate): 1040 | diffmask = core.std.Inflate(diffmask, planes=0) 1041 | 1042 | mask = core.std.Expr([diffmask,edgemask], 'x {thr} >= 0 y ?'.format(thr=thr)) 1043 | mask = mask.std.Inflate().std.Deflate() 1044 | out = core.std.MaskedMerge(src, sharp, mask, planes=0) 1045 | 1046 | if showmask: 1047 | out = mask 1048 | 1049 | return out 1050 | 1051 | 1052 | # Legacy DescaleAA alias 1053 | def ProtectedDebiXAA(src, w=1280, h=720, thr=10, expand=3, inflate=3, 1054 | bicubic=False, b=1/3, c=1/3, showmask=False, bits=None): 1055 | 1056 | if bicubic: 1057 | return DescaleAA(src, w=w, h=h, thr=thr, kernel='bicubic', b=b, c=c, taps=None, 1058 | expand=expand, inflate=inflate, showmask=showmask) 1059 | else: 1060 | return DescaleAA(src, w=w, h=h, thr=thr, kernel='bilinear', b=None, c=None, taps=None, 1061 | expand=expand, inflate=inflate, showmask=showmask) 1062 | 1063 | 1064 | """ 1065 | VapourSynth port of AviSynth's maa2 (https://github.com/AviSynth/avs-scripts) 1066 | 1067 | Works on any bitdepth 1068 | 1069 | """ 1070 | def maa(src, mask=None, chroma=None, ss=None, aa=None, aac=None, show=None): 1071 | 1072 | try: 1073 | import mvsfunc as mvf 1074 | except ImportError: 1075 | raise ImportError('maa: mvsfunc not found. Download it here: https://github.com/HomeOfVapourSynthEvolution/mvsfunc') 1076 | 1077 | def SangNomAA(src, ss=2.0, aa=48, aac=None): 1078 | ss_w = round(src.width * ss / 4) * 4 1079 | ss_h = round(src.height * ss / 4) * 4 1080 | out = core.resize.Spline36(src, ss_w, ss_h).std.Transpose() 1081 | out = core.sangnom.SangNom(out, aa=aa if aac is None else [aa, aac, aac]).std.Transpose() 1082 | out = core.sangnom.SangNom(out, aa=aa if aac is None else [aa, aac, aac]) 1083 | out = core.resize.Spline36(out, src.width, src.height) 1084 | return out 1085 | 1086 | # Type checking 1087 | kwargsdict = {'src': [src, (vs.VideoNode,)], 'mask': [mask, (int,)], 'ss': [ss, (int, float)], 1088 | 'aa': [aa, (int,)], 'aac': [aac, (int,)], 'show': [show, (bool,)]} 1089 | 1090 | for k, v in kwargsdict.items(): 1091 | if v[0] is not None and not isinstance(v[0], v[1]): 1092 | raise TypeError('maa: "{variable}" must be {types}!' 1093 | .format(variable=k, types=' or '.join([TYPEDICT[t] for t in v[1]]))) 1094 | 1095 | # Set defaults 1096 | if mask is None: 1097 | mask = 1 1098 | if chroma is None: 1099 | chroma = False 1100 | if ss is None: 1101 | ss = 2.0 1102 | if aa is None: 1103 | aa = 48 1104 | if aac is None: 1105 | aac = aa - 8 1106 | if show is None: 1107 | show = False 1108 | 1109 | # Value checking 1110 | if mask < -0xFF or mask > 1: 1111 | raise ValueError('maa: "mask" must be between -255 and 1!') 1112 | if ss <= 0: 1113 | raise ValueError('maa: "ss" must be > 0!') 1114 | 1115 | bits = src.format.bits_per_sample 1116 | sample_type = src.format.sample_type 1117 | maxvalue = (1 << bits) - 1 1118 | 1119 | if sample_type == vs.INTEGER: 1120 | mthresh = -mask * maxvalue // 0xFF if mask < 0 else 7 * maxvalue // 0xFF 1121 | mthreshc = mthresh - 6 * maxvalue // 0xFF 1122 | else: 1123 | mthresh = -mask / 0xFF if mask < 0 else 7 / 0xFF 1124 | mthreshc = mthresh - 6 / 0xFF 1125 | 1126 | if src.format.num_planes == 1: 1127 | chroma = False 1128 | 1129 | if mask != 0: 1130 | m = core.std.Sobel(mvf.GetPlane(src, 0)) 1131 | m = core.std.Binarize(m, mthresh) 1132 | if chroma: 1133 | mu = core.std.Sobel(mvf.GetPlane(src, 1)) 1134 | mu = core.std.Binarize(mu, mthreshc) 1135 | mv = core.std.Sobel(mvf.GetPlane(src, 2)) 1136 | mv = core.std.Binarize(mv, mthreshc) 1137 | m = core.std.ShufflePlanes([m,mu,mv], planes=[0,0,0], colorfamily=vs.YUV) 1138 | if not chroma: 1139 | c_aa = SangNomAA(mvf.GetPlane(src, 0), ss, aa) 1140 | else: 1141 | c_aa = SangNomAA(src, ss, aa, aac) 1142 | 1143 | if not chroma and src.format.num_planes != 1: 1144 | c_aa = core.std.ShufflePlanes([c_aa, mvf.GetPlane(src, 1), mvf.GetPlane(src, 2)], planes=[0,0,0], colorfamily=vs.YUV) 1145 | if mask == 0: 1146 | out = c_aa 1147 | elif show: 1148 | out = m 1149 | else: 1150 | out = core.std.MaskedMerge(src, c_aa, m) 1151 | return out 1152 | 1153 | 1154 | """ 1155 | VapourSynth port of TemporalDegrain (http://avisynth.nl/index.php/Temporal_Degrain) 1156 | 1157 | (only 8 bit YUV input) 1158 | 1159 | Differences: 1160 | 1161 | - all keyword arguments are now lowercase 1162 | - hq > 0 is not implemented 1163 | - gpu=True is not implemented 1164 | 1165 | """ 1166 | def TemporalDegrain(input_, denoise=None, gpu=False, sigma=16, bw=16, bh=16, pel=2, 1167 | blksize=8, ov=None, degrain=2, limit=255, sad1=400, sad2=300, hq=0): 1168 | 1169 | try: 1170 | import havsfunc as haf 1171 | except ImportError: 1172 | raise ImportError('TemporalDegrain: havsfunc not found. Download it here: https://github.com/HomeOfVapourSynthEvolution/havsfunc') 1173 | 1174 | if not isinstance(input_, vs.VideoNode): 1175 | raise TypeError('TemporalDegrain: "input_" must be a clip!') 1176 | if denoise is not None and not isinstance(denoise, vs.VideoNode): 1177 | raise TypeError('TemporalDegrain: "denoise" must be a clip!') 1178 | if not isinstance(gpu, bool): 1179 | raise TypeError('TemporalDegrain: "gpu" must be a bool!') 1180 | if gpu: 1181 | raise NotImplementedError('TemporalDegrain: "gpu=True" is not implemented!') 1182 | if not isinstance(sigma, int): 1183 | raise TypeError('TemporalDegrain: "sigma" must be an int!') 1184 | if not isinstance(bw, int): 1185 | raise TypeError('TemporalDegrain: "bw" must be an int!') 1186 | if not isinstance(bh, int): 1187 | raise TypeError('TemporalDegrain: "bh" must be an int!') 1188 | if not isinstance(pel, int): 1189 | raise TypeError('TemporalDegrain: "pel" must be an int!') 1190 | if not isinstance(blksize, int): 1191 | raise TypeError('TemporalDegrain: "blksize" must be an int!') 1192 | if ov is not None and not isinstance(ov, int): 1193 | raise TypeError('TemporalDegrain: "ov" must be an int!') 1194 | if not isinstance(degrain, int): 1195 | raise TypeError('TemporalDegrain: "degrain" must be an int!') 1196 | if not isinstance(limit, int): 1197 | raise TypeError('TemporalDegrain: "limit" must be an int!') 1198 | if not isinstance(sad1, int): 1199 | raise TypeError('TemporalDegrain: "sad1" must be an int!') 1200 | if not isinstance(sad2, int): 1201 | raise TypeError('TemporalDegrain: "sad2" must be an int!') 1202 | if not isinstance(hq, int): 1203 | raise TypeError('TemporalDegrain: "hq" must be an int!') 1204 | if hq > 0: 1205 | raise NotImplementedError('TemporalDegrain: "hq" > 0 is not implemented!') 1206 | 1207 | o = input_ 1208 | s2 = int(sigma * 0.625) 1209 | s3 = int(sigma * 0.375) 1210 | s4 = int(sigma * 0.250) 1211 | ow = int(bw / 2) 1212 | oh = int(bh / 2) 1213 | ov = int(blksize / 2) if not ov or ov*2 > blksize else ov 1214 | 1215 | if denoise: 1216 | filter_ = denoise 1217 | elif gpu: 1218 | filter_ = o.FFT3DGPU(sigma=sigma, sigma2=s2 , sigma3=s3, sigma4=s4, bt=4, bw=bw, bh=bh, ow=ow, oh=oh) # not implemented 1219 | else: 1220 | filter_ = core.fft3dfilter.FFT3DFilter(o, sigma=sigma, sigma2=s2, sigma3=s3, sigma4=s4, bt=4, bw=bw, bh=bh, ow=ow, oh=oh) 1221 | if hq >= 1: 1222 | filter_ = filter_.HQdn3D(4,3,6,3) # not implemented 1223 | 1224 | spat = filter_ 1225 | spatd = core.std.MakeDiff(o, spat) 1226 | 1227 | srch = filter_ 1228 | srch_super = core.mv.Super(filter_, pel=pel) 1229 | 1230 | if degrain == 3: 1231 | bvec3 = core.mv.Analyse(srch_super, isb=True, delta=3, blksize=blksize, overlap=ov) 1232 | else: 1233 | bvec3 = core.std.BlankClip() 1234 | if degrain >= 2: 1235 | bvec2 = core.mv.Analyse(srch_super, isb=True, delta=2, blksize=blksize, overlap=ov) 1236 | else: 1237 | bvec2 = core.std.BlankClip() 1238 | bvec1 = core.mv.Analyse(srch_super, isb=True, delta=1, blksize=blksize, overlap=ov) 1239 | fvec1 = core.mv.Analyse(srch_super, isb=False, delta=1, blksize=blksize, overlap=ov) 1240 | if degrain >= 2: 1241 | fvec2 = core.mv.Analyse(srch_super, isb=False, delta=2, blksize=blksize, overlap=ov) 1242 | else: 1243 | fvec2 = core.std.BlankClip() 1244 | if degrain == 3: 1245 | fvec3 = core.mv.Analyse(srch_super, isb=False, delta=3, blksize=blksize, overlap=ov) 1246 | else: 1247 | fvec3 = core.std.BlankClip() 1248 | 1249 | o_super = core.mv.Super(o, pel=2, levels=1) 1250 | 1251 | if degrain == 3: 1252 | nr1 = core.mv.Degrain3(o, o_super, bvec1, fvec1, bvec2, fvec2, bvec3, fvec3, thsad=sad1, limit=limit) 1253 | elif degrain == 2: 1254 | nr1 = core.mv.Degrain2(o, o_super, bvec1, fvec1, bvec2, fvec2, thsad=sad1, limit=limit) 1255 | else: 1256 | nr1 = core.mv.Degrain1(o, o_super, bvec1, fvec1, thsad=sad1, limit=limit) 1257 | nr1d = core.std.MakeDiff(o, nr1) 1258 | 1259 | dd = core.std.Expr([spatd, nr1d], 'x 128 - abs y 128 - abs < x y ?') 1260 | nr1x = core.std.MakeDiff(o, dd, planes=0) 1261 | 1262 | nr1x_super = core.mv.Super(nr1x, pel=2, levels=1) 1263 | 1264 | if degrain == 3: 1265 | nr2 = core.mv.Degrain3(nr1x, nr1x_super, bvec1, fvec1, bvec2, fvec2, bvec3, fvec3, thsad=sad2, limit=limit) 1266 | elif degrain == 2: 1267 | nr2 = core.mv.Degrain2(nr1x, nr1x_super, bvec1, fvec1, bvec2, fvec2, thsad=sad2, limit=limit) 1268 | else: 1269 | nr2 = core.mv.Degrain1(nr1x, nr1x_super, bvec1, fvec1, thsad=sad2, limit=limit) 1270 | 1271 | if hq >= 2: 1272 | nr2.HQDn3D(0,0,4,1) # not implemented 1273 | 1274 | s = haf.MinBlur(nr2, 1, 0) 1275 | alld = core.std.MakeDiff(o, nr2) 1276 | temp = core.rgvs.RemoveGrain(s, [11, 0]) 1277 | ssd = core.std.MakeDiff(s, temp) 1278 | ssdd = core.rgvs.Repair(ssd, alld, 1) 1279 | ssdd = core.std.Expr([ssdd, ssd], 'x 128 - abs y 128 - abs < x y ?') 1280 | 1281 | output = core.std.MergeDiff(nr2, ssdd, planes=0) 1282 | return output 1283 | 1284 | 1285 | # Helpers 1286 | 1287 | # Wrapper with fmtconv syntax that tries to use the internal resizers whenever it is possible 1288 | def Resize(src, w, h, sx=None, sy=None, sw=None, sh=None, kernel='spline36', taps=None, a1=None, 1289 | a2=None, a3=None, invks=None, invkstaps=None, fulls=None, fulld=None): 1290 | 1291 | bits = src.format.bits_per_sample 1292 | 1293 | if (src.width, src.height, fulls) == (w, h, fulld): 1294 | return src 1295 | 1296 | if kernel is None: 1297 | kernel = 'spline36' 1298 | kernel = kernel.lower() 1299 | 1300 | if invks and kernel == 'bilinear' and hasattr(core, 'unresize') and invkstaps is None: 1301 | return core.unresize.Unresize(src, w, h, src_left=sx, src_top=sy) 1302 | if invks and kernel in ['bilinear', 'bicubic', 'lanczos', 'spline16', 'spline36', 'spline64'] and hasattr(core, 'descale') and invkstaps is None: 1303 | return Descale(src, w, h, kernel=kernel, b=a1, c=a2, taps=taps) 1304 | if not invks: 1305 | if kernel == 'bilinear': 1306 | return core.resize.Bilinear(src, w, h, range=fulld, range_in=fulls, src_left=sx, src_top=sy, 1307 | src_width=sw, src_height=sh) 1308 | if kernel == 'bicubic': 1309 | return core.resize.Bicubic(src, w, h, range=fulld, range_in=fulls, filter_param_a=a1, filter_param_b=a2, 1310 | src_left=sx, src_top=sy, src_width=sw, src_height=sh) 1311 | if kernel == 'spline16': 1312 | return core.resize.Spline16(src, w, h, range=fulld, range_in=fulls, src_left=sx, src_top=sy, 1313 | src_width=sw, src_height=sh) 1314 | if kernel == 'spline36': 1315 | return core.resize.Spline36(src, w, h, range=fulld, range_in=fulls, src_left=sx, src_top=sy, 1316 | src_width=sw, src_height=sh) 1317 | if kernel == 'spline64': 1318 | return core.resize.Spline64(src, w, h, range=fulld, range_in=fulls, src_left=sx, src_top=sy, 1319 | src_width=sw, src_height=sh) 1320 | if kernel == 'lanczos': 1321 | return core.resize.Lanczos(src, w, h, range=fulld, range_in=fulls, filter_param_a=taps, 1322 | src_left=sx, src_top=sy, src_width=sw, src_height=sh) 1323 | return Depth(core.fmtc.resample(src, w, h, sx=sx, sy=sy, sw=sw, sh=sh, kernel=kernel, taps=taps, 1324 | a1=a1, a2=a2, a3=a3, invks=invks, invkstaps=invkstaps, fulls=fulls, fulld=fulld), bits) 1325 | 1326 | 1327 | def Debilinear(src, width, height, yuv444=False, gray=False, chromaloc=None): 1328 | return Descale(src, width, height, kernel='bilinear', taps=None, b=None, c=None, yuv444=yuv444, gray=gray, chromaloc=chromaloc) 1329 | 1330 | def Debicubic(src, width, height, b=0.0, c=0.5, yuv444=False, gray=False, chromaloc=None): 1331 | return Descale(src, width, height, kernel='bicubic', taps=None, b=b, c=c, yuv444=yuv444, gray=gray, chromaloc=chromaloc) 1332 | 1333 | def Delanczos(src, width, height, taps=3, yuv444=False, gray=False, chromaloc=None): 1334 | return Descale(src, width, height, kernel='lanczos', taps=taps, b=None, c=None, yuv444=yuv444, gray=gray, chromaloc=chromaloc) 1335 | 1336 | def Despline16(src, width, height, yuv444=False, gray=False, chromaloc=None): 1337 | return Descale(src, width, height, kernel='spline16', taps=None, b=None, c=None, yuv444=yuv444, gray=gray, chromaloc=chromaloc) 1338 | 1339 | def Despline36(src, width, height, yuv444=False, gray=False, chromaloc=None): 1340 | return Descale(src, width, height, kernel='spline36', taps=None, b=None, c=None, yuv444=yuv444, gray=gray, chromaloc=chromaloc) 1341 | 1342 | def Despline64(src, width, height, yuv444=False, gray=False, chromaloc=None): 1343 | return Descale(src, width, height, kernel='spline64', taps=None, b=None, c=None, yuv444=yuv444, gray=gray, chromaloc=chromaloc) 1344 | 1345 | 1346 | def Descale(src, width, height, kernel=None, custom_kernel=None, taps=None, b=None, c=None, yuv444=False, gray=False, chromaloc=None): 1347 | src_f = src.format 1348 | src_cf = src_f.color_family 1349 | src_st = src_f.sample_type 1350 | src_bits = src_f.bits_per_sample 1351 | src_sw = src_f.subsampling_w 1352 | src_sh = src_f.subsampling_h 1353 | 1354 | if src_cf == vs.RGB and not gray: 1355 | rgb = to_rgbs(src).descale.Descale(width, height, kernel, custom_kernel, taps, b, c) 1356 | return rgb.resize.Point(format=src_f.id) 1357 | 1358 | y = to_grays(src).descale.Descale(width, height, kernel, custom_kernel, taps, b, c) 1359 | y_f = core.query_video_format(vs.GRAY, src_st, src_bits, 0, 0) 1360 | y = y.resize.Point(format=y_f.id) 1361 | 1362 | if src_cf == vs.GRAY or gray: 1363 | return y 1364 | 1365 | if not yuv444 and ((width % 2 and src_sw) or (height % 2 and src_sh)): 1366 | raise ValueError('Descale: The output dimension and the subsampling are incompatible.') 1367 | 1368 | uv_f = core.query_video_format(src_cf, src_st, src_bits, 0 if yuv444 else src_sw, 0 if yuv444 else src_sh) 1369 | uv = src.resize.Spline36(width, height, format=uv_f.id, chromaloc_s=chromaloc) 1370 | 1371 | return core.std.ShufflePlanes([y,uv], [0,1,2], vs.YUV) 1372 | 1373 | 1374 | def to_grays(src): 1375 | return src.resize.Point(format=vs.GRAYS) 1376 | 1377 | 1378 | def to_rgbs(src): 1379 | return src.resize.Point(format=vs.RGBS) 1380 | 1381 | 1382 | def get_plane(src, plane): 1383 | return core.std.ShufflePlanes(src, plane, vs.GRAY) 1384 | 1385 | 1386 | def Depth(src, bits, dither_type='error_diffusion', range=None, range_in=None): 1387 | src_f = src.format 1388 | src_cf = src_f.color_family 1389 | src_st = src_f.sample_type 1390 | src_bits = src_f.bits_per_sample 1391 | src_sw = src_f.subsampling_w 1392 | src_sh = src_f.subsampling_h 1393 | dst_st = vs.INTEGER if bits < 32 else vs.FLOAT 1394 | 1395 | if isinstance(range, str): 1396 | range = RANGEDICT[range] 1397 | 1398 | if isinstance(range_in, str): 1399 | range_in = RANGEDICT[range_in] 1400 | 1401 | if (src_bits, range_in) == (bits, range): 1402 | return src 1403 | out_f = core.query_video_format(src_cf, dst_st, bits, src_sw, src_sh) 1404 | return core.resize.Point(src, format=out_f.id, dither_type=dither_type, range=range, range_in=range_in) 1405 | 1406 | 1407 | TYPEDICT = {vs.VideoNode: 'a clip', int: 'an int', float: 'a float', bool: 'a bool', str: 'a str', list: 'a list'} 1408 | 1409 | RANGEDICT = {'limited': 0, 'full': 1} 1410 | --------------------------------------------------------------------------------