Examples :: Load, convert and join with transition effect arbitrary clips

This script extends the basic ideas of the fifth and eighth examples. It applies a moderately complex filtering sequence (resizing and colorspace and fps conversion) based on the arguments' values and also joins the clips serially in a single timeline applying a relatively complex transition effect.

Shell scripts to automate the creation of the text files as well as the rendering of the .avs script are also provided. The later allow to treat the script as an Avisynth "program" which, in cooperation with a command line driven encoder (such as Avs2Avi, ffmpeg or VirtualDub plus a Sylia script) can fully automate complex NLE processes, reducing them to single shell command invocations.

First the script. The transition code is contained first, followed by the same clips' importing and filtering section that was presented at the eighth example. The last lines of the script are different though, to allow the more complex joining process that this example demonstrates.

# load required modules

LoadModule("avslib", "base", "constants")
LoadModule("avslib", "base", "conversion")
LoadPackage("avslib", "array")
LoadPackage("avslib", "clip")
LoadModule("avslib", "numeric", "rounding")
LoadModule("avslib", "string", "search")
LoadModule("avslib", "filters", "edit")
LoadModule("avslib", "filters", "stack")


# transition code

Function Crop4x4(int i, clip c) {
	cw = Round(c.Width / 4)
	ch = Round(c.Height / 4)
	row = Int(i / 4)  # [0..3]
	col = i % 4   # [0..3]
	return c.Crop(col * cw, row * ch, cw, ch, true)
}

# Using a single underscore is a good convention for private user globals;
# they do not clash with AVSLib namespace

global _crop_clip = BlankClip(length=0)

Function ClipDivide4x4(clip base) {
	loop = ArrayRange(0, 15, step=1)
	global _crop_clip = base
	return ArrayOpFunc(loop, "Crop4x4", "_crop_clip")
}

# assume each subclip is 15 frames long; appear is a flags array

global _fade_count = 0

# either blanks clip c or leaves it unchanged, depending on value of flag

Function _ovl_clip5(clip c, val aflag) {
    return aflag > 0 ? c : c.BlankClip() 
}

Function FadeSubClip(clip subclip, string appear) {
	ap = ArraySplit(appear, _fade_count, chunksize=5)
	# increase counter so that next call process next subclip
	global _fade_count = _fade_count + 1
	# split subclip to 5 pieces
	ac = ArrayCreate( \
		subclip.EditTrim(0, 3), \
		subclip.EditTrim(3, 6), \
		subclip.EditTrim(6, 9), \
		subclip.EditTrim(9, 12), \
		subclip.EditTrim(12, 15) \
		)
	# blank subclip in all parts where ap has zeros and recombine pieces
	video = ArrayOpArrayFunc(ac, ap, "_ovl_clip5").ArraySum()
	# keep audio from original clip to maintain precision
	return AudioDub(video, subclip)
}

# Creates a vanishing / appearing checkboard transition effect.
# Since we will use the function with EditJoin we know that there always be two clip arguments.

Function Transition(clip c1, clip c2) {
	start = c1.EditTrim(0, -8) # all but last 8
	ef1 = c1.EditTrim(-8) # last 8
	ef2 = c2.EditTrim(0, 7) # first 7
	end = c2.EditTrim(7) # all but first 7 

	# divide effect clips to a matrix of 4x4 subclips, each with 15 frames & merge (+)

	cr = ArrayOpArray(ef1.ClipDivide4x4(), ef2.ClipDivide4x4(), "+")

	# create subclip appearence flag arrays, one for each 3 frames (5 steps total)
	# in each step make 4 subclips to dissapear for ef1 and 4 to apear for ef2

	b1 = "1,1,0,1, 0,1,1,1, 1,0,1,1, 1,1,1,0"
	b2 = "1,1,0,1, 0,0,1,0, 1,0,0,1, 0,1,1,0"
	b3 = "0,0,0,1, 1,0,0,0, 0,0,0,1, 0,0,1,0"
	b4 = "0,0,1,1, 1,0,1,0, 0,1,0,1, 1,0,1,0"
	b5 = "1,0,1,1, 1,1,1,0, 0,1,0,1, 1,1,1,1"

	# multiplex flag arrays in order to create continuous blocks of flags 
	# (ie the columns of the table above) for each subclip

	appear = ArrayPlex(b1, b2, b3, b4, b5)
	global _fade_count = 0
	crf = ArrayOpFunc(cr, "FadeSubClip", StrQuote(appear))
	trans = Stack(crf, 4, 4) 
	return start + trans + end
}

