Examples :: Per frame filtering, exporting specific frame(s)

This series of example scripts demonstrates the capabilities offered by the filters of the new in AVSLib version 1.1.0 filters :: frames module. In particular, they serve as an example of how one can easily create new custom filters from them.

All example scripts export specific frames as images (using internally the ImageWriter standard Avisynth filter), but in a different flavor. All have the frame export facilities wrapped inside a custom function, so that they can easily be reused in your scripts

Lets now look closer at the first script. This defines a new filter, ExportFrames, that exports any frame(s) whose framenumber(s) is(are) contained in an array passed as argument.

LoadModule("avslib", "array", "properties")
LoadModule("avslib", "filters", "frames")

Function ExportFrames(clip clp, string frames, string "path", string "type", bool "info")
{
	# frames export runtime script; needs array::properties module
	FF_EXPORT = """
	expframes = %q
	file = %q
	type = LCase(%q)
	info = %b
	# Overlay is used to avoid colorspace conversion if type != ebmp
	expframes.ArrayContains(current_frame) ? ( \
		type == "ebmp" ? \
			ImageWriter(last, file, current_frame, current_frame + 1, type, info) \
		: \
			Overlay(last, ImageWriter(last.ConvertToRGB24, file, current_frame, \
			current_frame + 1, type, info), opacity=0, ignore_conditional=true) \
	) : last
	"""
	path = Default(path, "frame")
	type = Default(type, "ebmp")
	info = Default(info, false)
	return FrameFilter(clp, FF_EXPORT, frames, path, type, info)
}

The larger part of the filter is the runtime script that does the frame exporting. The script arguments are first assigned to local variables. This makes the script easier to read and saves the need to supply the same argument many times if it is used in more than one location in the script.

Then a simple test with the runtime variable current_frame is performed: if it is in the array with frame numbers passed in (the expframes local variable) then the frame is exported; else not. Using the AVSLib-supplied ArrayContains function makes this test a simple one-liner.

One of the design targets of the filter is to let the clip passed in to go through untouched, whatever is the export image type requested. This is of concern for types other than "ebmp", since then the ImageWriter standard Avisynth filter requires that the input is RGB24 and a colorspace conversion would then be required (because in order to export a frame the filter must be in the active filter chain, thus it must deliver the converted to RGB24 frame to its successor filter, for it to convert it back to the original colorspace).

Thus, the script forks on export image type with a conditional operator and if type != "ebmp" it combines ImageWriter with Overlay with an opacity of zero to achieve letting the source clip untouched.

