Examples :: Stack clips in a matrix

This script demonstrates the use of arrays for storing clips that will later be processed as a group and also the use of the Stack() filter, a handy way to quickly create 2D tables of clips.

First the script:

# load required modules

LoadModule("avslib", "array", "core")
LoadModule("avslib", "filters", "stack")

# load six clips

vids = ArrayCreate( \
	AVISource("file001.avi"), \
	AVISource("file002.avi"), \
	AVISource("file003.avi"), \
	AVISource("file004.avi"), \
	AVISource("file005.avi"), \
	AVISource("file006.avi") \

# stack them in a 2x3 matrix

return Stack(vids, 2, 3)

The script loads a bunch of clips directly into an array and then passes them to the Stack filter to arrange them in a 2 rows x 3 columns matrix.

Note that there is no restriction that the product (rows x columns) passed to Stack filter must be equal to the array's length. If it is less, the remaining clips will be discarded; if more, blank clips will be used as padding to fill the entire matrix.

This is a rather crude method to load sequentially numbered clips; a possible refinement could be like the following script excerpt:

LoadModule("avslib", "array", "operators")

names = "file001.avi,file002.avi,file003.avi,file004.avi,file005.avi,file006.avi"
vids = names.ArrayOpFunc("AVISource")
or, with just one more line of code, but allowing easier application to other numbers of clips:
LoadModule("avslib", "array", "operators")

Function MakeFName(int idx) { return "file" + String(idx, "%03.0f") + ".avi" }

idxs = ArrayRange(1, 6)
vids = idxs.ArrayOpFunc("MakeFName").ArrayOpFunc("AVISource")

Both refinements however cannot be considered general enough for covering all possible cases. A logical consequence would be to try and device a generic source function that would deliver an array of clips, given a path and start / stop indices, like for example the ImageSource standard Avisynth filter does.

The next script does exactly that:

LoadModule("avslib", "base", "core")
LoadModule("avslib", "array", "core")
LoadModule("avslib", "array", "operators")
LoadModule("avslib", "array", "slices")
LoadModule("avslib", "filters", "stack")

# Function to load sequentially numbered (.avi) clips

Function ClipSource(string path, int start, int end, int "pad")
	Assert(start >= 0, "ClipSource: 'start' must be >= 0")
	Assert(end >= 0, "ClipSource: 'end' must be >= 0")
	pad = Max2(0, Default(pad, 0))   # values <= 0 result in no padding
	fmt = "%0" + String(pad) + ".0f"
	fname = path + String(start, fmt) + ".avi"
	return start <= end \
		? ArrayJoin( ArrayCreate(AVISource(fname)), \
			ClipSource(path, start + 1, end, pad) ) \
		: ""

vids = ClipSource("file", 1, 6, 3)

# calculate appropriate rows and columns (as the StackToFit filter does)

vlen = vids.ArrayLen
rows = Round(Sqrt(vlen))
cols = Ceil(Sqrt(vlen))

# reduce size to avoid making the final clip too wide / tall

vids = vids.ArrayOpFunc("ReduceBy2")

# stack them in a rows x cols table

return Stack(vids, rows, cols)

The script loads a couple of additional modules needed by the implementation and then defines ClipSource, the newly-created generic function for loading arbitrary (.avi only) sources into an array.

ClipSource uses ArrayCreate in order to create a single element array for each recursive invocation and ArrayJoin for joining the arrays together. That way there is no need to care about the housekeeping of array elements' delimiters; ArrayJoin takes care of these.

Since ClipSource is a generic function that can return an arbitrary length array, the way to calculate rows and columns for the matrix should be made generic also. The solution chosen is to use the same equations that are used by the StackToFit filter; the situation is similar.

Afterwards, the clips are made smaller in order to fit easily in a computer screen (the assumption is that they have normal video dimensions) by a single call to ArrayOpFunc containing the proper standard Avisynth filter (here ReduceBy2) and finally they are stacked in the 2D matrix.