语法:控制结构

来自NMM Doc
跳到导航 跳到搜索

严格来讲,AviSynth语法只提供了一种控制结构(实际上有两种,另一种是条件运算符号,? : ,用在其他地方),那就是 try...catch 声明。

try...catch 声明

try...catch声明允许执行代码时产生可能的错误,并当错误发生时处理此错误。

完整的 try...catch语法表述如下:

try {
    ...
    statements
    ...
}
catch(err_msg) {
    ...
    statements
    ...
}

在catch区块中的字符串err_msg包含了AviSynth在执行try区块时产生的错误信息。这些文字和我们熟悉的当脚本发生严重错误的时候信息盒子里显示的错误信息一样。

可以查询这些文字(也就是普通的字符串变量)去查找特定的子字符串,判断发生的错误。这个技术可以产生有价值的结果(例子见此)。

其他(广义上)的控制结构

广义上看,AviSynth语法中有许多这样的元素,尽管本身不是控制结构,但当他们组合起来后,就成为了控制结构的等价形式。这些结构最后能完成复杂的编程任务。

考虑以下元素:

  1. Eval()声明允许执行脚本语言任意声明(它的哥们儿Apply能简化用名称调用函数)。
  2. 多行字符串,尤其是用三重引号(文字上是“"""”,三个引号序列)包围的多行字符串,因为允许很自然的书写字母
  3. Import声明,允许执行任意脚本,并返回一个值(而且这个值并不一定要是Clip;一个脚本能返回任何类型的值,返回的值可以赋给调用这个脚本的变量。)。
  4. 递归(能创建递归函数)。
  5. 控制函数,尤指Assert、Select、Default和NOP


前三个允许创建简单的区块声明,像分支区块(类似于 if...else控制结构)。下面有个简单的例子(上面的链接有详细的说明):

# define different filtering based on this flag
heavy_filtering = true
AviSource("c:\sources\mysource.avi")
# assign result of Eval() to a variable to preserve the value of the last special variable
dummy = flag ? 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)
Subtitle(stext)

第四个是一个语法提供的通用工具,可以批量运算、计算任意复杂的表达式。同时也是目前唯一的工具。

但这并不意味着AviSynth限制了我们的创造力;其实递归和赋值可以做到有循环结构的命令语言能做到的一切。只是大多数没有编程技巧的人不熟悉这种方法,原因在于函数程序设计不是ICT(信息通信技术)的技术的主修课,而是更加专业的科目。

第五种或多或少地要用在上述的脚本之内,以控制输入、设定数值、正确地运行某些结构。

来看几个例子吧,理解一些普遍的必须遵守的原则,才能最终理解、掌握、达到目的。

例1:创建一个返回重复n遍字符的序列的函数

例子中,我们用一个来自AvsLib的现成的例子:

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) : ""
}

递归就是在return声明中调用自身的用法。为了妥善处理,递归调用序列最后必须返回单个值。“strict”参数仅仅追加限制了使用函数时万一出错,只返回一个空字符串(而不是丢出来一个错误)。

例2:创建一个选择Clip里任意间隔的帧的函数

SelectEvery这类函数,能高效地选择任意帧组合,但是要求前后的帧之间需要有固定z帧数的间隔(换句话说,组合是周期性选择的)。为了以可变的间隔选择帧(非周期性地),我们必须借助于有递归的函数。

下面的函数是一个通用的帧选择滤镜,为了确定任意的间隔,需要用到一个用户自定义函数(func必须是次函数名),这个函数从区间[s_idx..e_idx)映射到帧的组合。func必须以一个整数作为参数,并且返回一个对应的被映射的帧数。

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)
}

递归的步骤(return声明中的前一个条件分支)的一部分仍然是包含函数本身的表达式。一般并不需要把递归步骤安排在分支里(依具体任务而定,也可以仅仅调用一下而已),但是当构建复杂的结构时,这种方法最常用。

Apply调用用户自定义函数计算帧数(更具鲁棒性的做法是用try...catch声明调用)。如果计算出的帧数在Clip帧数的范围内,则相应的帧就添加到结果上,否则递归中止。

下面的例子把会把这个设计阐述完整:

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

AviSource("my_200_frames.avi")
# 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)