The rest of the filter's code initialises the filter's optional arguments to default values (see the ImageWriter's documentation for details) and calls FrameFilter to do the actual processing.

expframes = "5,23,35,55,99"	# export these frames only
clp = AVISource("story.avi").ConvertToYUY2
ExportFrames(clp, expframes, "selected", "jpg")
Compare(last, clp)  # verify that clp is passed through ExportFrames untouched

The main body of the script tests the filter's implementation with a real source clip (substitute your own to verify) and a random selection of frames (expframes variable). The source clip, which in our test case was YV12, is converted to a colorspace supported by Compare which is used to verify that the input and the return value of the ExportFrames filter are identical.

The second script defines a new filter, ExportFramesEvery, that works in a similar way with the SelectEvery standard Avisynth filter but instead delivering a new clip with the selected frames it exports them as images and returns the input clip untouched.

Another difference with SelectEvery is that here offsets are contained in an AVSLib array; however if the default array delimiter is used when the filter is called, the array is simply the argument list that would be passed to SelectEvery after the "step_size" argument enclosed in double quotation marks.

The decision to use an array was to make easier supplying the rest arguments (from ImageWriter) that the filter supports. As a by-product this also raises the 60 arguments-limit of Avisynth; you can pass as many numbers inside the array as wished.

LoadModule("avslib", "array", "properties")
LoadModule("avslib", "filters", "frames")

Function ExportFramesEvery(clip clp, int step_size, string offsets, string "path", \
	string "type", bool "info")
{
	Assert(step_size > 0, "ExportFramesEvery: 'step_size' must be positive")
	# frames export runtime script; needs array::properties module
	FF_EXPORT_EVERY = """
	step = %i
	offsets = %q
	file = %q
	type = LCase(%q)
	info = %b
	# Overlay is used to avoid colorspace conversion if type != ebmp
	offsets.ArrayContains(current_frame % step) ? ( \
		type == "ebmp" ? \
			ImageWriter(last, file, current_frame, current_frame + 1, type, info) \
		: \
			Overlay(last, ImageWriter(last.ConvertToRGB24, file, current_frame, \
			current_frame + 1, type, info), opacity=0, ignore_conditional=true) \
	) : last
	"""
	path = Default(path, "frame")
	type = Default(type, "ebmp")
	info = Default(info, false)
	return FrameFilter(clp, FF_EXPORT_EVERY, step_size, offsets, path, type, info)
}

The differences with the previous filter are suprisingly little:

  1. An additional argument of the runtime script (step_size) is assigned to a local variable (step); the argument list of the call to FrameFilter is changed accordingly.
  2. The containment test (now on the offsets array) is done to the modulo of division of current_frame runtime variable with step (again with ArrayContains).

Here also the design target of the filter is to let the clip passed in to go through untouched, whatever is the export image type requested. Thus, the script forks on export image type with a conditional operator just like the previous example.

clp = AVISource("story.avi").ConvertToYUY2
ExportFramesEvery(clp, 12, "0,3,5,11", "sel_every_", "png")
Compare(last, clp)  # verify that clp is passed through ExportFramesEvery untouched

The main body of the script tests the filter's implementation with a real source clip (substitute your own to verify) and a random selection of frames, coded directly inside the filter call. The source clip, which in our test case was YV12, is converted to a colorspace supported by Compare, which is used to verify that the input and the return value of the ExportFramesEvery filter are identical.

The third script defines a new filter, ExportFramesReader, that gets the framenumbers to export from a text file such as those read by the ConditionalReader standard Avisynth filter. The text file, a sample of which is provided here, must contain int values different from zero for every frame that is to be exported.

LoadModule("avslib", "filters", "frames")

Function ExportFramesReader(clip clp, string framesfile, string "path", \
	string "type", bool "info")
{
	# frames export runtime script
	FF_EXPORT_READER = """
	file = %q
	type = LCase(%q)
	info = %b
	# Overlay is used to avoid colorspace conversion if type != ebmp
	${read1} != 0 ? ( \
		type == "ebmp" ? \
			ImageWriter(last, file, current_frame, current_frame + 1, type, info) \
		: \
			Overlay(last, ImageWriter(last.ConvertToRGB24, file, current_frame, \
			current_frame + 1, type, info), opacity=0, ignore_conditional=true) \
	) : last
	"""
	path = Default(path, "frame")
	type = Default(type, "ebmp")
	info = Default(info, false)
	return FrameFilterReader(clp, FF_EXPORT_READER, framesfile, path, type, info)
}

Again, the differences with the previous filters are very little, essentially the test condition. Here there is no need for containment test; the special ${read1} variable (it will be substituted by FrameFilterReader by the actual variable that is set on each frame by the contents of the text file) is directly compared to zero and if not equal the frame is exported.

Here also the design target of the filter is to let the clip passed in to go through untouched, whatever is the export image type requested. Thus, the script forks on export image type with a conditional operator just like the previous examples.

clp = AVISource("story.avi").ConvertToYUY2
ExportFramesReader(clp, "frames.txt", "reader", "jpeg")
Compare(last, clp)  # verify that clp is passed through ExportFramesReader untouched

The main body of the script tests the filter's implementation with a real source clip (substitute your own to verify) and a random selection of frames, coded inside the text file. The source clip, which in our test case was YV12, is converted to a colorspace supported by Compare, which is used to verify that the input and the return value of the ExportFramesReader filter are identical.