Background

A first glance at Avisynth documentation leaves the impression that aside from function definitions, block statements are not possible in Avisynth script. However, there are specific features of the language allowing the construction of block statements that have remained unaltered to date and probably will remain so in the future since block statements are very useful in extending the capabilities of the script language.

Indeed, in most programming and scripting languages, block statements are very useful tools for grouping together a set of operations that should be applied together under certain conditions. They are also useful in Avisynth scripts.

Assume, for example, that after an initial processing of your input video file, you want to further process your input differently (for example, apply a different series of filters or apply the same set of filters with different order) based on a certain condition calculated during the initial processing, which is coded at the value of Boolean variable cond.

Instead of making an ugly series of successive conditional assignments using the ternary (?:) operator, as in Example 1 below (items in brackets are not needed if you use the implicit last variable to hold the result):

Example 1

[result_1 = ]cond ? filter1_1 : filter2_1
[result_2 = ]cond ? filter1_2 : filter2_2
...
[result_n = ]cond ? filter1_n : filter2_n

It would be nice to be able to construct two blocks of filter operations and branch in a single step, as in the (ideal) Example 2 below:

Example 2

[result = ] cond ? {
    filter1_1
    filter1_2
    ...
    filter1_n
} : {
    filter2_1
    filter2_2
    ...
    filter2_n
}

Such a construction (and others) is possible; perhaps some constraints may apply, but you will nevertheless be capable of providing more powerful flow control to your scripts. The rest of this section will show you how to implement them.

Features enabling construction of block statements

The list below briefly presents the features making possible the creation of block statements in your script. Listed first are the more obvious ones, followed by those that are somewhat more esoteric and require a little digging inside the Avisynth documentation and experimenting with test cases to discover them.

Thus, any script written in Avisynth script language can be evaluated by Import. Import returns the return value of the script.
Despite the common misbelief that this can only be a clip, it can actually be any type of variable (a clip, a number, a bool, a string).
Also, as a side-effect of the script evaluation any functions and globals declared inside the imported script are accessible from the caller script, from the point of the Import call and afterwards.

Consider the following Example 3, of a (useless) script that returns some black frames followed by some white frames:

Example 3

c = BlankClip().Trim(0,23)
d = BlankClip(color=$ffffff).Trim(0,23)
b = true
dummy = b ? Eval("""
	k = c       # here comments are allowed!
	l = d
	return k    # this will be stored in dummy
	""") : Eval("""
	k = d
	l = c
	return k    # this will be stored in dummy
	""")
# variables declared inside a multiline string
# are available to the script after calling Eval
return k + l

Variables k, l are not declared anywhere before the evaluation of the if..else block. However, since Eval evaluates the string at the script-level context, it is as if the statements inside the string were written at the script level. Therefore, after Eval() they are available to the script. A few other interesting things to note are the following:

Implementation Guide

The features above can be used to construct block statements in various ways. The most common implementation cases are presented in this section, grouped by block statement type.

The if..else block statement

Using Eval and three-double-quotes quoted strings

This is by far the more flexible implementation, since the flow of text approaches most the "natural" (ie the commonly used in other languages) way of branching code execution.

Using the rather common case illustrated by Example 1, the solution would be (again items in square brackets are optional):

Example 4

[result = ] cond ? Eval("""
    filter1_1
    filter1_2
    ...
    filter1_n
  [ return {result of last filter} ]
    """) : Eval(""" 
    filter2_1
    filter2_2
    ...
    filter2_n
  [ return {result of last filter} ]
    """)

In short, you write the code blocks as if Avisynth script would support block statements and then enclose the blocks in three-double-quotes to make them multiline strings, wrap a call to Eval around each string and finally assemble Eval calls into a ternary operator statement.

The return statements at the end of each block are needed only if you want to assign a useful value to the result variable. If you simply want to execute the statements without returning a result, then you can ommit the return statement at the end of each block.

One important thing to note is that the assignment to result should not in general be removed because then the result of the entire Eval() would be assigned to the last implicit variable. last would then - unless a return statement is included in each block - contained an undefined variable, which most of the time is not what you want.

