View Full Version : [Idea] Ruby binding


MeteorRain
18th September 2026, 05:50
I think many of us are / were frustrated on AVS syntax when writing libs / tool packs. Some of the devs were working on improving the scripting syntax by adding more stuff like control flow (if/loop).

How about adding a ruby binding just like what AVS3 initially wanted to do?

How about being able to write ...


AVS.lwlibavvideosource("file.mp4")
.trim(0, -1000)
.neo_f3kdb(range: 15, y: 64, cb: 64, cr: 64, grainy: 0, grainc: 0)
.textsub("sub.ass")


Or


AVS.filter :edit, clip: :clip, w: :int, h: :int do |clip, w, h|
clip.BicubicResize(w, h, 0.333, 0.333)
end

AVS.lwlibavvideosource("file.mp4")
.edit(1280, 720)


Or


class QTGMC
def initialize(clip, **options)
@clip = clip
@options = options
end

def process
...
@clip
end
end

AVS.filter :QTGMC,
args: { clip: :clip },
options: {
Preset: :string,
TFF: :bool,
Sharpness: :float
...
} do |clip, **options|
QTGMC.new(clip, **options).process
end


And


# movie.avs
ImportRuby("qtgmc.avs.rb")
lwlibavvideosource("file.mp4").qtgmc()

MysteryX
18th September 2026, 06:28
Honestly, the main advantage of AviSynth is the simplicity of the language. If one wants advanced control flow with a more complex syntax, VapourSynth already fills that gap.

Not saying can't improve AviSynth; but should be done carefully and the benefits will be limited. Backward compatibility remains AVS' strength.

MeteorRain
18th September 2026, 07:52
It's not breaking AVS' compatibility at all. It's additional feature that's built upon the avs core, and should inter-op with AVS script.

MysteryX
18th September 2026, 13:23
Still, some of these syntaxes are pretty complex and don't fit AviSynth philosophy of syntax simplicity.

I'd start with: what is the actual gap? What's currently the syntax required to achieve a specific task? Is there something that can't be done at all? What's the proposed syntax to simplify it?

Start with the problem.

MeteorRain
18th September 2026, 19:08
No, nothing can't be done. Everything can be done by bombing the script with evals.

It's just not very encouraging that script author has to do all the eval hacks to accomplish easy tasks.

Eg. below code is from QTGMC


srchClip_us = (MEus) && SubPel != 1 ? LanczosResize(srchClip, (srchClip.width)*SubPel, (srchClip.height)*SubPel, src_left=SubPel == 4 ? 0.375 : 0.25, src_top=SubPel == 4 ? 0.375 : 0.25, taps=10) : srchClip
SubPel_us = (MEus) && SubPel != 1 ? SubPel : 1

srchSuper_us = IsClip(srchSuper_us) ? srchSuper_us : \
(maxTR > 0) ? defined(bomt) ? srchClip_us.MSuper( pel=(MEus) ? 1 : SubPel, sharp=SubPelInterp, hpad=hpad*SubPel_us, vpad=vpad*SubPel_us, chroma=ChromaMotion, mt=bomt ) : \
srchClip_us.MSuper( pel=(MEus) ? 1 : SubPel, sharp=SubPelInterp, hpad=hpad*SubPel_us, vpad=vpad*SubPel_us, chroma=ChromaMotion ) : NOP()

MAnalyseString = "blksize=BlockSize*SubPel_us, overlap=Overlap*SubPel_us, search=Search, searchparam=SearchParam, \
pelsearch=PelSearch, truemotion=TrueMotion, lambda=Lambda, lsad=LSAD, pnew=PNew, plevel=PLevel, \
global=GlobalMotion, DCT=DCT, chroma=ChromaMotion, temporal=temporal, trymany=trymany"

MRecalculateString = RefineMotion ? "overlap=halfoverlap*SubPel_us, blksize=halfblksize*SubPel_us, thSAD=halfthSAD, DCT=DCT, \
chroma=ChromaMotion, truemotion=TrueMotion, search=RefineMotionSearch, \
searchparam=RefineMotionRadius, lambda=Lambda_d2, pnew=PNew" : nop()

MRecalculateString2 = RefineMotion2 ? "overlap=qoverlap*SubPel_us, blksize=qblksize*SubPel_us, thSAD=qthSAD, DCT=DCT, \
chroma=ChromaMotion, truemotion=TrueMotion, search=RefineMotionSearch, \
searchparam=RefineMotionRadius, lambda=Lambda_d4, pnew=PNew" : nop()

MTString = defined(bomt) ? ", mt=bomt)" : ")"


bVec6c = IsClip(bVec6)
bVec6 = bVec6c ? bVec6 : \
(maxTR > 5) ? eval("srchSuper_us.MAnalyse( isb=true, delta=6, "+MAnalyseString+MTString) : NOP()

bVec6 = bVec6c || !RefineMotion || !(maxTR > 5) ? bVec6 : eval("srchSuper_us.MRecalculate(bVec6, "+MRecalculateString+MTString)
bVec6 = bVec6c || !RefineMotion2 || !(maxTR > 5) ? bVec6 : eval("srchSuper_us.MRecalculate(bVec6, "+MRecalculateString2+MTString)


bVec5c = IsClip(bVec5)
bVec5 = bVec5c ? bVec5 : \
(maxTR > 4) ? eval("srchSuper_us.MAnalyse( isb=true, delta=5, "+MAnalyseString+MTString) : NOP()

bVec5 = bVec5c || !RefineMotion || !(maxTR > 4) ? bVec5 : eval("srchSuper_us.MRecalculate(bVec5, "+MRecalculateString+MTString)
bVec5 = bVec5c || !RefineMotion2 || !(maxTR > 4) ? bVec5 : eval("srchSuper_us.MRecalculate(bVec5, "+MRecalculateString2+MTString)


bVec4c = IsClip(bVec4)
bVec4 = bVec4c ? bVec4 : \
(maxTR > 3) ? eval("srchSuper_us.MAnalyse( isb=true, delta=4, "+MAnalyseString+MTString) : NOP()

bVec4 = bVec4c || !RefineMotion || !(maxTR > 3) ? bVec4 : eval("srchSuper_us.MRecalculate(bVec4, "+MRecalculateString+MTString)
bVec4 = bVec4c || !RefineMotion2 || !(maxTR > 3) ? bVec4 : eval("srchSuper_us.MRecalculate(bVec4, "+MRecalculateString2+MTString)


bVec3c = IsClip(bVec3)
bVec3 = bVec3c ? bVec3 : \
(maxTR > 2) ? eval("srchSuper_us.MAnalyse( isb=true, delta=3, "+MAnalyseString+MTString) : NOP()

bVec3 = bVec3c || !RefineMotion || !(maxTR > 2) ? bVec3 : eval("srchSuper_us.MRecalculate(bVec3, "+MRecalculateString+MTString)
bVec3 = bVec3c || !RefineMotion2 || !(maxTR > 2) ? bVec3 : eval("srchSuper_us.MRecalculate(bVec3, "+MRecalculateString2+MTString)


bVec2c = IsClip(bVec2)
bVec2 = bVec2c ? bVec2 : \
(maxTR > 1) ? eval("srchSuper_us.MAnalyse( isb=true, delta=2, "+MAnalyseString+MTString) : NOP()

bVec2 = bVec2c || !RefineMotion || !(maxTR > 1) ? bVec2 : eval("srchSuper_us.MRecalculate(bVec2, "+MRecalculateString+MTString)
bVec2 = bVec2c || !RefineMotion2 || !(maxTR > 1) ? bVec2 : eval("srchSuper_us.MRecalculate(bVec2, "+MRecalculateString2+MTString)


bVec1c = IsClip(bVec1)
bVec1 = bVec1c ? bVec1 : \
(maxTR > 0) ? eval("srchSuper_us.MAnalyse( isb=true, delta=1, "+MAnalyseString+MTString) : NOP()

bVec1 = bVec1c || !RefineMotion || !(maxTR > 0) ? bVec1 : eval("srchSuper_us.MRecalculate(bVec1, "+MRecalculateString+MTString)
bVec1 = bVec1c || !RefineMotion2 || !(maxTR > 0) ? bVec1 : eval("srchSuper_us.MRecalculate(bVec1, "+MRecalculateString2+MTString)


fVec1c = IsClip(fVec1)
fVec1 = fVec1c ? fVec1 : \
(maxTR > 0) ? eval("srchSuper_us.MAnalyse( isb=false, delta=1, "+MAnalyseString+MTString) : NOP()

fVec1 = fVec1c || !RefineMotion || !(maxTR > 0) ? fVec1 : eval("srchSuper_us.MRecalculate(fVec1, "+MRecalculateString+MTString)
fVec1 = fVec1c || !RefineMotion2 || !(maxTR > 0) ? fVec1 : eval("srchSuper_us.MRecalculate(fVec1, "+MRecalculateString2+MTString)


fVec2c = IsClip(fVec2)
fVec2 = fVec2c ? fVec2 : \
(maxTR > 1) ? eval("srchSuper_us.MAnalyse( isb=false, delta=2, "+MAnalyseString+MTString) : NOP()

fVec2 = fVec2c || !RefineMotion || !(maxTR > 1) ? fVec2 : eval("srchSuper_us.MRecalculate(fVec2, "+MRecalculateString+MTString)
fVec2 = fVec2c || !RefineMotion2 || !(maxTR > 1) ? fVec2 : eval("srchSuper_us.MRecalculate(fVec2, "+MRecalculateString2+MTString)


fVec3c = IsClip(fVec3)
fVec3 = fVec3c ? fVec3 : \
(maxTR > 2) ? eval("srchSuper_us.MAnalyse( isb=false, delta=3, "+MAnalyseString+MTString) : NOP()

fVec3 = fVec3c || !RefineMotion || !(maxTR > 2) ? fVec3 : eval("srchSuper_us.MRecalculate(fVec3, "+MRecalculateString+MTString)
fVec3 = fVec3c || !RefineMotion2 || !(maxTR > 2) ? fVec3 : eval("srchSuper_us.MRecalculate(fVec3, "+MRecalculateString2+MTString)


fVec4c = IsClip(fVec4)
fVec4 = fVec4c ? fVec4 : \
(maxTR > 3) ? eval("srchSuper_us.MAnalyse( isb=false, delta=4, "+MAnalyseString+MTString) : NOP()

fVec4 = fVec4c || !RefineMotion || !(maxTR > 3) ? fVec4 : eval("srchSuper_us.MRecalculate(fVec4, "+MRecalculateString+MTString)
fVec4 = fVec4c || !RefineMotion2 || !(maxTR > 3) ? fVec4 : eval("srchSuper_us.MRecalculate(fVec4, "+MRecalculateString2+MTString)


fVec5c = IsClip(fVec5)
fVec5 = fVec5c ? fVec5 : \
(maxTR > 4) ? eval("srchSuper_us.MAnalyse( isb=false, delta=5, "+MAnalyseString+MTString) : NOP()

fVec5 = fVec5c || !RefineMotion || !(maxTR > 4) ? fVec5 : eval("srchSuper_us.MRecalculate(fVec5, "+MRecalculateString+MTString)
fVec5 = fVec5c || !RefineMotion2 || !(maxTR > 4) ? fVec5 : eval("srchSuper_us.MRecalculate(fVec5, "+MRecalculateString2+MTString)


fVec6c = IsClip(fVec6)
fVec6 = fVec6c ? fVec6 : \
(maxTR > 5) ? eval("srchSuper_us.MAnalyse( isb=false, delta=6, "+MAnalyseString+MTString) : NOP()

fVec6 = fVec6c || !RefineMotion || !(maxTR > 5) ? fVec6 : eval("srchSuper_us.MRecalculate(fVec6, "+MRecalculateString+MTString)
fVec6 = fVec6c || !RefineMotion2 || !(maxTR > 5) ? fVec6 : eval("srchSuper_us.MRecalculate(fVec6, "+MRecalculateString2+MTString)


#if MEus - scale back MVs to required subpel
bVec1 = (MEus && maxTR > 0) ? MScaleVect(bVec1, scale=SubPel, mode=1, adjustSubPel=true) : bVec1
fVec1 = (MEus && maxTR > 0) ? MScaleVect(fVec1, scale=SubPel, mode=1, adjustSubPel=true) : fVec1
bVec2 = (MEus && maxTR > 1) ? MScaleVect(bVec2, scale=SubPel, mode=1, adjustSubPel=true) : bVec2
fVec2 = (MEus && maxTR > 1) ? MScaleVect(fVec2, scale=SubPel, mode=1, adjustSubPel=true) : fVec2
bVec3 = (MEus && maxTR > 2) ? MScaleVect(bVec3, scale=SubPel, mode=1, adjustSubPel=true) : bVec3
fVec3 = (MEus && maxTR > 2) ? MScaleVect(fVec3, scale=SubPel, mode=1, adjustSubPel=true) : fVec3
bVec4 = (MEus && maxTR > 3) ? MScaleVect(bVec4, scale=SubPel, mode=1, adjustSubPel=true) : bVec4
fVec4 = (MEus && maxTR > 3) ? MScaleVect(fVec4, scale=SubPel, mode=1, adjustSubPel=true) : fVec4
bVec5 = (MEus && maxTR > 4) ? MScaleVect(bVec5, scale=SubPel, mode=1, adjustSubPel=true) : bVec5
fVec5 = (MEus && maxTR > 4) ? MScaleVect(fVec5, scale=SubPel, mode=1, adjustSubPel=true) : fVec5
bVec6 = (MEus && maxTR > 5) ? MScaleVect(bVec6, scale=SubPel, mode=1, adjustSubPel=true) : bVec6
fVec6 = (MEus && maxTR > 5) ? MScaleVect(fVec6, scale=SubPel, mode=1, adjustSubPel=true) : fVec6

