Control structures

From Avisynth wiki
Revision as of 16:17, 9 November 2017 by Raffriff42 (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

In the strict sense, AviSynth Syntax provides only one control structure, the try..catch statement.
(actually two, if we include the conditional operator ?:)


The try..catch statement

The try..catch statement permits the execution of code that may generate a run-time error with an error-handling section.

The full syntax of the try..catch statement is:

try {
catch(err_msg) {

The err_msg string in the catch block contains the text generated by AviSynth when the error inside the try block was encountered. This text is the same that would appear in the familiar MessageBox that shows up when a fatal script error occurs.

You can query the text (it is a normal string variable) to find specific substrings inside that indicate the error that has been encountered. This is a technique that can produce many useful results (for an example, see here).

Other control structures (in the broad sense)

In the broad sense, there are many elements in AviSynth Syntax that although not control structures by themselves, together allow the creation of language constructs equivalent to a control structure. Those constructs in turn allow the performance of complex programming tasks.

The elements under consideration are the following:

  1. The Eval() statement that allows execution of arbitrary script language statements (and its cousin Apply that simplifies calling functions by name).
  2. Multiline strings, and in particular multiline strings surrounded by triple quotes (the """ sequence of chars), since they allow string literals inside them naturally, as one would do in a normal AviSynth script.
  3. The Import() statement that allows execution of arbitrary scripts, which can return a value (not necessarily a clip; a script can return a value of any type, which can be assigned to a variable of the calling script).
  4. Recursion (the ability to create recursive functions).
  5. Control functions, in particular Assert, Select, Default and NOP.
  6. The external GScript tool augments the syntax by adding for loops, while loops and if-then-else blocks.
Note AVS+ adds native GScript support.

The first three elements allow one to create simple Block statements, such as branching blocks (the analog of the if..elseif..else control structure). A basic example is presented below (see the link above for more):

# define different filtering based on this flag
heavy_filtering = true
# assign result to a variable to preserve the value of the 'Last' special variable
dummy = heavy_filtering ? Eval("""
    Levels(0, 1.2, 255, 20, 235)
    stext = "heavily filtered"
    Spline36Resize(720, 400)
""") : Eval("""
    stext = "lightly filtered"
    BicubicResize(720, 400)
AddBorders(0, 40, 0, 40)

The fourth element (recursion) is the general tool provided by the syntax for operating on collections and calculating expressions of any complexity. It is also, currently, the only tool.

This does not mean that there is something that you can't do inside the AviSynth script language; in fact recursion together with assignment can achieve everything an imperative language with looping constructs can do—they just do so in a way that most people without functional programming skills are not familiar.

The fifth element (control functions Assert, Select, Default and NOP) are more or less necessary tools inside the above constructs, for controlling input, setting values and facilitating proper execution of the construct.

Let's look at some examples of recursion, to grasp the general pattern that one must follow:

Example 1: Create a function that returns a n-times repeated character sequence

We will use an existing implementation, from AVSLib as our example:

Function StrFill(string s, int count, bool "strict") {
    strict = Default(strict, true) 
    Assert((strict ? count >= 0 : true), 
    \ "StrFill: 'count' cannot be negative")
    return count > 0 ? s + StrFill(s, count - 1) : ""

The recursion is the call that the function makes to itself at the return statement. In order to be done properly, the sequence of recursive calls must eventually end to a single return value. Thus a recursive function's return statement will always use the conditional operator, ?:.

That is all there is to recursion; the other two lines (the control functions) are simply for ensuring that proper arguments are passed in. The "strict=false" option is for quietly (without throwing an error) returning an empty string.

Example 2: Create a function that selects frames of a clip in arbitrary intervals

Filters like SelectEvery allow the efficient selection of arbitrary sets of frames. They require that each set of frames has a constant frame separation with its successor and predecessor set (in other words, the sets are periodic on frame number). In order to select frames with varying separation (that is non-periodic) we have to resort to script functions that use recursion.

The function below is a generic frame selection filter, which in order to select arbitrary frames uses a user-defined function (the func argument must contain its name) that maps the interval [s_idx..e_idx) to the set of frames that will be selected. func must accept a single integer as argument and return the corresponding mapped frame number.

Function FSelectEvery(clip c, string func, int s_idx, int e_idx) {
    Assert(s_idx >= 0, \
     "FSelectEvery: start frame index (s_idx) is negative")
    f = Apply(func, s_idx)
    return (s_idx < e_idx && f >= 0 && f < c.Framecount) \
       ? c.Trim(f, -1) + FSelectEvery(c, func, s_idx + 1, e_idx) \
       : c.BlankClip(length=0)

The recursive step (first conditional branch in the return statement) is again an expression that involves the function as a sub-part. This is not necessary in the general case (depending on the specific task, it could also be just the function call) but it is the most usual case when building complex constructs.

Apply calls the user function to calculate the frame number (a more robust implementation would enclose this call in a try...catch block). If the frame number is within the clip's frames, then the associated frame is appended to the result, else recursion ends.

The following example will illustrate usage of the above function:

# my custom selector (x^2)
Function CalcFrame(int idx) { return Int(Pow(idx, 2)) }

# select up to 20 frames, mapped by CalcFrame
# in this case: 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196
FSelectEvery(last, "CalcFrame", 0, 20)

Back to AviSynth Syntax.

Personal tools