shurik_pronkin
2nd April 2026, 18:15
Virtual file bridge: pipe external tools into AviSynth without intermediate files (WinFsp + Python)
The problem
We all know AviSynth can pipe out easily — avs2pipemod, AVFS, etc. But piping into AviSynth has always been considered impossible because source filters (LWLibavVideoSource, FFmpegSource2) require seekable input for indexing.
This forces a painful workflow whenever an external tool sits in the middle of your filter chain. My case: Topaz Video AI's gcg-5 model (the only decent restoration/upscale filter they have — everything else is garbage, fight me). The pipeline should be:
AVS1 (InpaintDelogo + KillerSpots + stab2)
→ Topaz gcg-5 (2x upscale + cleanup)
→ AVS2 (RIFE + AiUpscale)
→ x264
But Topaz is a custom ffmpeg build with proprietary filters — it can't run inside AviSynth. So everyone does the same thing: encode to FFV1 intermediate, then load it back. For a 50-minute DVD episode that's ~100-150 GB of intermediate per episode. Multiply by a 10-episode series and you're babysitting disk space for days.
The community consensus has been: "pipe into AviSynth is impossible, use intermediate files." (See threads on VideoHelp, this forum, Topaz community — same answer everywhere.)
The solution
Python + WinFsp (Windows FUSE) + a blocking frame buffer.
The idea: create a virtual raw YUV file that appears as a complete, seekable file on a virtual drive (e.g. T:\bridge.yuv). AviSynth opens it via RawSourcePlus — no indexing needed, just width × height × frame_size math. When AviSynth reads frame N:
If frame N has already arrived from the external tool → return immediately
If frame N hasn't arrived yet → block the read until it does
AviSynth doesn't know anything is unusual. It just sees a file that "reads slowly." No black frames, no race conditions, no hacks.
Architecture
AVS1 (delogo + despot + deshake)
→ avs2pipemod -y4mp
→ Topaz ffmpeg (gcg-5, y4m stdout via pipe:1)
→ Python: y4m parser → FrameBuffer (RAM, ~50 frames sliding window)
→ WinFsp FUSE virtual file: T:\bridge.yuv
→ AVS2: RawSourcePlus("T:\bridge.yuv", w, h, "I420")
→ RIFE → AiUpscale
→ avs2pipemod → x264 → final.mkv
Key components:
FrameBuffer — thread-safe dict of frame_number → raw bytes. Writer thread adds frames, FUSE read handler retrieves them. Condition variable for blocking. Old frames evicted automatically (sliding window of ~50 frames behind read frontier). RAM usage: ~125 MB regardless of video length.
FUSE filesystem — single read-only file. getattr() reports full file size (frames × frame_size). read() translates byte offset → frame number, calls FrameBuffer which blocks if needed.
Y4M parser — strips y4m headers/frame markers from Topaz stdout, feeds raw planes to FrameBuffer.
Requirements
WinFsp (https://winfsp.dev/rel/) — free, stable FUSE driver for Windows (used by SSHFS, rclone, etc.)
Python 3.10+ with fusepy: pip install fusepy
RawSourcePlus (https://github.com/chikuzen/RawSource_2.6x/releases) — AviSynth+ plugin for raw YUV input
Test results
Source: 640×480 DivX5, 500 frames
Pipeline: gcg-5 2x → 1280×960 → RIFE 25→50fps → AiUpscale 2x → 2560×1920 → x264 crf20
Pass 1 (Topaz gcg-5): 500 frames @ 5.0 fps
Pass 2 (RIFE + AiUpscale + x264): 1000 frames @ 5.6 fps
Total: 3 min 18 sec
Intermediate file on disk: 0 bytes
RAM for frame buffer: ~125 MB peak
From the log — blocking reads working correctly, writer always ahead of reader:
[y4m] Frame 200 | buf=200 | reader@37 | lead=162
[y4m] Frame 400 | buf=320 | reader@130 | lead=269
Time savings estimate (real project)
"Born of Revolution" (Рождённая революцией), 10 episodes × 50 min, DVD source:
Without bridge (sequential): Pass1 4.2h + Pass2 1.9h = 6.1h per episode
With bridge (parallel): bottleneck = Topaz @ 5fps = ~4.2h per episode
Savings: ~19 hours over the series + ~1 TB less intermediate I/O
Beyond Topaz
This isn't Topaz-specific. The virtual file bridge works for any tool that outputs raw video or y4m to stdout:
VapourSynth filters you can't get in AviSynth (pipe vspipe output back into AVS)
External AI denoisers/upscalers with CLI interface
ffmpeg filter chains (deinterlace with bwdif, then continue in AVS)
Any situation where you currently write an intermediate file just to get back into AviSynth
The only constraint: the reader (AviSynth side) must be slower or equal to the writer (external tool). If the reader is faster, it blocks waiting for frames — which is correct behavior, not an error. Throughput equals the slower side.
AVS2 script example
Import("C:\Program Files (x86)\AviSynth+\aiupscale\AiUpscale-1-2-0.avsi")
Import("C:\Program Files (x86)\AviSynth+\aiupscale\Shader.avsi")
LoadPlugin("C:\Program Files (x86)\AviSynth+\aiupscale\Shader-x64.dll")
# Virtual file from bridge — no real file on disk
RawSourcePlus("T:\bridge.yuv", 1280, 960, "I420")
libplacebo_Deband()
Prefetch(0)
SCDetect(scFile="nul", search=4, searchparam=2, pel=2, thSCD1=200, thSCD2=100, mDiff=0.5, border=180, blksize=16, preblur_strength=2, downscale=2)
z_ConvertFormat(pixel_type="RGBPS", colorspace_op="709:709:709:l=>rgb:709:709:f")
Prefetch(0)
Rife(gpu_thread=1, model=73, sc_threshold=0.16, sc1=false, sc=true, fps_num=50, fps_den=1, uhd=true, skip=true)
z_ConvertFormat(pixel_type="YUV420P10", colorspace_op="rgb:709:709:f=>709:709:709:l")
Prefetch(2)
Crop(0, 180, -0, -0)
AiUpscale(Factor=2, Luma="HQ Sharp", Mode="Photo", Chroma="Lanczos", OutDepth=10)
Prefetch(2)
Usage
python topaz_bridge.py ^
--avs1 "D:\vto\01.avs" ^
--avs2 "D:\vto\01_pass2.avs" ^
--output "D:\vto\01_final.mkv" ^
--width 1440 --height 1152 --frames 75000
Source code
Full Python script (~300 lines) is available here: https://github.com/schpuppa-art/avs-virtual-bridge
Feedback welcome. In particular:
Has anyone tried a similar approach with VapourSynth's raws.Source? (it accepts pipe input natively, but I wanted to stay in AviSynth)
Is there interest in packaging this as a standalone tool (drag-and-drop BAT generator)?
Any edge cases I should test? (very long videos, VFR sources, high bit depth)
The problem
We all know AviSynth can pipe out easily — avs2pipemod, AVFS, etc. But piping into AviSynth has always been considered impossible because source filters (LWLibavVideoSource, FFmpegSource2) require seekable input for indexing.
This forces a painful workflow whenever an external tool sits in the middle of your filter chain. My case: Topaz Video AI's gcg-5 model (the only decent restoration/upscale filter they have — everything else is garbage, fight me). The pipeline should be:
AVS1 (InpaintDelogo + KillerSpots + stab2)
→ Topaz gcg-5 (2x upscale + cleanup)
→ AVS2 (RIFE + AiUpscale)
→ x264
But Topaz is a custom ffmpeg build with proprietary filters — it can't run inside AviSynth. So everyone does the same thing: encode to FFV1 intermediate, then load it back. For a 50-minute DVD episode that's ~100-150 GB of intermediate per episode. Multiply by a 10-episode series and you're babysitting disk space for days.
The community consensus has been: "pipe into AviSynth is impossible, use intermediate files." (See threads on VideoHelp, this forum, Topaz community — same answer everywhere.)
The solution
Python + WinFsp (Windows FUSE) + a blocking frame buffer.
The idea: create a virtual raw YUV file that appears as a complete, seekable file on a virtual drive (e.g. T:\bridge.yuv). AviSynth opens it via RawSourcePlus — no indexing needed, just width × height × frame_size math. When AviSynth reads frame N:
If frame N has already arrived from the external tool → return immediately
If frame N hasn't arrived yet → block the read until it does
AviSynth doesn't know anything is unusual. It just sees a file that "reads slowly." No black frames, no race conditions, no hacks.
Architecture
AVS1 (delogo + despot + deshake)
→ avs2pipemod -y4mp
→ Topaz ffmpeg (gcg-5, y4m stdout via pipe:1)
→ Python: y4m parser → FrameBuffer (RAM, ~50 frames sliding window)
→ WinFsp FUSE virtual file: T:\bridge.yuv
→ AVS2: RawSourcePlus("T:\bridge.yuv", w, h, "I420")
→ RIFE → AiUpscale
→ avs2pipemod → x264 → final.mkv
Key components:
FrameBuffer — thread-safe dict of frame_number → raw bytes. Writer thread adds frames, FUSE read handler retrieves them. Condition variable for blocking. Old frames evicted automatically (sliding window of ~50 frames behind read frontier). RAM usage: ~125 MB regardless of video length.
FUSE filesystem — single read-only file. getattr() reports full file size (frames × frame_size). read() translates byte offset → frame number, calls FrameBuffer which blocks if needed.
Y4M parser — strips y4m headers/frame markers from Topaz stdout, feeds raw planes to FrameBuffer.
Requirements
WinFsp (https://winfsp.dev/rel/) — free, stable FUSE driver for Windows (used by SSHFS, rclone, etc.)
Python 3.10+ with fusepy: pip install fusepy
RawSourcePlus (https://github.com/chikuzen/RawSource_2.6x/releases) — AviSynth+ plugin for raw YUV input
Test results
Source: 640×480 DivX5, 500 frames
Pipeline: gcg-5 2x → 1280×960 → RIFE 25→50fps → AiUpscale 2x → 2560×1920 → x264 crf20
Pass 1 (Topaz gcg-5): 500 frames @ 5.0 fps
Pass 2 (RIFE + AiUpscale + x264): 1000 frames @ 5.6 fps
Total: 3 min 18 sec
Intermediate file on disk: 0 bytes
RAM for frame buffer: ~125 MB peak
From the log — blocking reads working correctly, writer always ahead of reader:
[y4m] Frame 200 | buf=200 | reader@37 | lead=162
[y4m] Frame 400 | buf=320 | reader@130 | lead=269
Time savings estimate (real project)
"Born of Revolution" (Рождённая революцией), 10 episodes × 50 min, DVD source:
Without bridge (sequential): Pass1 4.2h + Pass2 1.9h = 6.1h per episode
With bridge (parallel): bottleneck = Topaz @ 5fps = ~4.2h per episode
Savings: ~19 hours over the series + ~1 TB less intermediate I/O
Beyond Topaz
This isn't Topaz-specific. The virtual file bridge works for any tool that outputs raw video or y4m to stdout:
VapourSynth filters you can't get in AviSynth (pipe vspipe output back into AVS)
External AI denoisers/upscalers with CLI interface
ffmpeg filter chains (deinterlace with bwdif, then continue in AVS)
Any situation where you currently write an intermediate file just to get back into AviSynth
The only constraint: the reader (AviSynth side) must be slower or equal to the writer (external tool). If the reader is faster, it blocks waiting for frames — which is correct behavior, not an error. Throughput equals the slower side.
AVS2 script example
Import("C:\Program Files (x86)\AviSynth+\aiupscale\AiUpscale-1-2-0.avsi")
Import("C:\Program Files (x86)\AviSynth+\aiupscale\Shader.avsi")
LoadPlugin("C:\Program Files (x86)\AviSynth+\aiupscale\Shader-x64.dll")
# Virtual file from bridge — no real file on disk
RawSourcePlus("T:\bridge.yuv", 1280, 960, "I420")
libplacebo_Deband()
Prefetch(0)
SCDetect(scFile="nul", search=4, searchparam=2, pel=2, thSCD1=200, thSCD2=100, mDiff=0.5, border=180, blksize=16, preblur_strength=2, downscale=2)
z_ConvertFormat(pixel_type="RGBPS", colorspace_op="709:709:709:l=>rgb:709:709:f")
Prefetch(0)
Rife(gpu_thread=1, model=73, sc_threshold=0.16, sc1=false, sc=true, fps_num=50, fps_den=1, uhd=true, skip=true)
z_ConvertFormat(pixel_type="YUV420P10", colorspace_op="rgb:709:709:f=>709:709:709:l")
Prefetch(2)
Crop(0, 180, -0, -0)
AiUpscale(Factor=2, Luma="HQ Sharp", Mode="Photo", Chroma="Lanczos", OutDepth=10)
Prefetch(2)
Usage
python topaz_bridge.py ^
--avs1 "D:\vto\01.avs" ^
--avs2 "D:\vto\01_pass2.avs" ^
--output "D:\vto\01_final.mkv" ^
--width 1440 --height 1152 --frames 75000
Source code
Full Python script (~300 lines) is available here: https://github.com/schpuppa-art/avs-virtual-bridge
Feedback welcome. In particular:
Has anyone tried a similar approach with VapourSynth's raws.Source? (it accepts pipe input natively, but I wanted to stay in AviSynth)
Is there interest in packaging this as a standalone tool (drag-and-drop BAT generator)?
Any edge cases I should test? (very long videos, VFR sources, high bit depth)