Therefore, as a rule of thumb:

The following real-case examples illustrate the above:

Example 5

This example uses a dummy variable (and thus no return statement inside strings). All assignemnts are made to script variables.

c = AviSource(...)
...
cond = {expr}
...
dummy = cond ? Eval("""
    text = "single double quotes are allowed inside three-double-quotes"
    pos = FindStr(text, "llo")   # comments also
    d = c.SubTitle(LeftStr(text, pos - 1))
""") : Eval(""" 
    text = "thus by using three-double-quotes you can write expressions like you do in a script"
    pos = FindStr(text, "tes")
    d = c.SubTitle(MidStr(text, pos + StrLen("tes")))
""")
return d

Example 6

This example assigns a different clip to d depending on the framecount of a source clip.

a = AVISource(...)
c = BlankClip().SubTitle("a test case for an if..else block statement")
d = a.Framecount >= c.Framecount ? Eval("""
    a = a.BilinearResize(c.Width, c.Height)
    c = c.Tweak(hue=120)
    return Overlay(a, c, opacity=0.5)
""") : Eval(""" 
    c = c.BilinearResize(a.Width, a.Height)
    a = a.Tweak(hue=120)
    return Overlay(c, a, opacity=0.5)
""")
return d

Example 7

This example is a recode of Example 6 using implicit assignment to the last special variable. Since the result of the entire Eval() is assigned to dummy the implicit assignments to last on each line of the string (including the last line of the string) are preserved and thus the desired result is obtained.

c = BlankClip().SubTitle("a test case for an if..else block statement")
AVISource(...)
dummy = last.Framecount >= c.Framecount ? Eval("""
    BilinearResize(c.Width, c.Height)
    c = c.Tweak(hue=120)
    Overlay(last, c, opacity=0.5)
""") : Eval(""" 
    c = c.BilinearResize(last.Width, last.Height)
    Tweak(hue=120)
    Overlay(c, last, opacity=0.5)
""")

The only disadvantage of this approach is that coding errors inside the string blocks are masked by the Eval() call, since the parser actually parses a single line of code:

[result = ] cond ? Eval("""block 1""") : Eval("""block 2""")

Thus, any error(s) inside the blocks will be reported as a single error happening on the above line. You will not be pointed to the exact line of error as in normal script flow. Therefore, you will have to figure out where exactly the error occured, which can be a great debuging pain, especially if you write big blocks.

Using separate scripts as blocks and the Import function

Using Example 1 as above, the solution would be (again items in square brackets are optional):

Example 8

Code of script file block1.avs:

filter1_1
filter1_2
...
filter1_n

Code of script file block2.avs:

filter2_1
filter2_2
...
filter2_n

Code of main script where the conditional branch is desired:

...
[result = ]cond ? Import("block1.avs") : Import("block2.avs")
...

In short, you create separate scripts for each block and then conditionally import them at the main script.

If you need to pass variables as "parameters" to the blocks, declare them global in your main script and just reference them into the block scripts. The following example demonstrates this:

Example 9

Code of script file block1.avs:

filter1_1(..., param1, ...)
filter1_2(..., param2, ...)
...
filter1_n(..., param3, ...)

Code of script file block2.avs:

filter2_1(..., param1, ...)
filter2_2(..., param2, ...)
...
filter2_n(..., param3, ...)

Code of main script where the conditional branch is desired:

# globals must be defined *before* importing the block script
global param1 = ...
global param2 = ...
global param3 = ...
...
[result = ]cond ? Import("block1.avs") : Import("block2.avs")
...

Using Import() instead of Eval() and three-double-quoted multiline strings has some disadvantages:

On the other hand:

