Skip to content

Instantly share code, notes, and snippets.

@v-fox
Forked from phiresky/motioninterpolation.vpy
Last active September 19, 2023 00:48
Show Gist options
  • Save v-fox/43c287426c366679afc4c65eece60cbc to your computer and use it in GitHub Desktop.
Save v-fox/43c287426c366679afc4c65eece60cbc to your computer and use it in GitHub Desktop.
On-CPU motion interpolation for ≤480@144, ~720@120, ≥1080p@60 playback in mpv

Realtime motion interpolating playback in mpv

This fork adds:

  • Interlacing detection with TDeintMod
  • Deinterlacing of selective marked frames via NNEDI3
  • Debanding via F3KDb for getting rid of blocky artefacts in low-res and low-bitrate videos, especially on videos encoded with pre-h264 codecs.
  • Necessary format and bitrate conversions with advanced dithering via FmtConv
  • MvTools' degraining step for clearing grain that may produce false vectors leading to interpolation artifacts, especially after debanding and dithering.
  • 4 quality presets for video-sizes approximate to ~480p, ~720p, ~1080p and ~1440p to avoid impractical use of interpolation.
  • High-quality shortcut-variables for beefy CPUs
  • options to avoid spoiling true HDR content with BYPASS_HDR & AVOID_DOWNSAMPLING_FORMAT_BITS (also bypasses upsampled 10-bit files that are popular for ripped anime)