# Expose search clip, motion search super clip and motion vectors to calling script through globals
QTGMC_SetUserGlobal( GlobalNames, "srchClip", srchClip, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "srchSuper", srchSuper, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "srchSuper_us", srchSuper, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec1", bVec1, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec1", fVec1, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec2", bVec2, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec2", fVec2, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec3", bVec3, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec3", fVec3, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec4", bVec4, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec4", fVec4, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec5", bVec5, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec5", fVec5, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec6", bVec6, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec6", fVec6, ReplaceGlobals )


Similar code piece in ruby


srchclip_us = if (s[:meus] && (s[:subpel] != 1))
AVS.lanczosresize(srchclip, (srchclip.width * s[:subpel]), (srchclip.height * s[:subpel]), src_left: ((s[:subpel] == 4) ? 0.375 : 0.25), src_top: ((s[:subpel] == 4) ? 0.375 : 0.25), taps: 10)
else
srchclip
end
subpel_us = ((s[:meus] && (s[:subpel] != 1)) ? s[:subpel] : 1)
srchsuper_us = if srchsuper_us.is_a?(AVS::Clip)
srchsuper_us
elsif (s[:maxtr] > 0)
srchclip_us.msuper(pel: (s[:meus] ? 1 : s[:subpel]), sharp: s[:subpelinterp], hpad: (hpad * subpel_us), vpad: (vpad * subpel_us), chroma: s[:chromamotion], mt: s[:bomt])
else
0
end
# Needed by shutter-blur refinement; the source omitted this initialization.
unless srchsuper.is_a?(AVS::Clip) || s[:maxtr] <= 0
srchsuper = s[:meus] ? srchclip.msuper(pel: s[:subpel], sharp: s[:subpelinterp],
hpad: hpad, vpad: vpad, chroma: s[:chromamotion], **mt_options) : srchsuper_us
end
analysis_options = {
blksize: s[:blocksize] * subpel_us, overlap: s[:overlap] * subpel_us,
search: s[:search], searchparam: s[:searchparam], pelsearch: s[:pelsearch],
truemotion: s[:truemotion], lambda: s[:lambda], lsad: s[:lsad],
pnew: s[:pnew], plevel: s[:plevel], global: s[:globalmotion], dct: s[:dct],
chroma: s[:chromamotion], temporal: s[:temporal], trymany: s[:trymany]
}.merge(mt_options)
refinements = []
[[s[:refinemotion], 2, s[:halfthsad], s[:lambda_d2]],
[s[:refinemotion2], 4, s[:qthsad], s[:lambda_d4]]].each do |enabled, divisor, sad, penalty|
next unless enabled
refinements << {overlap: avs_div(s[:overlap], divisor) * subpel_us,
blksize: avs_div(s[:blocksize], divisor) * subpel_us, thsad: sad,
dct: s[:dct], chroma: s[:chromamotion], truemotion: s[:truemotion],
search: s[:refinemotionsearch], searchparam: s[:refinemotionradius],
lambda: penalty, pnew: s[:pnew]}.merge(mt_options)
end
# Preserve original construction order: b6..b1, f1..f6.
[[backward, true, [5, 4, 3, 2, 1, 0]], [forward, false, [0, 1, 2, 3, 4, 5]]].each do |vectors, isb, order|
order.each do |index|
next if vectors[index].is_a?(AVS::Clip) || index >= s[:maxtr]
vector = srchsuper_us.manalyse(isb: isb, delta: index + 1, **analysis_options)
refinements.each { |params| vector = srchsuper_us.mrecalculate(vector, **params) }
vectors[index] = vector
end
end
# Convert search-grid vectors into output-grid vectors without overwriting
# cached search-grid handles (Ruby arrays have reference semantics).
search_backward, search_forward = backward.dup, forward.dup
if s[:meus]
6.times do |index|
next if index >= s[:maxtr]
backward[index] = backward[index].mscalevect(scale: s[:subpel], mode: 1, adjustsubpel: true)
forward[index] = forward[index].mscalevect(scale: s[:subpel], mode: 1, adjustsubpel: true)
end
end
set_user_global(s[:globalnames], "srchClip", srchclip, replace: s[:replaceglobals])
set_user_global(s[:globalnames], "ruby_cache_version", 1, replace: s[:replaceglobals])
set_user_global(s[:globalnames], "srchSuper", srchsuper, replace: s[:replaceglobals])
set_user_global(s[:globalnames], "srchSuper_us", srchsuper_us, replace: s[:replaceglobals])
6.times do |index|
set_user_global(s[:globalnames], "bVec#{index + 1}", search_backward[index], replace: s[:replaceglobals])
set_user_global(s[:globalnames], "fVec#{index + 1}", search_forward[index], replace: s[:replaceglobals])
end

MeteorRain
18th September 2026, 21:36
If you think Ruby is complex on syntax, that's fine. You are not forced to use any of those complex syntax to accomplish easy tasks.
Say you don't like writing loops like 6.times do |index| {}, sure, you can always copy-paste 6 times your code and write like how you wrote AVS code.
But at least when you do want to use the syntax, it's there for you to use.

With AVS syntax, you can barely write any library that doesn't have eval bombs.
Just look at how many evals in above code.

SetUserGlobal is based on eval.
> Eval( "global " + globalName + " = Value" )

GetUserGlobal is based on eval.
> Eval( globalName )

Passing parameters is based on eval.
> eval("srchSuper_us.MRecalculate(fVec6, "+MRecalculateString+MTString)

That's not philosophy of syntax simplicity, that's lacking basic functionality.

qyot27
18th September 2026, 22:20
Architecturally, maybe it would make sense to have some sort of runtime binding loader that can accept external definition modules, so that it would allow new bindings to be added dynamically or on-demand.

Unless it has to be more tightly bound to the core to effectively work.

