Log in

View Full Version : VHS post processing


Pages : [1] 2

Busty
12th May 2025, 20:41
Hey fellow doomers,

after working out my capture workflow, I'm on a quest to process my VHS captures for better viewing pleasure without loosing details. I learned I need to adress chroma noise and chroma shift, and would love to get rid of dots and horizontal lines. Noise reduction is something I'd like to apply at a minimum, as I don't want to lose detail.

There are lots of discussions and examples on this forum and I found really helpful posts with example scripts that I tried.

ATM, I would like to combine a

- script adressing chroma noise with CNR2 and a degrain function

- with a script including a function for chromashift

- and a script adressing white dots with Despot / depan


Now, I'm doing mostly copy and paste here without thoroughly understanding avisynth's syntax. (I did read about avisynth grammar
and can accomplish some easy scripts, but I still struggle to understand, let alone write advanced scripts)

So, I'm posting three scripts that work separately but not when I try to combine them. May I ask for assistance in combining them?
That would be awesome!

I managed to combine scripts 1 and 2, but when I try to put in script 3, I get an error: invalid argument: converttoYV12


script 1:

#JohnMeyer's Denoiser script for interlaced video using MDegrain2
#This is my recommended starting point script for VHS as of April, 2012

SetMemoryMax(768)

Loadplugin("C:\Program Files\AviSynth\plugins\mvtools2.dll")
LoadPlugin("c:\Program Files\AviSynth\plugins\Cnr2.dll")
#loadplugin("c:\Program Files\AviSynth\plugins\despot.dll")
#Loadplugin("C:\Program Files\AviSynth\plugins\removegrain.dll")
Import("C:\Program Files\AviSynth\plugins\LimitedSharpenFaster.avsi")

SetMTMode(5,4)
#Modify this line to point to your video file
source=AVISource("C:\VHS.avi").killaudio().AssumeTFF()
SetMTMode(2)

#Only use chroma restoration for analog source material
chroma=source.Cnr2("oxx",8,16,191,100,255,32,255,false) #VHS
#chroma=source.Cnr2("oxx",8,14,191,75,255,20,255,false) #Laserdisc
#Set overlap in line below to 0, 2, 4, 8. Higher number=better, but slower
#For VHS, 4,0 seems to work better than 8,2. Most of difference is in shadows
#However, 8,0 is good enough and MUCH faster. 8,2 doesn't seem to make much difference on VHS.

#output=MDegrain2i2(chroma,8,0,0)
output=MDegrain2i2(chroma,8,4,0) #Better, but slower

#output=IResize(output,720,480)

#stackvertical(source,output)
#stackhorizontal(source,output)
#return output.Levels(16, 1, 235, 0, 255, coring=false)

return output


#-------------------------------

function MDegrain2i2(clip source, int "blksize", int "overlap", int "dct")
{
Vshift=0 # 2 lines per bobbed-field per tape generation (PAL); original=2; copy=4 etc
Hshift=0 # determine experimentally
overlap=default(overlap,0) # overlap value (0 to 4 for blksize=8)
dct=default(dct,0) # use dct=1 for clip with light flicker

fields=source.SeparateFields() # separate by fields

#This line gets rid of chroma halo
fields=MergeChroma(fields,crop(fields,Hshift,Vshift,0,0).addborders(0,0,Hshift,Vshift))
#This line will shift chroma down and to the right instead of up and to the left
#fields=MergeChroma(fields,Crop(AddBorders(fields,Hshift,Vshift,0,0),0,0,-Hshift,-Vshift))

super = fields.MSuper(pel=2, sharp=1)
backward_vec2 = super.MAnalyse(isb = true, delta = 2, blksize=blksize, overlap=overlap, dct=dct)
forward_vec2 = super.MAnalyse(isb = false, delta = 2, blksize=blksize, overlap=overlap, dct=dct)
backward_vec4 = super.MAnalyse(isb = true, delta = 4, blksize=blksize, overlap=overlap, dct=dct)
forward_vec4 = super.MAnalyse(isb = false, delta = 4, blksize=blksize, overlap=overlap, dct=dct)

#Increasing thSAD doesn't seem to help
# MDegrain2(fields,super, backward_vec2,forward_vec2,backward_vec4,forward_vec4,thSAD=400)

# Eliminate next three lines and uncomment above line to use MDegrain2 instead of MDegrain3
backward_vec6 = super.MAnalyse(isb = true, delta = 6, blksize=blksize, overlap=overlap, dct=dct)
forward_vec6 = super.MAnalyse(isb = false, delta = 6, blksize=blksize, overlap=overlap, dct=dct)
MDegrain3(fields,super, backward_vec2,forward_vec2,backward_vec4,forward_vec4,backward_vec6,forward_vec6,thSCD1=400,thSAD=300)

#UnsharpMask( clip , int "strength" , int "radius" , int "threshold" )
#strength: strength. The default is 64.
#radius: the scope of the blurring process. The default is 3.
#threshold: threshold. Absolute value of the processing component is greater than the threshold blur. The default is 8.

unsharpmask(60,3,0) #not sure whether to put this before or after the weave.

#This function is unstable under SetMTMode
#limitedSharpenFaster(smode=1,strength=160,overshoot=50,radius=2, ss_X=1.5, SS_Y=1.5,dest_x=720,dest_y=480)
#LimitedSharpenFaster(strength=150) #Default strength=150

Weave()
}



script 2:

#Function FixChromaBleeding (clip input) {

# prepare to work on the V channel and reduce to speed up and filter noise
area = input.tweak(sat=4.0).VtoY.ReduceBy2

# select and normalize both extremes of the scale
red = area.Levels(255,1.0,255,255,0)
blue = area.Levels(0,1.0,0,0,255)

# merge both masks
mask = MergeLuma(red, blue, 0.5).Levels(250,1.0,250,255,0)