Tested and calibrated on Xeon E5-2697v2@3Ghz (similar in multithread performance to Ryzen 3800) & RX580 & OC 72 fps LCDs, used with those mpv parameters (the most important one is vapoursynth's 'concurrent-frames' being at least 30 and, preferably, 60):

profile=gpu-hq
gpu-sw=yes
# resample types just screw-up audio and eat up more CPU needlessly
video-sync=display-vdrop
video-sync-max-video-change=3
opengl-pbo=yes
icc-profile-auto=yes
# big fat LUT ?
icc-3dlut-size=256x256x256
# 3k for VA, 10-15k for CRT, 1k for TN & IPS
#icc-contrast=3000
# mpv's "dynamic brightness" calculations instead of HDR's built-in metadata ? said to be ugly… and glitchy but looks fine for me
hdr-compute-peak=yes
# don't hardcode that for HDR displays ? for SDR sane values are in range of 80-400
# current default is 203, some people advocate to use highest values for SDR at maxed-out brightness
# for low-brightness SDR displays 120-160 might be better
target-peak=160
# mpv is stupid about dithering, so better to manually set 'no' for TN and fake HDR (due to LCD scaler's dithering),
# '8' or 'auto' for honest VA and IPS and '10' for real HDR
dither-depth=auto
# recent versions of mpv & Mesa have bad performance regression with "error-diffusion", try "ordered" instead
dither=error-diffusion
zimg-dither=error-diffusion
# mpv recommends "floyd-steinberg" but vapoursynth-fmtconv promotes "sierra-2" and "burkes", so… use "sierra-3" ?
# "sierra-3" wants slightly more than 32kb "shared memory" on Vulkan but RX580 is limited to exactly that !
# if vapoursynth-mvtools is enabled then internal on-CPU dithering is used in it instead this compute-shader on-GPU stuff due to mpv attempting to put vapoursynth filters too early
error-diffusion=sierra-2
# supposedly smears on LCD, especially slow VA, but might work out fine for you
temporal-dither=yes
alpha=blend
blend-subtitles=no
scale=ewa_lanczossharp
scale-antiring=0
cscale=ewa_lanczossharp
scaler-resizes-only=yes
correct-downscaling=yes
linear-downscaling=yes
linear-upscaling=no
sigmoid-upscaling=yes
# zimg stuff is highly experimental
sws-allow-zimg=yes
sws-scaler=lanczos
zimg-scaler=lanczos
zimg-fast=no
# debanding is resource-heavy and washes out details, especially on low-res videos and with motion-interpolation enabled
# probably, should not be used unless annoying noticeable ringing is often encountered
# BUT it REALLY helps with poorly-encoded low-res videos
# vapoursynth filters may also have their own debanding and dithering !
deband=yes
# frame processing latency heavily increases with this
deband-iterations=1
# 256 and up produces very visible "dot matrix" artefacts and horrible blur with low range
deband-threshold=160
deband-grain=8
# optimal value depends on number of iterations
# 16-64 is good for 1, 8-48 - for 2, 4-16 for 4. lower is stronger and shorter but blurier
deband-range=64

scaler-lut-size=10
interpolation=yes
tscale=box
# try 'sphinx' or 'quadric'
tscale-window=sphinx
# try in range of 0.75-1.25
tscale-radius=0.75
tscale-clamp=0.0
# not needed if format conversion inside VS script is used !
#vf-pre=format=fmt=yuv422p
#hwdec-image-format=yuv422p
# with 2 'concurrent-frames' peformance is bad, 4-16 is janky, 30 is OK, 60 is good
# having more 'buffered-frames' should also be beneficial but practice shows no noticeable improvement
vf-add=vapoursynth=~/.config/mpv/vapoursynth/motioninterpolation.vpy:1:66
# HQ x2 step for low-res videos
glsl-shader="~/.config/mpv/shaders/opengl/FSRCNNX_x2_8-0-4-1.glsl"
# simpler additional x2 step to x4 lowest-res videos (rgb version should be used without previous yuv conversion)
glsl-shader="~/.config/mpv/shaders/compute-vulkan/ravu-r4-yuv.hook"
hr-seek-framedrop=no
autofit=70%x60%
# for Vulkan
gpu-api=vulkan
vulkan-swap-mode=fifo
swapchain-depth=4
vulkan-queue-count=8
vulkan-async-transfer=yes
vulkan-async-compute=yes
# audio
initial-audio-sync=yes
autosync=0
mc=0.4
ad-lavc-ac3drc=1.0
# force best resampling quality for 32-bit USB DACs
#audio-resample-filter-size=32
#audio-resample-phase-shift=12
#audio-resample-max-output-size=1
#audio-swresample-o=rematrix_volume=0.75,rematrix_maxval=3.0,dither_method=improved_e_weighted,resampler=swr,phase_shift=12,filter_size=32,cutoff=1.0,precision=32,async=1,max_soft_comp=1.0,filter_type=kaiser,kaiser_beta=12,output_sample_bits=32
audio-normalize-downmix=yes
gapless-audio=yes
audio-file-auto=fuzzy
# full-steam decoding, might want to limit to 2-4 when heavy CPU-only VS-scripts are used
vd-lavc-threads=0
ad-lavc-threads=0
# for AMD and Intel
hwdec=vaapi-copy
vd-lavc-dr=yes
# cache 
icc-cache-dir=~/.cache/mpv/icc
gpu-shader-cache-dir=~/.cache/mpv/shaders
watch-later-directory=~/.local/share/mpv/watch_later
cache=yes
cache-dir=~/.cache/mpv
cache-on-disk=no
cache-pause-initial=yes
demuxer-thread=yes
demuxer-lavf-buffersize=2097152
demuxer-readahead-secs=10
demuxer-max-bytes=512MiB
demuxer-max-back-bytes=256MiB
audio-reversal-buffer=128MiB
video-reversal-buffer=1024MiB
demuxer-seekable-cache=yes
force-seekable=yes
# subtitles
sub-auto=fuzzy
sub-ass-force-style=Kerning=yes
sub-ass-force-margins
sub-codepage=uchardet
sub-margin-y=2
sub-justify=left
sub-ass-justify=yes
# LUA scripting
load-scripts=yes
# see stats for benchmarking
#script=~/.config/mpv/scripts/stats.lua
#osd-msg3="[custom statistics]\n current vo:                          ${current-vo}\n current ao:                          ${current-ao}\n frames dropped by decoder:           ${decoder-frame-drop-count}\n frames dropped by vo:                ${frame-drop-count}\n frames delayed by vo:                ${vo-delayed-frame-count}\n mistimed frames:                     ${mistimed-frame-count}\n \n container FPS:               ${container-fps}\n estimated decoder/filter FPS:               ${estimated-vf-fps}\n estimated display FPS:               ${estimated-display-fps}\n vsync ratio:                         ${vsync-ratio}\n vsync jitter:                        ${vsync-jitter}\n last  A/V sync difference:         ${avsync}\n total A/V sync correction:           ${total-avsync-change}\n audio speed correction:              ${audio-speed-correction}\n video speed correction:              ${video-speed-correction}"

With use of igv/FSRCNN-TensorFlow and mpv-prescalers:ravu-r4.


Edit 2016-09: SVP now has a free (but proprietary) version of their software, which includes a GPU port of the MVTools functions, making this much less resource intense: https://www.svp-team.com/wiki/SVP:Linux (AUR)


run this for SVP-like motion interpolation:

cd ~/.config/mpv # or ~/.mpv

wget https://gist.githubusercontent.com/phiresky/4bfcfbbd05b3c2ed8645/raw/motioninterpolation.vpy

echo 'I vf toggle format=yuv420p,vapoursynth=~~/motioninterpolation.vpy:4:4' >> input.conf

# to enable it by default
echo 'vf=format=yuv420p,vapoursynth=~~/motioninterpolation.vpy:4:4' >> mpv.conf

now press shift+i while mpv is playing a video to toggle beautiful motion vector interpolation of the video framerate to your display refresh rate.

You need mpv compiled with VapourSynth support. Not sure what distributions do this, in Arch Linux you can get mpv-git or mpv-vapoursynth (AUR) and vapoursynth-plugin-mvtools.

It should also be possible to make this work through SMPlayer.

Warning: This is CPU intensive. The settings work on a i5-2500k cpu. If it lags you will have to change some parameters, look for 2500k in the script. If your CPU is faster remove that block.

If your CPU is really fast, try removing the 'mode' parameter and changing BlockFPS to FlowFPS, this gives much better results.

See the reddit post for more information

Encoding video to a higher framerate

Run this:

vspipe --arg in_filename=input.mkv --arg display_fps=60 --y4m motioninterpolation.vpy -|ffmpeg -i - -crf 18 output.mkv
#!/usr/bin/python
# vim: set ft=python:
# see the README at https://gist.github.com/v-fox/43c287426c366679afc4c65eece60cbc
# source: https://github.com/mpv-player/mpv/issues/2149
# source: https://github.com/mpv-player/mpv/issues/566
# source: https://github.com/haasn/gentoo-conf/blob/nanodesu/home/nand/.mpv/filters/mvtools.vpy
import vapoursynth as vs
import functools
# https://www.vapoursynth.com/2021/09/r55-audio-support-and-improved-performance/
core = vs.core
# enable any of those ONLY if you believe in the power of your CPU ! biggest overlap will give especially major quality improvement
# even on very strong system using combination of ≥10 target bit-depth, best DCT (1-4) and low block-sizes (16x2, 4x4, 8x8) will mass frame-skip or hang
BYPASS_HDR = False
# even 1080p shreds 12-core 3GHz CPUs with AVX, anything above has no chance
DOWNSCALE_TO_1080P = True
AVOID_DOWNSAMPLING_FORMAT_BITS = False
AVOID_UPSAMPLING_FORMAT_BITS = True
AVOID_DOWNSAMPLING_FORMAT = False
# main setting: "FLowFPS" versus "BlockFPS"
# Flow-mode gets rid of ugly blocking artefacts of Block-mode and interpolates fluidly but is very costly
INTERPOLATE_FUNCTION = "FlowFPS"
# override setting of quality profiles that may prefer BlockFPS ?
ALWAYS_FLOW = False
# always use 4x4 blocks ?
MIN_BLOCKSIZE = False
MAX_PRECISION = False
# decent DCT is costlier of all, "pure" DCT mode of "1" is almost impossible to work in realtime, so "3" is the best option
BEST_DCT = False
MAX_SEARCH = False
BEST_SEARCH = False
# these seem to be largely useless + both block subdivision with 2-stage analysis and overlap detection seem to be do more harm than good
BEST_SEARCH_POSTDIVIDE = False
MAX_OVERLAP_POSTDIVIDE = False
MAX_OVERLAP = False
DUAL_OVERLAP = True
AUTO_OVERLAP = False
AUTO_OVERLAP_POSTDIVIDE = False
DUAL_DCT = True
# interlacing detection via TDeintMod for tagging interlaced frames for further processing
INTERLACING_DETECTION = False
# detection can't guess type of interlacing, it has to be hardcoded: 0 for Bottom Field First, 1 is Top Field First
INTERLACING_TYPE = 0
# selective deinterlacing using NNEDI3 via vapoursynth-nnedi3 ≥ 12 which is VERY resource-heavy
# recently broken by https://github.com/vapoursynth/vapoursynth/blob/f5a443bf13d36615a3ea0174940be061d75b1731/src/core/vsapi.cpp#L360 !
# see https://github.com/vapoursynth/vapoursynth/issues/955
DEINTERLACE = False
# force up-sampling to 16 bit to avoid theoretical loss of quality ? that makes deinterlacing even more resource-heavy !
DEINTERLACE_UPSAMPLE = False
# deband in VS ? requires vapoursynth-f3kdb
# this is especially useful with videos encoded with pre-h264 codecs that often created ugly block artifacts
# but it also means that it may be wasteful on modern well-coded ≥720 videos UNLESS their bitrate is too low
DEBAND = True
DEBAND_UPSAMPLE = True
# convert video into YUV via VS ? requires vapoursynth-fmtconv
# format conversions can be skipped by setting its variables to "None" without quotes
# 444 for max quality, 422 for good, 420 for worse
# input and output formats should be the same or better than source video to avoid loss in quality
FORMAT_INPUT = None
# format for non-YUV sources when FORMAT_INPUT=None
FORMAT_INPUT_FALLBACK = 420
FORMAT_INPUT_FLOAT = 1
# set to the same as input to skip conversion right before output
FORMAT_OUTPUT = None
FORMAT_OUTPUT_FLOAT = FORMAT_INPUT_FLOAT
# after format conversion clip is 16 or 32 bits and needs to be down-converted to 8 but you may keep it bigger if you can
# especially if you play 10- or 12-bit videos on HDR displays for which you should use 10 and 16 bits respectively
# 8, 9, 10, 12, 16, 32 integer and float are supported as YUV, RGB is 16-bit integer or 32-bit float
# BUT mvtools' "Super" function does NOT support non-YUV, >16-bits or float ! so, down-sampling and dithering can use float as input but not as output
# dithering modes are 0=ordered, 1=closest round, 2=round, 3=Sierra-2-4A ED (fast), 4=Stucki ED (good edges, bad gradients), 5=Atkinson ED (visible patterns), 6=Floyd-Steinberg ED, 7=Ostromoukhov ED (slow and integer-only), 8=Void and cluster halftone
# dithering pattern can be 4/8/16/32 wide; rotation may not be good for slow displays, like VA
# resampling kernels are point (NN), rect (box), linear (bilinear), cubic (bicubic), lanczos (sinc), blackman, blackmanminlobe, spline/16/36/64, gauss, sinc
FORMAT_INPUT_BITS = 10
FORMAT_INPUT_KERNEL = "lanczos"
FORMAT_INPUT_KERNEL_TAPS = 6
FORMAT_INPUT_DITH_MODE = 8
FORMAT_INPUT_DITH_ROTATE = 0
FORMAT_INPUT_DITH_STATIC_NOISE = 0
FORMAT_INPUT_DITH_AMP_ORDERED = 2
FORMAT_INPUT_DITH_AMP_NOISE = 0
FORMAT_INPUT_DITH_PATTERN = 32
FORMAT_OUTPUT_BITS = None
FORMAT_OUTPUT_KERNEL = "cubic"
FORMAT_OUTPUT_KERNEL_TAPS = 6
FORMAT_OUTPUT_DITH_MODE = FORMAT_INPUT_DITH_MODE
FORMAT_OUTPUT_DITH_ROTATE = FORMAT_INPUT_DITH_ROTATE
FORMAT_OUTPUT_DITH_STATIC_NOISE = FORMAT_INPUT_DITH_STATIC_NOISE
FORMAT_OUTPUT_DITH_AMP_ORDERED = FORMAT_INPUT_DITH_AMP_ORDERED
FORMAT_OUTPUT_DITH_AMP_NOISE = FORMAT_INPUT_DITH_AMP_NOISE
FORMAT_OUTPUT_DITH_PATTERN = FORMAT_INPUT_DITH_PATTERN
# degrain in mvtools ? this should make picture "flat" and help with interpolation by getting rid of some false vectors
# this is extremely heavy operation that requires its own 1 or 2 passes of analyzation on 1-3 adjacent (back & forward) frames after all conversions but before interpolation
# using more than 1 frame may require increasing mpv's "buffered-frames" to the same value
DEGRAIN = False
DEGRAIN_CHROMA = True
DEGRAIN_FRAMES = 1
# http://avisynth.org.ru/mvtools/mvtools2.html#functions
# default is 400, less means interpolation will only happen when it will work well
# bigger means that bigger steps in movement (such as low-fps animation im anime) will be ignored
# but lower means that distortions like grain may be interpreted as movement
# …or is it vice versa ?
# default is 400, max is 16320 (255 pixels of movement), 320 means movement of 8*8 block for 5 pixels and is auto-scaled for other block sizes, 128 is safe value that still produces decent results
# increase it until you see "jumping square" artifacts but it's better to keep it low and decrease lsad & lambda and increase search quality instead
vector_length_in_pixels = 128
ignore_threshold = 64 * vector_length_in_pixels
# this (thscd2) supposed to avoid attempts in finding non-existent vectors between scene changes by setting limiter in-between values 0 (0%) and 255 (100%), default is 130 (51%)
# where ignore_threshold (thscd1) is defined in range 0-16320 (where 16320 is 8x8 block moving for 255 pixels)
# BUT it doesn't seem to be behaving that way. instead, every ignore_threshold values has corresponding hard-cut scene_change value where
# ugly scene-mixing is still cut off and overshooting it will make it useless. thscd1=512/768/1024/1536/2048/3072 & thscd2=125-176/18-64/36-70/8-40/18/3 has shown to work
# there seems to be no defined limits for thscd2 (NOT actually 255) and negative are silently accepted (but block any interpolation ?)…
# this has greatest effect with scenes with flashing lights
scene_change_percent = 76
# what is the limit for interpolating ? it seems that mvtools not just limited by amount of interpolated frames
# but size and amount of output frames in general, so doing more than 1440p@60 is impractical
# ~4.0 mpix is ~1440p and ~8.5 mpix is ~4K
max_mpix = 4.0
max_mpix_hq = 0.922
# the bigger the source's framerate, the more often frame analyzation should happen, so CPU load rises exponentially
# especially if software realtime format conversion is used in the player
min_src_fps = 9
max_src_fps_lq = 31
max_src_fps_hq = 51
# maximum allowed target frame-rate for interpolation
max_dst_fps = 145
# ideally, this should have the same precision as what GPU driver uses for driving the display but ≥8 gives out errors
# here's it's an actual value of monitor's refresh rate
dst_fps = float(display_fps)
dst_fps_num = int(dst_fps * 1e6)
dst_fps_den = int(1e6)
PLAYER_FAIL = False
# Getting frames
if "video_in" in globals():
# realtime
clip = video_in
# mpv may screw up and report 0 or something random as container_fps
if container_fps == 0:
print("[mvtools]: player screwed up in determining video's fps, so we skip all processing !")
PLAYER_FAIL = True
else:
# Needed because clip FPS is missing
print("[mvtools]: Container fps reported by mpv is {}".format(container_fps))
# how precise this should be ? definitively, not less than e4 but is bigger necessary ?
src_fps_num = int(container_fps * 1e8)
src_fps_den = int(1e8)
clip = core.std.AssumeFPS(clip, fpsnum = src_fps_num, fpsden = src_fps_den)
print("[mvtools]: Running in realtime with {} / {} initial src / dst fps".format(clip.fps_num / clip.fps_den, dst_fps_num / dst_fps_den))
else:
# run with vspipe
clip = core.ffms2.Source(source=in_filename)
print("[mvtools]: Running with vspipe with {} / {} initial src / dst fps".format(clip.fps_num / clip.fps_den, dst_fps_num / dst_fps_den))
if not PLAYER_FAIL:
# resolution in megapixels. 1080p ≈ 2MP, 720p ≈ 1MP
mpix = clip.width * clip.height / 1000000
src_fps = clip.fps_num / clip.fps_den
else:
mpix = 999
src_fps = 999
# finding out info about source video
FORMAT=clip.format.name
FORMAT_FAMILY=clip.format.color_family
FORMAT_TYPE="float" if clip.format.sample_type else "integer"
FORMAT_BITS=clip.format.bits_per_sample
FORMAT_SAMPLES_HORIZONTAL=clip.format.subsampling_w
FORMAT_SAMPLES_VERTICAL=clip.format.subsampling_h
FORMAT_PLANES=clip.format.num_planes
FORMAT_WIDTH=clip.width
FORMAT_HEIGHT=clip.height
print("[mvtools]: Video source is {}x{} {}-bit {} {} ({}) with {} {}/{}-subsampled planes".format(FORMAT_WIDTH, FORMAT_HEIGHT,\
FORMAT_BITS, FORMAT_TYPE, FORMAT, FORMAT_FAMILY, FORMAT_PLANES, FORMAT_SAMPLES_HORIZONTAL, FORMAT_SAMPLES_VERTICAL))
# don't mess with size by default
FORMAT_SCALE_HEIGHT = 1.0
FORMAT_SCALE_WIDTH = 1.0
DOWNSCALING_TO_1080P = False
if DOWNSCALE_TO_1080P:
if FORMAT_HEIGHT > 1080:
DOWNSCALING_TO_1080P = True
FORMAT_SCALE_HEIGHT = 1080 / FORMAT_HEIGHT
FORMAT_SCALE_WIDTH = FORMAT_SCALE_HEIGHT
FORMAT_WIDTH = int(FORMAT_WIDTH * FORMAT_SCALE_HEIGHT)
FORMAT_HEIGHT = int(FORMAT_HEIGHT * FORMAT_SCALE_HEIGHT)
mpix = FORMAT_WIDTH * FORMAT_HEIGHT / 1000000
print("[mvtools]: Forcibly downscaling video to {}x{} !!!".format(FORMAT_WIDTH, FORMAT_HEIGHT))
# Skip interpolation for >max_mpix megapixel or =>max_src_fps Hz content due to performance
if mpix > max_mpix_hq:
max_src_fps = max_src_fps_lq
else:
max_src_fps = max_src_fps_hq
if not (PLAYER_FAIL or \
(BYPASS_HDR and ((FORMAT_BITS > 8 and AVOID_DOWNSAMPLING_FORMAT_BITS) or (FORMAT != "YUV420P8" and AVOID_DOWNSAMPLING_FORMAT))) or \
(mpix > max_mpix) or \
(src_fps < min_src_fps) or \
(src_fps > max_src_fps) or \
(src_fps >= max_dst_fps)):
# number of layers in the clip. default is '0' meaning "auto" which, seams, to default to '6' and putting any more than that does no seem to work
# technically, "necessary" number of levels should depend on resolution, block-size and target operation but it doesn't seem to actually matter
# <3 values seem to be missing a lot of good vectors while >3 create a lot of bad vectors, so…
levels = 0
# default lsad seem to be 1200 and, as lambda, varies its behavior with number of levels and type of SAD scaling, defined by plevel
lsad = 768
#lsad = int(1200 / (6 / levels))
#lsad = int(1200 * (2 ** (ignore_threshold / 400)))
# low badsad seems to negate "rejected predictions", which leads to a lot of weird vectors, that are defined by limits set by pnew and ignore_threshold/thscd1
badsad = 16321
# TUNE pnew PARAMETER VERY CAREFULLY !
# too small will produces ugly "halo" artifacts on edges of moving objects
# too big will skip interpolation on many movements
# mvtools' default is 48, bigger overlap may compensate for lower, artifact-inducing values
# although, lambda may be more important than overlap in combating low vector penalty artifacts
# 112/64 pnew/pzero seems to be fine for 32x32 blocksizes on ~720p. as with lambda, seems it needs to be much smaller for bigger blocks
# BUT it's, most likely, the consequence of finding MUCH less vectors, meaning that bigger blocks may hard-cap interpolation
pnew_scale = False
pnew_offset = 0
pnew_offset_for_overlap = 0
pnew_offset_divisor = 0
pnew_offset_divisor_for_overlap = 0
# formula for blocksize 8x8, one after profiles below accounts for other sizes
pnew_8x8 = int(((2 ** (ignore_threshold / 16320)) - 1) * (256 - pnew_offset_divisor_for_overlap) + pnew_offset_for_overlap) if (MAX_OVERLAP_POSTDIVIDE or MAX_OVERLAP) else\
int(((2 ** (ignore_threshold / 16320)) - 1) * (256 - pnew_offset_divisor) + pnew_offset)
# smaller lambda_mult_last & lambda_mult_last_postdivide may introduce block-shaped artifacts
# bigger one will skip some interpolation but may compensate for artifacts due to small pnew
# mysteriously, lambda seems to be behaving the opposite it supposed to on high resolutions (≥1080p) and/or block-sizes (≥64):
# for some reason it's much more aggressive which forces smaller values or even zero to avoid excessive lambda-induced artifacts
# it supposed to be using (1000 * blksize * blksizev / 64) formula by default but maybe mvtools scales to blksize* internally while expecting 8x8 for all real sizes ?
# meaning that it's supposed to be just (lambda_mult_first * lambda_mult_last) ? or that >32 block-sizes just broken
# first multiplier is recommended to be 1000 and the last: in 0.5-2.0 range
lambda_mult_first = 1024
#lambda_mult_first = int(1024 / (6 / levels))
lambda_mult_last = 0.5
lambda_mult_last_withdivide = 1.2
lambda_mult_last_postdivide = 1.6
overlap_offset = 3
overlap_offset_divisor = 0
overlap_offset_postdivide = 1
overlap_offset_divisor_postdivide = 32
# defining default parameters
# 'opt' means "use CPU's vector function acceleration such as SSE and AVX"
# 'divide' means to divide blocks in half with simple (2) or simpler (1) vector calculations for them
# 'search' is select type of vector-searching function: 4 is fast and fine (h264-derived), 5 is good but slow, 3 is even slower and requiress negative searchparam
# 'searchparam' and 'pelsearch' are min and max search radius or vector-count, depending on the algorithm, supposedly
# 'chroma' is for using both contrast and colour for calculations or not
# 'truemotion' is "better" defaults that we override anyway
# 'blksize' & 'blksizev' for block sizes. theoretically: the bigger they are, the easier to calculate. practically: bigger blocks lose vectors, skip interpolation and produce unavoidable artifacts on complex surfaces
# 'dct' said to "improve motion vector estimation at luma flicker and fades" but anything other than 5-10 is slow, 9 & 10 are fastest, 5 may help with artefacts of translucent text, 1-2 are ideal but slowest
# 'pnew', 'pzero' and 'pglobal' set "replacement threshold" for vectors, similarly to 'ignore_threshold' but in reverse: lower values give smoother motion but with more artifacts
Analyze_Common_Params = {
'overlap': 0,
'overlapv': 0,
'divide': 0,
'search': 4,
'searchparam': 2,
'truemotion': True,
'pnew': 255,
'blksize': 32,
'blksizev': 32,
'dct': 10
}
# 'plevel' 0/1/2 is none/linear/quadratic scaling for "penalty factor lambda" where 'lambda' supposed to be another threshold against artifacts on non-global vectors
# 'lsad' is "SAD limit for lambda", whatever that means. "Local lambda is decreased if SAD value of vector predictor (formed from neighbor blocks) is greater than the limit. It prevents bad predictors using but decreases the motion coherence"…
## default 'lsad' is 1200, smaller values also seem to produce block-artifacts
# so, if default 320-400 thscd1 gets lsad=1200 and max thscd1 is 16320 where lsad > thscd1 doesn't make sense
# should we do lsad=3*thscd1 for thscd1=320 and lsad=1*thscd1 for thscd1=16320 with gradual multiplier step-down from 3 to 1 ?
## 'badsad' is threshold 'to make more wide second search for bad vectors' in 'badrange' that is "radius of wide search for bad blocks"
# default of 'badsad' is 10000 which disables it
# it's unclear what benefit it supposed to bring because in recommended values of 1000-2000
# it only results in blocky artifacts, with their amount depending on 'badrange'
# less 'levels' means simpler and faster calculations but more (good and bad) missed vectors. what is the default for Analyze and Super ?
# the fact that super-clip has bigger number of levels does not mean that they are actually used by analyzation and
# number of levels that analyzation wants depends on resolution of the video and size of blocks & overlap detection
# https://github.com/dubhater/vapoursynth-mvtools/blob/28f70ba737c6ac4e25d026536b5556e5c94ddde3/src/MVAnalyse.c#L569-L603
# it seem that vector size (maximum length of interpolation for a moving object) is tied to Analyze_Params['pelsearch'], Interpolate_Params['thscd1'] and Interpolate_Params['ml']
# maximum values is 255 pixels for virtual 8x8 block derived from pelsearch=256 for search-modes 3/4/5, thscd1=16320=8*8*255 and ml=255
# more allow interpolating on larger distances on low-framerate videos, like anime, but also may lead to false predictions and artifacts
Analyze_Params = {
'search_coarse': 4,
'trymany': True,
'lsad': lsad,
'badsad': badsad,
'badrange': 32,
'pzero': 248,
'pglobal': 0,
'plevel': 1,
'pelsearch': 256,
'levels': levels
}
# degrain analyzation at ~480p needs at minimum 4 levels but any >16x16 interpolation sometimes wants at least 6
# 'pel' means precision: 1 is pixel (may cause artifacts), 2 is half-pixel (default and recommended) and 4 is quarter-pixel (slow)
# 'sharp' is used for interpolation with pel 2|4: 0 for billinear 2-tap blur, 1 for bicubic 4 taps and 2 for Lanczos-like 6-tap Wiener
# 'rfilter' = 0 is nearest-neighbour filtering, 1 is simple triangle, 2 is default bilinear triangle, 3 is quadratic, 4 is cubic
Super_Params = {
'pel': 4,
'sharp': 1,
'rfilter': 4,
'levels': Analyze_Params['levels']
}
# Avisynth docs say: "ml parameter defines the scale of motion mask.
# When the vector's length (or other kind value) is superior or equal to ml,
# the output value is saturated to 255. The lesser value results to lesser output.
# The greater values are corresponded to more weak occlusion mask (as in MMask function, use it to tune and debug). Default=100"…
# …buuuuut practice of using vapoursynth shows that "255" is far from the maximum (which is unknown) and picture is more detailed with much larger values
# 2^32=4294967296 seems to give nice results without any performance strain, >255 values may work better with lower vector penalties of (pnew, pzero and pglobal) but higher lsad & lambda
# "blend: blend frames at scene change like ConvertFps if true, or repeat last frame like ChangeFps if false"… but that seem to work only when badsad & badrange feature is effectively disabled
## for FLowFPS
# mask=0 is simple backward and forward occlusion masks (used in versions up to 1.4.x, fastest);
# mask=1 is similar masks with additional switching to static zero vectors at occlusion areas (similar to v1.5.x);
# mask=2 is for using extra vectors from adjacent frames for decreasing objects halo at occlusion areas (v1.8, slowest). Default=2.
# for BlockFPS
# mode=0 - average of fetched forward and backward partial motion compensation (fastest).
# mode=1 - static median.
# mode=2 - dynamic median.
# mode=3 - time weigthed combination of fetched forward blocks masked by shifted backward and fetched backward masked by shifted forward (now default mode);
# mode=4 - mode 3 mixed with simple static time average by occlusion mask of shifted blocks.
# mode=5 - occlusion mask (for debug).
# mode=6 to 8 - modes like 3 to 5 but SAD mask is used as occlusion mask.
Interpolate_Params = {
'thscd1': ignore_threshold,
'thscd2': int((255 / 100) * scene_change_percent),
'ml': 16320,
'blend': False,
'mask': 2,
'mode': 3
}
# chroma should NOT be used in interlacing detection
# defaults are cthresh=6, blockx=16, blocky=16, chroma=False, mi=64
Interlace_Detection_Params = {
'chroma': False,
'cthresh': 7,
'blockx': 128,
'blocky': 128,
'mi': (128 * 128 // 8)
}
DeInterlace_Params = {
'order': INTERLACING_TYPE,
'field': INTERLACING_TYPE,
'mode': 0,
'length': 12,
'mtype': 1,
'ttype': 5,
'athresh': 3,
'expand': 1
}
# use 'combed_only' options to ensure to skip non-interlaced frames
DeInterlace_Params_nnedi3 = {
'field': INTERLACING_TYPE,
'combed_only': True
}
# see https://github.com/SAPikachu/flash3kyuu_deband/blob/master/docs/source/usage.rst
DeBand_Params = {
'range': 21,
'y': 160,
'cb': 160,
'cr': 160,
'grainy': 16,
'grainc': 16,
'sample_mode': 2,
'blur_first': False,
'dynamic_grain': True,
'dither_algo': 3,
'random_algo_ref': 2,
'random_algo_grain': 2,
'random_param_ref': 0.25,
'random_param_grain': 0.1
}
# defaults for optional degrain step
# player's degrain might be less precise but much more efficient… if it bothers to do it _before_ VS filter
DeGrain_Params = {
'thsad': 128,
'limit': 255,
'thscd1': Interpolate_Params['thscd1'],
'thscd2': Interpolate_Params['thscd2']
}
DeGrain_Super_Params = {
'pel': 4,
'sharp': 1,
'rfilter': Super_Params['rfilter']
}
DeGrain_Analyze_Params = {
'truemotion': False,
'trymany': False,
'pnew': 256,
'lsad': Analyze_Params['lsad'],
'pzero': 255,
'pglobal': 0,
'plevel': Analyze_Params['plevel'],
'search': 4,
'search_coarse': 4,
'searchparam': 2,
'pelsearch': 128,
'blksize': 128,
'blksizev': 128,
'overlap': 0,
'divide': 0,
'dct': Analyze_Common_Params['dct']
}
# Interpolating for too high fps is too CPU-expensive, smoothmotion can handle the rest.
# see the description of these parameters in http://avisynth.org.ru/mvtools/mvtools2.html#functions
# this is calibrated for Intel Xeon 2650v2 (Ryzen 1600) and mpv's "vf-pre=format=fmt=<X>" conversion to yuv422p or yuv440p
# more detailed formats (ideally, yuv444p10) use more CPU but lesser ones (yuv420p) ruin details
# using worse formats or disabling 'overlap' & 'divide' and 'badrange' will allow usage of smaller 'blocksize'
# dct modes <5 are extremely slow in all cases
if mpix > 2.1:
# half-assed effort for 1080p-max_mpix before giving up completely
quality = 'low'
INTERLACING_DETECTION = False
Interlace_Detection_Params['cthresh'] = 12
DEINTERLACE = False
DEBAND = False
DEGRAIN = False
DEGRAIN_CHROMA = False
DEGRAIN_FRAMES = 1
DeGrain_Params['thsad'] = 96
DUAL_DCT = False
DUAL_OVERLAP = False
#FORMAT_INPUT = 420
FORMAT_INPUT_KERNEL = "cubic"
FORMAT_INPUT_KERNEL_TAPS = 4
FORMAT_INPUT_DITH_MODE = 3
FORMAT_INPUT_DITH_PATTERN = 32
Interlace_Detection_Params['blockx'] = 64
Interlace_Detection_Params['blocky'] = Interlace_Detection_Params['blockx']
# interlaced crap at that resolution is unlikely, unless it's raw "Ultra HD TV" footage from assholes
Interlace_Detection_Params['mi'] = Interlace_Detection_Params['blockx'] * Interlace_Detection_Params['blocky'] // 2
#pnew_offset = 32
#pnew_offset_divisor = 208
#overlap_offset_divisor_postdivide = 0
#lambda_mult_last //= 4
#lambda_mult_last_withdivide //= 4
#lambda_mult_last_postdivide //= 4
#Analyze_Params['lsad'] //= 4
#INTERPOLATE_FUNCTION = 'BlockFPS'
#Interpolate_Params['thscd1'] = 8192
Interpolate_Params['mode'] = 3
#Analyze_Common_Params['lambda'] = int(lambda_mult_first * lambda_mult_last)
#if mpix < 3.6:
# Analyze_Common_Params['blksize'] = 32
# Analyze_Common_Params['blksizev'] = 16
#else:
# Analyze_Common_Params['blksize'] = 64
# Analyze_Common_Params['blksizev'] = 32
#Analyze_Common_Params['divide'] = 0
#Analyze_Common_Params['overlap'] = 4
#Analyze_Common_Params['overlapv'] = 4
#Analyze_Common_Params['dct'] = 10
Analyze_Common_Params['search'] = 4
#Analyze_Common_Params['searchparam'] = 256 // levels
Analyze_Params['search_coarse'] = 4
Analyze_Params['pelsearch'] = 128
#Analyze_Params['trymany'] = False
#Analyze_Params['plevel'] = 2
#Analyze_Params['levels'] = 6
#Super_Params['levels'] = 6
Super_Params['pel'] = 1
Super_Params['sharp'] = 0
# don't interpolate sub-30 content past ~50 and the rest - past ~60
if (src_fps < 31.0 and dst_fps > 59.952):
dst_fps = 59.952
else:
dst_fps = src_fps
elif mpix > 1.1:
# intermediate, compromised quality for ~1080p
quality = 'medium'
#MAX_SEARCH = True
# the only place you would stumble at 1920x1080 interlaced video is shitty 1080i HDTV
INTERLACING_DETECTION = False
Interlace_Detection_Params['cthresh'] = 11
DEINTERLACE = False
DEBAND = True
DEBAND_UPSAMPLE = False
DEGRAIN = True if src_fps < 29 else False
DEGRAIN_CHROMA = True
DEGRAIN_FRAMES = 2
DeGrain_Params['thsad'] = 96
DUAL_DCT = False
DUAL_OVERLAP = False
#FORMAT_INPUT = 420
#FORMAT_INPUT_KERNEL = "cubic"
#FORMAT_INPUT_KERNEL_TAPS = 4
#FORMAT_INPUT_DITH_PATTERN = 16
Interlace_Detection_Params['blockx'] = 32
Interlace_Detection_Params['blocky'] = Interlace_Detection_Params['blockx']
Interlace_Detection_Params['mi'] = Interlace_Detection_Params['blockx'] * Interlace_Detection_Params['blocky'] // 4
#pnew_offset = 4
#pnew_offset_divisor = 112
#overlap_offset = 0
#overlap_offset_divisor_postdivide = 0
#lambda_mult_last //= 2
#lambda_mult_last_withdivide //= 2
#lambda_mult_last_postdivide //= 2
#Analyze_Params['lsad'] = 1024
#INTERPOLATE_FUNCTION = 'BlockFPS'
#Interpolate_Params['thscd1'] = 12228
#Interpolate_Params['mode'] = 4
#Analyze_Common_Params['lambda'] = int(lambda_mult_first * lambda_mult_last)
#Analyze_Common_Params['blksize'] = 16
#Analyze_Common_Params['blksizev'] = 8
#Analyze_Common_Params['divide'] = 0
#Analyze_Common_Params['overlap'] = 4
#Analyze_Common_Params['overlapv'] = 4
#Analyze_Common_Params['dct'] = 5
Analyze_Common_Params['search'] = 4
#Analyze_Common_Params['searchparam'] = 256 // levels
#Analyze_Params['global'] = 96
Analyze_Params['search_coarse'] = 4
Analyze_Params['pelsearch'] = 256
#Analyze_Params['trymany'] = False
#Analyze_Params['plevel'] = 1
#Analyze_Params['levels'] = 6
#Super_Params['levels'] = Analyze_Params['levels']
#Super_Params['pel'] = 1
#Super_Params['sharp'] = 0
# generally, don't interpolate sub-50 content past 50
if (src_fps < 31.0 and dst_fps > 75.0 and mpix < 1.6):
DEGRAIN = True if src_fps < 31 else False
DEGRAIN_FRAMES = 1
Super_Params['pel'] = 2
Super_Params['sharp'] = 1
dst_fps = 75.0
elif (src_fps < 31.0 and dst_fps > 59.952 and mpix < 2.1) or DOWNSCALING_TO_1080P:
DEBAND = False
#DEGRAIN = True if src_fps < 25 and FORMAT_BITS <= 8 else False
DEGRAIN = False
DEGRAIN_CHROMA = False
DEGRAIN_FRAMES = 1
#FORMAT_INPUT_KERNEL_TAPS = 4
#Analyze_Common_Params['blksize'] = 16
#Analyze_Common_Params['blksizev'] = 16
Super_Params['pel'] = 2
Super_Params['sharp'] = 1
dst_fps = 59.952
elif dst_fps > max_dst_fps:
dst_fps = max_dst_fps
# change to 0.4 to exclude "high-res" DVD sources to ~720p category and to 0.44 - to include them here
elif mpix < 0.44:
# highest quality for lowest-res, ≤480p content
quality = 'extreme'
#MAX_OVERLAP = True
# set type 5 search by default with option to selectively override later
#BEST_SEARCH = True
#MAX_SEARCH = True
# always search for interlacing in near-DVD sources
#INTERLACING_DETECTION = True
Interlace_Detection_Params['cthresh'] = 6
#DEINTERLACE = True
#DEINTERLACE_UPSAMPLE = True
DeInterlace_Params['length'] = 6
DeInterlace_Params['mtype'] = 2
DeInterlace_Params['ttype'] = 5
DeInterlace_Params['expand'] = 0
DeInterlace_Params['athresh'] = -1
#DeInterlace_Params_nnedi3['combed_only'] = False
DEBAND = True
DEBAND_UPSAMPLE = True
#DeBand_Params['range'] = 24
#DeBand_Params['y'] = 320
#DeBand_Params['cb'] = 320
#DeBand_Params['cr'] = 320
# addition of noise is better used with degraining
#DeBand_Params['grainy'] = 16
#DeBand_Params['grainc'] = 16
#DeBand_Params['dynamic_grain'] = True
DEGRAIN = True
DEGRAIN_CHROMA = True
DeGrain_Params['thsad'] = 64
#DUAL_DCT = True
#DUAL_OVERLAP = False
if mpix < 0.24:
INTERLACING_DETECTION = False
DEINTERLACE = False
# DCT 1-4 (>4 can't be used on <8x8) and search=5 SLAM the CPU ! so, only pick one ?
#BEST_DCT = True
#BEST_SEARCH = False
#Analyze_Params['search_coarse'] = 3
#Analyze_Common_Params['search'] = 3
Analyze_Params['badrange'] = -24
AVOID_DOWNSAMPLING_FORMAT = True
AVOID_DOWNSAMPLING_FORMAT_BITS = True
FORMAT_INPUT_BITS = 10
DEGRAIN_FRAMES = 3
Interlace_Detection_Params['blockx'] = 8
Interlace_Detection_Params['blocky'] = Interlace_Detection_Params['blockx']
Interlace_Detection_Params['mi'] = Interlace_Detection_Params['blockx'] * Interlace_Detection_Params['blocky'] // 4
pnew_offset_divisor = 208
overlap_offset_divisor_postdivide = 32
if mpix < 0.05:
# at this size degrain analyzation function may fail with number-of-levels errors
#DEGRAIN = False
# it's either 16*2 with dct=0 or 16x8 with dct=4
#Analyze_Common_Params['blksize'] = 8
#Analyze_Common_Params['blksizev'] = 8
#Analyze_Common_Params['divide'] = 0
#Analyze_Common_Params['overlap'] = 2
#Analyze_Common_Params['overlapv'] = 2
Analyze_Common_Params['dct'] = 3
DeGrain_Analyze_Params['dct'] = 3
elif mpix < 0.12:
#Analyze_Common_Params['blksize'] = 16
#Analyze_Common_Params['blksizev'] = 8
#Analyze_Common_Params['divide'] = 0
#Analyze_Common_Params['overlap'] = 2
#Analyze_Common_Params['overlapv'] = 2
Analyze_Common_Params['dct'] = 3
DeGrain_Analyze_Params['dct'] = 3
else:
#Analyze_Common_Params['blksize'] = 16
#Analyze_Common_Params['blksizev'] = 16
#Analyze_Common_Params['divide'] = 0
#Analyze_Common_Params['overlap'] = 8
#Analyze_Common_Params['overlapv'] = 0
Analyze_Common_Params['dct'] = 3
DeGrain_Analyze_Params['dct'] = 8
#Analyze_Common_Params['pnew'] *= 4
#Analyze_Params['pzero'] = Analyze_Common_Params['pnew']
#Analyze_Common_Params['searchparam'] = 1
Analyze_Params['pelsearch'] = 4
#Super_Params['pel'] = 4
#Super_Params['sharp'] = 2
else:
if mpix > 0.26:
# some video at this resolution make deinterlacer shit the bed and kill whole pipeline
INTERLACING_DETECTION = False
DEINTERLACE = False
#BEST_SEARCH = False
#Analyze_Params['search_coarse'] = 5
#Analyze_Common_Params['search'] = 5
# degraining might break with some block-size on very low-res videos
#DEGRAIN = False
# >1 frames require bigger 'buffered-frames' in mpv which increases seek latency
DEGRAIN_FRAMES = 2
DeGrain_Super_Params['pel'] = 2
Interlace_Detection_Params['blockx'] = 8
Interlace_Detection_Params['blocky'] = Interlace_Detection_Params['blockx']
Interlace_Detection_Params['mi'] = Interlace_Detection_Params['blockx'] * Interlace_Detection_Params['blocky'] // 5
pnew_offset_divisor = 208
overlap_offset_divisor_postdivide = 32
#Interpolate_Params['thscd1'] = 16320
# that pretty much the only size where 16x2 is viable
# but it's incompatible with division, overlap detection and DCT>4
#Analyze_Common_Params['blksize'] = 16
#Analyze_Common_Params['blksizev'] = 16
#Analyze_Common_Params['divide'] = 0
#Analyze_Common_Params['overlap'] = 4
#Analyze_Common_Params['overlapv'] = 4
# even at that resolution, you can't get away with DCT<5 with <8x8 block-sizes
Analyze_Common_Params['dct'] = 8
DeGrain_Analyze_Params['dct'] = 8
#Analyze_Common_Params['pnew'] *= 5
#Analyze_Params['pzero'] = Analyze_Common_Params['pnew']
#Analyze_Common_Params['searchparam'] = 4
Analyze_Params['pelsearch'] = 6
#Analyze_Params['levels'] = -6
#Super_Params['levels'] = 2
#Super_Params['pel'] = 2
#Super_Params['sharp'] = 1
FORMAT_INPUT_KERNEL = "spline64"
FORMAT_INPUT_KERNEL_TAPS = 8
#FORMAT_INPUT_FLOAT = 0
#FORMAT_INPUT_DITH_MODE = 7
#FORMAT_INPUT_DITH_ROTATE = 1
FORMAT_INPUT_DITH_STATIC_NOISE = 0
FORMAT_INPUT_DITH_PATTERN = 4
# addition of noise is better used with degraining
#FORMAT_INPUT_DITH_AMP_NOISE = 1
#overlap_offset_divisor_postdivide = 32
#lambda_mult_last = 0.1
#lambda_mult_last_withdivide = 0.1
#lambda_mult_last_postdivide = 0.2
INTERPOLATE_FUNCTION = 'FlowFPS'
#Interpolate_Params['mask'] = 2
#lambda_mult_last = 0.5
#Analyze_Common_Params['lambda'] = int(lambda_mult_first * lambda_mult_last_withdivide)
#Analyze_Common_Params['blksize'] = 8
#Analyze_Common_Params['blksizev'] = 8
#Analyze_Common_Params['divide'] = 0
#Analyze_Common_Params['dct'] = 4
#DeGrain_Analyze_Params['dct'] = 4
#Analyze_Common_Params['overlap'] = 2
#Analyze_Common_Params['overlapv'] = 2
#Analyze_Common_Params['dct'] = 8
Analyze_Common_Params['search'] = 3
#Analyze_Common_Params['searchparam'] = 4
# penalty might not be needed with search=3
#Analyze_Common_Params['pnew'] = 12
#Analyze_Params['pzero'] = 6
Analyze_Params['search_coarse'] = 3
#Analyze_Params['pelsearch'] = ignore_threshold // 64
#Analyze_Params['trymany'] = True
#Analyze_Params['plevel'] = 2
#Analyze_Params['levels'] = 2
#Super_Params['levels'] = 2
Super_Params['pel'] = 4
Super_Params['sharp'] = 2
# don't interpolate sub-50 content past 120 but allow >50 to go up to max_dst_fps
if (src_fps <= 50.0 and dst_fps > 145.0):
dst_fps = 145.0
elif dst_fps > max_dst_fps:
dst_fps = max_dst_fps
else:
# strong quality for ~720p
quality = 'high'
#MAX_SEARCH = True
if mpix > 0.6:
# don't waste CPU on searching for interlacing on near-HD sources
#INTERLACING_DETECTION = False
Interlace_Detection_Params['cthresh'] = 10
# but still deinterlace if sources are somehow already marked to be such
#DEINTERLACE = True
DeInterlace_Params['length'] = 10
else:
#INTERLACING_DETECTION = True
Interlace_Detection_Params['cthresh'] = 8
#DEINTERLACE = True
#DEINTERLACE_UPSAMPLE = True
DeInterlace_Params['length'] = 8
DEBAND = True
DEBAND_UPSAMPLE = True
#DeBand_Params['grainy'] = 24
#DeBand_Params['grainc'] = 24
DEGRAIN = True
DEGRAIN_CHROMA = True
DEGRAIN_FRAMES = 1
DeGrain_Params['thsad'] = 96
#DUAL_DCT = True
#FORMAT_INPUT_DITH_STATIC_NOISE = 0
FORMAT_INPUT_DITH_PATTERN = 8
#INTERLACING_TYPE = 0
Interlace_Detection_Params['blockx'] = 16
Interlace_Detection_Params['blocky'] = Interlace_Detection_Params['blockx']
# DVD remux crap now falls under this category, so interlacing is very likely
Interlace_Detection_Params['mi'] = Interlace_Detection_Params['blockx'] * Interlace_Detection_Params['blocky'] // 6
#Interlace_Detection_Params['cthresh'] = 6
#DeInterlace_Params['length'] = 6
#DeInterlace_Params['mtype'] = 2
#DeInterlace_Params['ttype'] = 5
#DeInterlace_Params['expand'] = 3
#DeInterlace_Params['athresh'] = -1
#DeInterlace_Params_nnedi3['combed_only'] = False
#pnew_offset = 8
#pnew_offset_divisor = 96
#overlap_offset_divisor = 0
#overlap_offset_divisor_postdivide = 16
#lambda_mult_last = 1.2
#lambda_mult_last_withdivide = 1.2
#lambda_mult_last_postdivide = 1.6
INTERPOLATE_FUNCTION = 'FlowFPS'
#Interpolate_Params['thscd1'] = 12228
#Interpolate_Params['mode'] = 7
#lambda_mult_last = 1.25
#Analyze_Common_Params['lambda'] = int(lambda_mult_first * lambda_mult_last_withdivide)
if mpix < 0.54:
#Analyze_Common_Params['blksize'] = 8
#Analyze_Common_Params['blksizev'] = 8
#Analyze_Common_Params['divide'] = 0
#Analyze_Common_Params['overlap'] = 4
#Analyze_Common_Params['overlapv'] = 4
Super_Params['pel'] = 4
Super_Params['sharp'] = 2
else:
#Analyze_Common_Params['blksize'] = 16
#Analyze_Common_Params['blksizev'] = 8
#Analyze_Common_Params['divide'] = 0
#Analyze_Common_Params['overlap'] = 2
#Analyze_Common_Params['overlapv'] = 2
Super_Params['pel'] = 2
Super_Params['sharp'] = 2
#Analyze_Common_Params['dct'] = 5
Analyze_Common_Params['search'] = 5
#Analyze_Common_Params['searchparam'] = 256 // levels
#Analyze_Common_Params['pnew'] = 32
#Analyze_Params['pzero'] = Analyze_Common_Params['pnew']
Analyze_Params['search_coarse'] = 5
Analyze_Params['pelsearch'] = 6
#Analyze_Params['trymany'] = False
#Analyze_Params['plevel'] = 1
#Analyze_Params['levels'] = -4
#Super_Params['levels'] = 8
#Super_Params['pel'] = 2
#Super_Params['sharp'] = 2
# don't interpolate sub-60 content past ~60 but allow >60 to go up to max_dst_fps
if (src_fps < 25.0 and dst_fps > 145.0):
dst_fps = 145
elif (25.0 < src_fps < 59.952 and dst_fps > 120.0):
dst_fps = 120.0
elif dst_fps > max_dst_fps:
dst_fps = max_dst_fps
# setup for common super-clip parameters
if MAX_PRECISION:
Super_Params['pel'] = 4
Super_Params['sharp'] = 2
Super_Params['rfilter'] = 4
# with different blocksizes behaviour changes erratically, so it's impossible to scale automatically here
# real minimal blocksize is 4x4 and 16x2 but they are pretty impossible for realtime
# so use mvtools' default of 8x8 which is also crazy CPU strain
if MIN_BLOCKSIZE:
Analyze_Common_Params['blksize'] = 4
Analyze_Common_Params['blksizev'] = 4
# setup for common analyzation parameters
if ("levels" not in Analyze_Params):
Analyze_Params['levels'] = 0
if BEST_SEARCH:
search_previous = Analyze_Common_Params['search']
Analyze_Common_Params['search'] = 5
Analyze_Params['search_coarse'] = 5
#if Analyze_Common_Params['blksize'] > 16:
# Analyze_Common_Params['searchparam'] = 4
# Analyze_Params['pelsearch'] = 16
#elif Analyze_Common_Params['blksize'] > 4:
# Analyze_Common_Params['searchparam'] = 3
# Analyze_Params['pelsearch'] = 12
#else:
# Analyze_Common_Params['searchparam'] = 2
# Analyze_Params['pelsearch'] = 6
if MAX_SEARCH:
Analyze_Params['trymany'] = True
if BEST_DCT:
Analyze_Common_Params['dct'] = 3
if AUTO_OVERLAP:
# when 'divide' is active, overlapX must be in range from 4 to blksizeX/2 and be divisible by 4
# with lower block sizes minimal overlap seems to be fine but bigger they are, bigger it needs to be, so let's scale it
if Analyze_Common_Params['divide'] > 0:
Analyze_Common_Params['overlap'] = int(((((((256 - Analyze_Common_Params['blksize']) / 2) / (256 - (overlap_offset_divisor * 2))) * (Analyze_Common_Params['blksize'] / 2)) + 4) - 1) / 4) * 4
Analyze_Common_Params['overlapv'] = int(((((((256 - Analyze_Common_Params['blksizev']) / 2) / (256 - (overlap_offset_divisor * 2))) * (Analyze_Common_Params['blksizev'] / 2)) + 4) - 1) / 4) * 4
else:
Analyze_Common_Params['overlap'] = int((((((256 - Analyze_Common_Params['blksize']) / 2) / (128 - overlap_offset_divisor)) * (Analyze_Common_Params['blksize'] / 2)) + overlap_offset) / 4) * 2
Analyze_Common_Params['overlapv'] = int((((((256 - Analyze_Common_Params['blksize']) / 2) / (128 - overlap_offset_divisor)) * (Analyze_Common_Params['blksizev'] / 2)) + overlap_offset) / 4) * 2
if MAX_OVERLAP:
Analyze_Common_Params['overlap'] = int(Analyze_Common_Params['blksize'] / 2)
Analyze_Common_Params['overlapv'] = int(Analyze_Common_Params['blksizev'] / 2)
# don't try to enforce exact number of level on non-default overlap because it will likely fail
# use 0 or negative values to ignore "coarse" levels
# this may be better off being set explicitly in profiles
#Analyze_Params['levels'] = -2
if (MAX_OVERLAP_POSTDIVIDE and (not DUAL_OVERLAP)):
Analyze_Common_Params['overlap'] = 0
Analyze_Common_Params['overlapv'] = 0
# scale "optimal" pnew for blocksize weirdness ?
#if ((Analyze_Common_Params['blksize'] + Analyze_Common_Params['blksizev']) / 2) > 8:
# Analyze_Common_Params['pnew'] = int(Analyze_Common_Params['pnew'] // (((Analyze_Common_Params['blksize'] + Analyze_Common_Params['blksizev']) / 2) / 8) * 1.8)
# Analyze_Params['pzero'] = int(Analyze_Common_Params['pnew'] * 0.875)
if pnew_scale == True:
pnew = int(((2 ** (Interpolate_Params['thscd1'] / 16320)) - 1) * (256 - (pnew_offset_divisor_for_overlap + ((Analyze_Common_Params['blksize']+Analyze_Common_Params['blksizev'])/2) - 8)) + pnew_offset_for_overlap) if (MAX_OVERLAP_POSTDIVIDE or MAX_OVERLAP) else\
int(((2 ** (Interpolate_Params['thscd1'] / 16320)) - 1) * (256 - (pnew_offset_divisor + ((Analyze_Common_Params['blksize']+Analyze_Common_Params['blksizev'])/2) - 8)) + pnew_offset)
Analyze_Common_Params['pnew'] = pnew
Analyze_Params['pzero'] = int(pnew * 0.75)
# clip vector penalty values to allowed range
if Analyze_Common_Params['pnew'] < 0:
Analyze_Common_Params['pnew'] = 0
elif Analyze_Common_Params['pnew'] > 256:
Analyze_Common_Params['pnew'] = 256
if Analyze_Params['pzero'] < 0:
Analyze_Params['pzero'] = 0
elif Analyze_Params['pzero'] > 256:
Analyze_Params['pzero'] = 256
# override thscd1, lsad and badsad on per-profile basis ?
#if Analyze_Params['pelsearch'] < Analyze_Common_Params['blksize']:
# Analyze_Params['pelsearch'] = Analyze_Common_Params['blksize'] * 2
# there is no point in searching farther than 256 pixels
#if Analyze_Params['pelsearch'] >= 256:
# Analyze_Params['pelsearch'] = 256
# requires radius-based search
#if Analyze_Common_Params['search'] != (0 or 1):
#Interpolate_Params['ml'] = int(100 + (155 * (Analyze_Params['pelsearch'] / 256)))
# there is no point in allowing interpolation for changes that are never detected during low-radius analyzation
#Interpolate_Params['thscd1'] = (8 * 8) * Interpolate_Params['ml']
# supposedly, it, in addition to vector-penalty, also can clean out bad vectors but sacrifices lambda which may lead to low-lambda artifacts
#if Analyze_Common_Params['pnew'] > 0:
# Analyze_Params['lsad'] = int((8 * 8) * Interpolate_Params['ml'] * (Analyze_Common_Params['pnew'] / 256))
#else:
# Analyze_Params['lsad'] = 12
## guessing game…
#Interpolate_Params['thscd1'] = int(16320 * ((Analyze_Params['pelsearch'] - 1) / 255))
#Interpolate_Params['thscd1'] = int(16320 * (Interpolate_Params['ml'] / 255))
#Analyze_Params['lsad'] = (8 * 8) * Analyze_Params['pelsearch']
#Analyze_Params['lsad'] = int(16320 * ((Analyze_Params['pelsearch'] - 1) / 255))
#Analyze_Params['lsad'] = int(16320 * (Interpolate_Params['ml'] / 255))
#Analyze_Params['lsad'] = int(Interpolate_Params['thscd1'] * 2/3)
#Analyze_Params['lsad'] = int((1152 * ((Analyze_Common_Params['blksize'] + Analyze_Common_Params['blksize']) // 2)) / (Interpolate_Params['thscd1'] / 384))
#Analyze_Params['lsad'] = int((1200 / (8 * 8)) * (Analyze_Common_Params['blksize'] * Analyze_Common_Params['blksizev']))
# account for aggressive increase of lambda with levels… but doesn't it equals to plevel=0 then ?
#if Analyze_Params['plevel'] == 1:
# lambda_mult_first /= Super_Params['levels']
#elif Analyze_Params['plevel'] == 2:
# lambda_mult_first /= Super_Params['levels']**2
if "badrange" not in Analyze_Params:
Analyze_Params['badrange'] = Analyze_Common_Params['blksize'] * 3
if Analyze_Common_Params['divide'] > 0:
#if Analyze_Params['lsad'] > Interpolate_Params['thscd1']:
# Analyze_Params['badsad'] = Analyze_Params['lsad'] + 1
#else:
# Analyze_Params['badsad'] = Interpolate_Params['thscd1'] + 1
if Analyze_Params['pelsearch'] < 256:
Analyze_Params['badrange'] = Analyze_Params['pelsearch']
lambda_mult_last = lambda_mult_last_withdivide
if "lambda" not in Analyze_Common_Params:
# scale for blocksize as per avisynth documentation ?
Analyze_Common_Params['lambda'] = int(lambda_mult_first * (Analyze_Common_Params['blksize'] * Analyze_Common_Params['blksizev'] / 64) * lambda_mult_last)
# or assume that mvtools scale to blocksize internally from virtual 8x8 values ?
#Analyze_Common_Params['lambda'] = int(lambda_mult_first * lambda_mult_last)
# or lower it for bigger blocksizes ?
#if ((Analyze_Common_Params['blksize'] + Analyze_Common_Params['blksizev']) / 2) > 8:
# Analyze_Common_Params['lambda'] = int(lambda_mult_first * lambda_mult_last / (((Analyze_Common_Params['blksize'] + Analyze_Common_Params['blksizev']) / 2) / 8) * 1.6)
# Analyze_Params['lsad'] = int(Analyze_Common_Params['lambda'] * 1.2)
# increase lambda for sub-8 block sizes to avoid artifacts ?
if ((Analyze_Common_Params['blksize'] or Analyze_Common_Params['blksizev']) < 8):
lambda_mult_first *= 64 // (Analyze_Common_Params['blksize'] * Analyze_Common_Params['blksizev'])
# ideally, this should have the same precision as what GPU driver uses for driving the display but ≥8 gives out errors
# here's it's the target interpolation frame-rate
dst_fps_num = int(dst_fps * 1e6)
dst_fps_den = int(1e6)
print("[mvtools]: Reflowing from {} fps to {} fps with {} quality".format(clip.fps_num / clip.fps_den, dst_fps_num / dst_fps_den,quality))
# converting to 444 is ideal but also too much for realtime, 422 is OK, 420 is minimum
if (FORMAT_INPUT != None and FORMAT_INPUT_BITS != None):
FORMAT_INPUT_CONSTANT = getattr(vs,"YUV"+str(FORMAT_INPUT)+"P"+str(FORMAT_INPUT_BITS))
else:
FORMAT_INPUT_CONSTANT = 0
if ((FORMAT_INPUT != None) and (FORMAT_FAMILY > FORMAT_INPUT_CONSTANT) and (not AVOID_DOWNSAMPLING_FORMAT)) or\
(FORMAT_FAMILY != vs.YUV) or DOWNSCALING_TO_1080P:
# force conversion if format is not supported by mvtools even when it configured to be skipped
if FORMAT_INPUT == None:
FORMAT_INPUT = FORMAT_INPUT_FALLBACK
if FORMAT_FAMILY != vs.YUV:
print("[mvtools-conversion-input]: forcing conversion of non-YUV format to YUV{}".format(FORMAT_INPUT))
input_resample_type = "32-bit float" if FORMAT_INPUT_FLOAT == 1 else "16-bit integer"
print("[mvtools-conversion-input]: converting clip into {} yuv{} with {} kernel and {} taps".format(\
input_resample_type, FORMAT_INPUT, FORMAT_INPUT_KERNEL, FORMAT_INPUT_KERNEL_TAPS))
clip = core.fmtc.resample(clip=clip, css = FORMAT_INPUT, kernel = FORMAT_INPUT_KERNEL, taps = FORMAT_INPUT_KERNEL_TAPS,\
scalev = FORMAT_SCALE_HEIGHT, scaleh = FORMAT_SCALE_WIDTH)
elif FORMAT_INPUT == None:
print("[mvtools-conversion-input]: skipping format conversion because target format was not selected")
elif FORMAT_FAMILY <= FORMAT_INPUT_CONSTANT:
print("[mvtools-conversion-input]: skipping format up-conversion because current format {} ({}) is ≤ to target YUV{}P{} ({})".format(\
FORMAT, FORMAT_FAMILY, FORMAT_INPUT, FORMAT_INPUT_BITS, FORMAT_INPUT_CONSTANT))
elif AVOID_DOWNSAMPLING_FORMAT:
print("[mvtools-conversion-input]: skipping format down-conversion of {} ({}) to YUV{}P{} ({})".format(\
FORMAT, FORMAT_FAMILY, FORMAT_INPUT, FORMAT_INPUT_BITS, FORMAT_INPUT_CONSTANT))
# set interlacing property ?
if INTERLACING_DETECTION:
print("[mvtools-deinterlacing]: analyzing and tagging frames for interlacing")
clip_combed = core.tdm.IsCombed(clip, **Interlace_Detection_Params)
def set_frame_interlacing(n, f):
fout = f.copy()
# set "_Combed" property for every frame that may or may not (most likely) proper "_FieldBased" property
if (("_FieldBased" not in fout.props) and ("_Combed" in fout.props)):
# 0 is non-interlaced, 1 is interlaced, bottom field first, 2 is interlaced, top field first
# how to detect THAT shit ?!
fout.props['_FieldBased'] = INTERLACING_TYPE + 1
# but here 0 is BFF and 1 is TFF
fout.props['_Field'] = INTERLACING_TYPE
# make sure that both properties are present !
if (("_FieldBased" in fout.props) and ("_Combed" not in fout.props)):
fout.props['_Combed'] = 1
return fout
core.std.ModifyFrame(clip_combed, clips=clip_combed, selector=set_frame_interlacing)
if DEINTERLACE:
if (DEINTERLACE_UPSAMPLE and (clip.format.bits_per_sample != 16)):
print("[mvtools-deinterlacing]: up-sampling to 16-bit to avoid bad deinterlacing")
clip = core.fmtc.bitdepth(clip=clip, bits = 16, \
dmode = FORMAT_INPUT_DITH_MODE, dyn = FORMAT_INPUT_DITH_ROTATE, staticnoise = FORMAT_INPUT_DITH_STATIC_NOISE,\
ampo = FORMAT_INPUT_DITH_AMP_ORDERED, ampn = FORMAT_INPUT_DITH_AMP_NOISE, patsize = FORMAT_INPUT_DITH_PATTERN)
print("[mvtools-deinterlacing]: doing NNEDI3 deinterlacing pass for frames with '_FieldBased' and '_Combed' properties")
# this may require set '_Combed' property even when auto-detection failed but '_FieldBased' was present
clip_deint = core.tdm.TDeintMod(clip, **DeInterlace_Params,\
edeint=core.nnedi3.nnedi3(clip_combed, **DeInterlace_Params_nnedi3))
def conditionalDeint(n, f, orig, deint):
if f.props['_Combed'] > 0:
return deint
else:
return orig
clip = core.std.FrameEval(clip, functools.partial(conditionalDeint, orig=clip, deint=clip_deint), prop_src=clip_combed)
# delete interlacing properties now ?
core.std.RemoveFrameProps(clip, props="_FieldBased _Field _Combed")
# deband before the rest ?
if DEBAND:
if (DEBAND_UPSAMPLE and (clip.format.bits_per_sample != 16)):
print("[mvtools-debanding]: up-sampling to 16-bit to avoid weak dithering of deband plugin")
clip = core.fmtc.bitdepth(clip=clip, bits = 16, \
dmode = FORMAT_INPUT_DITH_MODE, dyn = FORMAT_INPUT_DITH_ROTATE, staticnoise = FORMAT_INPUT_DITH_STATIC_NOISE,\
ampo = FORMAT_INPUT_DITH_AMP_ORDERED, ampn = FORMAT_INPUT_DITH_AMP_NOISE, patsize = FORMAT_INPUT_DITH_PATTERN)
print("[mvtools-debanding]: smoothing-out YUV planes with {}/{}/{} ({}/{} grain) threshold in range {} with 16-bit output".format(\
DeBand_Params['y'], DeBand_Params['cb'], DeBand_Params['cr'], DeBand_Params['grainy'], DeBand_Params['grainc'], DeBand_Params['range']))
clip = core.f3kdb.Deband(clip, output_depth=16, **DeBand_Params)
else:
print("[mvtools-debanding]: skipping smoothing and hoping that player had sense to deband before VS filter")
# resample functions also converts the clip into 16 bits which is too much for realtime processing
FORMAT_BITS_CURRENT = clip.format.bits_per_sample
if ((FORMAT_INPUT_BITS == None) and (FORMAT_BITS_CURRENT > FORMAT_BITS)) or (AVOID_UPSAMPLING_FORMAT_BITS and (FORMAT_INPUT_BITS > FORMAT_BITS)):
FORMAT_INPUT_BITS = FORMAT_BITS
if FORMAT_INPUT_BITS != None:
if (AVOID_DOWNSAMPLING_FORMAT_BITS and (FORMAT_INPUT_BITS < FORMAT_BITS)):
FORMAT_INPUT_BITS = FORMAT_BITS
if (FORMAT_INPUT_BITS != None) and ((FORMAT_INPUT_BITS < FORMAT_BITS_CURRENT) or (FORMAT_BITS_CURRENT > FORMAT_INPUT_BITS)):
print("[mvtools-conversion-input]: dithering {}-bit clip into {}-bit".format(FORMAT_BITS_CURRENT, FORMAT_INPUT_BITS))
clip = core.fmtc.bitdepth(clip=clip, bits = FORMAT_INPUT_BITS, \
dmode = FORMAT_INPUT_DITH_MODE, dyn = FORMAT_INPUT_DITH_ROTATE, staticnoise = FORMAT_INPUT_DITH_STATIC_NOISE,\
ampo = FORMAT_INPUT_DITH_AMP_ORDERED, ampn = FORMAT_INPUT_DITH_AMP_NOISE, patsize = FORMAT_INPUT_DITH_PATTERN)
elif FORMAT_INPUT_BITS == None:
print("[mvtools-conversion-input]: skipping dithered bitdepth conversion because target depth was not selected")
elif FORMAT_INPUT_BITS > FORMAT_BITS_CURRENT:
print("[mvtools-conversion-input]: skipping dithered bitdepth up-conversion because current depth {} ≤ to target {} depth".format(FORMAT_BITS_CURRENT, FORMAT_INPUT_BITS))
elif FORMAT_INPUT_BITS == (FORMAT_BITS or FORMAT_BITS_CURRENT):
print("[mvtools-conversion-input]: skipping dithered bitdepth conversion because current and target depths both are {}".format(FORMAT_BITS_CURRENT))
### actual interpolation preparations start here
## degrain before interpolation ?
if DEGRAIN:
# degrain to avoid creating false vectors for noise ? plane=0 for luma only, otherwise - 4.
# Use 1, 2 or 3 frames ? Each analyzation is very CPU-intensive !
if DEGRAIN_CHROMA:
DEGRAIN_PLANE = 4
degrain_chroma_status = "with"
else:
DEGRAIN_PLANE = 0
degrain_chroma_status = "without"
print("[mvtools-degrain]: degraining {} chroma using {} previous & following frames".format(degrain_chroma_status, DEGRAIN_FRAMES))
# don't upscale layers for degraining stronger than for interpolation
if DeGrain_Super_Params['pel'] > Super_Params['pel']:
DeGrain_Super_Params['pel'] = Super_Params['pel']
#DeGrain_Super_Params['levels'] = Super_Params['levels']
sup = core.mv.Super(clip, **DeGrain_Super_Params)
# analyzation of vectors in the previous frames
#DeGrain_Analyze_Params['levels'] = Analyze_Params['levels']
#DeGrain_Analyze_Params['blksize'] = Analyze_Common_Params['blksize']
#DeGrain_Analyze_Params['blksizev'] = Analyze_Common_Params['blksizev']
# be more conservative on degrain step to avoid doubling artifacts during actual interpolation ?
#if 'lsad' in Analyze_Params:
# DeGrain_Analyze_Params['lsad'] = int(Analyze_Params['lsad'] * 1.8)
#if 'lambda' in Analyze_Common_Params:
# DeGrain_Analyze_Params['lambda'] = int(Analyze_Common_Params['lambda'] * 1.8)
# is overlap detection even necessary for degraining ?
#DeGrain_Analyze_Params['overlap'] = Analyze_Common_Params['overlap']
#DeGrain_Analyze_Params['overlapv'] = Analyze_Common_Params['overlapv']
#DeGrain_Analyze_Params['dct'] = Analyze_Common_Params['dct']
#DeGrain_Analyze_Params['searchparam'] = Analyze_Common_Params['searchparam']
#DeGrain_Analyze_Params['pnew'] = Analyze_Common_Params['pnew']
#DeGrain_Analyze_Params['pzero'] = Analyze_Params['pzero']
#DeGrain_Analyze_Params['pelsearch'] = Analyze_Params['pelsearch']
#DeGrain_Analyze_Params['plevel'] = Analyze_Params['plevel']
#DeGrain_Analyze_Params['trymany'] = Analyze_Params['trymany']
if 'badsad' in Analyze_Params:
DeGrain_Analyze_Params['badsad'] = Analyze_Params['badsad']
# just to make sure
DeGrain_Params['thsadc'] = DeGrain_Params['thsad']
bvec1 = core.mv.Analyse(sup, delta = 1, isb=True, chroma=DEGRAIN_CHROMA, **DeGrain_Analyze_Params)
if DEGRAIN_FRAMES > 1:
bvec2 = core.mv.Analyse(sup, delta = 2, isb=True, chroma=DEGRAIN_CHROMA, **DeGrain_Analyze_Params)
if DEGRAIN_FRAMES > 2:
bvec3 = core.mv.Analyse(sup, delta = 3, isb=True, chroma=DEGRAIN_CHROMA, **DeGrain_Analyze_Params)
# analyzation of vectors in the upcoming frames
fvec1 = core.mv.Analyse(sup, delta = 1, isb=False, chroma=DEGRAIN_CHROMA, **DeGrain_Analyze_Params)
if DEGRAIN_FRAMES > 1:
fvec2 = core.mv.Analyse(sup, delta = 2, isb=False, chroma=DEGRAIN_CHROMA, **DeGrain_Analyze_Params)
if DEGRAIN_FRAMES > 2:
fvec3 = core.mv.Analyse(sup, delta = 3, isb=False, chroma=DEGRAIN_CHROMA, **DeGrain_Analyze_Params)
# actual degraining filter
if DEGRAIN_FRAMES == 1:
clip = core.mv.Degrain1(clip, sup, bvec1, fvec1, plane=DEGRAIN_PLANE, **DeGrain_Params)
elif DEGRAIN_FRAMES == 2:
clip = core.mv.Degrain2(clip, sup, bvec1, fvec1, bvec2, fvec2, plane=DEGRAIN_PLANE, **DeGrain_Params)
elif DEGRAIN_FRAMES == 3:
clip = core.mv.Degrain3(clip, sup, bvec1, fvec1, bvec2, fvec2, bvec3, fvec3, plane=DEGRAIN_PLANE, **DeGrain_Params)
else:
print("[mvtools-degrain]: skipping degrain due to wrong frame number: {} !".format(DEGRAIN_FRAMES))
else:
print("[mvtools-degrain]: skipping degrain because it was not requested")
## [re]do super-clip for interpolation after degrain step or lack there of
if Super_Params['pel'] > 1:
if Super_Params['sharp'] == 0:
super_sharp = " bilinear "
elif Super_Params['sharp'] == 1:
super_sharp = " bicubic 4-tap Catmull-Rom "
elif Super_Params['sharp'] == 2:
super_sharp = " 6-tap Wiener "
if Super_Params['pel'] == 2:
super_pel = "half-pixel precision with" + str(super_sharp) + "subpixel interpolation"
elif Super_Params['pel'] == 4:
super_pel = "quarter-pixel precision with" + str(super_sharp) + "subpixel interpolation"
else:
super_pel = "unknown precision"
else:
super_pel = "pixel precision"
if Super_Params['rfilter'] == 0:
super_rfilter = "simple 4-pixel averaging"
elif Super_Params['rfilter'] == 1:
super_rfilter = "triangle (shifted) smoothing"
elif Super_Params['rfilter'] == 2:
super_rfilter = "triangle bilinear smoothing"
elif Super_Params['rfilter'] == 3:
super_rfilter = "quadratic smoothing"
elif Super_Params['rfilter'] == 4:
super_rfilter = "cubic smoothing"
else:
super_rfilter = "unknown smoothing"
if ("levels" not in Super_Params):
Super_Params['levels'] = 0
if (Super_Params['levels'] != 0):
super_levels = Super_Params['levels']
else:
super_levels = "default number of"
print("[mvtools-super]: {} levels with {}, {}".format(super_levels, super_rfilter, super_pel))
sup = core.mv.Super(clip, **Super_Params)
## 2-stage analyzation for interpolation
block_type = "subdivided-in-4" if Analyze_Common_Params['divide'] > 0 else "undivided"
search_type = "+" if Analyze_Params['trymany'] else "-"
if Analyze_Params['plevel'] == 0:
plevel_type = "no pen-scaling"
elif Analyze_Params['plevel'] == 1:
plevel_type = "linear pen-scaling"
elif Analyze_Params['plevel'] == 2:
plevel_type = "quad pen-scaling"
if (Analyze_Params['levels'] != 0):
analyze_levels = Analyze_Params['levels']
else:
analyze_levels = "auto"
print("[mvtools-analysis]: {}/{} {} blocks, {}/{}/{} new/0/global vec-penalties, {}-level {}/{}:{}/{} search{}, {}/{} overlap, {}/{} badSAD/range, {} λSAD | {} λ with {}, DCT mode:{}".format(\
Analyze_Common_Params['blksize'], Analyze_Common_Params['blksizev'], block_type,\
Analyze_Common_Params['pnew'], Analyze_Params['pzero'], Analyze_Params['pglobal'], analyze_levels,\
Analyze_Params['search_coarse'], Analyze_Common_Params['search'], Analyze_Common_Params['searchparam'], Analyze_Params['pelsearch'], search_type,\
Analyze_Common_Params['overlap'], Analyze_Common_Params['overlapv'], Analyze_Params['badsad'], Analyze_Params['badrange'],\
Analyze_Params['lsad'], Analyze_Common_Params['lambda'], plevel_type, Analyze_Common_Params['dct']))
bvec1 = core.mv.Analyse(sup, isb=True, **Analyze_Common_Params, **Analyze_Params)
fvec1 = core.mv.Analyse(sup, isb=False, **Analyze_Common_Params, **Analyze_Params)
## reanalyzation to filter out bad vectors, introduced by high thscd1 with low lambda & lsad & badsad, and accommodate block division
# make sure to skip it when unneeded
if Analyze_Common_Params['divide'] > 0:
# doing divide twice makes no sense
# disabling it also lifts the requirement of being divisible by 4 or 2 for values of overlapX but overlap is still picky and will refuse many odd values
Analyze_Common_Params['divide'] = 0
Analyze_Common_Params['blksize'] = int(Analyze_Common_Params['blksize'] / 2)
Analyze_Common_Params['blksizev'] = int(Analyze_Common_Params['blksizev'] / 2)
# lambda does not seem to matter much on reanalysis but what is the proper way to calculate it anyway ?
if (Analyze_Common_Params['lambda'] == int(lambda_mult_first * lambda_mult_last_withdivide)):
Analyze_Common_Params['lambda'] = int(lambda_mult_first * lambda_mult_last_postdivide)
elif (Analyze_Common_Params['lambda'] != 0):
Analyze_Common_Params['lambda'] = int(lambda_mult_first * (Analyze_Common_Params['blksize'] * Analyze_Common_Params['blksizev'] / 64) * lambda_mult_last_postdivide)
if ((DUAL_OVERLAP and ((Analyze_Common_Params['overlap'] or Analyze_Common_Params['overlapv']) > 0)) or\
((Analyze_Common_Params['overlap'] and Analyze_Common_Params['overlapv']) == 0)):
if MAX_OVERLAP_POSTDIVIDE:
Analyze_Common_Params['overlap'] = int(Analyze_Common_Params['blksize'] / 2)
Analyze_Common_Params['overlapv'] = int(Analyze_Common_Params['blksizev'] / 2)
elif AUTO_OVERLAP_POSTDIVIDE:
Analyze_Common_Params['overlap'] = int((((((256 - Analyze_Common_Params['blksize']) / 2) / (128 - overlap_offset_divisor_postdivide)) * Analyze_Common_Params['blksize']/2) + overlap_offset_postdivide) / 4) * 2
Analyze_Common_Params['overlapv'] = int((((((256 - Analyze_Common_Params['blksize']) / 2) / (128 - overlap_offset_divisor_postdivide)) * Analyze_Common_Params['blksizev']/2) + overlap_offset_postdivide) / 4) * 2
else:
Analyze_Common_Params['overlap'] = int(Analyze_Common_Params['overlap'] / 2)
Analyze_Common_Params['overlapv'] = int(Analyze_Common_Params['overlapv'] / 2)
else:
Analyze_Common_Params['overlap'] = 0
Analyze_Common_Params['overlapv'] = 0
# but always do heaviest version twice in max quality mode
if (DUAL_DCT and BEST_DCT):
Analyze_Common_Params['dct'] = 3
elif not DUAL_DCT:
Analyze_Common_Params['dct'] = 0
if (BEST_SEARCH and (not BEST_SEARCH_POSTDIVIDE)):
Analyze_Common_Params['search'] = search_previous
# scale "optimal" pnew for blocksize weirdness ?
if pnew_scale == True:
pnew = int(((2 ** (Interpolate_Params['thscd1'] / 16320)) - 1) * (256 - (pnew_offset_divisor_for_overlap + ((Analyze_Common_Params['blksize']+Analyze_Common_Params['blksizev'])/2) - 8)) + pnew_offset_for_overlap) if (MAX_OVERLAP_POSTDIVIDE or MAX_OVERLAP) else\
int(((2 ** (Interpolate_Params['thscd1'] / 16320)) - 1) * (256 - (pnew_offset_divisor + ((Analyze_Common_Params['blksize']+Analyze_Common_Params['blksizev'])/2) - 8)) + pnew_offset)
if pnew < 0: pnew = 0
elif pnew > 256: pnew = 256
Analyze_Common_Params['pnew'] = pnew
block_type = "subdivided-in-4" if Analyze_Common_Params['divide'] > 0 else "undivided"
search_type = "+" if Analyze_Params['trymany'] else "-"
print("[mvtools-reanalysis]: {}/{} {} blocks, {} new vector penalty, {}:{} re-search, {}/{} overlap, {} λ, DCT mode:{}".format(\
Analyze_Common_Params['blksize'], Analyze_Common_Params['blksizev'], block_type, Analyze_Common_Params['pnew'],\
Analyze_Common_Params['search'], Analyze_Common_Params['searchparam'],\
Analyze_Common_Params['overlap'], Analyze_Common_Params['overlapv'],\
Analyze_Common_Params['lambda'], Analyze_Common_Params['dct']))
bvec1 = core.mv.Recalculate(sup, bvec1, **Analyze_Common_Params)
fvec1 = core.mv.Recalculate(sup, fvec1, **Analyze_Common_Params)
## actual interpolation
# force max-quality interpolation ?
if ALWAYS_FLOW:
INTERPOLATE_FUNCTION = "FlowFPS"
Interpolate_Params['mask'] = 2
# python is getting pissy if parameter, that is not used by the function, is defined
if INTERPOLATE_FUNCTION == "FlowFPS":
del Interpolate_Params['mode']
elif INTERPOLATE_FUNCTION == "BlockFPS":
del Interpolate_Params['mask']
interpolation_option = "mode:" + str(Interpolate_Params['mode']) if INTERPOLATE_FUNCTION == "BlockFPS" else "mask:" + str(Interpolate_Params['mask'])
print("[mvtools-interpolation]: {} function with {} option and {} ignoreSAD".format(INTERPOLATE_FUNCTION, interpolation_option, Interpolate_Params['thscd1']))
clip = getattr(core.mv,INTERPOLATE_FUNCTION)(clip, sup, bvec1, fvec1, num=dst_fps_num, den=dst_fps_den, **Interpolate_Params)
## convert format before output ?
if (FORMAT_OUTPUT != None and FORMAT_OUTPUT_BITS != None):
FORMAT_OUTPUT_CONSTANT = getattr(vapoursynth,"YUV"+str(FORMAT_OUTPUT)+"P"+str(FORMAT_OUTPUT_BITS))
else:
FORMAT_OUTPUT_CONSTANT = 0
FORMAT_FAMILY_CURRENT=clip.format.color_family
if ((FORMAT_OUTPUT != None and (FORMAT_FAMILY_CURRENT > FORMAT_OUTPUT_CONSTANT) and (not AVOID_DOWNSAMPLING_FORMAT)) and (FORMAT_OUTPUT != FORMAT_INPUT)):
output_resample_type = "32-bit float" if FORMAT_OUTPUT_FLOAT == 1 else "16-bit integer"
print("[mvtools-conversion-output]: converting clip into {} yuv{} with {} kernel and {} taps".format(output_resample_type, FORMAT_OUTPUT, FORMAT_OUTPUT_KERNEL, FORMAT_OUTPUT_KERNEL_TAPS))
clip = core.fmtc.resample(clip=clip, flt = FORMAT_OUTPUT_FLOAT, css = FORMAT_OUTPUT, kernel = FORMAT_OUTPUT_KERNEL, taps = FORMAT_OUTPUT_KERNEL_TAPS)
elif FORMAT_OUTPUT == None:
print("[mvtools-conversion-output]: skipping format conversion because target format was not selected")
elif FORMAT_OUTPUT == (FORMAT_INPUT or FORMAT_FAMILY_CURRENT):
print("[mvtools-conversion-output]: skipping format conversion because current and target format are the same YUV{}".format(FORMAT_OUTPUT))
elif FORMAT_FAMILY_CURRENT <= FORMAT_OUTPUT_CONSTANT:
print("[mvtools-conversion-output]: skipping format up-conversion because current format {} ({}) is ≤ to target YUV{}P{} ({})".format(\
FORMAT, FORMAT_FAMILY, FORMAT_INPUT, FORMAT_INPUT_BITS, FORMAT_INPUT_CONSTANT))
elif (AVOID_DOWNSAMPLING_FORMAT and (FORMAT_FAMILY_CURRENT > FORMAT_OUTPUT_CONSTANT)):
print("[mvtools-conversion-output]: skipping format down-conversion of {} ({}) to YUV{}P{} ({})".format(\
FORMAT, FORMAT_FAMILY, FORMAT_INPUT, FORMAT_INPUT_BITS, FORMAT_INPUT_CONSTANT))
# reduce anomaly upsampled format to something expected
FORMAT_BITS_CURRENT = clip.format.bits_per_sample
if ((FORMAT_OUTPUT_BITS == None) and (FORMAT_BITS_CURRENT > (FORMAT_BITS and FORMAT_INPUT_BITS))):
if FORMAT_BITS_CURRENT > FORMAT_INPUT_BITS:
FORMAT_OUTPUT_BITS = FORMAT_INPUT_BITS
else:
FORMAT_OUTPUT_BITS = FORMAT_BITS
if FORMAT_OUTPUT_BITS != None:
if (AVOID_DOWNSAMPLING_FORMAT_BITS and (FORMAT_OUTPUT_BITS < FORMAT_BITS)):
FORMAT_OUTPUT_BITS = FORMAT_BITS
if (FORMAT_OUTPUT_BITS != None) and ((FORMAT_OUTPUT_BITS < FORMAT_BITS_CURRENT) or (FORMAT_BITS_CURRENT > FORMAT_OUTPUT_BITS)):
print("[mvtools-conversion-output]: dithering clip into {}-bit".format(FORMAT_INPUT_BITS))
clip = core.fmtc.bitdepth(clip=clip, bits = FORMAT_OUTPUT_BITS,\
dmode = FORMAT_OUTPUT_DITH_MODE, dyn = FORMAT_OUTPUT_DITH_ROTATE, staticnoise = FORMAT_OUTPUT_DITH_STATIC_NOISE,\
ampo = FORMAT_OUTPUT_DITH_AMP_ORDERED, ampn = FORMAT_OUTPUT_DITH_AMP_NOISE, patsize = FORMAT_OUTPUT_DITH_PATTERN)
elif FORMAT_OUTPUT_BITS == None:
print("[mvtools-conversion-output]: skipping dithered bitdepth conversion because target depth was not selected")
elif FORMAT_OUTPUT_BITS > FORMAT_BITS_CURRENT:
print("[mvtools-conversion-output]: skipping dithered bitdepth up-conversion because current depth {} ≤ to target {} depth".format(FORMAT_BITS_CURRENT, FORMAT_OUTPUT_BITS))
elif FORMAT_OUTPUT_BITS == (FORMAT_BITS or FORMAT_BITS_CURRENT):
print("[mvtools-conversion-output]: skipping dithered bitdepth conversion because current and target depths both are {}".format(FORMAT_BITS_CURRENT))
## pumping out finished frames
clip.set_output()
else:
if PLAYER_FAIL:
print("[mvtools-bail]: Skipping motion interpolation due to player's stupidity in determining video's framerate !!!")
elif (BYPASS_HDR and ((FORMAT_BITS > 8) or (FORMAT != "YUV420P8"))):
print("[mvtools-bail]: Skipping motion interpolation due to high CPU demands for processing HDR content !!!")
elif mpix > max_mpix:
print("[mvtools-bail]: Skipping motion interpolation for {} megapixel video that surpasses limit of {} !!!".format(mpix,max_mpix))
elif src_fps < min_src_fps:
print("[mvtools-bail]: Skipping motion interpolation for {} Hz video that's slower than minimum limit of {} !!!".format(src_fps,min_src_fps))
elif src_fps > max_src_fps:
print("[mvtools-bail]: Skipping motion interpolation for {} Hz video that surpasses maximum limit of {} !!!".format(src_fps,max_src_fps))
elif src_fps >= max_dst_fps:
print("[mvtools-bail]: Skipping meaningless {}->{} interpolation !!!".format(src_fps,max_dst_fps))
else:
print("[mvtools-bail]: Skipping motion interpolation for some reason…")
clip = video_in
clip.set_output()
@Animenosekai
Copy link

Animenosekai commented Apr 27, 2020

What dependencies do we need?
Also if you had a way to get rid of "Python exception: No attribute with the name mv exists. Did you mistype a plugin namespace?" while I downloaded mvtools on macOS I would be super grateful

@v-fox
Copy link
Author

v-fox commented Apr 27, 2020

What dependencies do we need?
Also if you had a way to get rid of "Python exception: No attribute with the name mv exists. Did you mistype a plugin namespace?" while I downloaded mvtools on macOS I would be super grateful

All dependencies are literally listed in the README along with the links to their sources. I don't know how things work on MacOS but you need mpv that's built with vapoursynth support and mvtools that's compiled against the same VS version as mpv. The script should also work with vspipe binary in terminal but I have not tested that, some minor changes may be required.

@Animenosekai
Copy link

What dependencies do we need?
Also if you had a way to get rid of "Python exception: No attribute with the name mv exists. Did you mistype a plugin namespace?" while I downloaded mvtools on macOS I would be super grateful

All dependencies are literally listed in the README along with the links to their sources. I don't know how things work on MacOS but you need mpv that's built with vapoursynth support and mvtools that's compiled against the same VS version as mpv. The script should also work with vspipe binary in terminal but I have not tested that, some minor changes may be required.

Thank you very much for your awesome script!
Everything seems to work perfectly now that I installed all the dependencies (just needed to search a little deeper to find homebrew versions)
(Though when I try to enable your script it lags as hell and doesn't really work, I still can use the original script and I'll try to optimize the mac for it to work with your script)

@Animenosekai
Copy link

I'll put what I did to install all the dependencies after

@v-fox
Copy link
Author

v-fox commented Apr 27, 2020

Thank you very much for your awesome script!
Everything seems to work perfectly now that I installed all the dependencies (just needed to search a little deeper to find homebrew versions)
(Though when I try to enable your script it lags as hell and doesn't really work, I still can use the original script and I'll try to optimize the mac for it to work with your script)

I've mentioned my CPU for a reason: even it barely works at 99%-100% on all cores and doesn't drop frames only because of heavy optimization for strict process priorities and overall system real-time guarantees for audio and video. Anything lower than Xeon E5-2650v2@3Gh or Ryzen 1600 will get destroyed. You also need no less than 16 "concurrent-frames" as script option, like "motioninterpolation.vpy:1:24". This will introduce heavy seeking latency but at least eliminate random frame drops due to some kind of desynchronization in mpv's decoding/rendering pipeline.

You may try lowering quality. Such as changing Flow function to Block (but this often looks really bad), disabling debanding (bad for <720p and many low-bitrate 1080p videos), increasing block-size (but this will make it miss a lot of vectors and not interpolate).

@Animenosekai
Copy link

Animenosekai commented Apr 27, 2020

macOS users - to install all the dependencies

You first need to install Homebrew (which will let you install all kinds of packages) at https://brew.sh/

Then open terminal and run in the right order:

brew tap bl4cc4t/vsplugins
brew install --HEAD vsp-nnedi3
brew install --HEAD vsp-flash3kyuu_deband
brew install --HEAD vsp-tdeintmod
brew tap noctem/vapoursynth
brew install --HEAD fmtconv
brew install --HEAD vs-mvtools

When everything is installed go to /usr/local/Homebrew/Library/Taps/bl4cc4t/homebrew-vsplugins/ and open linkvsp.sh
This will link all the new packages to be used in scripts.
Now you just need to download v-fox's scripts and it should work as said in readme.me.

If something is missing just ask me (I might have forgotten something)

@Animenosekai
Copy link

Thank you very much for your awesome script!
Everything seems to work perfectly now that I installed all the dependencies (just needed to search a little deeper to find homebrew versions)
(Though when I try to enable your script it lags as hell and doesn't really work, I still can use the original script and I'll try to optimize the mac for it to work with your script)

I've mentioned my CPU for a reason: even it barely works at 99%-100% on all cores and doesn't drop frames only because of heavy optimization for strict process priorities and overall system real-time guarantees for audio and video. Anything lower than Xeon E5-2650v2@3Gh or Ryzen 1600 will get destroyed. You also need no less than 16 "concurrent-frames" as script option, like "motioninterpolation.vpy:1:24". This will introduce heavy seeking latency but at least eliminate random frame drops due to some kind of desynchronization in mpv's decoding/rendering pipeline.

You may try lowering quality. Such as changing Flow function to Block (but this often looks really bad), disabling debanding (bad for <720p and many low-bitrate 1080p videos), increasing block-size (but this will make it miss a lot of vectors and not interpolate).

Yea I'll try to optimize it thank's!

@csolisr
Copy link

csolisr commented Dec 9, 2020

To which packages do the dependencies correspond in Arch Linux? Or should I build all the linked items from source code?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment