語法:控制結構

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