# expand to cover beyond the bleeding areas and shift to compensate the resizing
mask = mask.ConvertToRGB32.GeneralConvolution(0,"0 0 0 0 0 1 1 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0").ConvertToYV12

# back to full size and binarize (also a trick to expand)
mask = mask.BilinearResize(Width(input),Height(input)).Levels(10,1.0,10,0,255)

# prepare a version of the image that has its chroma shifted and less saturated
input_c = input.ChromaShift(C=-4).tweak(sat=0.8)

# combine both images using the mask
return input.overlay(input_c,mask=mask,mode="blend",opacity=1)
}



script 3:

# removal of white dots


LoadPlugin("C:\Program Files\AviSynth\plugins\depan.dll")
LoadPlugin("C:\Program Files\AviSynth\plugins\despot.dll")

AVISource("C:\VHS.avi").AssumeTFF

i = ConvertToYV12()
d = DePanEstimate(i, trust=3)
DePanInterleave(i, data=d)
DeSpot(p1=90, p2=15, pwidth=200, pheight=2, mthres=20, motpn=true, dilate=1, seg=1, sign=-1, maxpts=10)
SelectEvery(3, 1)

VideoMilk78
12th May 2025, 21:18
Maybe because you're not converting anything to yv12?

Busty
12th May 2025, 22:38
your comment gave me a hint what to take a look at, thanks.

It seems that the function MDegrain2i2 needs the word "source=" in front of the input video to operate.

The conversion to YV12 works when I delete the word "source=" in front of my video path. However, then I get an error message stating "I don't know what 'source' means".

So, if I want the output of DeSpot to be processed by MDegrain2i2, what phrase would I need to put after the processing steps of DeSpot?

VideoMilk78
12th May 2025, 23:31
You then need to define the despotted clip then you can use than clip and run it through MDegrain2i2.
I don't like to give paste in solutions to beginners as I find that doesn't really help, often it only creates more error... This is what helped me learn

Busty
13th May 2025, 23:42
I do understand that concept of learning, I appreciate your input, but I really try to find out things myself before I post questions like this. A lot of the times I find a solution. With this one however, I'm stuck.

I guess I can enter a line "return output" after DeSpot, but MDegrain212 needs a defined source, so I think I would need a name of a clip. If I enter the name of the source clip; I fear MDegrain2i2 would process the original file, not the one processed by DeSpot. I cannot find a hint how to define a source that was created within the script.

DTL
14th May 2025, 05:04
In your script you use the very old denoise functions like from the beginning of mvtools in end of 200x. Mdegrain2i2 is sort of simple example how to use mvtools for motion-compensated denoising of interlaced clips. It is now about 0.2 century old and outdated.
To the end of the active AVS+ usage at the beginning of 202x there were developed most advanced denoisers used mvtools engine like SMDegrain on mvtools-2.7.45 and also some alternative version of mvtools branched from 2.7.45 with more advanced MAnalyse and MVs processing-refining mostly described in https://forum.doom9.org/showthread.php?t=183517 . Some practical usage you can see in M_QTGMC script. General ideas on better MVs at denoising:
1. Use multi-generation MVs refining with dual-input MAnalyse where one source is always input full-noised and second input is previous generation processed clip (by MDegrain or any other denoise engine)
2. Use voting for best MV (some averaging of 3-D objects like MV of dx,dy and dissimilarity metric members) from several MVs sources - like make sevral different denoise examples (like BM3D and KNL and MDegrain) and send to next generation MAnalyse and feed MAnalyse output to MVs voting engine (MAverage()) to select most probable MV for next generation of denoise. Also possibe at block-level in MAnalyse for Area analysis of some surrounding of current position of block in blocks tesselation grid and also processing of several motion outputs with averaging engine.
3. Use multi-level MVs search with refining from larger block size (more stable with low quality source) to smaller (better tracking of small objects and complex transforms).
4. Use several different denoise engines outputs in the engine for select the most different samples values from smoothed version (anti-detail loss method). This may make denoise quality somehow worse but save more from blurring.
5. Use MDegrainN for larger tr-values with more shorter scripting (or newer features).
6. Use 'prefiltering' as some pre-processing of input source before feeding to MAnalyse engine (one of its inputs). For prefiltering any method may be used like simple more or less blur or any complex pre-denoiser engine (like BM3D or KNL). In the mvtools-only multi-generation MVs refining the previous generation denoise output used as 'prefiltered' for next generation MAnalyse (one of inputs).

This may make quality of MVs somehow better and lower blurring of output result by MDegrain because of bad blends from bad MVs. At the low quality sources and fast motion this may make some better quality denoise.

Emulgator
14th May 2025, 08:15
script 1:
return output
This forces output from here, anything down from here is never called.
Comment that out.
# return output

script 3:
AVISource("C:\VHS.avi").AssumeTFF
This envokes script 3 on untouched source.
Comment this line out:
#AVISource("C:\VHS.avi").AssumeTFF
Then this script will work on implicit last,
meaning the output from previous script 1.

