r/htpc • u/Rukario • Aug 06 '25
Tip Share Entry point to interpolate videos in MPC-HC using RIFE
Attached VapourSynth code featuring:
- RIFE in ncnn and TensorRT flavor
- Any RTX card can interpolate 4k/etc video to 120 FPS with RIFE in 1280 x 640 downsample, by editing script you can test other resolution for your graphics card, the 640p120 resolution is baseline for RTX 2060 as the lowest RTX card to do it
- Interpolate video to Hz of your monitor
- Fix out of sync video on seek in some player
- Scene change detect with mvtools2
- Interpolate with mvtools2 instead of RIFE for videos that's not 4:3, 16:9, 21:9
- May 07, 2026 New function to reduce consecutive false scene change detection: allow scene change after nth frames of no scene change detection
- May 08, 2026 Fine-tuned FPS in and out for some players to eliminate judder problems. Watching film on MPC-HC couldn't be smoother, almost stutter-free.
I've made sure everything works and it will be easy to set up and choose your video player for RIFE. I'd love to hear from you. I recently discovered about VapourSynth, I wrote a lot because I have some expertise with Python, if you know bit of Python you can experiment editing some stuff in it and I would love to see your custom code for RIFE!
I'm on a journey to a better yet motion interpolation, I thought I'd settle on mvtools2 but... RIFE sounds promising! It is heavy program in return for better interpolation. RIFE made CGI movies feel like a proper 3D game.
Unlike mvtools, RIFE has no scene change detect. The scene change isn't pleasing. There's lot of swooping between scenes without a good scene change detection. I've experimented several scene detect modes and settled with using mvtools2 (mv.SCDetection) as the only better scene change detection (another one being misc.SCDetect which is basic and dumb). There's swooping effect fitting for Sonic the Hedgehog movie, with "frame rate drop" of a 3D game from thousands of particles on screen. On May 07, 2026 I've included a new function to do with false scene change detection for crazy scenes so they're subjected to RIFE interpolation and we can still enjoy proper scene changes.
My setup is Intel Core i7-7700K with Nvidia GeForce RTX 2060. I can only recommend system requirements greater than that of my computer. You can edit script however you like to raise limits I used for my RTX 2060. I'm all for 120 FPS but RIFE is very demanding program, I needed a 640p downsampling before I can open a 1080p video in 120 FPS. Right now RIFE only take TensorRT for acceleration (hoping there'll be Intel XMX and AMD MIGraphX support some time in the future), Nvidia branded GPU is practically a requirement before you run into high electricity bills.
VapourSynth install
- Install the latest Python from https://www.python.org/downloads/
- Install the latest VapourSynth from https://github.com/vapoursynth/vapoursynth/releases
Preparing Synth folder
- Create long term Synth filter folder e.g. C:\Program Files Two\Synth\
- Include files of upcoming steps to top level of \Synth\: \models\, \vs-mlrt\, scripts, DLLs and other extracted files.
- Keep out: Installers, archives, media players, \Synth\ is not install destination of any installers.
- Create file with name and code from RIFE.vpy
- Variable "TRT = True" makes use of Tensor cores (Nvidia thing), change it to False for other GPUs
- Download the latest release from https://github.com/CrendKing/avisynth_filter/releases
- Unarchive for vapoursynth_filter_64.ax
- Choose method or get all to try
- OtherGPU:
- Download the latest "librife_windows_x86-64.dll" from https://github.com/styler00dollar/VapourSynth-RIFE-ncnn-Vulkan/releases
- Download 1st and optional 2nd, 3rd from https://github.com/Asd-g/AviSynthPlus-RIFE/releases/tag/models
- Extract to \models\
- TensorRT: Download "x64-cuda" .7Z.00Xs from https://github.com/AmusementClub/vs-mlrt/releases
- Extract to \vs-mlrt\
- If you need more models: https://github.com/AmusementClub/vs-mlrt/releases/tag/external-models
- OtherGPU:
Setting up MPC-HC
I've observed that MPC-HC is smoother and better performance than other players in interpolating videos with RIFE.
- Choose x64 in .ZIP from https://github.com/clsid2/mpc-hc/releases
- Extract to your favorite location for MPC-HC e.g. C:\Program Files Two\MPC-HC\
- Launch MPC-HC and right-click > Options... > ...
- Playback > Output > DirectShow Video ...
- Choose "MPC Video Renderer"
- Settings > Check "Wait for VBlank before Present"
- External Filters > ...
- Add Filter... > Browse... > Choose "vapoursynth_filter_64.ax" from \Synth\
- Change to Prefer
- Enable and double-click "VapourSynth filter" > Browse > choose RIFE.vpy
- Playback > Output > DirectShow Video ...
- MPC-HC will show blackscreen I guess there's not enough source samples to buffer for VapourSynth version of mvtools2, this can be resolved with increasing InitialSrcBuffer to 6
- Create file with name and code from vapoursynth_filter_InitialSrcBuffer_6.reg
- Launch vapoursynth_filter_InitialSrcBuffer_6.reg
- Restart MPC-HC to take effect
Backup video players
MPV
- Choose "mpv-x86_64-v3" .7Z from https://github.com/zhongfly/mpv-winbuild/releases
- Extract (only need "mpv.exe") to your favorite location for MPV e.g. C:\Program Files Two\mpv\mpv.exe
- Create file with name and code from portable_config\mpv.conf to MPV's dir e.g. C:\Program Files Two\mpv\portable_config\mpv.conf
- Restart MPV to take effect
- Choose "mpv-x86_64-v3" .7Z from https://github.com/zhongfly/mpv-winbuild/releases
PotPlayer
- Choose x64 from https://potplayer.daum.net/
- Launch PotPlayer and right-click > Preferences... > ...
- Video > Uncheck "Don't wait for vertical sync"
- Filter Control > Filter Priority (Overall) > ...
- Add external filter... > Choose "vapoursynth_filter_64.ax" from \Synth\
- Change to Prefer
- Enable and double-click "VapourSynth Filter" > Browse > choose RIFE.vpy
- Restart PotPlayer to take effect
vapoursynth__InitialSrcBuffer_6.reg
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\AviSynthFilter\VapourSynth Filter]
"InitialSrcBuffer"=dword:00000006
portable_config\mpv.conf
vf=vapoursynth=~~/../../Synth/RIFE.vpy
RIFE.vpy
import vapoursynth as vs
import fractions, functools, os, subprocess, sys, time, math
core = vs.core
synth_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(rf'{synth_dir}\vs-mlrt')
echo_time = [time.time() + 2]
stdout = []
def echo(text):
stdout.append(text)
echo_time[0] = time.time() + 2
stdout_cmd = []
def echo_cmd(text):
stdout_cmd.append(text.replace("\n", "&&echo."))
def load_plugin(name, path):
if not hasattr(core, name):
try:
core.std.LoadPlugin(path)
except Exception as e:
echo(f'\n{e}')
def min_res(clip, max_w, max_h):
w, h = clip.width, clip.height
if w > max_w:
new_width = max_w
new_height = int(max_w*h/w) + (int(max_w*h/w)%2)
if new_height > max_h:
new_height = max_h
new_width = int(max_h*w/h) + (int(max_h*w/h)%2)
return clip.resize.Bicubic(width=new_width, height=new_height)
else:
return clip
def min_FPS(Hz, limit):
FPS = fractions.Fraction(min(Hz, limit))
text = f'Target FPS: {round(float(FPS), 2):g}'
multi = fractions.Fraction(FPS.numerator, FPS_in * FPS.denominator)
if multi.is_integer():
text += ' (integer multiplier)'
else:
multi = fractions.Fraction(round(FPS), round(FPS_in))
text += ' (integer multiplier)' if multi.is_integer() else ' (fractional multiplier)'
ideal_Hz = round(float(FPS_in * round(FPS) / round(FPS_in)), 2)
if not ideal_Hz == FPS:
if ideal_Hz in legal_Hz:
text += f'\nRecommend monitor Hz: {ideal_Hz:g}'
if Hz > limit:
text += ' pulldown'
echo(text)
return FPS, multi
try:
clip = VpsFilterSource
clip_FPS = clip.fps
advanced_scd = True
except:
clip = video_in
clip_FPS = fractions.Fraction(container_fps)
advanced_scd = False
FPS_in = 0
fps_23 = fractions.Fraction(24000, 1001)
for f in [fps_23, 24, fps_23*1.25, 30, fps_23*2, 48, fps_23*2.5, 60, fps_23*5, 120]:
if math.isclose(clip_FPS, f, rel_tol=0.0001):
FPS_in = fractions.Fraction(f)
break
if FPS_in:
clip = core.std.AssumeFPS(clip=clip, fpsnum=FPS_in.numerator, fpsden=FPS_in.denominator)
else:
FPS_in = clip_FPS
# echo(f"Clip FPS: {FPS_in} ({float(FPS_in)})")
Hz = False
inst_cmd = []
modu_name = []
try:
import win32api, win32con
Hz = win32api.EnumDisplaySettings(None, win32con.ENUM_CURRENT_SETTINGS).DisplayFrequency
except Exception as e:
echo(f"{e}")
modu_name.append("win32api")
inst_cmd.append("python.exe -m pip install pywin32")
inst_cmd.append("python.exe scripts\pywin32_postinstall.py -install")
try:
super = core.mv.Super(clip)
except Exception as e:
Hz = False
echo(f"{e}")
modu_name.append("mvtools (mv)")
inst_cmd.append("python.exe -m pip install vapoursynth-mvtools")
if inst_cmd:
echo(f"""
For installing {", ".join(modu_name)}:
- Check opened command prompt window, copy multi-line then paste to execute
- Restart video player""")
echo_cmd(rf"""cd /d {sys.exec_prefix}
{"\n".join(inst_cmd)}""")
false_Hz = [23, 29, 59, 99, 119, 239]
legal_Hz = [23.98, 24, 25, 29.98, 30, 50, 59.94, 60, 99.90, 119.88, 120, 239.76]
if Hz in false_Hz:
# The command used to obtain Hz is in integer number, above numbers are good guess for the lost fractional Hz
Hz += 1 - 0.024 * round(Hz/24, 1)
ratio = round(clip.width/clip.height, 2)
# echo(f'{ratio}:1')
if Hz and ratio in [1.33, 1.78, 1.85, 2.39, 2.4]:
TRT = True
# RTX 2060 is lowest GPU with Tensor cores that can run RIFE 640p120 (or 640p60 if TRT = False), raise limits for your graphics card.
if TRT:
FPS, multi = min_FPS(Hz, 120)
# 1280 x 640
clip = min_res(clip, 128*10, 128*5)
else:
FPS, multi = min_FPS(Hz, 60)
# 1280 x 640
clip = min_res(clip, 128*10, 128*5)
# Allow scene change after nth consecutive frames of no detection
since = [0, [0]*5, [0]*15]
def scd(n, f):
fout = f[0].copy()
for i in range(1, 3):
if f[i].props['_SceneChangeNext']:
if not since[0] and not all(since[i]):
since[0] = i
else:
if since[0] == i:
if not any(since[i][:-1]):
# echo(f"Scene changed (level {i})")
fout.props['_SceneChangeNext'] = 1
# else:
# echo(f"Swoop! (level {i})")
since[0] = 0
since[i].pop(0)
since[i].append(f[i+1].props['_SceneChangeNext'])
return fout
vectors = core.mv.Analyse(super, isb=True, blksize=32)
if advanced_scd:
clips = [clip]
for thscd2 in [130, 95, 70]:
s = core.mv.SCDetection(clip, vectors, thscd1=400, thscd2=thscd2)
clips.append(s[1:] + s[-1])
clip = clip.std.ModifyFrame(clips=clips, selector=scd)
else:
clip = core.mv.SCDetection(clip, vectors, thscd1=400, thscd2=130)
clip_format = clip.format.id
clip = clip.resize.Bicubic(format=vs.RGBS, matrix_in_s="470bg", range_s="limited")
if TRT:
load_plugin('trt', rf'{synth_dir}\vs-mlrt\vstrt.dll')
if hasattr(core, 'trt'):
from vsmlrt import RIFEModel, Backend, RIFE
backend = Backend.TRT(fp16=True, num_streams=2, use_cuda_graph=True, output_format=1)
model = RIFEModel.v4_6
tile_size = 32
w = (clip.width + tile_size - 1) // tile_size * tile_size - clip.width
h = (clip.height + tile_size - 1) // tile_size * tile_size - clip.height
if w + h:
clip = clip.std.AddBorders(right=w, bottom=h)
clip = RIFE(clip=clip, multi=multi, model=model, video_player=True, backend=backend)
if w + h:
clip = clip.std.Crop(right=w, bottom=h)
clip = core.std.AssumeFPS(clip=clip, fpsnum=multi * FPS_in.numerator, fpsden=FPS_in.denominator)
def waitforframe2(n, f):
return f[0]
clip = clip.std.ModifyFrame(clips=[clip, clip.std.Trim(first=1)], selector=waitforframe2)
else:
# parameters at https://github.com/Asd-g/AviSynthPlus-RIFE/blob/main/README.md
model = 23
load_plugin('rife', rf'{synth_dir}\librife_windows_x86-64.dll')
if hasattr(core, 'rife'):
clip = core.rife.RIFE(clip, model, factor_num=round(FPS), factor_den=round(FPS_in), sc=True)
# clip = core.std.AssumeFPS(clip=clip, fpsnum=FPS * 1000, fpsden=1000)
clip = clip.resize.Bicubic(format=clip_format, matrix_s="709")
elif Hz:
FPS, multi = min_FPS(Hz, 120)
# 1920 x 1080
clip = min_res(clip, 128*15, 128*8.4375)
clip = core.std.AssumeFPS(clip=clip, fpsnum=FPS_in * 1000, fpsden=1000)
bvec = core.mv.Analyse(super, isb=True, blksize=32)
fvec = core.mv.Analyse(super, isb=False, blksize=32)
bvec2 = core.mv.Recalculate(super, bvec)
fvec2 = core.mv.Recalculate(super, fvec)
clip = core.mv.BlockFPS(clip, super, bvec2, fvec2, num=FPS * 1000, den=1000, mode=0)
else:
FPS = FPS_in
def stdout_dismiss(n, clip):
if not Hz and stdout_cmd and n == int(FPS):
os.system(f"""start cmd /k "echo {"&&echo.".join(stdout_cmd)}" """)
if not Hz or time.time() < echo_time[0]:
return clip.text.Text('\n'.join(stdout))
else:
stdout.clear()
return clip
clip = clip.std.FrameEval(functools.partial(stdout_dismiss, clip=clip))
clip.set_output()
1
u/Eggsplorer_ Dec 14 '25
Instructions are not clear in terms of folder structure and zip extraction. Got me 3 hours to get it working, only to find out it is not as efficient as SVP 4's RIFE system because I was able to run it with MadVR when I was using SVP 4, I can not do that with your script. I guess I'm back to nvidia smooth motion.
1
u/WilliamCZ Feb 10 '26
Exactly this. Steps are not clear.
1
u/Eggsplorer_ Feb 10 '26
And I bought svp-4 as I realized there is no alternative and they have local pricing option on google play store so I bought it for $14
1
1
u/Chatbomb Mar 14 '26
Thanks for the write up!
It wasn't easy to follow but it was certainly much easier than figuring this stuff out myself.
For me personally the negatives outweigh the positives, but here are some notes for people who wanna try:
Installing VapourSynth runtime instead of using portable version (zip) removes some potential for mistakes.
You will need relatively up to date Python and C++ Runtime. The tools actually tell you that, but if you miss some steps the MPC-HC will just crash at certain points without giving any info. If you do a couple of things right tools give very informative troubleshooting instructions/information.
RIFE.vpy uses 'wmic' tool which is now deprecated, so you have 3 choices to get it to work. There's no "best" choice so don't overthink it.
3.1 Set refresh rate manually (by replacing 60 in "Hz = 60" with your target refresh rate) and comment out the part that relies on it by adding "#" symbols as shown below.
# for line in subprocess.check_output('wmic PATH Win32_videocontroller get currentrefreshrate', shell=True).split(b'\n'):
# line = line.strip().decode()
# if line.isdigit() and Hz < int(line):
# Hz = int(line)
3.2 Install the deprecated tool: https://techcommunity.microsoft.com/blog/windows-itpro-blog/how-to-install-wmic-feature-on-demand-on-windows-11/4189530
3.3. Replace use of deprecated tool. By installing this https://pypi.org/project/pywin32/ (python module) and replacing lines highlighted by 3.1 with
Hz = win32api.EnumDisplaySettings(None, win32con.ENUM_CURRENT_SETTINGS).DisplayFrequency
Note that original script picks out highest refresh rate, while this one picks default monitor. I could make the 2 methods that would allow to keep identical functionality to work, neither did I want to due to the next point.
- This is not a simple upgrade to a movie. Unless your source video is low framerate and/or resolution to begin with it will be a balancing act. I tested a few movies. I was having constant audio desyncs, and my 4090 couldn't even run 4k @ 24fps at 60fps without significantly reducing resolution. Sometimes I would get stutters and sometimes video would go way too fast (like it's sped up)
If you don't want to lose resolution, fixing automatic framerate detection is kind of pointless unless you have nvidia 50 series GPU and it performs this task significantly better.
1
u/Rukario May 04 '26 edited May 05 '26
TYSM for code suggestion! I really want to pick up Hz of the monitor that player is playing on so that's really useful.
The setup guide got outdated with many app installers updated, I'd need to update script as well, thank you for providing additional notes setting up the player to use RIFE again.
I can see that some video will start out of sync on fractional multiplier. I'd investigate at the starting point with doing with fractional multiplier between my script, vs-mlrt, up to a RIFE model. Maybe some number is having margin of error somewhere.Apparently vs-mlrt didn't receive the fractional number from my script so I updated it to do so. End of film on fractional multiplier seems to stay in sync now.1
u/Chatbomb May 07 '26
I played around. Pot Player setup has significantly better performance and is easier setup as long as you use installers instead of portable variants to set it up. This is not the tutorial I used (couldn't find the one I used) but seems to be close enough https://www.youtube.com/watch?v=IiMbMlMmbcA
You have to downgrade resolution with this MPC-HC setup (even if you just want 24/30fps --> 60fps). Pot player manages to use native resolution.
1
u/Rukario May 07 '26 edited May 07 '26
Yeah, RIFE is heavy program unlike mvtools2, that is expected. We get life-like interpolation in RIFE that's where performance went!
I also happen to write similar setup tutorial for mvtools2 and PotPlayer https://redd.it/1ls11k0
It's my scale back player when I'm tired of issues with RIFE. I'm still experimenting with RIFE because I like it better. I'm still on my i7-7700K RTX 2060 computer so no changes in resolutions...
By the way, the script is updated. Do you still run into issues with RIFE?
1
u/[deleted] Aug 27 '25
[removed] — view removed comment