wonkey_monkey
18th September 2026, 23:36
Avisynth+ already has if {} else {}, for loops, and arrays, which would go quite some way to tidying up QTGMC without resorting to another language, if anyone was of a mind to do so, e.g you can replace these parts:

bVec1 = QTGMC_GetUserGlobal( GlobalNames, "bVec1", ReuseGlobals )
fVec1 = QTGMC_GetUserGlobal( GlobalNames, "fVec1", ReuseGlobals )
bVec2 = QTGMC_GetUserGlobal( GlobalNames, "bVec2", ReuseGlobals )
fVec2 = QTGMC_GetUserGlobal( GlobalNames, "fVec2", ReuseGlobals )
bVec3 = QTGMC_GetUserGlobal( GlobalNames, "bVec3", ReuseGlobals )
fVec3 = QTGMC_GetUserGlobal( GlobalNames, "fVec3", ReuseGlobals )
bVec4 = QTGMC_GetUserGlobal( GlobalNames, "bVec4", ReuseGlobals )
fVec4 = QTGMC_GetUserGlobal( GlobalNames, "fVec4", ReuseGlobals )
bVec5 = QTGMC_GetUserGlobal( GlobalNames, "bVec5", ReuseGlobals )
fVec5 = QTGMC_GetUserGlobal( GlobalNames, "fVec5", ReuseGlobals )
bVec6 = QTGMC_GetUserGlobal( GlobalNames, "bVec6", ReuseGlobals )
fVec6 = QTGMC_GetUserGlobal( GlobalNames, "fVec6", ReuseGlobals )

[...]

bVec6c = IsClip(bVec6)
bVec6 = bVec6c ? bVec6 : \
(maxTR > 5) ? eval("srchSuper_us.MAnalyse( isb=true, delta=6, "+MAnalyseString+MTString) : NOP()

bVec6 = bVec6c || !RefineMotion || !(maxTR > 5) ? bVec6 : eval("srchSuper_us.MRecalculate(bVec6, "+MRecalculateString+MTString)
bVec6 = bVec6c || !RefineMotion2 || !(maxTR > 5) ? bVec6 : eval("srchSuper_us.MRecalculate(bVec6, "+MRecalculateString2+MTString)


bVec5c = IsClip(bVec5)
bVec5 = bVec5c ? bVec5 : \
(maxTR > 4) ? eval("srchSuper_us.MAnalyse( isb=true, delta=5, "+MAnalyseString+MTString) : NOP()

bVec5 = bVec5c || !RefineMotion || !(maxTR > 4) ? bVec5 : eval("srchSuper_us.MRecalculate(bVec5, "+MRecalculateString+MTString)
bVec5 = bVec5c || !RefineMotion2 || !(maxTR > 4) ? bVec5 : eval("srchSuper_us.MRecalculate(bVec5, "+MRecalculateString2+MTString)


bVec4c = IsClip(bVec4)
bVec4 = bVec4c ? bVec4 : \
(maxTR > 3) ? eval("srchSuper_us.MAnalyse( isb=true, delta=4, "+MAnalyseString+MTString) : NOP()

bVec4 = bVec4c || !RefineMotion || !(maxTR > 3) ? bVec4 : eval("srchSuper_us.MRecalculate(bVec4, "+MRecalculateString+MTString)
bVec4 = bVec4c || !RefineMotion2 || !(maxTR > 3) ? bVec4 : eval("srchSuper_us.MRecalculate(bVec4, "+MRecalculateString2+MTString)


bVec3c = IsClip(bVec3)
bVec3 = bVec3c ? bVec3 : \
(maxTR > 2) ? eval("srchSuper_us.MAnalyse( isb=true, delta=3, "+MAnalyseString+MTString) : NOP()

bVec3 = bVec3c || !RefineMotion || !(maxTR > 2) ? bVec3 : eval("srchSuper_us.MRecalculate(bVec3, "+MRecalculateString+MTString)
bVec3 = bVec3c || !RefineMotion2 || !(maxTR > 2) ? bVec3 : eval("srchSuper_us.MRecalculate(bVec3, "+MRecalculateString2+MTString)


bVec2c = IsClip(bVec2)
bVec2 = bVec2c ? bVec2 : \
(maxTR > 1) ? eval("srchSuper_us.MAnalyse( isb=true, delta=2, "+MAnalyseString+MTString) : NOP()

bVec2 = bVec2c || !RefineMotion || !(maxTR > 1) ? bVec2 : eval("srchSuper_us.MRecalculate(bVec2, "+MRecalculateString+MTString)
bVec2 = bVec2c || !RefineMotion2 || !(maxTR > 1) ? bVec2 : eval("srchSuper_us.MRecalculate(bVec2, "+MRecalculateString2+MTString)


bVec1c = IsClip(bVec1)
bVec1 = bVec1c ? bVec1 : \
(maxTR > 0) ? eval("srchSuper_us.MAnalyse( isb=true, delta=1, "+MAnalyseString+MTString) : NOP()

bVec1 = bVec1c || !RefineMotion || !(maxTR > 0) ? bVec1 : eval("srchSuper_us.MRecalculate(bVec1, "+MRecalculateString+MTString)
bVec1 = bVec1c || !RefineMotion2 || !(maxTR > 0) ? bVec1 : eval("srchSuper_us.MRecalculate(bVec1, "+MRecalculateString2+MTString)


fVec1c = IsClip(fVec1)
fVec1 = fVec1c ? fVec1 : \
(maxTR > 0) ? eval("srchSuper_us.MAnalyse( isb=false, delta=1, "+MAnalyseString+MTString) : NOP()

fVec1 = fVec1c || !RefineMotion || !(maxTR > 0) ? fVec1 : eval("srchSuper_us.MRecalculate(fVec1, "+MRecalculateString+MTString)
fVec1 = fVec1c || !RefineMotion2 || !(maxTR > 0) ? fVec1 : eval("srchSuper_us.MRecalculate(fVec1, "+MRecalculateString2+MTString)


fVec2c = IsClip(fVec2)
fVec2 = fVec2c ? fVec2 : \
(maxTR > 1) ? eval("srchSuper_us.MAnalyse( isb=false, delta=2, "+MAnalyseString+MTString) : NOP()

fVec2 = fVec2c || !RefineMotion || !(maxTR > 1) ? fVec2 : eval("srchSuper_us.MRecalculate(fVec2, "+MRecalculateString+MTString)
fVec2 = fVec2c || !RefineMotion2 || !(maxTR > 1) ? fVec2 : eval("srchSuper_us.MRecalculate(fVec2, "+MRecalculateString2+MTString)