script 2 (this is actually a function, but you never call it):
#Function FixChromaBleeding (clip input) {
The # comments the function out.
Remove the #
Function FixChromaBleeding (clip input) {
and now you may call the function from script 1 or the last script.
---------------
If such processing gives a good result after these corrections I can not tell, would need a sample of your source please.
And denoising: as DTL mentions, there is newer and better. You may try the following and lower tr as you see fit
SMDegrain(tr=6, thSAD=400, RefineMotion=true, contrasharp=true, interlaced=true, plane=4, prefilter=8, chroma=true, Show=false) #5: DFTTest, 6: KNLMeansCL, 7: DGDenoise, 8: BM3D
Or even better: lose interlaced, use QTGMCp and bob up to 50p/59.94p.
This denoises internally already. If this is not sufficient enough, then afterwards
SMDegrain(tr=6, thSAD=400, RefineMotion=true, contrasharp=true, interlaced=false, plane=4, prefilter=8, chroma=true, Show=false) #5: DFTTest, 6: KNLMeansCL, 7: DGDenoise, 8: BM3D

Busty
17th May 2025, 23:02
@ DTL:
thanks for the explanation. When reading different threads, it's a bit complicated to determine which denoising methods are superior, so It's nice to have it summed up.
Having installed avisynth and virtualdub via PlayOnMac, I always thought I was stuck with avisynth 2.6, but after your comment I thought I'd have a go at installing avisynth+ 3.7.5 and it installed like a breeze. So I have a whole new world of tools available. I will try some things in the coming week and report back.

Busty
17th May 2025, 23:09
@ Emulgator:

that's a great explanation of my script errors, highly appreciated, I learned something:-)

Still the same error remains: ConverttoYV12: invalid argument

or, when I remove "source" in front of my input video line; i get "I don't know what 'source' is"

This might be obsolete when I try yours and DTL's suggestions, but I'd still like to understand what is the cause of and solution to it.

Emulgator
17th May 2025, 23:23
Please post your recent script you are working from.

Busty
18th May 2025, 18:37
gladly, it's just a combination of what I posted above with your suggestions edited in:

#JohnMeyer's Denoiser script for interlaced video using MDegrain2
#This is my recommended starting point script for VHS as of April, 2012

SetMemoryMax(768)

Loadplugin("C:\Program Files\AviSynth\plugins\mvtools2.dll")
LoadPlugin("c:\Program Files\AviSynth\plugins\Cnr2.dll")
#loadplugin("c:\Program Files\AviSynth\plugins\despot.dll")
#Loadplugin("C:\Program Files\AviSynth\plugins\removegrain.dll")
Import("C:\Program Files\AviSynth\plugins\LimitedSharpenFaster.avsi")

SetMTMode(5,4)
#Modify this line to point to your video file
source=AVISource("C:\VHS.avi").killaudio().AssumeTFF()
SetMTMode(2)

#Only use chroma restoration for analog source material
chroma=source.Cnr2("oxx",8,16,191,100,255,32,255,false) #VHS
#chroma=source.Cnr2("oxx",8,14,191,75,255,20,255,false) #Laserdisc
#Set overlap in line below to 0, 2, 4, 8. Higher number=better, but slower
#For VHS, 4,0 seems to work better than 8,2. Most of difference is in shadows
#However, 8,0 is good enough and MUCH faster. 8,2 doesn't seem to make much difference on VHS.

#output=MDegrain2i2(chroma,8,0,0)
output=MDegrain2i2(chroma,8,4,0) #Better, but slower

#output=IResize(output,720,480)

#stackvertical(source,output)
#stackhorizontal(source,output)
#return output.Levels(16, 1, 235, 0, 255, coring=false)

#return output


#-------------------------------

function MDegrain2i2(clip source, int "blksize", int "overlap", int "dct")
{
Vshift=0 # 2 lines per bobbed-field per tape generation (PAL); original=2; copy=4 etc
Hshift=0 # determine experimentally
overlap=default(overlap,0) # overlap value (0 to 4 for blksize=8)
dct=default(dct,0) # use dct=1 for clip with light flicker

fields=source.SeparateFields() # separate by fields

#This line gets rid of chroma halo
fields=MergeChroma(fields,crop(fields,Hshift,Vshift,0,0).addborders(0,0,Hshift,Vshift))
#This line will shift chroma down and to the right instead of up and to the left
#fields=MergeChroma(fields,Crop(AddBorders(fields,Hshift,Vshift,0,0),0,0,-Hshift,-Vshift))

super = fields.MSuper(pel=2, sharp=1)
backward_vec2 = super.MAnalyse(isb = true, delta = 2, blksize=blksize, overlap=overlap, dct=dct)
forward_vec2 = super.MAnalyse(isb = false, delta = 2, blksize=blksize, overlap=overlap, dct=dct)
backward_vec4 = super.MAnalyse(isb = true, delta = 4, blksize=blksize, overlap=overlap, dct=dct)
forward_vec4 = super.MAnalyse(isb = false, delta = 4, blksize=blksize, overlap=overlap, dct=dct)

#Increasing thSAD doesn't seem to help
# MDegrain2(fields,super, backward_vec2,forward_vec2,backward_vec4,forward_vec4,thSAD=400)

# Eliminate next three lines and uncomment above line to use MDegrain2 instead of MDegrain3
backward_vec6 = super.MAnalyse(isb = true, delta = 6, blksize=blksize, overlap=overlap, dct=dct)
forward_vec6 = super.MAnalyse(isb = false, delta = 6, blksize=blksize, overlap=overlap, dct=dct)
MDegrain3(fields,super, backward_vec2,forward_vec2,backward_vec4,forward_vec4,backward_vec6,forward_vec6,thSCD1=400,thSAD=300)

#UnsharpMask( clip , int "strength" , int "radius" , int "threshold" )
#strength: strength. The default is 64.
#radius: the scope of the blurring process. The default is 3.
#threshold: threshold. Absolute value of the processing component is greater than the threshold blur. The default is 8.

unsharpmask(60,3,0) #not sure whether to put this before or after the weave.

#This function is unstable under SetMTMode
#limitedSharpenFaster(smode=1,strength=160,overshoot=50,radius=2, ss_X=1.5, SS_Y=1.5,dest_x=720,dest_y=480)
#LimitedSharpenFaster(strength=150) #Default strength=150

Weave()
}



Function FixChromaBleeding (clip input) {

# prepare to work on the V channel and reduce to speed up and filter noise
area = input.tweak(sat=4.0).VtoY.ReduceBy2

# select and normalize both extremes of the scale
red = area.Levels(255,1.0,255,255,0)
blue = area.Levels(0,1.0,0,0,255)

# merge both masks
mask = MergeLuma(red, blue, 0.5).Levels(250,1.0,250,255,0)

# expand to cover beyond the bleeding areas and shift to compensate the resizing
mask = mask.ConvertToRGB32.GeneralConvolution(0,"0 0 0 0 0 1 1 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0").ConvertToYV12

# back to full size and binarize (also a trick to expand)
mask = mask.BilinearResize(Width(input),Height(input)).Levels(10,1.0,10,0,255)

# prepare a version of the image that has its chroma shifted and less saturated
input_c = input.ChromaShift(C=-4).tweak(sat=0.8)

# combine both images using the mask
return input.overlay(input_c,mask=mask,mode="blend",opacity=1)
}



# removal of white dots


LoadPlugin("C:\Program Files\AviSynth\plugins\depan.dll")
LoadPlugin("C:\Program Files\AviSynth\plugins\despot.dll")

i = ConvertToYV12()
d = DePanEstimate(i, trust=3)
DePanInterleave(i, data=d)
DeSpot(p1=90, p2=15, pwidth=200, pheight=2, mthres=20, motpn=true, dilate=1, seg=1, sign=-1, maxpts=10)
SelectEvery(3, 1)

johnmeyer
18th May 2025, 19:54
I didn't remember posting my original denoising script with all of my alternative ideas still intact. It makes if pretty confusing.

If you just want to denoise, don't forget to uncomment "# return output". You did that in post #1, but you have to remove the # in order to get the script to do anything.

As someone already suggested, you may need to do a colorspace conversion in the initial line:

source=AVISource("C:\VHS.avi").killaudio().AssumeTFF().ConvertToYV12()

Make sure to change the "AssumeTFF()" to "AssumeBFF()" if your interlaced video is bottom field first.

My ancient script uses an old version of multi-threading ("SetMTMode"). This probably won't work if you are using a reasonably modern version of AVISynth. Until you get the script working I would suggest you simply delete those two SetMTMode lines.

The white dot removal section should be moved up to the end of my script (right after the "#return output" line). If you want it to do anything, keep the comment symbol (#) in front of "return output" and then feed "output" to the first line in the despotting section, e.g., i = output.ConvertToYV12()Since, if you do what I suggest above, you have already done that color conversion, you can simply write:

i = output

Busty
18th May 2025, 23:34
Thank you John, that did it. The script opens. Now I can play with it and tweak it.

Just to understand it: Should something be different when I add "return output" at the very end of the script? I had the impression that the removal of white dots didn't work when I added that line.

I'd love to try SMDeGrain or KNLMeansCL or BM3D, but I still can't get any of those to work. Avisynth+ didn't help there.

KNLMeansCL gives me an "unrecognized system exception".

BM3DCUDA claims "module not found", which I think refers to CUDA CUDA seems to be unsupported by my Graphics Card (Nvidia Geforce 8800 GT).

SMDegrain won't open because of an exe error, which might be a 32bit / 64bit mismatch between app and plugin. Is the latest version of SMDeGrain 64bit, and if so, is there a 32bit version available?

DTL
19th May 2025, 06:21
SMDegrain is only a script but used lots of plugins (as pre-filters or masking and more) and you need to get all used plugins in 32bit versions (or set lowest settings to use a few plugins). Better to use 64bit processing software. Try to get all required plugins as 64bit builds.

Busty
19th May 2025, 14:07
it seems to be SMDegrain itself that does not work for me. I removed all plugins from the plugin folder but SMDegrain and still got this error.

I tried the current version 4.7.0d and version 1.8d, which were the only ones I could find, they both return this error.

My installation of Avisynth can load MVTools2, Dither and MaskTools, other dependencies are listed as optional.

johnmeyer
19th May 2025, 15:53
The script holds intermediate steps in variables, like "source," "chroma," and "output." At the end of the script you have to "return" one of those variables. This is usually the result of the last variable assignment which, in my original script that ends with "return output," is the variable "output." Whatever is contained in that variable is the video that your script produces.

Busty
19th May 2025, 17:21
The script holds intermediate steps in variables, like "source," "chroma," and "output." At the end of the script you have to "return" one of those variables. This is usually the result of the last variable assignment which, in my original script that ends with "return output," is the variable "output." Whatever is contained in that variable is the video that your script produces.

I see, so when I add "return output" at the end of that script, it gives me the result of MDegrain2i2, because that's where the term output is defined.

Thanks for explaining, I appreciate it.

johnmeyer
19th May 2025, 18:07
I see, so when I add "return output" at the end of that script, it gives me the result of MDegrain2i2, because that's where the term output is defined. Yes, and that's true even if there are other operations further down in the script. Those will get ignored.

Busty
28th May 2025, 14:07
I mainly used your (johnmeyer's) script as a base because I wanted to adress chroma noise and chroma shift, the denoising part is mostly a bonus. (Maybe I'll replace that part at some point as suggested)

However, I still notice some double edges, which I guess is either chroma bleeding or halo or something like that.

I upload a 20 sec sample as an example what I'm dealing with. Have a look at the face in frame 456 for example, on the forehead there is a red doubling to the right. I tried to shift it with chromashift which resulted in the same thing, only on the left side.
Or on frame 578 the red is still right of the drummers arm, and when I shift that , like 6 pixel to the left, I see it appearing left from the singer's head.

There's also a similar effect visible on the TV logo, is that the same issue? What exactly am I looking at, e.g. what would you call this issue?

Can this script be tweaked to adress this (on the halo line) or would I need to insert something else?

Edit: Sample upload needs to wait,I get an error from the site (security token missing) and guess I need to wait for an admin to answer...

Busty
2nd June 2025, 17:15
ok, here are two files, one is an unalterered 20 sec video and one is the output of the script. Any tips how to get rid of the chroma bleed - or shift?

https://limewire.com/?referrer=pq7i8xx7p2

johnmeyer
2nd June 2025, 19:39
Your link didn't take me to a file and instead just took me to the file sharing site.

Therefore, I don't know what your chroma problem looks like. The CNR function is for chroma shimmering, not chroma shift. If you have a colored halo which follows the outline of an object at the transition between light and dark, you need to set a non-zero value for HShift and/or VShift (found in the MDegrain2 function). In the last code you posted, you had enabled the feature but still had Hshift set to 0. Try 1, 2, or 3 and see what you get. If you set it too high, you'll create halos on the other side. I don't think negative values are needed for VHS, but I haven't used it for a long time, so I can't say for sure.

As my comment in the code you posted said, you need to "determine experimentally" (i.e., trial and error) to see what value works for your clip.

Hshift=0 # determine experimentally

Busty
2nd June 2025, 22:03
ok, great, I'll try that.

my bad concerning the links. For me, it points to the site showing both links, but here are the actual links to the files:

https://limewire.com/d/ijXMk#B1VXd3KPAJ
https://limewire.com/d/eGm8J#xgqeAYGxAZ

Busty
3rd June 2025, 14:20
I tested changing the shift values and have the impression that a vertical and horizontal shift of 2 yields the most appropriate result for this video.

I *think* it looks better. There were some greenish lines at the top before shifting, afterwards they are at the bottom and on the right side. (I guess I can make them disappear if I crop the fuzzy edges after shifting, I'll try that with a future capture)

However, there is still some halo or something similar around the edges, just not as vibrant as before. Can these be adressed somehow?

https://limewire.com/d/gnkVw#2kCAj6IaY2

Edit: let me know if I should use a different upload service.

Emulgator
4th June 2025, 00:07
mediainfo says about the source 770x574x25.
Since this had been a PAL broadcast any "untouched" capture should return PAL 720x576x25i.
Before the search for those missing lines ends in vain: How did you obtain that capture ?
Any uneven cropping before your desired processing destroys the hints your next algos have to rely on.
Cropping should be applied later.

Busty
4th June 2025, 22:15
Right. Thanks for looking at my files.

With that capture, I made a mistake while cropping and padding the edges. My capture card (MiroMotion DC30+) creates 768 x 576 square pixel and I wanted to get rid of the fuzzy edges, I just messed up the padding part. With "unaltered" I meant that it's not compressed or converted.

I see your point and I will move the crop/pad part to the end of my processing chain. Thanks for pointing that out.

In fact I captured that video again:

https://limewire.com/d/8b7Y8#A4tTwZhxaW (unchanged)
https://limewire.com/d/4fouD#Olhfo0Ok7S (run through script)

I noticed a part of the video where the chroma shift is way worse than before, look at the drum sticks and the red near it:

https://limewire.com/d/5Dj4S#INxuVeq1UA (unchanged)
https://limewire.com/d/tgI65#VAp0kZ6N93 (run through script)

Is this chroma processing using temporal information? If so, this video might have an issue. I don't know why, but there is something going on with the field order. After processing, when I play deinterlaced with yadif2, playback is stuttering. I can repair that, but if chroma processing uses temporal information, the fields issue might disturb the outcome. I described it here:

https://forum.doom9.org/showthread.php?p=2019258#post2019258

Emulgator
6th June 2025, 23:23
MiroMotion DC30+ (1998) should deliver MJPEG ? Well then...
Sample 3: There had been recoding damage already, Histogram has holes/peaks.
Capturing device: I was recommended a Hauppauge USB Live 2 here, and it can indeed deliver 4:2:2 8bit uncompressed,
and is less picky about TBC issues than my other Video ADC (Blackmagic Intensity Shuttle USB3, 4:2:2 10bit uncompressed)

A quick and dirty approach, not tailored in any way.
No caring about Dehaloing /Chroma here, this is up to you.
v=LWLibavVideoSource"<yourpathhere>"
a=LWLibavAudioSource"<yourpathhere>""
AudioDub(v,a)
propSet("_FieldBased",1)
#QTGMC(EdiMode="BWDIF+NNEDI3", tr2=3, sharpness=1.0, Lossless=2, SourceMatch=3, Sbb=1, ShowSettings=false, Denoiser="KNLMeansCL", NoiseTR=2)
QTGMCp(InputType=0,EdiMode="BWDIF", tr2=3, Rep1=2, RepChroma=true, ChromaMotion=false, DenoiseMC=true, sharpness=2.0, lossless=0, NoiseProcess=1, GrainRestore=0.0, NoiseRestore=0.5, ChromaNoise=true, show=false)
ChromaShiftSP(X=2.0, Y=2.0)
SMDegrain(tr=6, thSAD=400, RefineMotion=true, contrasharp=false, interlaced=false, plane=4, prefilter=8, chroma=true, Show=false) #5: DFTTest, 6: KNLMeansCL, 7: DGDenoise, 8: BM3D
MedSharp(str=3)

Busty
9th June 2025, 11:51
Thanks again, Emulator.

The DC30+ runs with a driver from squared5 which supports multicodec input, I can use every codec quicktime can read. I guess you are talking about the pinnacle windows driver, which is less flexible iirc.
The Happauge USB Live 2 only has windows drivers, so I can't go for that one on a mac, but I'm quite content with my DC30+. But I'd sure like to hear anyone's opinion on that card or / and my capture quality.

here's a link to the driver and description
http://www.squared5.com/svideo/dc30-xact-mac.html

I tested some different devices (Aurora Fuse and Fuse X, Miglia Alchemy DVR, Canopus ADVC-300 Blackmagic Intensity Shuttle TB) and liked the DC30+ most.

Good observation on the histogram of the processed file, I took a look at it after your comment and see the luma levels being too widespread. I'll have to examine which part of the script is responsible for that (I'm open to suggestions about that though:-))

On the unprocessed capture the highs seem to be fine around 225, maybe the lows are a bit tight with a value of 10?

ATM I'm trying to test your suggested script. As I'm at a 32bit setup, I can only use BWDIF 1.2.1 latest, which does lack some settings QTGMCp wants to set, namely thr and pass. I also had to change SMDegrain's prefilter to 5 because I cant' use CUDA (yet) and delete the show=false argument. Processing runs at 0.7 fps...
The rsulting file has luma low value nailed to 0, is that how it is supposed to be? Doesn't this need to be at 16, lowest?

While the result looks pretty good regarding noise, I would rather not deinterlace the file itself but only when playing back, so QTGMC might not be the path to go for me.

And I still see these red areas around the drum sticks on sample 3, sometimes preceeding, sometimes following the drum stick, of which I don't know why there are there.

So, your comment made me aware of color space issues once more.

In the end, I want to digitize about 200 VHS tapes, 95% recorded with the same recorder from tv concert broadcasts. The capture part is solved, and i'm looking for a mostly automated process after capture. I'm getting myself a CUDA-compatible GPU for BM3D denoising, but am still searching for a way to adress chroma noise / bleed / halo and of course I want to stay within valid color spaces after all the conversion.

Because I don't want to adress every video with dedicated settings, I'd go for more sensitive settings that don't overdo anything.

Is there a way to examine a whole video for color values and shift them automatically to a desired value while staying in YUV? Would that make some sense?

Busty
10th June 2025, 15:23
regarding the levels: I can't see which part of the initial JohnMeyer's script produces these extremes. Is there distortion already or is it still within the representable range of values?

I can tame them with autolevels, but I don't know if that is sufficient: When I add autolevels at the end of the script, does it just shift values that are already distorted before?

Emulgator
11th June 2025, 10:37
Well, your side has to run on a mac, and I can not tell how, but I get the suspicion that there might be something wrong in your decoding chain, Quicktime, that is...
I get moving drumsticks without any temporal artifacts, and no crushed blacks, Y sits nicely 16..230.

Selur
11th June 2025, 15:03
I was playing around with BasicVSR++ to see when the debluring model (7,8) were useful, I did a few test-encodes (using this Vapoursynth script (https://pastebin.com/45HrNMa8) and only switching the models).
The conclusion was, that drum sticks are too fast for the deblurring models of BasicVSR++, but in case anyone is interested I uploaded the clips (https://www.mediafire.com/folder/lrda8gcfhktws/basic_vsr_models).

Cu Selur

Busty
11th June 2025, 17:41
Emulgator,

yes, the "red stick ghost" seems to be gone when using your untailored script. But the reported levels are still on 0 / 255 for that frame. It doesn't look blown though and the luma curve looks fine, as far as I can tell. I cannot wrap my head around why this is that way.

What I also don't understand is: When I apply autolevels, the values look fine, but the image has visible distortion.

As you are using the same file as me and get satisfactory results, any problems need to be in my usage of avisynth or virtualdub. The problem is already seen in virtualdub.

Maybe I changed your script in a way that results to this (or I might need to check plugin versions):

loadplugin("C:\Program Files\AviSynth\plugins\masktools2.dll")
loadplugin("C:\Program Files\AviSynth\plugins\mvtools2.dll")
loadplugin("C:\Program Files\AviSynth\plugins\nnedi3.dll")
loadplugin("C:\Program Files\AviSynth\plugins\RgTools.dll")
#loadplugin("C:\Program Files\AviSynth\plugins\SMDegrain.avsi")
loadplugin("C:\Program Files\AviSynth\plugins\AutoLevels_x86.dll")


AVISource("Z:\Volumes\Video\sample3_unaltered.avi").ConverttoYUV422
propSet("_FieldBased",1)
QTGMCp(InputType=0,EdiMode="BWDIF", tr2=3, Rep1=2, RepChroma=true, ChromaMotion=false, DenoiseMC=true, sharpness=2.0, lossless=0, NoiseProcess=1, GrainRestore=0.0, NoiseRestore=0.5, ChromaNoise=true)
ChromaShiftSP(X=2.0, Y=2.0)
SMDegrain(tr=6, thSAD=400, RefineMotion=true, contrasharp=false, interlaced=false, plane=4, prefilter=5, chroma=true, Show=false) #5: DFTTest, 6: KNLMeansCL, 7: DGDenoise, 8: BM3D
MedSharp(str=3)
#Autolevels(border=8)
Crop(2,0,0,-8)
#ConvertToYV12(interlaced=true)
ColorYUV(Analyze=true)
#turnright.Histogram.turnleft
Histogram("Levels")
#Histogram("color2")
return last

untailoredScript Sample: https://postimg.cc/hJFpVhfq
untailoredScriptWithAutolevels Sample: https://postimg.cc/xXN6kLjR

2nd untailoredScript Sample: https://postimg.cc/JtK6S1db
2nd untailoredScriptWithAutolevels Sample: https://postimg.cc/xkKg5YzN

Emulgator
11th June 2025, 18:12
LWLibavVideoSource please, and no ConvertToYUV422.

Sharc
11th June 2025, 19:34
.... And I still see these red areas around the drum sticks on sample 3, sometimes preceeding, sometimes following the drum stick, of which I don't know why there are there.

Sample3: Your script (which script did you use?) messes up something (chroma). See the red ghosts around the drum sticks (picture on the right).

https://mega.nz/file/Sd1ChKxJ#XrtRLlD9-coxfoXOBfwWRbxFWaTfnbszS-cbvXQnuIE

Edit: While I posted ..... Seems that got clarified and sorted out. Never mind.

Busty
12th June 2025, 12:49
LWLibavVideoSource works as input, but without conversion I get an error "YUY2 format not allowed", appearantly from ExTools and SharpenersPack.

Changing the input filter does not help the min and max levels.

Emulgator, does my file open for you without conversion, or which conversion did you do?

And it seems I spoke too soon regarding the ghosts around the drum sticks, they are still there at some frames, for example at frames 160-167.

I used the script Emulgator thankfully provided, as well as the one based on JohnMeyer's script on page 1 of this thread https://forum.doom9.org/showthread.php?p=2018663#post2018663

The red ghost sticks are present with both scripts.

Emulgator
12th June 2025, 13:10
mediainfo sees "sample3_unaltered.avi" as ULY2 4:2:2 8bit.
LWLibavVideoSource decodes "sample3_unaltered.avi" as YV16 here, not YUY2.
BSVideoSource decodes "sample3_unaltered.avi" as YV16 here, not YUY2.
FFVideoSource decodes "sample3_unaltered.avi" as YV16 here, not YUY2.
I apply no conversion, and get all frames without chroma ghosts.

If there are ghosts, then there is wrong choma placement involved. Why only applicable for a few frames, I can not tell.
UT has changed over the times, there are incompatibilities across versions !
On this emergency replacement system of mine I have no standalone UTVideo installed,
so the AviSynth decoders use their internal UT implementation here.

Try uncompressed.

Sharc
12th June 2025, 14:02
Sample3_tweaked.avi is a flawed interlaced 4:2:2 to 4:2:0 (YV12) conversion IMO, therefore the chroma ghosting. See the attached slowmotion video. On the right you can see that the chroma (U,V) advances not correctly in sync with the bobbed (deinterlaced) source fields. It advances at framerate (baserate) only, producing the ghosts.
https://mega.nz/file/XJtiHKAB#RBoXm-ZnsTvFMxTFPUfdc_uJ8EVGRv4LUJuvzHgnTMI

Added: And for comparison the Samle3_unaltered which is interlaced 4:2:2. One can see that the chroma (U,V on the right side) advances correctly and synchronously with the bobbed fields.
https://mega.nz/file/aZdDlbYS#YrSCVm6bGSXcceecQZVNUvjsAE7IPkyyLLIXXLpGEhQ

So the conversion from interlaced 4:2:2 to interlaced 4:2:0 was not done properly IMO (Script, encoder ... whatever).

Busty
12th June 2025, 19:07
@ Emulgator

mediainfo reports the same for me.

I tried the original captured file, before I converted to UTVideo. That one is reported as 2vuy 4:2:2 lossless by mediainfo. I also captured a part again, just to make sure.

It does open too, but still needs conversion; this time, I did converttoYV16. The min / max values are still 0 / 255 (or nearby for any other frame), so UTVideo seems to not be responsible.

But I found that when I delete the MedSharp part of the script, the values do not sit at 0 / 255 but within a very much better range. Still more expanded than the original file, but not always scratching on 0 / 255.

It's still a mystery to me why it opens without color space conversion for you but not for me. Thanks for all the input and time!

@ sharc:

thanks for taking the time to produce the examples and upload them.
Just so I understand correctly: Could a conversion from interlaced 4:2:2 to 4:2:0 produce this behaviour? Is this drum stick situation maybe a good example of the benefits of 4:2:2, because it moves so fast that 4:2:0 is just too slow at half the speed of 4:2:2? Or could this be produced by wrong field order? I'm asking because at one point this video had field order issues that did not show upon normal playback, but only when deinterlaced with yadif2. Fields/frames moved back and forth which reminded me of the red ghost sticks sometimes preceeding, sometimes following the luma drum stick.

Sharc
12th June 2025, 20:54
But I found that when I delete the MedSharp part of the script, the values do not sit at 0 / 255 but within a very much better range. Still more expanded than the original file, but not always scratching on 0 / 255.
Sharpening often causes sharpening artifacts like overshoots (halos) around edges, extending the luma shortly to >235 and <16 locally. Use a waveform monitor rather than coloryuv(analyze=true) which catches any instantaneous local min/max extremes which can be misleading.

Just so I understand correctly: Could a conversion from interlaced 4:2:2 to 4:2:0 produce this behaviour?
Yes, if the conversion is done incorrectly.
Is this drum stick situation maybe a good example of the benefits of 4:2:2, because it moves so fast that 4:2:0 is just too slow at half the speed of 4:2:2?
No. Imagine that all DVD's, Blu-rays, Broadcast video are 4:2:0. It has nothing to do with motion speed but rather with spatial color resolution (as you can see on the size of the U,V panels of my uploaded chroma motion examples). The ghosting is basically everywhere present in your tweaked video, one just doesn't notice the "ghosts" in static scenes. It becomes visible in motion scenes only.
Or could this be produced by wrong field order?
No. This would just produce jerkiness.


- How did you convert the unaltered 4:2:2 source to 4:2:0 for your tweaked variant? Dis you use ConvertToYV12() instead of ConvertToYV12(interlaced=true) while the video was still interlaced?

- Maybe your script accepts planar 4:2:2 YUV only, so you could try ConvertToYV16(interlaced=true) which converts stacked to planar format.

Revisit your script. (I didn't follow the entire history of this thread, so I may have missed something).

Emulgator
12th June 2025, 23:13
Med Sharp will indeed exaggerate values, as this is to be expected with any sharpening kernel.
As Sharc mentioned.
Just uncomment it, it was just cosmetic to show possibilities.

Just so I understand correctly: Could a conversion from interlaced 4:2:2 to 4:2:0 produce this behaviour?

Yes.

PAL, (NTSC, SECAM) had been broadcast linewise, so, taking into account that any broadcast chroma has to be restricted in bandwidth from the start anyway,
the 4:2:2 professional storage subsampling was the appropriate sampling format for all systems BEFORE broadcasting,

After broadcasting on PAL consumer side 4:2:0 had been chosen as the storage format (as the halfway effficient way out),
because every other 64µs chroma line had to be transmitted phase alternated, on receiver side stored away in an 64µs analog delay line
and processed with the previous line, canceling out out phase error, but effectively halving chroma line resolution.

Then digitally storing this as a 4:2:0 576i25 "field-frame" and not as 2 288p50 fields had (quote:) "(and 'i' means it is) irreversively destroyed"
any sane way of linewise chroma representation for decades to come. Well, these days one can reconstruct from that mess.

BUT: As long as you have an analog linewise recording (which VHS is), staying at 4:2:2 is of advantage as long as the processing chain deals with interlaced.
Once an elevated deinterlacing-with-bobbing-algo has successfully restored temporally and spatially clean full frames, knowing about these PAL implications,
you may resort to 4.2:0 for storage, because the restored chroma from that limited transmission bandwidth now is just worth these 4:2:0. (From VHS much less, and luma-guided chroma restoration out of the equation, of course)

SECAM (Séquentiel couleur à mémoire) would as well use a 64µs line worth of chroma storage, IIRC.
(As I was born into SECAM land ;-) I still remember these potted-in-blue delay lines like HxWxD 50x30x8mm³ from my color TV excursions.)

NTSC would need a different approach.
Since there is no chroma line storage buffer involved, chroma is indeed transmitted unique for every line (although even less bandwidth given).
Hence the appropriate subsampling representation on consumer side had been chosen as 4:1:1 (NTSC-DV, that is),
in the end sharing the same storage requirements between both systems.

Sharc
13th June 2025, 07:25
Just to add that PAL may suffer from Hanover bars caused by non perfect cancellation. These usually manifest as horizontal color stripes. I doubt however that Hanover bars are the main problem with the OP's tweaked 4:2:0 file with the red ghosts, as these would also be present in his original unaltered 4:2:2 capture. So I still think the OP should revisit his 4:2:2 -> 4:2:0 conversion, or stay in 4:2:2 throughout his workflow.

Busty
13th June 2025, 12:22
Yes, I indeed converted with ConvertToYV12(). It was AssumeTFF().ConverttoYV12(). Does the AssumeTFF() argument change anything? If not, then you found the reason for ghosts playing drums:-) Thank you for finding and for explaining that to me. I learn things here that I never would have thought I'd need to deal with... Really appreciated!

I converted to ConverttoYV12 because I had Despot in my script. I just read on Despot's description that it also supports "special planar YUY2" format (is that YV16 then?), so maybe I can change that to stay in 4:2:2. Which I'd prefer either way. Also, that script is not final yet, so requirements from plugins may change. I'll keep an eye out for proper conversions.

Thank you again Emulgator & Sharc for this excursion.

Back to my task to find a good way for chroma shift, but maybe I'll wait for my CUDA-compatible GPU first...

Sharc
13th June 2025, 13:11
Your Sample3_unaltered.avi is interlaced, Bottom Field First, hence AssumeBFF() would be correct, regardless what MediaInfo may tell you.
If you want or need to convert to 4:2:0, use ConvertToYV12(interlaced=true)
If you want/need 4:2:2 planar use ConvertToYV16(interlaced=true) instead.

(In any case you should always know whether your video is interlaced or progressive before applying a certain filter, and use the filters accordingly. Take note that many filters work on progressive video only and are not interlace aware. Filtering of interlaced video is a topic of its own).

lollo2
13th June 2025, 17:14
If you want/need 4:2:2 planar use ConvertToYV16(interlaced=true) instead.

from 4:2:2 interleaved (YUY2) to 4:2:2 planar (YV16) the parameter interlaced=true is not needed ;)

from http://www.avisynth.nl/index.php/Convert:
Note, interlaced=true has an effect only on YV12↔YUY2 or YV12↔RGB conversions. More about that can be found here.

Sharc
13th June 2025, 17:30
from 4:2:2 interleaved (YUY2) to 4:2:2 planar (YV16) the parameter interlaced=true is not needed ;)

from http://www.avisynth.nl/index.php/Convert:
Note, interlaced=true has an effect only on YV12↔YUY2 or YV12↔RGB conversions. More about that can be found here.
Yep, thanks. I left it routinely in as it does no harm either ;)

