Understanding containers

Container types are implemented as specially delimited strings. Elements are accessed (retrieved,set) with the use of functions, where an integer index argument specifies the desired element to operate with. This is the only way to implement them with the capabilities offered by the Avisynth script language while retaining relative simplicity and atomicity of container types' variables.

Table of contents

Implementation selections

The valid index range was selected to be from zero to number-of-elements minus one, both because it is the more usual case and because it makes coding easier. Furthermore, it was decided to allow negative indices, in the style used by the Python programming language.

Thus, the access functions add number-of-elements to negative indices and then check if resulting index is in the interval [0..number-of-elements); if yes the element is accessed, else an 'index error' occures. This makes easier to access container elements backwards from the end (for example, to access the last element, use an index of -1).

The index is checked during each element or subrange access for containment in the interval [0..number-of-elements); this assures that an out of bound index (a common programming error) will be reported to the user.

In order to implement clip containers a scheme was deviced for copying clips passed to containers in "reserved" global variables and storing these global variables' names into the container.

Rather than having a special type of clip container it was chosen to create a unified implementation of container types which can handle every possible type. For this purpose special handler functions were created to transparently set/retrieve variables of the appropriate type to/from the string representation of them in the container. The actions of the handler depend on the type of operation:

 
OperationHandler's actions

Retrieve an element

Evaluate the element as an expression (by passing it to the Eval() standard Avisynth function).

If the element evaluates, return the result of the evaluation.

Else, return the element itself (ie the string stored in element).

Assign to an element

If the new value is of the clip type, copy it to a newly created "reserved" global variable1 and assign the name of the global variable to the element.

Else, assing the result of applying String() to new value.

1 The variable is created by combining a prefix with the string representation of an integer counter which is incremented every time a call to the creation function is made.

 

The element delimiter was chosen to be variable and not hard-coded, in order to allow the maximum of flexibility to users. A reasonable default (the comma character) was selected and special globals hold the variables associated with the delimiters. These were made "private" and accessible by special functions in order to aid error-free manipulation of them.

For the same reason the element handler functions were also chosen to be variable and accessible by special functions. However, since this feature is considered to be developer-level, no additional documentation but the source code will be provided.

[<< to top]

Impact of selections on functionality

A first consequence of the above setup is that containers can be created by assigning appropriate string expressions to variables, such as in the examples below.

# assume library's defaults for delimiters apply
ar1 = "1.2, 3.4, 4.5, 6.7"    # whitespace is ignored (allowed)
ar2 = "true, false, true"
ar3 = "This, is, a, nice, day." # whitespace in strings is NOT ignored
# clip arrays can also be directly coded, if elements are globals
global clip1 = AVISource(...)
global clip2 = AVISource(...)
global clip3 = AVISource(...)
global clip4 = AVISource(...)
ar4 = "clip1,clip2,clip3,clip4" 

A second one is that accessing containers' elements is slower than directly accessing variables because:

However, keep in mind that the extra time needed when parsing a script to produce frames is compensated more than enough from the time gained during the development of the script by the flexibility, ease and speed of coding that containers offer.

Taking into account that this extra time will be in the majority of cases a small fraction of the time needed to process the video streams, most of the time using containers will be the best choice of implementation.

Another consequence is that container variables can contain apart from the usual case of one type of elements, the following:

In fact one can even store entire scripts in a container (if working only with globals) by embedding blocks of code in double quotes and assigning them to the container elements [1].

The following examples illustrate the above.

inclip = AVISource(...)
ovclip = AVISource(...)
global rect = BlankClip(width=320, height=240, color=$ffffff)
mixar = "0, 0, true, rect, 0.8" 
# mixed arrays can be used as structures
# for reducing arguments, specifying presets, etc.
clipvar = some_user_filter(inclip, ovclip, mixar)
...
branch = ...
# construct an array of size 3 with code blocks
# note the unescaped with backslash lines
codar = \
    "global c = BlankClip(color=$ff00cc)
     global d = c.Levels(0,1.0,255,0,128)" 
     \ + "#" + \
    "global c = BlankClip(color=$00ff00)
     global d = c.ConvertToYUY2().Tweak(hue=135)"
     \ + "#" + \
    "global c = ColorBars()
     global d = c.Levels(0,1.4,255,100,200).ConvertToYUY2().Tweak(hue=150)"