fVec3c = IsClip(fVec3)
fVec3 = fVec3c ? fVec3 : \
(maxTR > 2) ? eval("srchSuper_us.MAnalyse( isb=false, delta=3, "+MAnalyseString+MTString) : NOP()

fVec3 = fVec3c || !RefineMotion || !(maxTR > 2) ? fVec3 : eval("srchSuper_us.MRecalculate(fVec3, "+MRecalculateString+MTString)
fVec3 = fVec3c || !RefineMotion2 || !(maxTR > 2) ? fVec3 : eval("srchSuper_us.MRecalculate(fVec3, "+MRecalculateString2+MTString)


fVec4c = IsClip(fVec4)
fVec4 = fVec4c ? fVec4 : \
(maxTR > 3) ? eval("srchSuper_us.MAnalyse( isb=false, delta=4, "+MAnalyseString+MTString) : NOP()

fVec4 = fVec4c || !RefineMotion || !(maxTR > 3) ? fVec4 : eval("srchSuper_us.MRecalculate(fVec4, "+MRecalculateString+MTString)
fVec4 = fVec4c || !RefineMotion2 || !(maxTR > 3) ? fVec4 : eval("srchSuper_us.MRecalculate(fVec4, "+MRecalculateString2+MTString)


fVec5c = IsClip(fVec5)
fVec5 = fVec5c ? fVec5 : \
(maxTR > 4) ? eval("srchSuper_us.MAnalyse( isb=false, delta=5, "+MAnalyseString+MTString) : NOP()

fVec5 = fVec5c || !RefineMotion || !(maxTR > 4) ? fVec5 : eval("srchSuper_us.MRecalculate(fVec5, "+MRecalculateString+MTString)
fVec5 = fVec5c || !RefineMotion2 || !(maxTR > 4) ? fVec5 : eval("srchSuper_us.MRecalculate(fVec5, "+MRecalculateString2+MTString)


fVec6c = IsClip(fVec6)
fVec6 = fVec6c ? fVec6 : \
(maxTR > 5) ? eval("srchSuper_us.MAnalyse( isb=false, delta=6, "+MAnalyseString+MTString) : NOP()

fVec6 = fVec6c || !RefineMotion || !(maxTR > 5) ? fVec6 : eval("srchSuper_us.MRecalculate(fVec6, "+MRecalculateString+MTString)
fVec6 = fVec6c || !RefineMotion2 || !(maxTR > 5) ? fVec6 : eval("srchSuper_us.MRecalculate(fVec6, "+MRecalculateString2+MTString)


#if MEus - scale back MVs to required subpel
bVec1 = (MEus && maxTR > 0) ? MScaleVect(bVec1, scale=SubPel, mode=1, adjustSubPel=true) : bVec1
fVec1 = (MEus && maxTR > 0) ? MScaleVect(fVec1, scale=SubPel, mode=1, adjustSubPel=true) : fVec1
bVec2 = (MEus && maxTR > 1) ? MScaleVect(bVec2, scale=SubPel, mode=1, adjustSubPel=true) : bVec2
fVec2 = (MEus && maxTR > 1) ? MScaleVect(fVec2, scale=SubPel, mode=1, adjustSubPel=true) : fVec2
bVec3 = (MEus && maxTR > 2) ? MScaleVect(bVec3, scale=SubPel, mode=1, adjustSubPel=true) : bVec3
fVec3 = (MEus && maxTR > 2) ? MScaleVect(fVec3, scale=SubPel, mode=1, adjustSubPel=true) : fVec3
bVec4 = (MEus && maxTR > 3) ? MScaleVect(bVec4, scale=SubPel, mode=1, adjustSubPel=true) : bVec4
fVec4 = (MEus && maxTR > 3) ? MScaleVect(fVec4, scale=SubPel, mode=1, adjustSubPel=true) : fVec4
bVec5 = (MEus && maxTR > 4) ? MScaleVect(bVec5, scale=SubPel, mode=1, adjustSubPel=true) : bVec5
fVec5 = (MEus && maxTR > 4) ? MScaleVect(fVec5, scale=SubPel, mode=1, adjustSubPel=true) : fVec5
bVec6 = (MEus && maxTR > 5) ? MScaleVect(bVec6, scale=SubPel, mode=1, adjustSubPel=true) : bVec6
fVec6 = (MEus && maxTR > 5) ? MScaleVect(fVec6, scale=SubPel, mode=1, adjustSubPel=true) : fVec6

