「语法:控制结构」修訂間的差異

出自NMM Doc
跳至導覽 跳至搜尋
(创建新页面为 '{{模板:正在翻译}} 严格来讲,AviSynth语法只提供了一种控制结构(实际上有两种,另一种是条件运算符号,?...')
 
 
第1行: 第1行:
{{模板:正在翻译}}
 严格来讲,[[语法|AviSynth语法]]只提供了一种控制结构(实际上有两种,另一种是条件[[语法:运算符号|运算符号]],? : ,用在其他地方),那就是 try...catch 声明。
 严格来讲,[[语法|AviSynth语法]]只提供了一种控制结构(实际上有两种,另一种是条件[[语法:运算符号|运算符号]],? : ,用在其他地方),那就是 try...catch 声明。
【【目录】】


== try...catch 声明 ==
== try...catch 声明 ==
第31行: 第27行:
 考虑以下元素:
 考虑以下元素:


[[控制函数|Eval()]]声明允许执行脚本语言任意声明(它的哥们儿Apply能简化用名称调用函数)。
:#[[控制函数|Eval()]]声明允许执行脚本语言任意声明(它的哥们儿Apply能简化用名称调用函数)。
多行字符串,尤其是用三重引号(文字上是“"""”,三个引号序列)包围的多行字符串
:# 多行字符串,尤其是用三重引号(文字上是“"""”,三个引号序列)包围的多行字符串 ,因为允许很自然的书写字母
:#Import声明,允许执行任意脚本,并返回一个值(而且这个值并不一定要是Clip;一个脚本能返回任何类型的值,返回的值可以赋给调用这个脚本的[[语法:脚本变量|变量]]。)。
:#递归(能创建递归函数)。
:#控制函数,尤指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遍字符的序列的函数 ===
例子中,我们用一个来自[http://avslib.sourceforge.net/ 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)
 
[[Category:AviSynth 语法]]

於 2010年2月5日 (五) 18:00 的最新修訂

嚴格來講,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)