# load a text file containing dir /b or equivalent output
# surrounded by triple quotes at the start and end of file

dir = Import("files.txt")

# load two text files containing integers 
# (target width and height for the clips in dir)

tw = Import("width.txt")
th = Import("height.txt")

Assert(tw.IsInt && th.IsInt, "Input Error: width or/and height files contain invalid data")
 
# after the line below dir is an array of filenames! 
# but be careful if you hand-type arrays...

ArrayDelimiterSet(CRLF)

# cleanup dir from invalid files (CInt is used to convert true/false to 1/0)

okflag = dir.ArrayOpFunc("Exist").ArrayOpFunc("CInt")
dir = dir.ArrayReduce(okflag)

# read in clips

clp = dir.ArrayOpFunc("DirectShowSource")

# force clip parameters (dimensions, framerate, colorspace) to be the same
# assume all clips are progressive

b_same_fps = (clp.ArrayOpFunc("Framerate").ArrayDistinct().ArrayLen() == 1)
pixel_type = clp.JointPixelType()
fps = clp.JointFPS()

# to assure that target width, height match any clip's colospace 
# restrictions we allow for the more restrictive type (yv12)

tw = RoundBs(tw, 4)
th = RoundBs(th, 4)

# now convert clips (resize, convertto..., changefps)

clp = clp.ArrayOpFunc("Spline36Resize", String(tw) + "," + String(th))
clp = clp.ArrayOpFunc("ConvertTo" + pixel_type)
clp = b_same_fps \
	? clp \
	: clp.ArrayOpFunc("ConvertFPS", String(fps))

# restore now default array delimiter, in order for transition code to work
# restore also delimiters inside clp (a simple strreplace op.)

old = ArrayDelimiterReset()
clp = StrReplace(clp, old, ",")

# join clips together one after the other, applying EditJoin with 
# custom user function (our transition) in each join

# we cannot pass a quoted string to sum_args (even with three double quotes), 
# so we assign the string to a global and pass in the global's name unquoted

global _tr_func = "Transition"

return clp.ArraySum(sum_func="EditJoin", sum_args="op=EDOP_USER, extra_info=_tr_func")

In the example above the script first loads and auto-convert all supplied clips to compatible characteristics for splicing. See examples five and eight for details.

Afterwards, the now homogenised clips are integrated together with EditJoin in just a single line of code, thanks to the array operators facilities of AVSLib (see the related tutorial for a more in-depth presentation).

The transition filter is passed in as a global, because quoted arguments do not work well inside ArraySum. Although this is a hack it does not hurt to do so, since globals will always be evaluated correctly even when this limitation will eventually be arised at the future.

As in the fifth and eighth examples, the creation of the .txt files can easily be automated with a custom shell script. A working example regarding the clips (say named "avidir.cmd") is provided below:

@echo off
echo """
for %%f in (%1) do @echo %%~ff
echo """

The shell script takes one (needed) argument, a wildcard specification which can include path information in case the files are not at the current directory. The script outputs absolute filenames, so that the calling avisynth script can be located anywhere in the filesystem.

The final step to automate the process is to make another custom shell script (say "makeavi.cmd") that will update the contents of the text files and (optionally, depending on your needs) invoke the encoder. For example:

@echo off
avidir %1 > files.txt
echo %2 > width.txt
echo %3 > height.txt
avs2avi script.avs -l %4

The top level shell script takes four arguments, a wildcard specification, two integer values for the width and heigh and an additional 4th argument (a filename with the codec settings). It is assumed that the encoder executable is placed at a folder included in the PATH environment variable.

Thus you can invoke a single shell command in order to import, NLE process and encode a set of clips, such as:

makeavi [wildcard] [width] [height] [codecsettings]

If another encoder is used , the top level script must be modified appropriately to account for the different command line options needed by the encoder.