USB video grabbers automatically replace disturbed frames (with bad sync) by identical copies of the last intact frame. As a result parts of the grabbed video are filled with one ore more duplicate frames.
Especially grabbings from VHS suffer from this problem.
A grabbing from a bad VHS copy that was recorded on a cheap recorder without a sync track had up to 1500 duplicate frames per hour.
..abc...........opq..stuvwxyz......... Original frames of the movie (good frames)
..a-c...........o-q..s------z......... VHS with drop outs (tape wrinkel)
..aac...........ooq..sssssssz......... Output of USB Video Grabber (duplicates instead of original frames)
Figg 1: Duplicate frames replace corrupted frames
The following script searches for duplicate frames. From the result it generates a second script, that is using
Mud Funky's Morph function to
replace duplicate frames by interpolated frames. The result is very smooth and looks fine.
Version 0.7a of the script.
PHP Code:
# http://forum.doom9.org/showthread.php?p=1563921
# Morphdups.avs by Sven_x 2012 version 0.7a
# search duplicate frames in a clip and generate a function "morphmydups.avs"
# that interpolates between the border frames
# Guide:
# Run a first pass with this script. It searches for duplicate frames.
# From that it generates a script with a function "morphmydups.avs"
# that you can use to replace automaticly the duplicate frames by interpolations
# To do:
# perhaps YDifferenceFromPrevious(N) can be replaced by YDifferenceToNext(N-1)
# Changelog
# YDifferenceFromPrevious() can ONLY be used within conditional functions, as it changes on every frame
# Also remember that conditional variables must be assigned bottom up.
# http://avisynth.org.ru/docs/english/corefilters/conditionalfilter.htm
# source and parameters ..................................................................................
global source1=AVISource("J:\test\test79400.avi")
#DirectshowSource("raw.avi",30,true,false,true)
#global source1=SegmentedAVISource("segmented.avi")
last=source1
ConvertToYV12() # YV12 needed for MVTools and for CCE Basic
reduce=true #true = crop output to 16x16 px to speed up encoding (150%) if you run the script by encoding
# variables .................................................................................
dup_thresh=0.0001 #threshold, below threshold is considered as duplicate frame
sc_change_thresh=5.1 #a difference above this threshold is considered as scene change (excluded from list)
gap_pos=0 #Gap start
gap_l=0 #Gap length
pregap=0 #Gap YDifference before gap
postgap=0 #Gap YDifference after gap
gap_end=false # Bolean, triggers writing of gap properties into dup.txt file
gapmarker="" #Visual Gap length marker
#Test function: display parameter results in clip .................................................................................
#ScriptClip("""Subtitle("frame_number: "+String(current_frame)+", YdiffP: "+String(ydfp)+", YdiffN: "+String(ydfn)) """\
# +"""Subtitle(x=8, Y=20,text_color=$FF4422,\
# "pos: "+String(gap_pos)+", gap_l: "+String(gap_l)+", pregap: "+String(pregap)\
# +", postgap: "+String(postgap)+", gapmarker: "+LeftStr("===============",gap_l) ) """)
#Search gaps with duplicate frames and write dups_report.txt + txt-files
#................................................................................
fg1() #find begin of gap
fg2() #find middle of gap
fg3() #find end of gap
colon=" " #define colon
WriteFileStart("morphmydups.avs", """ "# Function that replaces dups by interpolated streams of frames" +chr(13) """, \
+""" "# generated by MorphDups.avs v0.7, see http://forum.doom9.org/showpost.php?p=1561928&postcount=52" +chr(13)+chr(13)""", \
+""" "# needs:" +chr(13)""", \
+""" "# - Mud Funky's Morph function, see http://forum.doom9.org/showpost.php?p=1499620&postcount=2" +chr(13)""", \
+""" "# - MvTools2" +chr(13)+chr(13) """,\
+""" "# Example: Morph (19,22) replaces frame 20 and 21 by an interpolation between 19 and 22" +chr(13)+chr(13) """,\
+""" "# sc_change_thresh=" ""","sc_change_thresh",""" " # morph statements are disabled when YDifferenceToNext is greater" +chr(13)+chr(13) """,\
+""" "function morphmydups (clip c) {" +chr(13) """,\
+""" " c #load clip" """)
WriteFileIf("morphmydups.avs", "gap_end==true && postgap<sc_change_thresh", """ " morph(" """, "gap_pos-1", """ "," """ ,"gap_pos+gap_l" ,\
+""" ") # YDiffNext=" ""","postgap" ,""" ", length = " """ , "gap_l", """LeftStr("===========",gap_l)""")
# when postgap > sc_change_thresh morph statements are disabled by a leading "'"
WriteFileIf("morphmydups.avs", "gap_end==true && postgap>=sc_change_thresh", """ " # morph(" """, "gap_pos-1", """ "," """ ,"gap_pos+gap_l" ,\
+""" ") # YDiffNext=" ""","postgap" ,""" ", length = " """ , "gap_l", """LeftStr("===========",gap_l)""")
WriteFileEnd ("morphmydups.avs", """ chr(13) +"} # End of function" +chr(10)""" )
# functions to locate gap ..............................................................
# only one boolean expression per FrameEvaluate
# begin of gap: gap_pos=current_frame, gap_l=1
# YDifferenceFromPrevious() > dup_thresh && YDifferenceToNext() < dup_thresh ?
function fg1(clip c) {
c=FrameEvaluate(c,"gap_pos= ydfp > dup_thresh && ydfn < dup_thresh ? current_frame+1 : gap_pos" )
c=FrameEvaluate(c,"gap_l= ydfp > dup_thresh && ydfn < dup_thresh ? 0 : gap_l" )
c=FrameEvaluate(c,"pregap= ydfp > dup_thresh && ydfn < dup_thresh ? ydfp : pregap" )
return(c)
}
#middle gap: gap_l=gap_l+1
#YDifferenceFromPrevious() < dup_thresh && YDifferenceToNext() < dup_thresh ?
function fg2(clip c) {
c=FrameEvaluate(c,"gap_l= ydfp < dup_thresh && ydfn < dup_thresh ? gap_l+1 : gap_l" )
return(c)
}
#end gap: gap_l=gap_l+1 , gap_end=true, postgap=yDifferenceToNext, call writefileif
#YDifferenceFromPrevious() < dup_thresh && YDifferenceToNext() > dup_thresh ?
#store ydiffp and ydiffn in variables
function fg3(clip c) {
c=FrameEvaluate(c,"gap_end= ydfp < dup_thresh && ydfn > dup_thresh && current_frame > 0 ? true : false" )
c=FrameEvaluate(c,"gap_l= ydfp < dup_thresh && ydfn > dup_thresh ? gap_l+1 : gap_l" )
c=FrameEvaluate(c,"ydfp=YDifferenceFromPrevious(source1)"\
+"ydfn=YDifferenceToNext(source1)" \
+"postgap= ydfp < dup_thresh && ydfn > dup_thresh ? ydfn : postgap" )
return(c)
}
#Cropping to increase encoding speed ......................................................................
#cropping increases speed up to 400% for first pass if you run the script by encoding
reduce ? last.crop(0, 0, 16, 16) : last
The generated output of the script, named
morphmydups.avs, looks so:
Code:
function morphmydups (clip c) {
c
# morph(56,67) # YDiffNext=10.803629, length = 10==========
morph(103,105) # YDiffNext=4.838520, length = 1=
morph(106,108) # YDiffNext=4.491889, length = 1=
morph(109,111) # YDiffNext=4.452096, length = 1=
} # End of function
Please note, that this script provides you with the possibility of interactive correction. You get a list that you can edit. Before rendering the whole video, you can open the script in AvsP and test only that gaps that look critical. Either because they are marked as very long gaps (========) , or because YDifference across the gap is to high. In the latter case the script automaticly disables those morph statements that have a YDifference above a limit. You have the opportunity to delete the "#'" characters, thus re-enabling interpolation across that gap.
And this is how a calling script may look:
PHP Code:
#0. Imports, Loadplugin ..................................................................................
import ("J:\plugins\Morph1b.avs") # overlays info text on interpolated frames
#import ("J:\plugins\Morph.avs")
loadplugin ("J:\plugins\mvtools2.dll")
import ("morphmydups.avs")
stack=true #true= debug view: small version of source and interpolated are stacked
#1. Open file ..................................................................................
AVIFileSource("J:\test\test79400.avi")
source=last
#morph function ..................................................................................
morphmydups(source)
#Debug view ..................................................................................
stack ? stackvertical(last, source.subtitle("Input (unprocessed)") ).BilinearResize(width(source)/2, height(source)) : last
Changelog:
Version 0.7a implemented correction from Gavino, please see posting below
German explanation/deutsche Erklärung
hier.