├── soifunc ├── py.typed ├── __init__.py ├── deband.py ├── resize.py ├── denoise.py └── interpolate.py ├── poetry.toml ├── .gitignore ├── .pre-commit-config.yaml ├── pyproject.toml ├── README.md ├── LICENSE ├── CHANGELOG.md └── typings └── vsmlrt └── __init__.pyi /soifunc/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | # For libraries it is not necessary to commit the lockfile 3 | poetry.lock 4 | /.mypy_cache 5 | *.pyc 6 | /.idea 7 | CLAUDE.md 8 | -------------------------------------------------------------------------------- /soifunc/__init__.py: -------------------------------------------------------------------------------- 1 | from .deband import * # noqa: F401, F403 2 | from .denoise import * # noqa: F401, F403 3 | from .interpolate import * # noqa: F401, F403 4 | from .resize import * # noqa: F401, F403 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pycqa/isort 3 | rev: 7.0.0 4 | hooks: 5 | - id: isort 6 | args: ["--profile", "black", "--filter-files"] 7 | - repo: https://github.com/psf/black 8 | rev: 25.9.0 9 | hooks: 10 | - id: black 11 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "soifunc" 3 | version = "0.13.0" 4 | description = "Soichiro's VapourSynth Functions Collection" 5 | authors = ["Josh Holmer "] 6 | license = "MIT" 7 | readme = "README.md" 8 | 9 | [tool.poetry.dependencies] 10 | python = ">=3.12,<4.0" 11 | vapoursynth = ">=68" 12 | vsjetpack = "^0.8.2" 13 | 14 | [tool.poetry.group.dev.dependencies] 15 | black = "^25.9.0" 16 | isort = "^7.0.0" 17 | pre-commit = "^4.2.0" 18 | 19 | [build-system] 20 | requires = ["poetry-core"] 21 | build-backend = "poetry.core.masonry.api" 22 | 23 | [tool.isort] 24 | profile = "black" 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## soifunc 2 | 3 | Vapoursynth scripts that might be useful to someone 4 | 5 | ### Installation 6 | 7 | #### Arch Linux 8 | 9 | Install from [AUR](https://aur.archlinux.org/packages/vapoursynth-plugin-soifunc-git) 10 | 11 | #### Other 12 | 13 | First install the required plugins which are not available in pip: 14 | 15 | - [neo_f3kdb](https://github.com/HomeOfAviSynthPlusEvolution/neo_f3kdb) 16 | - [znedi3](https://github.com/sekrit-twc/znedi3) 17 | - [nnedi3_resample](https://github.com/HomeOfVapourSynthEvolution/nnedi3_resample) 18 | 19 | Install from pip: 20 | 21 | ```bash 22 | pip install soifunc 23 | ``` 24 | 25 | Or the latest git version: 26 | 27 | ```bash 28 | pip install git+https://github.com/shssoichiro/soifunc.git 29 | ``` 30 | 31 | ### Usage 32 | 33 | Please see the inline codedoc comments for usage details. 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Josh Holmer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /soifunc/deband.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from vsdeband import f3k_deband 4 | from vsmasktools import dre_edgemask 5 | from vstools import ( 6 | ConstantFormatVideoNode, 7 | InvalidVideoFormatError, 8 | VariableFormatError, 9 | check_variable, 10 | core, 11 | vs, 12 | ) 13 | 14 | __all__ = [ 15 | "retinex_deband", 16 | ] 17 | 18 | 19 | def retinex_deband( 20 | clip: vs.VideoNode, 21 | threshold: int, 22 | showmask: bool = False, 23 | ) -> vs.VideoNode: 24 | """Debanding using contrast-adaptive edge masking. 25 | 26 | Args: 27 | clip: Input video (8-16bit YUV required). 28 | threshold: Debanding strength (0-255). Default ~16-48 recommended. 29 | showmask: If True, return edge mask instead of debanded clip. 30 | 31 | Returns: 32 | Debanded video clip or edge mask. 33 | 34 | Note: 35 | Does not add grain. Use vsdeband.AddNoise for post-denoising. 36 | """ 37 | if threshold < 0 or threshold > 255: 38 | raise ValueError(f"threshold must be between 0-255, got {threshold}") 39 | 40 | if not check_variable(clip, retinex_deband): 41 | raise VariableFormatError("clip must have constant format and fps") 42 | 43 | if ( 44 | clip.format.color_family != vs.YUV 45 | or clip.format.sample_type != vs.INTEGER 46 | or clip.format.bits_per_sample > 16 47 | ): 48 | raise InvalidVideoFormatError( 49 | retinex_deband, 50 | clip.format, 51 | "The format {format.name} is not supported! It must be an 8-16bit integer YUV bit format!", 52 | ) 53 | 54 | mask: ConstantFormatVideoNode = dre_edgemask.CLAHE(clip) 55 | 56 | if showmask: 57 | return mask 58 | 59 | # The threshold value that `retinex_deband` takes is relative 60 | # to 8-bit videos, but `f3kdb` changed their threshold 61 | # values to be relative to 10-bit videos some time after this 62 | # function was created. To keep this function compatible, 63 | # we shift our threshold from 8-bit to 10-bit. 64 | deband = f3k_deband(clip, thr=(threshold << 2)) 65 | return core.std.MaskedMerge(deband, clip, mask) 66 | -------------------------------------------------------------------------------- /soifunc/resize.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from vsaa.deinterlacers import NNEDI3 4 | from vskernels import Hermite, LeftShift, Spline36, TopShift 5 | from vsscale import ArtCNN 6 | from vstools import ( 7 | VariableFormatError, 8 | check_variable_format, 9 | is_gpu_available, 10 | join, 11 | vs, 12 | ) 13 | 14 | __all__ = [ 15 | "good_resize", 16 | ] 17 | 18 | 19 | def good_resize( 20 | clip: vs.VideoNode, 21 | width: int, 22 | height: int, 23 | shift: tuple[TopShift | list[TopShift], LeftShift | list[LeftShift]] = (0, 0), 24 | gpu: bool | None = None, 25 | anime: bool = False, 26 | ) -> vs.VideoNode: 27 | """High quality resizing filter 28 | 29 | Parameters 30 | ---------- 31 | clip: VideoNode 32 | Video clip to apply resizing to. 33 | width: int 34 | Target width to resize to. 35 | height: int 36 | Target height to resize to. 37 | shift: tuple[float, float], optional 38 | Horizontal and vertical amount of shift to apply. 39 | gpu: bool, optional 40 | Whether to allow usage of GPU for ArtCNN. 41 | Defaults to None, which will auto-select based on available mlrt and hardware. 42 | anime: bool, optional 43 | Enables scalers that are better tuned toward anime. 44 | Defaults to False. 45 | """ 46 | 47 | if gpu is None: 48 | gpu = is_gpu_available() 49 | 50 | is_upscale = clip.width < width or clip.height < height 51 | chroma_scaler = Spline36() 52 | 53 | # We've ended up where the only special case is anime + upscale + GPU enabled 54 | if anime and is_upscale and gpu: 55 | luma_scaler = ArtCNN(scaler=Hermite(sigmoid=True)) 56 | elif is_upscale: 57 | luma_scaler = NNEDI3(scaler=Hermite(sigmoid=True)) 58 | else: 59 | luma_scaler = Hermite(sigmoid=True) 60 | 61 | if not check_variable_format(clip, "good_resize"): 62 | raise VariableFormatError("Invalid clip format for good_resize") 63 | 64 | luma = luma_scaler.scale(clip, width, height, shift) 65 | 66 | # Grayscale doesn't need chroma processing 67 | if clip.format.num_planes == 1: 68 | return luma 69 | 70 | chroma = chroma_scaler.scale(clip, width, height, shift) 71 | 72 | return join(luma, chroma) 73 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 0.13.0 (unreleased) 4 | 5 | - [Breaking] Remove the deprecated `noisy` parameter from `mc_dfttest` 6 | - [Feature] Make `tr` parameter configurable for denoisers 7 | - [Feature] Add `replace_dupes` function to interpolate duplicate frames with RIFE 8 | - Various code cleanup 9 | 10 | ## Version 0.12.0 11 | 12 | - [Breaking] Update for vs-jetpack 0.7, this required removing the `HybridScaler` class (well, probably not required, but it was the easiest way to accomplish fixing `good_resize`) 13 | 14 | ## Version 0.11.3 15 | 16 | - Use GPU backend selection logic from vs-jetpack 17 | 18 | ## Version 0.11.2 19 | 20 | - Replace SSIM scaler with Hermite. SSIM scaler has had numerous bugs and it's not worth messing with anymore. 21 | 22 | ## Version 0.11.1 23 | 24 | - Change the default ML runtime, since vs-mlrt on AUR doesn't provide TRT_RTX and I don't feel like figuring out how to add it 25 | - Lazy import `BackendT` so that vs-mlrt is actually an optional dependency 26 | 27 | ## Version 0.11.0 28 | 29 | - [Breaking] Update for vs-jetpack 0.5 30 | - [Feature] Add some utilities for interpolating frames 31 | 32 | ## Version 0.10.1 33 | 34 | - Use nnedi3 for upscaling by default as it is slightly sharper and has less ringing than EwaLanczos 35 | 36 | ## Version 0.10.0 37 | 38 | - [Breaking] Remove the `use_waifu2x` parameter from `good_resize` (use the `gpu` parameter instead--this will auto-detect by default) 39 | - Rework defaults for anime mode in `good_resize` 40 | - Simplify `good_resize` code 41 | 42 | ## Version 0.9.2 43 | 44 | - Refine block size selection 45 | 46 | ## Version 0.9.1 47 | 48 | - Fix denoisers for resolutions below 1080p 49 | 50 | ## Version 0.9.0 51 | 52 | - [Breaking] Upgrade denoise functions for the vs-jetpack 0.3 update 53 | 54 | ## Version 0.8.2 55 | 56 | - Use CLAHE instead of Retinex for deband edge masking. Leaving function name unchanged for backwards compatibility. 57 | 58 | ## Version 0.8.1 59 | 60 | - Fix an imports issue, whoops 61 | 62 | ## Version 0.8.0 63 | 64 | - Remove `SQTGMC` 65 | - Add `hqbm3d` 66 | - Fix dependency versions, including Vapoursynth 67 | 68 | ## Version 0.7.0 69 | 70 | - Add `anime` parameter to `good_resize` 71 | - Update `good_resize` kernels 72 | - Live action will use EwaLanczos for luma upscaling, SSIM for luma downscaling, and Spline36 for chroma 73 | - Anime will use Waifu2x for upscaling and sigmoid Catrom for downscaling 74 | - Fix `good_resize` `gpu` parameter to auto-detect by default 75 | - Deprecate `SQTGMC`. Please return to using `havsfunc.QTGMC` instead. 76 | 77 | ## Version 0.6.0 78 | 79 | - remove functions that were previously deprecated 80 | - fixes for compat with most recent dependency versions 81 | - expose SAD parameters in SQTGMC 82 | - fix some presets in SQTGMC, mainly use a not-stupid nnsize 83 | - remove dependency on havsfunc 84 | 85 | ## Version 0.5.0 86 | 87 | - Deprecate functions that have (basically) equivalent functionality with existing functions in the interest of reducing code duplication and people trying to run the exact same functions from multiple \*funcs 88 | - Update existing functions with updated tooling that should both run faster and be more resistant to unintended user input 89 | - Improve some typing and make the package overall a bit more Pythonic, as well as make it a tad more typesafe in certain scenarios 90 | - More useful and informative exceptions 91 | - Expand some functions with functionality that can be built on in the future (i.e. presets) 92 | 93 | ## Version 0.4.2 94 | 95 | - Fix Descale again, I need a better way to test these things before I push them 96 | 97 | ## Version 0.4.1 98 | 99 | - Add `downscale_only` param to `Descale` function 100 | - Expose `DescaleMask` as a separate function 101 | 102 | ## Version 0.4.0 103 | 104 | - Deprecate `ClipLimited`, use `std.Limiter` instead 105 | - Add `Descale` function 106 | 107 | ## Version 0.3.5 108 | 109 | - Fix bug where SQGTMC may throw an error 110 | 111 | ## Version 0.3.4 112 | 113 | - Fix threshold on Deband to scale correctly so that we have finer tuned debanding thresholds, which apparently never worked correctly in vs-deband if you were using neo_f3kdb. 114 | 115 | ## Version 0.3.3 116 | 117 | - Things were still broken in the last version so I removed the vsdeband plugin entirely and now we just call neo_f3kdb directly 118 | - It turns out the bug in f3kdb that was the reason for the vsdeband script being needed had been fixed in neo_f3kdb a long time ago anyway. 119 | 120 | ## Version 0.3.2 121 | 122 | - Fix compatibility with a breaking change that vs-deband made [here](https://github.com/Irrational-Encoding-Wizardry/vs-deband/commit/f9a9a9b3fed8319e0ec4c2237e6f9cd215b61619) 123 | 124 | ## Version 0.3.1 125 | 126 | - Internal refactoring 127 | - Fix potential syntax issue with `MagicDenoise` 128 | 129 | ## Version 0.3.0 130 | 131 | - Add Clybius's `MagicDenoise` function 132 | 133 | ## Version 0.2.0 134 | 135 | - Add gpu support for upscaling to `GoodResize` via `gpu` and `device` parameters. 136 | - Add SQGTMC, a simplified version of QTGMC, also with gpu support. 137 | - Add havsfunc as a dependency 138 | 139 | ## Version 0.1.0 140 | 141 | - Initial release 142 | -------------------------------------------------------------------------------- /soifunc/denoise.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Callable, Optional 4 | 5 | import vsdenoise 6 | from vsdenoise import DFTTest, bm3d, mc_degrain 7 | from vstools import core, vs 8 | 9 | __all__ = ["MCDenoise", "magic_denoise", "hqbm3d", "mc_dfttest"] 10 | 11 | 12 | def hqbm3d( 13 | clip: vs.VideoNode, 14 | luma_str: float = 0.5, 15 | chroma_str: float = 0.4, 16 | profile: bm3d.Profile = bm3d.Profile.FAST, 17 | tr: int = 1, 18 | ) -> vs.VideoNode: 19 | """ 20 | High-quality presets for motion compensated denoising. 21 | Uses BM3D for luma and nl_means for chroma. 22 | This is a good denoiser for preserving detail on high-quality sources. 23 | 24 | Sane strength values will typically be below 1.0. 25 | """ 26 | blksize = select_block_size(clip) 27 | mv = mc_degrain( 28 | clip, 29 | preset=vsdenoise.MVToolsPreset.HQ_SAD, 30 | tr=2, 31 | thsad=100, 32 | refine=3 if blksize > 16 else 2, 33 | blksize=blksize, 34 | prefilter=vsdenoise.Prefilter.DFTTEST( 35 | clip, 36 | sloc=[(0.0, 1.0), (0.2, 4.0), (0.35, 12.0), (1.0, 48.0)], 37 | ssystem=1, 38 | full_range=3.5, 39 | planes=0, 40 | ), 41 | planes=None, 42 | ) 43 | out = bm3d(clip, sigma=luma_str, tr=tr, ref=mv, profile=profile, planes=0) 44 | return vsdenoise.nl_means(out, h=chroma_str, tr=tr, ref=mv, planes=[1, 2]) 45 | 46 | 47 | def mc_dfttest( 48 | clip: vs.VideoNode, 49 | thSAD: int = 75, 50 | tr: int = 2, 51 | ) -> vs.VideoNode: 52 | """ 53 | A motion-compensated denoiser using DFTTEST. 54 | Even at the default `thSAD` of 75, it works well at eliminating noise. 55 | Turn it up to 150 or more if you really need to nuke something. 56 | It does a decent job at preserving details, but not nearly as good 57 | as bm3d, so this is not recommended on clean, high-quality sources. 58 | """ 59 | blksize = select_block_size(clip) 60 | return mc_degrain( 61 | clip, 62 | prefilter=vsdenoise.Prefilter.DFTTEST, 63 | preset=vsdenoise.MVToolsPreset.HQ_SAD, 64 | thsad=thSAD, 65 | tr=tr, 66 | refine=3 if blksize > 16 else 2, 67 | blksize=blksize, 68 | ) 69 | 70 | 71 | def select_block_size(clip: vs.VideoNode): 72 | if clip.width * clip.height >= 1920 * 800: 73 | return 64 74 | if clip.width * clip.height >= 1280 * 720: 75 | return 32 76 | return 16 77 | 78 | 79 | def MCDenoise( 80 | clip: vs.VideoNode, 81 | denoiser: Callable[..., vs.VideoNode], 82 | prefilter: Optional[vs.VideoNode] = None, 83 | ) -> vs.VideoNode: 84 | """ 85 | Applies motion compensation to a denoised clip to improve detail preservation. 86 | Credit to Clybius for creating this code. 87 | 88 | Params: 89 | - `denoiser`: A function defining how to denoise the motion-compensated frames. 90 | Params can be added using `functools.partial`. 91 | - `prefilter`: An optional prefiltered input clip to enable better searching for motion vectors 92 | 93 | Example usage: 94 | ```python 95 | import soifunc 96 | from vsdenoise import DFTTest 97 | import functools # functools is built in to python 98 | denoiser = functools.partial(DFTTest().denoise, sloc=1.5, tr=1) 99 | clip = soifunc.MCDenoise(clip, denoiser) 100 | ``` 101 | """ 102 | prefilter = prefilter or clip 103 | # one level (temporal radius) is enough for MRecalculate 104 | super = core.mv.Super(prefilter, hpad=16, vpad=16, levels=1) 105 | # all levels for MAnalyse 106 | superfilt = core.mv.Super(clip, hpad=16, vpad=16) 107 | 108 | # Generate motion vectors 109 | backward2 = core.mv.Analyse( 110 | superfilt, isb=True, blksize=16, overlap=8, delta=2, search=3, truemotion=True 111 | ) 112 | backward = core.mv.Analyse( 113 | superfilt, isb=True, blksize=16, overlap=8, search=3, truemotion=True 114 | ) 115 | forward = core.mv.Analyse( 116 | superfilt, isb=False, blksize=16, overlap=8, search=3, truemotion=True 117 | ) 118 | forward2 = core.mv.Analyse( 119 | superfilt, isb=False, blksize=16, overlap=8, delta=2, search=3, truemotion=True 120 | ) 121 | 122 | # Recalculate for higher consistency / quality 123 | backward_re2 = core.mv.Recalculate( 124 | super, backward2, blksize=8, overlap=4, search=3, truemotion=True 125 | ) 126 | backward_re = core.mv.Recalculate( 127 | super, backward, blksize=8, overlap=4, search=3, truemotion=True 128 | ) 129 | forward_re = core.mv.Recalculate( 130 | super, forward, blksize=8, overlap=4, search=3, truemotion=True 131 | ) 132 | forward_re2 = core.mv.Recalculate( 133 | super, forward2, blksize=8, overlap=4, search=3, truemotion=True 134 | ) 135 | 136 | # Pixel-based motion comp 137 | # Generate hierarchical frames from motion vector data 138 | backward_comp2 = core.mv.Flow(clip, super, backward_re2) 139 | backward_comp = core.mv.Flow(clip, super, backward_re) 140 | forward_comp = core.mv.Flow(clip, super, forward_re) 141 | forward_comp2 = core.mv.Flow(clip, super, forward_re2) 142 | 143 | # Interleave the mocomp'd frames 144 | interleave = core.std.Interleave( 145 | [forward_comp2, forward_comp, clip, backward_comp, backward_comp2] 146 | ) 147 | 148 | clip = denoiser(interleave) 149 | 150 | # Every 5 frames, select the 3rd/middle frame (second digit counts from 0) 151 | return core.std.SelectEvery(clip, 5, 2) 152 | 153 | 154 | def magic_denoise(clip: vs.VideoNode) -> vs.VideoNode: 155 | """ 156 | Clybius's magic denoise function. 157 | 158 | Uses dark magic to denoise heavy grain from videos. 159 | Zero parameters, only magic. 160 | """ 161 | super = core.mv.Super(clip, hpad=16, vpad=16, rfilter=4) 162 | superSharp = core.mv.Super(clip, hpad=16, vpad=16, rfilter=4) 163 | 164 | backward2 = core.mv.Analyse( 165 | super, isb=True, blksize=16, overlap=8, delta=2, search=3, dct=6 166 | ) 167 | backward = core.mv.Analyse(super, isb=True, blksize=16, overlap=8, search=3, dct=6) 168 | forward = core.mv.Analyse(super, isb=False, blksize=16, overlap=8, search=3, dct=6) 169 | forward2 = core.mv.Analyse( 170 | super, isb=False, blksize=16, overlap=8, delta=2, search=3, dct=6 171 | ) 172 | 173 | backward2 = core.mv.Recalculate( 174 | super, backward2, blksize=8, overlap=4, search=3, divide=2, dct=6 175 | ) 176 | backward = core.mv.Recalculate( 177 | super, backward, blksize=8, overlap=4, search=3, divide=2, dct=6 178 | ) 179 | forward = core.mv.Recalculate( 180 | super, forward, blksize=8, overlap=4, search=3, divide=2, dct=6 181 | ) 182 | forward2 = core.mv.Recalculate( 183 | super, forward2, blksize=8, overlap=4, search=3, divide=2, dct=6 184 | ) 185 | 186 | backward_re2 = core.mv.Finest(backward2) 187 | backward_re = core.mv.Finest(backward) 188 | forward_re = core.mv.Finest(forward) 189 | forward_re2 = core.mv.Finest(forward2) 190 | 191 | clip = core.mv.Degrain2( 192 | clip, 193 | superSharp, 194 | backward_re, 195 | forward_re, 196 | backward_re2, 197 | forward_re2, 198 | thsad=220, 199 | thscd1=300, 200 | ) 201 | 202 | return DFTTest().denoise( 203 | clip, 204 | sloc=[(0.0, 0.8), (0.06, 1.1), (0.12, 1.0), (1.0, 1.0)], 205 | pmax=1000000, 206 | pmin=1.25, 207 | ftype=DFTTest.FilterType.MULT_RANGE, 208 | tbsize=3, 209 | ssystem=1, 210 | ) 211 | -------------------------------------------------------------------------------- /soifunc/interpolate.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | import vstools 6 | from vsscale import autoselect_backend 7 | from vstools import vs 8 | 9 | if TYPE_CHECKING: 10 | from vsmlrt import backendT 11 | 12 | __all__ = ["rate_doubler", "decimation_fixer", "replace_dupes"] 13 | 14 | 15 | def rate_doubler( 16 | clip: vs.VideoNode, multi: int = 2, backend: backendT | None = None 17 | ) -> vs.VideoNode: 18 | """ 19 | A utility to scale the framerate of a video via frame interpolation. 20 | 21 | Probably shouldn't just go spraying this everywhere, 22 | it's more for fun and science than anything. 23 | """ 24 | import vsmlrt 25 | 26 | width = clip.width 27 | height = clip.height 28 | format = clip.format 29 | matrix = vstools.Matrix.from_video(clip) 30 | transfer = vstools.Transfer.from_video(clip) 31 | primaries = vstools.Primaries.from_video(clip) 32 | clip = clip.misc.SCDetect() 33 | clip = clip.resize.Bicubic( 34 | format=vs.RGBS, 35 | width=next_multiple_of(64, width), 36 | height=next_multiple_of(64, height), 37 | ) 38 | clip = vsmlrt.RIFE( 39 | clip, 40 | multi=multi, 41 | model=vsmlrt.RIFEModel.v4_25_heavy, 42 | # Why these defaults? Because running ML stuff on AMD on Windows sucks hard. 43 | # Trial and error led me to finally find that ORT_DML works. 44 | backend=(backend if backend else autoselect_backend()), 45 | ) 46 | # TODO: Handle other chroma samplings 47 | clip = clip.resize.Bicubic( 48 | format=format, 49 | width=width, 50 | height=height, 51 | matrix=matrix, 52 | transfer=transfer, 53 | primaries=primaries, 54 | ) 55 | return clip 56 | 57 | 58 | def replace_dupes( 59 | clip: vs.VideoNode, 60 | max_length: int = 5, 61 | backend: backendT | None = None, 62 | threshold: float = 0.001, 63 | ) -> vs.VideoNode: 64 | """ 65 | Detects strings of duplicate frames in a video and replaces them 66 | with interpolated frames from RIFE. 67 | 68 | Max number of continuous duplicates to detect is determined by the `max_length` parameter. 69 | `threshold` is the maximum average pixel difference (0-1 scale) to consider frames as duplicates. 70 | Lower values are stricter (frames must be more similar to be considered duplicates). 71 | """ 72 | import vsmlrt 73 | 74 | # Store original properties 75 | width = clip.width 76 | height = clip.height 77 | format = clip.format 78 | matrix = vstools.Matrix.from_video(clip) 79 | transfer = vstools.Transfer.from_video(clip) 80 | primaries = vstools.Primaries.from_video(clip) 81 | 82 | # Compute frame differences using PlaneStats 83 | # This compares each frame with the previous one 84 | diff_clip = clip.std.PlaneStats(clip[0] + clip) 85 | 86 | # Prepare clip for RIFE (convert to RGBS and resize to multiple of 64) 87 | rife_clip = clip.resize.Bicubic( 88 | format=vs.RGBS, 89 | width=next_multiple_of(64, width), 90 | height=next_multiple_of(64, height), 91 | ) 92 | 93 | # Create interpolated frames using RIFE (double the framerate) 94 | interpolated = vsmlrt.RIFE( 95 | rife_clip, 96 | multi=2, 97 | model=vsmlrt.RIFEModel.v4_25_heavy, 98 | backend=(backend if backend else autoselect_backend()), 99 | ) 100 | 101 | # Convert interpolated frames back to original format 102 | interpolated = interpolated.resize.Bicubic( 103 | format=format, 104 | width=width, 105 | height=height, 106 | matrix=matrix, 107 | transfer=transfer, 108 | primaries=primaries, 109 | ) 110 | 111 | # Track sequence state for lazy evaluation 112 | state = {"prev_len": 0} 113 | 114 | def select_frame(n): 115 | """ 116 | Select interpolated frame if current frame is a duplicate, 117 | otherwise use original. Copies PlaneStatsDiff property to output 118 | to help users calibrate the threshold parameter. 119 | """ 120 | if n == 0 or n == clip.num_frames - 1: 121 | state["prev_len"] = 0 122 | # Frame 0 and final frame are never duplicates 123 | # (no previous frame for 0, no next frame for final) 124 | output = clip[n : n + 1] 125 | diff_val = ( 126 | 0.0 127 | if n == 0 128 | else diff_clip.get_frame(n).props.get("PlaneStatsDiff", 1.0) 129 | ) 130 | return output.std.SetFrameProp(prop="PlaneStatsDiff", floatval=diff_val) 131 | 132 | # Get difference from PlaneStats (lazy evaluation) 133 | f = diff_clip.get_frame(n) 134 | diff = f.props.get("PlaneStatsDiff", 1.0) 135 | 136 | # Determine if this is a duplicate 137 | if diff < threshold: 138 | new_len = state["prev_len"] + 1 139 | if new_len <= max_length: 140 | state["prev_len"] = new_len 141 | is_dupe = True 142 | else: 143 | state["prev_len"] = 0 144 | is_dupe = False 145 | else: 146 | state["prev_len"] = 0 147 | is_dupe = False 148 | 149 | if is_dupe: 150 | # Use interpolated frame between previous and current 151 | # If the original sequence is 0 1 2 where 0 and 1 are dupes, 152 | # the interpolated sequence will have 0 1 2 3 4 5 153 | # where 3 is the interpolated frame we want to fetch 154 | # to replace frame 1.. 155 | 156 | output = interpolated[n * 2 + 1 : n * 2 + 2] 157 | else: 158 | output = clip[n : n + 1] 159 | 160 | # Attach PlaneStatsDiff property to output frame for threshold calibration 161 | return output.std.SetFrameProp(prop="PlaneStatsDiff", floatval=diff) 162 | 163 | # Apply frame selection with lazy evaluation 164 | result = clip.std.FrameEval(select_frame) 165 | 166 | return result 167 | 168 | 169 | def decimation_fixer( 170 | clip: vs.VideoNode, cycle: int, offset: int = 0, backend: backendT | None = None 171 | ) -> vs.VideoNode: 172 | """ 173 | Attempts to interpolate frames that were removed by bad decimation. 174 | Only works with static decimation cycles. 175 | `cycle` should be the output cycle, i.e. what did the idiot who decimated this 176 | pass into the decimation filter to achieve this monstrosity? 177 | 178 | Yeah, I know, "ThiS is bAd AND yOu shoUldn'T Do IT". 179 | Maybe people shouldn't decimate clips that don't need decimation. 180 | Sometimes you can't "just get a better source". 181 | """ 182 | import vsmlrt 183 | 184 | if offset >= cycle - 1: 185 | raise Exception("offset must be less than cycle - 1") 186 | if cycle <= 0: 187 | raise Exception("cycle must be greater than zero") 188 | 189 | width = clip.width 190 | height = clip.height 191 | fps = clip.fps 192 | format = clip.format 193 | input_cycle = cycle - 1 194 | matrix = vstools.Matrix.from_video(clip) 195 | transfer = vstools.Transfer.from_video(clip) 196 | primaries = vstools.Primaries.from_video(clip) 197 | clip = clip.misc.SCDetect() 198 | clip = clip.resize.Bicubic( 199 | format=vs.RGBS, 200 | width=next_multiple_of(64, width), 201 | height=next_multiple_of(64, height), 202 | ) 203 | doubled = vsmlrt.RIFE( 204 | clip, 205 | model=vsmlrt.RIFEModel.v4_25_heavy, 206 | backend=(backend if backend else autoselect_backend()), 207 | ) 208 | 209 | out_clip = None 210 | # This is the frame after our insertion point 211 | src_frame = offset 212 | last_src_frame = 0 213 | # This is the frame we want to grab from the doubled clip 214 | doub_frame = offset * 2 - 1 215 | while src_frame < clip.num_frames: 216 | if src_frame > 0: 217 | interp = doubled[doub_frame] 218 | if out_clip is None: 219 | out_clip = clip[last_src_frame:src_frame] + interp 220 | else: 221 | out_clip = out_clip + clip[last_src_frame:src_frame] + interp 222 | last_src_frame = src_frame 223 | src_frame += input_cycle 224 | doub_frame += input_cycle * 2 225 | out_clip += clip[last_src_frame:] 226 | out_clip = out_clip.std.AssumeFPS( 227 | fpsnum=fps.numerator * cycle // input_cycle, fpsden=fps.denominator 228 | ) 229 | 230 | out_clip = out_clip.resize.Bicubic( 231 | format=format, 232 | width=width, 233 | height=height, 234 | matrix=matrix, 235 | transfer=transfer, 236 | primaries=primaries, 237 | ) 238 | return out_clip 239 | 240 | 241 | def next_multiple_of(multiple: int, param: int) -> int: 242 | rem = param % multiple 243 | if rem == 0: 244 | return param 245 | return param + (multiple - rem) 246 | -------------------------------------------------------------------------------- /typings/vsmlrt/__init__.pyi: -------------------------------------------------------------------------------- 1 | """ 2 | This type stub file was generated by pyright. 3 | """ 4 | 5 | import copy 6 | import enum 7 | import math 8 | import os 9 | import os.path 10 | import platform 11 | import subprocess 12 | import sys 13 | import tempfile 14 | import time 15 | import typing 16 | import zlib 17 | from dataclasses import dataclass, field 18 | from fractions import Fraction 19 | 20 | import vapoursynth as vs 21 | from vapoursynth import core 22 | 23 | __version__ = ... 24 | __all__ = [ 25 | "Backend", 26 | "BackendV2", 27 | "Waifu2x", 28 | "Waifu2xModel", 29 | "DPIR", 30 | "DPIRModel", 31 | "RealESRGAN", 32 | "RealESRGANModel", 33 | "RealESRGANv2", 34 | "RealESRGANv2Model", 35 | "CUGAN", 36 | "RIFE", 37 | "RIFEModel", 38 | "RIFEMerge", 39 | "SAFA", 40 | "SAFAModel", 41 | "SAFAAdaptiveMode", 42 | "SCUNet", 43 | "SCUNetModel", 44 | "SwinIR", 45 | "SwinIRModel", 46 | "ArtCNN", 47 | "ArtCNNModel", 48 | "inference", 49 | "flexible_inference", 50 | ] 51 | 52 | def get_plugins_path() -> str: ... 53 | 54 | plugins_path: str = ... 55 | trtexec_path: str = ... 56 | migraphx_driver_path: str = ... 57 | tensorrt_rtx_path: str = ... 58 | models_path: str = ... 59 | 60 | class Backend: 61 | @dataclass(frozen=False) 62 | class ORT_CPU: 63 | """backend for cpus""" 64 | 65 | num_streams: int = ... 66 | verbosity: int = ... 67 | fp16: bool = ... 68 | fp16_blacklist_ops: typing.Optional[typing.Sequence[str]] = ... 69 | supports_onnx_serialization: bool = ... 70 | 71 | @dataclass(frozen=False) 72 | class ORT_CUDA: 73 | """backend for nvidia gpus 74 | 75 | basic performance tuning: 76 | set fp16 = True (on RTX GPUs) 77 | 78 | Semantics of `fp16`: 79 | Enabling `fp16` will use a built-in quantization that converts a fp32 onnx to a fp16 onnx. 80 | If the input video is of half-precision floating-point format, 81 | the generated fp16 onnx will use fp16 input. 82 | The output format can be controlled by the `output_format` option (0 = fp32, 1 = fp16). 83 | 84 | Disabling `fp16` will not use the built-in quantization. 85 | However, if the onnx file itself uses fp16 for computation, 86 | the actual computation will be done in fp16. 87 | In this case, the input video format should match the input format of the onnx, 88 | and the output format is inferred from the onnx. 89 | """ 90 | 91 | device_id: int = ... 92 | cudnn_benchmark: bool = ... 93 | num_streams: int = ... 94 | verbosity: int = ... 95 | fp16: bool = ... 96 | use_cuda_graph: bool = ... 97 | fp16_blacklist_ops: typing.Optional[typing.Sequence[str]] = ... 98 | prefer_nhwc: bool = ... 99 | output_format: int = ... 100 | tf32: bool = ... 101 | supports_onnx_serialization: bool = ... 102 | 103 | @dataclass(frozen=False) 104 | class OV_CPU: 105 | """backend for x86 cpus 106 | 107 | basic performance tuning: 108 | set bf16 = True (on Zen4) 109 | increase num_streams 110 | """ 111 | 112 | fp16: bool = ... 113 | num_streams: typing.Union[int, str] = ... 114 | bind_thread: bool = ... 115 | fp16_blacklist_ops: typing.Optional[typing.Sequence[str]] = ... 116 | bf16: bool = ... 117 | num_threads: int = ... 118 | supports_onnx_serialization: bool = ... 119 | 120 | @dataclass(frozen=False) 121 | class TRT: 122 | """backend for nvidia gpus 123 | 124 | basic performance tuning: 125 | set fp16 = True (on RTX GPUs) 126 | increase num_streams 127 | increase workspace 128 | set use_cuda_graph = True 129 | """ 130 | 131 | max_shapes: typing.Optional[typing.Tuple[int, int]] = ... 132 | opt_shapes: typing.Optional[typing.Tuple[int, int]] = ... 133 | fp16: bool = ... 134 | device_id: int = ... 135 | workspace: typing.Optional[int] = ... 136 | verbose: bool = ... 137 | use_cuda_graph: bool = ... 138 | num_streams: int = ... 139 | use_cublas: bool = ... 140 | static_shape: bool = ... 141 | tf32: bool = ... 142 | log: bool = ... 143 | use_cudnn: bool = ... 144 | use_edge_mask_convolutions: bool = ... 145 | use_jit_convolutions: bool = ... 146 | heuristic: bool = ... 147 | output_format: int = ... 148 | min_shapes: typing.Tuple[int, int] = ... 149 | faster_dynamic_shapes: bool = ... 150 | force_fp16: bool = ... 151 | builder_optimization_level: int = ... 152 | max_aux_streams: typing.Optional[int] = ... 153 | short_path: typing.Optional[bool] = ... 154 | bf16: bool = ... 155 | custom_env: typing.Dict[str, str] = ... 156 | custom_args: typing.List[str] = ... 157 | engine_folder: typing.Optional[str] = ... 158 | max_tactics: typing.Optional[int] = ... 159 | tiling_optimization_level: int = ... 160 | l2_limit_for_tiling: int = ... 161 | supports_onnx_serialization: bool = ... 162 | 163 | @dataclass(frozen=False) 164 | class OV_GPU: 165 | """backend for nvidia gpus 166 | 167 | basic performance tuning: 168 | set fp16 = True 169 | increase num_streams 170 | """ 171 | 172 | fp16: bool = ... 173 | num_streams: typing.Union[int, str] = ... 174 | device_id: int = ... 175 | fp16_blacklist_ops: typing.Optional[typing.Sequence[str]] = ... 176 | supports_onnx_serialization: bool = ... 177 | 178 | @dataclass(frozen=False) 179 | class NCNN_VK: 180 | """backend for vulkan devices 181 | 182 | basic performance tuning: 183 | set fp16 = True (on modern GPUs) 184 | increase num_streams 185 | """ 186 | 187 | fp16: bool = ... 188 | device_id: int = ... 189 | num_streams: int = ... 190 | supports_onnx_serialization: bool = ... 191 | 192 | @dataclass(frozen=False) 193 | class ORT_DML: 194 | """backend for directml (d3d12) devices""" 195 | 196 | device_id: int = ... 197 | num_streams: int = ... 198 | verbosity: int = ... 199 | fp16: bool = ... 200 | fp16_blacklist_ops: typing.Optional[typing.Sequence[str]] = ... 201 | supports_onnx_serialization: bool = ... 202 | 203 | @dataclass(frozen=False) 204 | class MIGX: 205 | """backend for amd gpus 206 | 207 | basic performance tuning: 208 | set fp16 = True 209 | """ 210 | 211 | device_id: int = ... 212 | fp16: bool = ... 213 | opt_shapes: typing.Optional[typing.Tuple[int, int]] = ... 214 | fast_math: bool = ... 215 | exhaustive_tune: bool = ... 216 | num_streams: int = ... 217 | short_path: typing.Optional[bool] = ... 218 | custom_env: typing.Dict[str, str] = ... 219 | custom_args: typing.List[str] = ... 220 | supports_onnx_serialization: bool = ... 221 | 222 | @dataclass(frozen=False) 223 | class OV_NPU: 224 | """backend for intel npus""" 225 | 226 | supports_onnx_serialization: bool = ... 227 | 228 | @dataclass(frozen=False) 229 | class ORT_COREML: 230 | """backend for coreml""" 231 | 232 | num_streams: int = ... 233 | verbosity: int = ... 234 | fp16: bool = ... 235 | fp16_blacklist_ops: typing.Optional[typing.Sequence[str]] = ... 236 | ml_program: int = ... 237 | supports_onnx_serialization: bool = ... 238 | 239 | @dataclass(frozen=False) 240 | class TRT_RTX: 241 | """backend for nvidia rtx gpus 242 | 243 | basic performance tuning: 244 | set fp16 = True 245 | increase num_streams 246 | increase workspace 247 | set use_cuda_graph = True 248 | """ 249 | 250 | fp16: bool = ... 251 | device_id: int = ... 252 | workspace: typing.Optional[int] = ... 253 | verbose: bool = ... 254 | use_cuda_graph: bool = ... 255 | num_streams: int = ... 256 | use_edge_mask_convolutions: bool = ... 257 | builder_optimization_level: int = ... 258 | max_aux_streams: typing.Optional[int] = ... 259 | short_path: typing.Optional[bool] = ... 260 | custom_env: typing.Dict[str, str] = ... 261 | custom_args: typing.List[str] = ... 262 | engine_folder: typing.Optional[str] = ... 263 | max_tactics: typing.Optional[int] = ... 264 | tiling_optimization_level: int = ... 265 | l2_limit_for_tiling: int = ... 266 | supports_onnx_serialization: bool = ... 267 | 268 | backendT = typing.Union[ 269 | Backend.OV_CPU, 270 | Backend.ORT_CPU, 271 | Backend.ORT_CUDA, 272 | Backend.TRT, 273 | Backend.OV_GPU, 274 | Backend.NCNN_VK, 275 | Backend.ORT_DML, 276 | Backend.MIGX, 277 | Backend.OV_NPU, 278 | Backend.ORT_COREML, 279 | Backend.TRT_RTX, 280 | ] 281 | fallback_backend: typing.Optional[backendT] = ... 282 | 283 | @enum.unique 284 | class Waifu2xModel(enum.IntEnum): 285 | anime_style_art = ... 286 | anime_style_art_rgb = ... 287 | photo = ... 288 | upconv_7_anime_style_art_rgb = ... 289 | upconv_7_photo = ... 290 | upresnet10 = ... 291 | cunet = ... 292 | swin_unet_art = ... 293 | swin_unet_photo = ... 294 | swin_unet_photo_v2 = ... 295 | swin_unet_art_scan = ... 296 | 297 | def Waifu2x( 298 | clip: vs.VideoNode, 299 | noise: typing.Literal[-1, 0, 1, 2, 3] = ..., 300 | scale: typing.Literal[1, 2, 4] = ..., 301 | tiles: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 302 | tilesize: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 303 | overlap: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 304 | model: Waifu2xModel = ..., 305 | backend: backendT = ..., 306 | preprocess: bool = ..., 307 | ) -> vs.VideoNode: ... 308 | @enum.unique 309 | class DPIRModel(enum.IntEnum): 310 | drunet_gray = ... 311 | drunet_color = ... 312 | drunet_deblocking_grayscale = ... 313 | drunet_deblocking_color = ... 314 | 315 | def DPIR( 316 | clip: vs.VideoNode, 317 | strength: typing.Optional[typing.Union[typing.SupportsFloat, vs.VideoNode]], 318 | tiles: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 319 | tilesize: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 320 | overlap: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 321 | model: DPIRModel = ..., 322 | backend: backendT = ..., 323 | ) -> vs.VideoNode: ... 324 | @enum.unique 325 | class RealESRGANModel(enum.IntEnum): 326 | animevideo_xsx2 = ... 327 | animevideo_xsx4 = ... 328 | animevideov3 = ... 329 | animejanaiV2L1 = ... 330 | animejanaiV2L2 = ... 331 | animejanaiV2L3 = ... 332 | animejanaiV3_HD_L1 = ... 333 | animejanaiV3_HD_L2 = ... 334 | animejanaiV3_HD_L3 = ... 335 | Ani4Kv2_G6i2_Compact = ... 336 | Ani4Kv2_G6i2_UltraCompact = ... 337 | 338 | RealESRGANv2Model = RealESRGANModel 339 | 340 | def RealESRGAN( 341 | clip: vs.VideoNode, 342 | tiles: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 343 | tilesize: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 344 | overlap: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 345 | model: RealESRGANv2Model = ..., 346 | backend: backendT = ..., 347 | scale: typing.Optional[float] = ..., 348 | ) -> vs.VideoNode: ... 349 | 350 | RealESRGANv2 = ... 351 | 352 | def CUGAN( 353 | clip: vs.VideoNode, 354 | noise: typing.Literal[-1, 0, 1, 2, 3] = ..., 355 | scale: typing.Literal[2, 3, 4] = ..., 356 | tiles: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 357 | tilesize: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 358 | overlap: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 359 | backend: backendT = ..., 360 | alpha: float = ..., 361 | version: typing.Literal[1, 2] = ..., 362 | conformance: bool = ..., 363 | ) -> vs.VideoNode: 364 | """ 365 | denoising strength: 0 < -1 < 1 < 2 < 3 366 | 367 | version: (1 or 2) 368 | 1 -> legacy, 369 | 2 -> pro (only models for "noise" in [-1, 0, 3] and "scale" in [2, 3] are published currently) 370 | """ 371 | ... 372 | 373 | def get_rife_input(clip: vs.VideoNode) -> typing.List[vs.VideoNode]: ... 374 | @enum.unique 375 | class RIFEModel(enum.IntEnum): 376 | """ 377 | Starting from RIFE v4.12 lite, this interface does not provide forward compatiblity in enum values. 378 | """ 379 | 380 | v4_0 = ... 381 | v4_2 = ... 382 | v4_3 = ... 383 | v4_4 = ... 384 | v4_5 = ... 385 | v4_6 = ... 386 | v4_7 = ... 387 | v4_8 = ... 388 | v4_9 = ... 389 | v4_10 = ... 390 | v4_11 = ... 391 | v4_12 = ... 392 | v4_12_lite = ... 393 | v4_13 = ... 394 | v4_13_lite = ... 395 | v4_14 = ... 396 | v4_14_lite = ... 397 | v4_15 = ... 398 | v4_15_lite = ... 399 | v4_16_lite = ... 400 | v4_17 = ... 401 | v4_17_lite = ... 402 | v4_18 = ... 403 | v4_19 = ... 404 | v4_20 = ... 405 | v4_21 = ... 406 | v4_22 = ... 407 | v4_22_lite = ... 408 | v4_23 = ... 409 | v4_24 = ... 410 | v4_25 = ... 411 | v4_25_lite = ... 412 | v4_25_heavy = ... 413 | v4_26 = ... 414 | v4_26_heavy = ... 415 | 416 | def RIFEMerge( 417 | clipa: vs.VideoNode, 418 | clipb: vs.VideoNode, 419 | mask: vs.VideoNode, 420 | scale: float = ..., 421 | tiles: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 422 | tilesize: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 423 | overlap: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 424 | model: RIFEModel = ..., 425 | backend: backendT = ..., 426 | ensemble: bool = ..., 427 | _implementation: typing.Optional[typing.Literal[1, 2]] = ..., 428 | ) -> vs.VideoNode: 429 | """temporal MaskedMerge-like interface for the RIFE model 430 | 431 | Its semantics is similar to core.std.MaskedMerge(clipa, clipb, mask, first_plane=True), 432 | except that it merges the two clips in the time domain and you specify the "mask" based 433 | on the time point of the resulting clip (range (0,1)) between the two clips. 434 | """ 435 | ... 436 | 437 | def RIFE( 438 | clip: vs.VideoNode, 439 | multi: typing.Union[int, Fraction] = ..., 440 | scale: float = ..., 441 | tiles: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 442 | tilesize: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 443 | overlap: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 444 | model: RIFEModel = ..., 445 | backend: backendT = ..., 446 | ensemble: bool = ..., 447 | video_player: bool = ..., 448 | _implementation: typing.Optional[typing.Literal[1, 2]] = ..., 449 | ) -> vs.VideoNode: 450 | """RIFE: Real-Time Intermediate Flow Estimation for Video Frame Interpolation 451 | 452 | multi, scale is based on vs-rife. 453 | 454 | For the best results, you need to perform scene detection on the input clip 455 | (e.g. misc.SCDetect, mv.SCDetection) before passing it to RIFE. 456 | Also note that the quality of result is strongly dependent on high quality 457 | scene detection and you might need to tweak the scene detection parameters 458 | and/or filter to achieve the best quality. 459 | 460 | Args: 461 | multi: Multiple of the frame counts, can be a fractions.Fraction. 462 | Default: 2. 463 | 464 | scale: Controls the process resolution for optical flow model. 465 | 32 / fractions.Fraction(scale) must be an integer. 466 | scale=0.5 is recommended for 4K video. 467 | 468 | _implementation: (None, 1 or 2, experimental and maybe removed in the future) 469 | Switch between different onnx implementation. 470 | Implmementation will be selected based on internal heuristic if it is None. 471 | """ 472 | ... 473 | 474 | @enum.unique 475 | class SAFAModel(enum.IntEnum): 476 | v0_1 = ... 477 | v0_2 = ... 478 | v0_3 = ... 479 | v0_4 = ... 480 | v0_5 = ... 481 | 482 | @enum.unique 483 | class SAFAAdaptiveMode(enum.IntEnum): 484 | non_adaptive = ... 485 | adaptive1x = ... 486 | adaptive = ... 487 | 488 | def SAFA( 489 | clip: vs.VideoNode, 490 | tiles: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 491 | tilesize: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 492 | overlap: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 493 | model: SAFAModel = ..., 494 | adaptive: SAFAAdaptiveMode = ..., 495 | backend: backendT = ..., 496 | ) -> vs.VideoNode: 497 | """SAFA: Scale-Adaptive Feature Aggregation for Efficient Space-Time Video Super-Resolution""" 498 | ... 499 | 500 | @enum.unique 501 | class SCUNetModel(enum.IntEnum): 502 | scunet_color_15 = ... 503 | scunet_color_25 = ... 504 | scunet_color_50 = ... 505 | scunet_color_real_psnr = ... 506 | scunet_color_real_gan = ... 507 | scunet_gray_15 = ... 508 | scunet_gray_25 = ... 509 | scunet_gray_50 = ... 510 | 511 | def SCUNet( 512 | clip: vs.VideoNode, 513 | tiles: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 514 | tilesize: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 515 | overlap: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 516 | model: SCUNetModel = ..., 517 | backend: backendT = ..., 518 | ) -> vs.VideoNode: 519 | """Practical Blind Denoising via Swin-Conv-UNet and Data Synthesis 520 | 521 | Unlike vs-scunet v1.0.0, the default model is set to scunet_color_real_psnr due to the color shift. 522 | """ 523 | ... 524 | 525 | @enum.unique 526 | class SwinIRModel(enum.IntEnum): 527 | lightweightSR_DIV2K_s64w8_SwinIR_S_x2 = ... 528 | lightweightSR_DIV2K_s64w8_SwinIR_S_x3 = ... 529 | lightweightSR_DIV2K_s64w8_SwinIR_S_x4 = ... 530 | realSR_BSRGAN_DFOWMFC_s64w8_SwinIR_L_x4_GAN = ... 531 | realSR_BSRGAN_DFOWMFC_s64w8_SwinIR_L_x4_PSNR = ... 532 | classicalSR_DF2K_s64w8_SwinIR_M_x2 = ... 533 | classicalSR_DF2K_s64w8_SwinIR_M_x3 = ... 534 | classicalSR_DF2K_s64w8_SwinIR_M_x4 = ... 535 | classicalSR_DF2K_s64w8_SwinIR_M_x8 = ... 536 | realSR_BSRGAN_DFO_s64w8_SwinIR_M_x2_GAN = ... 537 | realSR_BSRGAN_DFO_s64w8_SwinIR_M_x2_PSNR = ... 538 | realSR_BSRGAN_DFO_s64w8_SwinIR_M_x4_GAN = ... 539 | realSR_BSRGAN_DFO_s64w8_SwinIR_M_x4_PSNR = ... 540 | grayDN_DFWB_s128w8_SwinIR_M_noise15 = ... 541 | grayDN_DFWB_s128w8_SwinIR_M_noise25 = ... 542 | grayDN_DFWB_s128w8_SwinIR_M_noise50 = ... 543 | colorDN_DFWB_s128w8_SwinIR_M_noise15 = ... 544 | colorDN_DFWB_s128w8_SwinIR_M_noise25 = ... 545 | colorDN_DFWB_s128w8_SwinIR_M_noise50 = ... 546 | CAR_DFWB_s126w7_SwinIR_M_jpeg10 = ... 547 | CAR_DFWB_s126w7_SwinIR_M_jpeg20 = ... 548 | CAR_DFWB_s126w7_SwinIR_M_jpeg30 = ... 549 | CAR_DFWB_s126w7_SwinIR_M_jpeg40 = ... 550 | colorCAR_DFWB_s126w7_SwinIR_M_jpeg10 = ... 551 | colorCAR_DFWB_s126w7_SwinIR_M_jpeg20 = ... 552 | colorCAR_DFWB_s126w7_SwinIR_M_jpeg30 = ... 553 | colorCAR_DFWB_s126w7_SwinIR_M_jpeg40 = ... 554 | 555 | def SwinIR( 556 | clip: vs.VideoNode, 557 | tiles: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 558 | tilesize: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 559 | overlap: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 560 | model: SwinIRModel = ..., 561 | backend: backendT = ..., 562 | ) -> vs.VideoNode: 563 | """SwinIR: Image Restoration Using Swin Transformer""" 564 | ... 565 | 566 | @enum.unique 567 | class ArtCNNModel(enum.IntEnum): 568 | ArtCNN_C4F32 = ... 569 | ArtCNN_C4F32_DS = ... 570 | ArtCNN_C16F64 = ... 571 | ArtCNN_C16F64_DS = ... 572 | ArtCNN_C4F32_Chroma = ... 573 | ArtCNN_C16F64_Chroma = ... 574 | ArtCNN_R16F96 = ... 575 | ArtCNN_R8F64 = ... 576 | ArtCNN_R8F64_DS = ... 577 | ArtCNN_R8F64_Chroma = ... 578 | ArtCNN_C4F16 = ... 579 | ArtCNN_C4F16_DS = ... 580 | 581 | def ArtCNN( 582 | clip: vs.VideoNode, 583 | tiles: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 584 | tilesize: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 585 | overlap: typing.Optional[typing.Union[int, typing.Tuple[int, int]]] = ..., 586 | model: ArtCNNModel = ..., 587 | backend: backendT = ..., 588 | ) -> vs.VideoNode: 589 | """ArtCNN (https://github.com/Artoriuz/ArtCNN)""" 590 | ... 591 | 592 | def get_engine_path( 593 | network_path: str, 594 | min_shapes: typing.Tuple[int, int], 595 | opt_shapes: typing.Tuple[int, int], 596 | max_shapes: typing.Tuple[int, int], 597 | workspace: typing.Optional[int], 598 | fp16: bool, 599 | device_id: int, 600 | use_cublas: bool, 601 | static_shape: bool, 602 | tf32: bool, 603 | use_cudnn: bool, 604 | input_format: int, 605 | output_format: int, 606 | builder_optimization_level: int, 607 | max_aux_streams: typing.Optional[int], 608 | short_path: typing.Optional[bool], 609 | bf16: bool, 610 | engine_folder: typing.Optional[str], 611 | ) -> str: ... 612 | def trtexec( 613 | network_path: str, 614 | channels: int, 615 | opt_shapes: typing.Tuple[int, int], 616 | max_shapes: typing.Tuple[int, int], 617 | fp16: bool, 618 | device_id: int, 619 | workspace: typing.Optional[int] = ..., 620 | verbose: bool = ..., 621 | use_cuda_graph: bool = ..., 622 | use_cublas: bool = ..., 623 | static_shape: bool = ..., 624 | tf32: bool = ..., 625 | log: bool = ..., 626 | use_cudnn: bool = ..., 627 | use_edge_mask_convolutions: bool = ..., 628 | use_jit_convolutions: bool = ..., 629 | heuristic: bool = ..., 630 | input_name: str = ..., 631 | input_format: int = ..., 632 | output_format: int = ..., 633 | min_shapes: typing.Tuple[int, int] = ..., 634 | faster_dynamic_shapes: bool = ..., 635 | force_fp16: bool = ..., 636 | builder_optimization_level: int = ..., 637 | max_aux_streams: typing.Optional[int] = ..., 638 | short_path: typing.Optional[bool] = ..., 639 | bf16: bool = ..., 640 | custom_env: typing.Dict[str, str] = ..., 641 | custom_args: typing.List[str] = ..., 642 | engine_folder: typing.Optional[str] = ..., 643 | max_tactics: typing.Optional[int] = ..., 644 | tiling_optimization_level: int = ..., 645 | l2_limit_for_tiling: int = ..., 646 | ) -> str: ... 647 | def get_mxr_path( 648 | network_path: str, 649 | opt_shapes: typing.Tuple[int, int], 650 | fp16: bool, 651 | fast_math: bool, 652 | exhaustive_tune: bool, 653 | device_id: int, 654 | short_path: typing.Optional[bool], 655 | ) -> str: ... 656 | def migraphx_driver( 657 | network_path: str, 658 | channels: int, 659 | opt_shapes: typing.Tuple[int, int], 660 | fp16: bool, 661 | fast_math: bool, 662 | exhaustive_tune: bool, 663 | device_id: int, 664 | input_name: str = ..., 665 | short_path: typing.Optional[bool] = ..., 666 | custom_env: typing.Dict[str, str] = ..., 667 | custom_args: typing.List[str] = ..., 668 | ) -> str: ... 669 | def tensorrt_rtx( 670 | network_path: str, 671 | channels: int, 672 | shapes: typing.Tuple[int, int], 673 | fp16: bool, 674 | device_id: int, 675 | workspace: typing.Optional[int] = ..., 676 | verbose: bool = ..., 677 | use_cuda_graph: bool = ..., 678 | use_edge_mask_convolutions: bool = ..., 679 | input_name: str = ..., 680 | builder_optimization_level: int = ..., 681 | max_aux_streams: typing.Optional[int] = ..., 682 | short_path: typing.Optional[bool] = ..., 683 | custom_env: typing.Dict[str, str] = ..., 684 | custom_args: typing.List[str] = ..., 685 | engine_folder: typing.Optional[str] = ..., 686 | max_tactics: typing.Optional[int] = ..., 687 | tiling_optimization_level: int = ..., 688 | l2_limit_for_tiling: int = ..., 689 | ) -> str: ... 690 | def calc_size(width: int, tiles: int, overlap: int, multiple: int = ...) -> int: ... 691 | def calc_tilesize( 692 | tiles: typing.Optional[typing.Union[int, typing.Tuple[int, int]]], 693 | tilesize: typing.Optional[typing.Union[int, typing.Tuple[int, int]]], 694 | width: int, 695 | height: int, 696 | multiple: int, 697 | overlap_w: int, 698 | overlap_h: int, 699 | ) -> typing.Tuple[typing.Tuple[int, int], typing.Tuple[int, int]]: ... 700 | def init_backend( 701 | backend: backendT, trt_opt_shapes: typing.Tuple[int, int] 702 | ) -> backendT: ... 703 | def inference_with_fallback( 704 | clips: typing.List[vs.VideoNode], 705 | network_path: typing.Union[bytes, str], 706 | overlap: typing.Tuple[int, int], 707 | tilesize: typing.Tuple[int, int], 708 | backend: backendT, 709 | path_is_serialization: bool = ..., 710 | input_name: str = ..., 711 | batch_size: int = ..., 712 | ) -> vs.VideoNode: ... 713 | def inference( 714 | clips: typing.Union[vs.VideoNode, typing.List[vs.VideoNode]], 715 | network_path: str, 716 | overlap: typing.Tuple[int, int] = ..., 717 | tilesize: typing.Optional[typing.Tuple[int, int]] = ..., 718 | backend: backendT = ..., 719 | input_name: typing.Optional[str] = ..., 720 | batch_size: int = ..., 721 | ) -> vs.VideoNode: ... 722 | def flexible_inference_with_fallback( 723 | clips: typing.List[vs.VideoNode], 724 | network_path: typing.Union[bytes, str], 725 | overlap: typing.Tuple[int, int], 726 | tilesize: typing.Tuple[int, int], 727 | backend: backendT, 728 | path_is_serialization: bool = ..., 729 | input_name: str = ..., 730 | flexible_output_prop: str = ..., 731 | batch_size: int = ..., 732 | ) -> typing.List[vs.VideoNode]: ... 733 | def flexible_inference( 734 | clips: typing.Union[vs.VideoNode, typing.List[vs.VideoNode]], 735 | network_path: str, 736 | overlap: typing.Tuple[int, int] = ..., 737 | tilesize: typing.Optional[typing.Tuple[int, int]] = ..., 738 | backend: backendT = ..., 739 | input_name: typing.Optional[str] = ..., 740 | flexible_output_prop: str = ..., 741 | batch_size: int = ..., 742 | ) -> typing.List[vs.VideoNode]: ... 743 | def get_input_name(network_path: str) -> str: ... 744 | def bits_as(clip: vs.VideoNode, target: vs.VideoNode) -> vs.VideoNode: ... 745 | 746 | class BackendV2: 747 | """simplified backend interfaces with keyword-only arguments 748 | 749 | More exposed arguments may be added for each backend, 750 | but existing ones will always function in a forward compatible way. 751 | """ 752 | 753 | @staticmethod 754 | def TRT( 755 | *, 756 | num_streams: int = ..., 757 | fp16: bool = ..., 758 | tf32: bool = ..., 759 | output_format: int = ..., 760 | workspace: typing.Optional[int] = ..., 761 | use_cuda_graph: bool = ..., 762 | static_shape: bool = ..., 763 | min_shapes: typing.Tuple[int, int] = ..., 764 | opt_shapes: typing.Optional[typing.Tuple[int, int]] = ..., 765 | max_shapes: typing.Optional[typing.Tuple[int, int]] = ..., 766 | force_fp16: bool = ..., 767 | use_cublas: bool = ..., 768 | use_cudnn: bool = ..., 769 | device_id: int = ..., 770 | **kwargs 771 | ) -> Backend.TRT: ... 772 | @staticmethod 773 | def NCNN_VK( 774 | *, num_streams: int = ..., fp16: bool = ..., device_id: int = ..., **kwargs 775 | ) -> Backend.NCNN_VK: ... 776 | @staticmethod 777 | def ORT_CUDA( 778 | *, 779 | num_streams: int = ..., 780 | fp16: bool = ..., 781 | cudnn_benchmark: bool = ..., 782 | device_id: int = ..., 783 | **kwargs 784 | ) -> Backend.ORT_CUDA: ... 785 | @staticmethod 786 | def OV_CPU( 787 | *, 788 | num_streams: typing.Union[int, str] = ..., 789 | bf16: bool = ..., 790 | bind_thread: bool = ..., 791 | num_threads: int = ..., 792 | **kwargs 793 | ) -> Backend.OV_CPU: ... 794 | @staticmethod 795 | def ORT_CPU(*, num_streams: int = ..., **kwargs) -> Backend.ORT_CPU: ... 796 | @staticmethod 797 | def OV_GPU( 798 | *, 799 | num_streams: typing.Union[int, str] = ..., 800 | fp16: bool = ..., 801 | device_id: int = ..., 802 | **kwargs 803 | ) -> Backend.OV_GPU: ... 804 | @staticmethod 805 | def ORT_DML( 806 | *, device_id: int = ..., num_streams: int = ..., fp16: bool = ..., **kwargs 807 | ) -> Backend.ORT_DML: ... 808 | @staticmethod 809 | def MIGX( 810 | *, 811 | fp16: bool = ..., 812 | opt_shapes: typing.Optional[typing.Tuple[int, int]] = ..., 813 | **kwargs 814 | ) -> Backend.MIGX: ... 815 | @staticmethod 816 | def OV_NPU(**kwargs) -> Backend.OV_NPU: ... 817 | @staticmethod 818 | def ORT_COREML( 819 | *, num_streams: int = ..., fp16: bool = ..., **kwargs 820 | ) -> Backend.ORT_COREML: ... 821 | @staticmethod 822 | def TRT_RTX( 823 | *, 824 | num_streams: int = ..., 825 | fp16: bool = ..., 826 | workspace: typing.Optional[int] = ..., 827 | use_cuda_graph: bool = ..., 828 | device_id: int = ..., 829 | **kwargs 830 | ) -> Backend.TRT_RTX: ... 831 | 832 | def fmtc_resample(clip: vs.VideoNode, **kwargs) -> vs.VideoNode: ... 833 | def parse_trt_version(version: int) -> typing.Tuple[int, int, int]: ... 834 | --------------------------------------------------------------------------------