# because code blocks contain commas, # was used as delimiter
# that's why we have to tell AVSLib to use our delimiter
ArrayDelimiterSet("#")
temp = codar.ArrayGet(branch > 2 ? 0 : (branch < 0 ? 1 : 2) )
ArrayDelimiterReset()
# and restore to defaults when finished (see below)
new_clip = some_user_filter(d)
...

Be aware though that this functionality is rarely needed; wrapping code to functions or using the container's operator functions provided by AVSLib is a more scallable and easier to understand alternative.

Of course, this added flexibility also means that one must be careful with the types stored in the container, since most of the time uniformity is the desired feature (after all, the major purpose for working with containers is to perform a series of operations to a group of similar objects).

In particular the following note has to be made:

When creating containers with strings one must be aware that due to the evaluation of each string in a retrieve action, there is the possibility of retrieving a global's value instead of the string if the contents of the string are the same with the name of the global.

In addition, the facility provided by Avisynth to allow calling a function without parentheses means that all function names, both standard and user-defined, obey the same rule. This may lead to strange errors since a function called without parentheses tries first to operate on the implicit variable last.

For example:

global animals = 34
species = ArrayCreate("fishes", "birds", "animals", "serpents", "humans")
t = species.ArrayGet(2)    # t is 34 (an integer) and not "animals"!!
# 'not ' will produce a strange error in ArrayOpValue if the bool package
# is loaded due to the existence of the 'Not' function in the package
# (lang is case insensitive!) and the fact that functions can be called without parentheses
# (which results in "last" implicit variable supplied as argument).
a9 = "is ,is ,has ,was "
a14 = ArrayOpValue(a9, "not ", "+") 
# an error pointing that Avisynth forgotten a previously declared
# local variable appears

In addition, one must be careful if he/she decides to play with the container's delimiters. Since these are global variables, the effect of setting them to a different value is immediate. The following example demonstrates this issue:

coords = "100, 10, 200, 25, 300, 40, 400, 80"
# change delimiter to # to allow a string-array with commas inside.
ArrayDelimiterSet("#")
subtitles = "Hey friend, how are you?#Fine, thanks!#Nice weather today."
# This won't work; the entire coords array will be returned.
x0 = coords.ArrayGet(0)
# This won't work either; script execution will halt with an 'index error'.
y0 = coords.ArrayGet(1)
sub1 = subtitles.ArrayGet(0)
sub2 = subtitles.ArrayGet(1)
...

The correct way to code in such situations is to organise access to non-standard arrays in blocks and wrap each block with calls to the pair of ArrayDelimiterSet() and ArrayDelimiterReset() functions, as demonstrated by the following example:

coords = "100, 10, 200, 25, 300, 40, 400, 80"
# change delimiter to # to allow a string-array with commas inside.
subtitles = "Hey friend, how are you?#Fine, thanks!#Nice weather today."
x0 = coords.ArrayGet(0)
y0 = coords.ArrayGet(1)
# wrap blocks of operations on non-standard arrays;
# do not access normal arrays inside these blocks.
ArrayDelimiterSet("#")
sub1 = subtitles.ArrayGet(0)
sub2 = subtitles.ArrayGet(1)
ArrayDelimiterReset()
...

[<< to top]

What containers can do for you

Well, actually nearly everything, since they are a general purpose programming facility. That's why they are a standard element of nearly every programming and scripting language.

A few possibilities specific to Avisynth usage are outlined at the list below. Keep in mind though that this is a very small set; the only limit is imagination.

See the examples at the container operators tutorial and the source code of the test scripts included in the distribution of AVSLib to get an inshight of real use cases of container types.

[<< to top]

Hints for effective use

The following list provides some guidelines for effective use of containers.


[1]: Make sure that the code blocks do not generate errors because these will be masked by the try..catch block of the container's retrieve handler and it will appear that the scheme does not work, while it actually does. A hint to debugging: If an error appears inside a code block, only the globals assigned before the error occured will have the correct values. That way you can trace, by examining all associated globals, where the error occured.