One useful general purpose application of this implementation is to prototype, test and debug a block conditional branch and then recode it (by adding the Eval() and three-double-quotes wrapper code and removing the global keyword before the parameter's declarations) so that a single script using multiline strings as blocks is created. This workaround compensates for the main disadvantage of the Eval() and three-double-quotes implementation.

Using functions (one function for each block)

This is the most "loyal" to the Avisynth script's grammar approach. Using Example 1 as above, the solution would be (again items in square brackets are optional):

Example 10

Function block_if_1()
{
    filter1_1
    filter1_2
    ...
    filter1_n
}

Function block_else_1()
{
    filter2_1
    filter2_2
    ...
    filter2_n
}
...
[result = ]cond ? block_if_1() : block_else_1()
...

In short, you create separate functions for each block and then conditionally call them at the branch point.

If you need to pass variables as "parameters" to the blocks, either declare them global in your main script and just reference them into the functions or - better - use argument lists at the functions. The following example demonstrates this:

Example 11

Function block_if_1(arg1, arg2, arg3, ...)
{
    filter1_1(..., arg1, ...)
    filter1_2(..., arg2, ...)
    ...
    filter1_n(..., arg3, ...)
}

Function block_else_1(arg1, arg2, arg3, ...)
{
    filter2_1(..., arg1, ...)
    filter2_2(..., arg2, ...)
    ...
    filter2_n(..., arg3, ...)
}
...
[result = ]cond \
    ? block_if_1(arg1, arg2, arg3, ...) \
    : block_else_1(arg1, arg2, arg3, ...)
...

Compared to the other two implementations this one has the following disadvantages:

On the other hand:

The if..elif..else block statement

By nesting If..Else block expressions inside the ternary operator, you can create entire if..elseif...else conditional constructs of any level desired to accomodate more complex needs.

A generic example for each if..else implementation presented above is following. Of course, any combination of the three above pure cases is possible.

Using Eval and three-double-quotes quoted strings

The solution would be (again items in square brackets are optional):

Example 12

[result = \]
    cond_1 ? Eval("""
        statement 1_1
        ...
        statement 1_n
    """) : ( \ 
    cond_2 ? Eval(""" # inner a?b:c must be enclosed in parentheses 
        statement 2_1
        ...           # since backslash line continuation is used between Eval blocks
        statement 2_n # place comments only inside the strings
    """) : ( \ 
    ...
    cond_n ? Eval("""
        statement n_1
        ...
        statement n_n
    """) \
    : Eval("""
        statement n+1_1
        ...
        statement n+1_n
    """)...))  # n closing parentheses; 1 for Eval(), n-1 to balance the opening ones

Using separate scripts as blocks and the Import function

The solution would be (again items in square brackets are optional):

Example 13

# here no comments are allowed; every line but the last must end with a \
[result = \]
    cond_1 ? \
        Import("block1.avs") : ( \
    cond_2 ? \
        Import("block2.avs") : ( \
    ...
    cond_n ? \
        Import("blockn.avs") \
    : \
        Import("block-else.avs") \
    )...))  # n-1 closing parentheses to balance the opening ones

Using functions (one function for each block)

The solution would be (again items in square brackets are optional):

Example 14

# here no comments are allowed; every line but the last must end with a \
[result = \]
    cond_1 ? \
        function_block_1({arguments}) : ( \
    cond_2 ? \
        function_block_2({arguments}) : ( \
    ...
    cond_n ? \
        function_block_n({arguments}) \
    : \
        function_block_else({arguments}) \
    )...))  # n-1 closing parentheses to balance the opening ones

The for..next block statement

TODO...

The do..while and do..until block statements

TODO...

Deciding which implementation to use

To be frank, there is no clear-cut answer to this question; it depends on the purpose that the script will serve, your coding abilities and habits, whether there are ready-made components available and what type are they (scripts, function libraries, etc.) and similar factors.

Thus, only some generic guidelines will be presented here, grouped on the type of block statement

The if..else and if..elif..else block statements

The for..next block statement

TODO...

The do..while and do..until block statements

TODO...

References

[1] http://www.avisynth.org/stickboy/ternary_eval.html

[2] http://forum.doom9.org/showthread.php?t=102929

[3] http://forum.doom9.org/showthread.php?p=732882#post732882

Back to IntermediateTips