# Expose search clip, motion search super clip and motion vectors to calling script through globals
QTGMC_SetUserGlobal( GlobalNames, "srchClip", srchClip, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "srchSuper", srchSuper, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "srchSuper_us", srchSuper, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec1", bVec1, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec1", fVec1, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec2", bVec2, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec2", fVec2, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec3", bVec3, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec3", fVec3, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec4", bVec4, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec4", fVec4, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec5", bVec5, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec5", fVec5, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "bVec6", bVec6, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec6", fVec6, ReplaceGlobals )

[...]

stable = (TR2 == 0) ? addNoise1 : \
(TR2 == 1) ? eval("addNoise1.MDegrain1( stableSuper, bVec1,fVec1, thSAD=ThSAD2, thSCD1=ThSCD1,thSCD2=ThSCD2" + sMDegnl + ")") : \
(TR2 == 2) ? eval("addNoise1.MDegrain2( stableSuper, bVec1,fVec1, bVec2,fVec2, thSAD=ThSAD2, thSCD1=ThSCD1,thSCD2=ThSCD2" + sMDegnl + ")") : \
(TR2 == 3) ? eval("addNoise1.MDegrain3( stableSuper, bVec1,fVec1, bVec2,fVec2, bVec3,fVec3, thSAD=ThSAD2, thSCD1=ThSCD1,thSCD2=ThSCD2" + sMDegnl + ")") : \
(TR2 == 4) ? eval("addNoise1.MDegrain4( stableSuper, bVec1,fVec1, bVec2,fVec2, bVec3,fVec3, bVec4,fVec4, thSAD=ThSAD2, thSCD1=ThSCD1,thSCD2=ThSCD2" + sMDegnl + ")") : \
(TR2 == 5) ? eval("addNoise1.MDegrain5( stableSuper, bVec1,fVec1, bVec2,fVec2, bVec3,fVec3, bVec4,fVec4, bVec5,fVec5, thSAD=ThSAD2, thSCD1=ThSCD1,thSCD2=ThSCD2" + sMDegnl + ")") : \
eval("addNoise1.MDegrain6( stableSuper, bVec1,fVec1, bVec2,fVec2, bVec3,fVec3, bVec4,fVec4, bVec5,fVec5, bVec6,fVec6, thSAD=ThSAD2, thSCD1=ThSCD1,thSCD2=ThSCD2" + sMDegnl + ")")
stable = (TR2 > 0) ? lsb ? stable.ditherpost(mode=6, slice=false) : n16 ? stable.ConvertTo8bit(dither=1) : stable : stable


with (not rigorously tested, but produces identical result with QTGMC defaults):

bVecs = []
fVecs = []

for (i = 0, 5) {
bVecs = ArrayAdd(bVecs, QTGMC_GetUserGlobal( GlobalNames, "bVec" + string(i + 1), ReuseGlobals ) )
fVecs = ArrayAdd(fVecs, QTGMC_GetUserGlobal( GlobalNames, "fVec" + string(i + 1), ReuseGlobals ) )
}

[...]

for (i = 0, 5) {
if (!IsClip(bVecs[i])) {
if (maxTR > i) { bVecs = ArraySet(bVecs, eval(Format("srchSuper_us.MAnalyse( isb=true, delta={}, "+MAnalyseString+MTString, i + 1)), i) }
if (RefineMotion && maxTR > i) { bVecs = ArraySet(bVecs, eval("srchSuper_us.MRecalculate(bVecs[i], "+MRecalculateString+MTString), i) }
if (RefineMotion2 && maxTR > i) { bVecs = ArraySet(bVecs, eval("srchSuper_us.MRecalculate(bVecs[i], "+MRecalculateString2+MTString), i) }
}

if (!IsClip(fVecs[i])) {
if (maxTR > i) { fVecs = ArraySet(fVecs, eval(Format("srchSuper_us.MAnalyse( isb=false, delta={}, "+MAnalyseString+MTString, i + 1)), i) }
if (RefineMotion && maxTR > i) { fVecs = ArraySet(fVecs, eval("srchSuper_us.MRecalculate(bVecs[i], "+MRecalculateString+MTString), i) }
if (RefineMotion2 && maxTR > i) { fVecs = ArraySet(fVecs, eval("srchSuper_us.MRecalculate(bVecs[i], "+MRecalculateString2+MTString), i) }
}

if (MEus && maxTR > 0) {
bVecs = ArraySet(bVecs, MScaleVect(bVecs[i], scale=SubPel, mode=1, adjustSubPel=true), i)
fVecs = ArraySet(fVecs, MScaleVect(fVecs[i], scale=SubPel, mode=1, adjustSubPel=true), i)
}

QTGMC_SetUserGlobal( GlobalNames, "bVec" + string(i + 1), bVecs[i], ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "fVec" + string(i + 1), fVecs[i], ReplaceGlobals )
}


# Expose search clip, motion search super clip and motion vectors to calling script through globals
QTGMC_SetUserGlobal( GlobalNames, "srchClip", srchClip, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "srchSuper", srchSuper, ReplaceGlobals )
QTGMC_SetUserGlobal( GlobalNames, "srchSuper_us", srchSuper, ReplaceGlobals )

[...]

if (TR2 == 0) {
stable = addNoise1
} else {
vecs = ""
for (i = 0, TR2 - 1) {
vecs = vecs + Format("bVecs[{}],fVecs[{}], ", i, i)
}
stable = eval(Format("addNoise1.MDegrain{}( stableSuper, {} thSAD=ThSAD2, thSCD1=ThSCD1,thSCD2=ThSCD2{})", TR2, vecs, sMDegnl))
}

There's also another option for monkeying with syntax... (https://forum.doom9.org/showthread.php?t=183853)

MeteorRain
19th September 2026, 00:12
wonkey_monkey:

Yes, if people want to stay with AVS syntax and keep using eval for the work, that's their choice. But right now they don't have much choice beyond what AVS offers now.

shebang_mod was a good attempt. I spend a few hours brainstorming on how to improve AVS scripting such as allowing multi line code or adding hashmap. But after a few hours I figured it's probably easier to add a new binding to the eco system than to keep patching the existing script engine. You can still write your familiar AVS script, lib author can utilize more advanced language features, and both will work together. You can have QTGMC written in ruby and main script written in simple AVS format.

If you are interested to try out, here are the links
- Test binary: https://github.com/HomeOfAviSynthPlusEvolution/AviSynthGarnet/actions/runs/35403714101
- Examples: https://github.com/HomeOfAviSynthPlusEvolution/AviSynthGarnet/tree/master/examples

This is alpha-level early test build for evaluation purpose, not final working product.

MysteryX
19th September 2026, 09:10
Then there's the question: if you want to replace AviSynth syntax with Ruby syntax;

Does VapourSynth Python syntax do exactly what you want?

Do other AviSynth users see enough value or need in it?

What you're suggesting more looks like an alternative script parser, a replacement instead of enhancement.

MysteryX
19th September 2026, 09:39
What could be done: an alternative Ruby script parser that runs on top of AviSynth engine (or VapourSynth, could support both) that bypasses the standard script restrictions and calls the engine directly.

Decoupling the engine from the script syntax is a good thing.

MeteorRain
19th September 2026, 10:15
I don't like Python as a language, nor do I like to have a hard requirement of installing a Python environment.

mruby runs embedded, means it's just a binding that runs code.
I'm not forced to install full MRI ruby environment or full Gem eco system and stuff.
Put the dll in the autoload directory, then start writing ruby code, boom, it works. It's that easy. (Or integrate the module into AVS like what I do in AVSminus.)

What could be done: an alternative Ruby script parser that runs on top of AviSynth engine (or VapourSynth, could support both) that bypasses the standard script restrictions and calls the engine directly.

Decoupling the engine from the script syntax is a good thing.

That's exactly what I'm doing though? You can write libraries in ruby while still call those libraries in vanilla AVS script. Maybe see the examples that I shared? Or did I misunderstand what you said?

MeteorRain
19th September 2026, 10:35
Decoupling the engine [...] is a good thing.

Actually I have been doing this for a while now on the AVSminus project. I have already decoupled Converts, Resamplers, Expr, and many internal filters, into their standalone git repos. The core was packed with too much stuff in the tree that it's just too difficult to properly test it and manage it.

After decoupling them into individual repos I was able to cover most of the execution paths with unit and functional testing. Convert video has about 100 groups of tests covering about 4,000 paths, composition has about 60 groups of tests covering about 65,000 paths (about 1,600,000 data points validated), etc.

Once the core is properly trimmed down, then we can start working on the core part. Life will be much easier by then.

orchid
19th September 2026, 11:20
You're reinventing the wheel. Everything you've described is already how VapourSynth works.
The point of continuing to use AviSynth is the minimal syntax, if you want something more complex you might as well just use VapourSynth.

You say you don't like Python, but you can just make a Ruby binding for VapourSynth, which would be *much* less work than trying to implement custom binding support into AviSynth.
So I don't understand the point of this suggestion.

P.S.: Instead of reinventing your own OOP QTGMC as well, I already did that here (https://forum.doom9.org/showthread.php?t=187077).

DTL
19th September 2026, 11:47
Simplification of the kernel filtergraph and separation from core filters can help in the next step to performance at current end-users massive multicore host hardware - total and single CPU core caches optimized filterchains processing. It is a sort of smart-filter connection to form a sort of script-based auto-area shader (expanded pixel-shader) to be processed inside a single CPU core with much better memory subsystem performance. Are VapourSynth filters and filtergraph managers able to do it already ?

MeteorRain
19th September 2026, 13:42
You're reinventing the wheel. Everything you've described is already how VapourSynth works.
The point of continuing to use AviSynth is the minimal syntax, if you want something more complex you might as well just use VapourSynth.

You say you don't like Python, but you can just make a Ruby binding for VapourSynth, which would be *much* less work than trying to implement custom binding support into AviSynth.
So I don't understand the point of this suggestion.

P.S.: Instead of reinventing your own OOP QTGMC as well, I already did that here (https://forum.doom9.org/showthread.php?t=187077).

Of course I'm reinventing the wheel. VapourSynth is also reinventing the wheel, they could have just overhauled AVS and added a Python binding and call it a day, why bother?

Making ruby binding for VapourSynth would probably be just as much work as implementing ruby binding for AviSynth, same amount of work to integrate mruby, same API boundary to talk to the core. What makes the new binding much less work to add to VS than to AVS? I don't understand.

And I'm not reinventing my own OOP QTGMC, because my plan was to convert your python version of QTGMC into Ruby to begin with.

I like Ruby's philosophy, that in Ruby there's more than one way of doing the same thing.

Then the Zen of Python's principle, There should be one—and preferably only one—obvious way to do it.

Maybe you are just trying to say using VapourSynth is the only one way, and all other options are wrong and pointless?

Unfortunately I believe in people should have the freedom to do things however they want, and choose whatever tool they love to use.

orchid
19th September 2026, 14:43
Of course I'm reinventing the wheel. VapourSynth is also reinventing the wheel, they could have just overhauled AVS and added a Python binding and call it a day, why bother?


Well, for one, VapourSynth was designed from the ground up with multithreading, high bitdepth support, format extensibility, etc in mind. It was also released at a time where AviSynth MT was quite poor. A lot of the benefits VapourSynth has wouldn't have been possible without a complete break from the legacy ecosystem.

Making ruby binding for VapourSynth would probably be just as much work as implementing ruby binding for AviSynth, same amount of work to integrate mruby, same API boundary to talk to the core. What makes the new binding much less work to add to VS than to AVS? I don't understand.


My point is that VapourSynth already officially supports alternative bindings, and people have made their own bindings (TypeScript, Lua, Rust, etc.). Theres plenty of examples to reference and you don't need to make upstream changes.

Maybe you are just trying to say using VapourSynth is the only one way, and all other options are wrong and pointless?

Unfortunately I believe in people should have the freedom to do things however they want, and choose whatever tool they love to use.


You're free to do what you want, but I find it odd that you're taking the hard path when most of the work you want to do has already been done elsewhere—especially when what you're essentially suggesting here is "VapourSynth, but not VapourSynth". That's all.

Myrsloik
19th September 2026, 14:54
Of course I'm reinventing the wheel. VapourSynth is also reinventing the wheel, they could have just overhauled AVS and added a Python binding and call it a day, why bother?

Making ruby binding for VapourSynth would probably be just as much work as implementing ruby binding for AviSynth, same amount of work to integrate mruby, same API boundary to talk to the core. What makes the new binding much less work to add to VS than to AVS? I don't understand.


I didn't reinvent any wheels 15 or so years ago. I did start with AVS and removed all original code that would need to be rewritten to support new scripting features, stable multithreading, make it portable and clearly separate the core from the scripting language. After stripping that all I had left was a single malloc()/free() pair and a dream. Trying to decouple my decisions from the circumstances in which they were made is not something I appreciate.

Anyway, on to the main observations I've made in regards to this:


VS did have multiple additional scripting language bindings developed. Specifically LUA and Javascript. None of them ever caught on because python is good enough. Python is arguably a bit nicer for loops, functions and such than avs script but you'll probably encounter the same resistance.
Users have to learn a new language. Ruby is more or less extinct if you look at language usage statistics. If I had to go and do things again and python wasn't a thing I'd probably go with modern javascript (not necessarily nodejs as well).
The general inertia. Unless your goal is to make Avs++ instead of Avs- everyone will still write scripts to the lowest common denominator. I personally don't care if you try to fragment the "Avs community" or similar. We've all tried to do that, right? The problem is having something compelling enough that it actually gets used.

real.finder
19th September 2026, 19:20
I don't like Python as a language, nor do I like to have a hard requirement of installing a Python environment.


I am with MeteorRain here

and AviSynth+ is so advance now thanks to pinterf, Asd-g and others!

but I dont mind adding optional more advanced things like the topic of this Thread

BTW MeteorRain, maybe you will be interested in https://forum.doom9.org/showthread.php?p=1822291#post1822291 too

anyway, I dont think anyone will mind adding new things as long as it wont affect the encoding script (*.avs), aka the simplicity of usage for the production

MeteorRain
19th September 2026, 23:15
VS did have multiple additional scripting language bindings developed. Specifically LUA and Javascript. None of them ever caught on because python is good enough. Python is arguably a bit nicer for loops, functions and such than avs script but you'll probably encounter the same resistance.
Users have to learn a new language. Ruby is more or less extinct if you look at language usage statistics. If I had to go and do things again and python wasn't a thing I'd probably go with modern javascript (not necessarily nodejs as well).
The general inertia. Unless your goal is to make Avs++ instead of Avs- everyone will still write scripts to the lowest common denominator. I personally don't care if you try to fragment the "Avs community" or similar. We've all tried to do that, right? The problem is having something compelling enough that it actually gets used.


Again, it's additional binding. I already made it a plugin so no one is forced to learn anything new to live with it. The same ruby plugin works fine with AVS+, anyone who's staying with AVS+ can just download the dll, put it into autoload directory, and start using libraries in Ruby. And no users are forced to learn a new language. Only people who are willing to write filters and scripts in Ruby can learn it, or not if they aren't, they can still write everything in AVS script. No one stops them from using anything they like.

Besides, AVS- maintains full compatibility with AVS+, so no fragmentation is being created, not like when VS was released and I had to port a few plugins to VS back then. Besides that, I spent time making DualSynth framework to try to defragment the eco system between AVS and VS.

I can strongly feel it that you folks are trying to apply the Zen of Python here, that things must be done in one preferred way. To you, adding Ruby binding to AVS means user must do everything in Ruby, creating AVS- means user must pick a brand new eco system. None of that is true in this case.

MeteorRain
19th September 2026, 23:39
Well, for one, VapourSynth was designed from the ground up with [...]
That's still reinventing wheels, all depending on how much improvement counts as reinventing wheels. VS made aluminum alloy and rubber wheels replacing AVS OG wood wheels, we can still say it's reinventing wheels.

Actually, the proper term here should be remanufacturing the wheels. I'm not reinventing the tech, I'm reusing the tech and adding a different wheel on it.

My point is that VapourSynth already officially supports alternative bindings, and people have made their own bindings (TypeScript, Lua, Rust, etc.). Theres plenty of examples to reference and you don't need to make upstream changes.
I'm not making much upstream changes either. Most things that need to be changed are bugs that were not exposed before, and they need to be fixed anyway. Other than that, no upstream change is needed.

You're free to do what you want, but I find it odd that you're taking the hard path when most of the work you want to do has already been done elsewhere—especially when what you're essentially suggesting here is "VapourSynth, but not VapourSynth". That's all.
I'm fully aware of that. I worked on VS eco system since it came out years ago, I was one of the founder of HomeOfVapourSynthEvolution project that worked on porting AVS plugins to VS. That wouldn't stop me from creating new stuff in AVS eco system.

Let me ask one question: Can you use VapourSynth without installing Python?
This project allows you to use AVS with Ruby binding without installing Ruby. This alone makes it different to VS.

real.finder
20th September 2026, 04:43
BTW MeteorRain, maybe you will be interested in https://forum.doom9.org/showthread.php?p=1822291#post1822291 too

btw, VapourSynth use "plugin.function ()" so if you support it inside the Ruby I think that dont need any change

however, I think in avs syntax that will not work, so you can only support it inside Ruby or it has to be "vs_plugin_function ()" for the avs/avsi

also from what I can see, you have to use "ImportRuby()"?

can that be autoloaded with prefix "rb_" so it can be used as rb_qtgmc() in *.avs production/encoding script?

ChaosKing
20th September 2026, 10:59
Another advantage of Python is its prominent role in scientific research. You have tons of algorithms and libs, just add a vs-wrapper and you're good to go.

You can add Ruby support. However, I don't think this will gain significant traction in the Avisynth community.

MeteorRain
20th September 2026, 12:04
btw, VapourSynth use "plugin.function ()" so if you support it inside the Ruby I think that dont need any change

however, I think in avs syntax that will not work, so you can only support it inside Ruby or it has to be "vs_plugin_function ()" for the avs/avsi

also from what I can see, you have to use "ImportRuby()"?

can that be autoloaded with prefix "rb_" so it can be used as rb_qtgmc() in *.avs production/encoding script?

I'm evaluating as I'm working on DS2 project in parallel. Adding a shim for VS plugins might be possible, if all functionalities can be translated to AVS equivalent. GPU support is lacking, but most plugins should work to some extent.

For AVS supporting namespaced plugin we can always come up some tricks, like prefixing functions with namespaces etc.

As for ImportRuby, we can do autoload as well. Above is only a proof of concept, we can drive it forward with improvements.

orchid
20th September 2026, 19:51
Let me ask one question: Can you use VapourSynth without installing Python?

AFAIK, yes. Not through the official installer though, as Python has more or less been adopted as the "official" scripting language.

Anyway, a bit off-topic perhaps, but:
And I'm not reinventing my own OOP QTGMC, because my plan was to convert your python version of QTGMC into Ruby to begin with.


Keep in mind AVS will need plugin-side bug fixes & features for this to be possible (at least, without making significant logic changes). AVS mvtools is missing proper fp32 support & weight support in degrain. I also had to fix a bug in AddGrain for correct fp32 "Generate" NoiseDeint. Probably many other things I don't remember.

Happy to answer any questions regarding implementation details on Discord if need be.

That said, I received significant pushback on release despite the massive amount of fixes & improvements. AVS users are even less subject to change, so I can't imagine the response will be positive for you either. Good luck.

DTL
20th September 2026, 20:29
It looks like the float samples format is no one uses with mvtools (and qtgmc) because it is slow but makes about zero quality addition. Maybe someone wants to use a linear HDR workflow and needs to process in float32 precision.

For performance of motion estimation it may be better to use hardware motion estimation engines from accelerators. But it may make filter OS or API or vendor dependent and some filters developers not like this. The quality of hardware ME engines may be not great but performance at good quality is much better. The overlap modes for block artifacts removal can be interpolated from non-overlapped hardware ME output easily enough and makes good quality.

Interpolation of MVs overlap may be extracted from MDegrainN implementation to separate filter to be added to any script. Non overlapped MAnalyse runs also onCPU significantly faster. Also good quality/performance balance achieved with diagonal 2x overlap faster in comparison with full 4x overlap only supported by MAnalyse in current versions (though 2x diagonal overlapped scan can be also easily added to MAnalyse too).

MeteorRain
20th September 2026, 22:33
Keep in mind AVS will need plugin-side bug fixes & features for this to be possible (at least, without making significant logic changes). AVS mvtools is missing proper fp32 support & weight support in degrain. I also had to fix a bug in AddGrain for correct fp32 "Generate" NoiseDeint. Probably many other things I don't remember.

Happy to answer any questions regarding implementation details on Discord if need be.


Appreciate the help!

Porting plugin is also on the plan. I would imagine at least mv+fft+deband will need to be ported or changed to fit the modern days. That's already on my plan.


That said, I received significant pushback on release despite the massive amount of fixes & improvements. AVS users are even less subject to change, so I can't imagine the response will be positive for you either. Good luck.

Well I'm not pushing anyone to adopt to new tech. As I mentioned earlier, I'm providing alternative approach for users. Users can stay with Windows XP and old stuff if they want, but anyone is free to upgrade to modern stuff because the option is provided now.

MeteorRain
21st September 2026, 06:44
https://github.com/HomeOfAviSynthPlusEvolution/AviSynthGarnet/actions/runs/35564348273

Now supporting polyglot single file mode. Put `ruby __END__` at the top to switch to ruby syntax parsing for *.avs file.

example.avs

ruby __END__

AVS.lwlibavvideosource('1.ts')
.lanczosresize(1280,720)


Also added autoloading of *.avs.rb, but it's also possible to write ruby code within *.avsi using above trick.