Lucky38
16th February 2026, 09:25
hello,

i'm looking any help with removing horizontal lines on this picture:

https://i.postimg.cc/sXWp38dy/sample.png

mainly those on top of the pictures.

i have used Busty's script to clean up some problems, but i can't remove those lines...

rgr
16th February 2026, 14:53
Every frame or every other? A video clip would be helpful.

Lucky38
16th February 2026, 15:24
Every frame or every other? A video clip would be helpful.

https://mega.nz/file/JdBRzDJQ#nmJg67FetxfPyjFJMKc9pdxqalIps17HvYPwKXwQa3M

rgr
16th February 2026, 15:25
https://mega.nz/file/JdBRzDJQ#nmJg67FetxfPyjFJMKc9pdxqalIps17HvYPwKXwQa3M

"Nie można uzyskać dostępu do pliku"

Edit: Już OK, musiałem się wylogować ze swojego konta.

Edit2: Straszna kiszka, 25p zamiast 50p lub 50i. Trzeba zgrać jeszcze raz i niekoniecznie do MPEG2.

Lucky38
16th February 2026, 15:31
"Nie można uzyskać dostępu do pliku"

Edit: Już OK, musiałem się wylogować ze swojego konta.

Edit2: Straszna kiszka, 25p zamiast 50p lub 50i. Trzeba zgrać jeszcze raz i niekoniecznie do MPEG2.

uzywam easy CAP +VHS to DVD 3.0

nie mam jak lepiej tego zgrac.

edit1: no chyba że polecasz jakis sprzęt lepszy do tego zadania.

edit2: moze inny soft?

rgr
16th February 2026, 15:47
uzywam easy CAP +VHS to DVD 3.0

nie mam jak lepiej tego zgrac.
edit1: no chyba że polecasz jakis sprzęt lepszy do tego zadania.
edit2: moze inny soft?

Zgraj VirtualDubem bezstratnie (Huffyuv, FFV1, ewentualnie ProRes). W innych wątkach znajdziesz mnóstwo informacji jak to zrobić. Takie pasy występują też przy kiepskim VCR.