Blockanweisungen

Hintergrund

Auf den ersten Blick hinterlässt die Avisynth Dokumentation den Eindruck, dass abgesehen von Funktionsdefinitionen, Blockbefehle im Avisynth Skript nicht möglich sind. Allerdings gibt es Besonderheiten der Sprache, die den Bau von Block-Anweisungen ermöglichen, die bis heute unverändert geblieben sind. Wahrscheinlich wird dies auch in die Zukunft so bleiben, da Block-Anweisungen sehr nützlich sind, und die Möglichkeiten der Scriptsprache ausweiten.

In der Tat sind in den meisten Programmier-und Skriptsprachen Block-Anweisungen sehr nützliche Werkzeuge, um eine Reihe von Operationen zusammen zu fassen, die gemeinsam unter bestimmten Voraussetzungen ausgeführt werden sollen. Sie sind auch in Avisynth-Skripten nützlich.

Nehmen wir zum Beispiel an, dass du nach einer anfänglichen Verarbeitung deiner Videodatei, diese unterschiedlich weiter verarbeiten willst (zum Beispiel willst du eine andere Serie von Filtern verwenden oder den selben Filtersatz mit einer anderen Reihenfolge anwenden) und zwar basierend auf einer bestimmten Bedingung, die während der ersten Verarbeitungsstufe berechnet wird. Die Bedingung ist abgelegt in der booleschen Variable cond.

Anstatt eine hässliche Reihe von aufeinanderfolgenden bedingten Zuweisungen zu machen mittels des bedingten (ternären)  Operator, ?:, wie in Beispiel 1 unten gezeigt wird (Elemente in eckigen Klammern sind nicht erforderlich, wenn du die implizite Sondervariable last nutzt, um das Ergebnis darin zu erhalten) ...:

Beispiel 1

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

... wäre es schön, wenn man zwei Blöcke von Filter-Operationen und eine Verzweigung in einem einzigen Schritt konstruieren könnte, wie im (idealen) Beispiel 2 unten:

Beispiel 2

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

Tatsächlich ist etwas möglich, dass sich dieser Konstruktion (und anderen) nähert - vielleicht mit einigen Einschränkungen, aber du wirst dennoch in der Lage sein, stärkere Flusskontrolle in deinen Skripten auszuüben. Der Rest dieses Abschnitts wird zeigen, wie man das umsetzt.

Features, die die Konstruktion von Blockbefehlen erlauben

Die folgende Liste stellt kurz die Features vor, die die Schaffung von Blockbefehlen in deinem Skript ermöglichen. Zuerst sind die offensichtlichen Möglichkeiten aufgeführt, gefolgt von denen, die etwas versteckter sind und die es erfordern, ein wenig in der Avisynth-Dokumentation zu graben und mit Testfällen zu experimentieren, um sie zu entdecken.

Betrachte das folgende Beispiel 3, eines (nutzlosen) Scripts, dass einige schwarze Frames gefolgt von einigen weißen zurückgibt:

Beispiel 3

 c = BlankClip().Trim(0,23)
d = BlankClip(color=$ffffff).Trim(0,23)
b = true
dummy = b ? Eval("""
k = c # Hier sind Kommentare erlaubt!
l = d
return k # Das wird in dummy gespeichert
""") : Eval("""
k = d
l = c
return k # Das wird in dummy gespeichert
""")
# Variablen, die deklariert werden in einem Multizeilen-String
# sind erreichbar vom Script nach dem Aufruf von Eval
return k + l

Die Variablen k, l sind nirgendwo vor der Auswertung des if...else Blocks deklariert. Da jedoch Eval die Zeichenfolge im Skript-level Kontext auswertet, ist es so, als ob die Anweisungen innerhalb des Strings auf der Skript-Ebene geschrieben wurden. Deshalb sind sie nach Eval() für das Skript verfügbar.

Ein paar andere interessante Dinge sind die folgenden:

Anleitung

Die oben beschriebenen Features können verwendet werden, um Block Aussagen auf verschiedene Weise zu konstruieren. Die häufigsten Anwendungsfälle werden in diesem Abschnitt vorgestellt, gruppiert nach dem Typ der Blockanweisung.

Das if..else Blockstatement

Eval() mit Strings in dreifachen Doppelanführungestrichen verwenden

Dies ist bei weitem die flexiblere Implementierung, da der Textfluss hier am meisten der "natürlichen" (d.h. üblicherweise in anderen Sprachen verwendetem) Art der Verzweigung von Codeausführung ähnelt.

Der ziemlich übliche Fall aus Beispiel 1 würde dann so gelöst werden (Elemente in eckigen Klammern sind optional):

Beispiel  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} ]
""")

Kurz ausgedrückt, schreibst du die Code-Blöcke so, als ob Avisynth script Block-Anweisungen unterstützen würde und dann schließt du die Blöcke in dreifachen doppelten Anführungszeichen ein, um sie zu mehrzeiligen Strings zu machen, wickelst einen Aufruf  von Eval um jeden String und schließlich fasst du die Eval Aufrufe in einem bedingten Operatorstatement zusammen.

Die return-Anweisungen am Ende jedes Blocks werden nur benötigt, wenn du der result Variable einen nützlichen Wert zuweisen willst. Wenn du einfach nur die Anweisungen ausführen lassen willst, ohne ein Rückgabeergebniss zu brauchen, kannst du die return-Anweisung am Ende jedes Blocks weglassen.

Es ist wichtig anzumerken, dass der implizite Einsatz der Sondervariable last wie gewohnt in den Eval Blöcken weiter funktioniert. Wenn das Ergebnis von Eval einer Variable zugewiesen wird, wird last nicht durch den letzten Ausdruck im Block aktualisiert (mit oder ohne return-Anweisung), aber sie wird aktualisiert (falls zutreffend) bei den anderen Aussagen im Block.

Wenn die Block-Anweisung ein Ergebnis erzeugt, das du weiter verwenden möchtest, wird das übersichtlichter, wenn du eine Zeile return {result} als letzte Zeile eines jeden Blocks einfügst, aber das Schlüsselwort return ist nicht zwingend notwendig.

Die folgenden echten Fallbeispiele veranschaulichen das oben gesagte:

Beispiel  5 In diesem Beispiel werden die Ergebnisse Scriptvariablen zugewiesen, so dass sich last nicht ändert.

c = AviSource(...)
...
cond = {expr}
...
cond ? Eval("""
text = "Einfache doppelte Anführungsstriche sind innerhalb dreifacher Anführungsstriche erlaubt"
pos = FindStr(text, "laub") # Kommentare ebenfalls
d = c.Subtitle(LeftStr(text, pos - 1))
""") : Eval("""
text = "Daher kannst du mit Hilfe von Dreifach-Anführungsstrichen Ausdrücke wie in einem Skript schreiben"
pos = FindStr(text, "lfe")
d = c.SubTitle(MidStr(text, pos + StrLen("lfe")))
""")
return d

Beispiel 6 Dieses Beispiel weist d einen anderen Clip zu abhängig von der Framecount eines Quellclips.

a = AviSource(...)
c = BlankClip().Subtitle("Ein Test für einen if..else Blockbefehl")
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

Beispiel  7 Dieses Beispiel ist eine Neucodierung von Beispiel 6, das impliziter Zuordnung zur Sondervariable last verwendet. Da das Ergebnis des gesamten Eval() nicht einer anderen Variable zugeordnet wird, bleibt die impliziten Zuordnung zu last in jeder Zeile des Strings erhalten (einschließlich der letzten Zeile des Strings) und somit wird das gewünschte Ergebnis erreicht.

c = BlankClip().SubTitle("Ein Test für einen if..else Blockbefehl")
AviSource(...)
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)
""")

Der einzige Nachteil des Eval Ansatzes ist, dass Codierungsfehler innerhalb des Stringblocks durch den Eval()-Aufruf maskiert sind, da der Parser tatsächlich eine einzige Zeile Code analysiert:

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

Daher werden alle Fehler im Inneren des Blocks als ein einziger Fehler gemeldet, der in der oberen Zeile liegt. Du wirst also nicht auf die genaue Position der Fehler hingewiesen, wie es im normalen Skript der Fall wäre. Daher musst du selbst herausfinden, wo genau der Fehler aufgetreten ist. Das kann einigen Schmerz beim Debugging hervorrufen, besonders wenn du große Blöcke schreibst.

Separate Scripts als Blöcke mittels der Import() funktion verwenden

Wenn wir wieder Beispiel 1 von oben verwenden, wäre die Lösung (Ausdrücke in eckigen Klammern sind wieder optional):

Beispiel  8 Code von Scriptdatei block1.avs:

filter1_1
filter1_2
...
filter1_n

Code von Scriptdatei block2.avs:

filter2_1
filter2_2
...
filter2_n

Code des Hauptscripts, wo die bedingte Verzweigung erfordert wird:

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

Kurz gesagt, erstellst du separate Skripte für jeden Block und importierst sie dann bedingt im Haupt-Skript.

Wenn du Variablen als "Parameter" an die Blöcke übergeben musst, deklariere diese in deinem Haupt-Skript und verweise einfach auf sie in den Block-Skripten. Das folgende Beispiel verdeutlicht das:

Beispiel  9 Code von Scriptdatei block1.avs:

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

Code von Scriptdatei block2.avs:

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

Code des Hauptscripts, wo die bedingte Verzweigung erfordert wird:

# Variablen müssen definiert werden *bevor* das Blockscript importiert wird
param1 = ...
param2 = ...
param3 = ...
...
[result = ]cond ? Import("block1.avs") : Import("block2.avs")
...

Das Verwenden von Import() anstelle von Eval() mit mehrzeiligen Strings in drei doppelten Anführungszeichen hat einige Nachteile:

Andererseits:

Eine nützliche Allzweckanwendung dieser Implementierung ist es, eine bedingte Blockverzweigung zu entwerfen, zu testen und zu debuggen und sie danach im Skript direkt zu recoden (indem du Eval() und drei doppelte Anführungszeichen Wrapper-Code verwendest und das Schlüsselwort global vor den Parameterdeklarationen entfernst), so dass ein einziges Skript mit mehrzeiligen Strings als Blöcken entsteht. Dieser Workaround kompensiert den Hauptnachteil von Eval() mit drei doppelten Anführungszeichen.

Funktionen verwenden (eine Funktion für jeden Block)

Dies ist der am meisten "loyale" Ansatz für die Avisynth Skript-Syntax. Für Beispiel 1 von oben wäre die Lösung (wieder sind Elemente in eckigen Klammern optional):

Beispiel  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()
...

Kurz gesagt, erstellst du separate Funktionen für jeden Block und dann rufst du sie auf über eine bedingte Verzweigung.

Wenn du Variablen als "Parameter" an die Blöcke übergeben musst, deklariere diese in deinem Haupt-Skript als globale Variablen und verweise auf sie in den Funktionen oder - besser - verwende Argument-Listen bei den Funktionen:

Beispiel  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, ...)
...

Im Vergleich zu den beiden anderen Implementierungen hat diese folgende Nachteile:

Andererseits:

Das if..elif..else Blockstatement

Durch die Verschachtelung von If .. Else Blöcken innerhalb des bedingten Operators, kannst du ganze bedingte  if.. elseif ... else Konstrukte mit jeder  gewünschten Verschachtelungstiefe bauen, um es komplexeren Aufgaben anzupassen.

Ein allgemeines Beispiel für jede if..else Umsetzung, die oben vorgestellte wurde, folgt hier. Natürlich ist jede Kombination der drei oben genannten reinen Fälle möglich.

Eval() mit Strings in drei Anführungsstrichen verwenden

Die Lösung wäre (wieder sind Elemente in eckigen Klammern optional):

Beispiel  12

[result = \]
cond_1 ? Eval("""
statement 1_1
...
statement 1_n
""") : [(] \
cond_2 ? Eval(""" # innere a?b:c eingeschlossen in Klammern für mehr Klarheit (optional)
statement 2_1
... # da Backslash Zeilenforstsetzung zwischen Eval Blöcken verwendet wird
statement 2_n # Kommentare nur innerhalb der Strings schreiben
""") : [(] \
...
cond_n ? Eval("""
statement n_1
...
statement n_n
""") \
 : Eval("""
statement n+1_1
...
statement n+1_n
""")[...))] # 1 Schließende Klammer für Eval() + n-1 , um die öffnenden auszugleichen (falls verwendet)

Separate Scripte als Blöcke mit der Import() Funktion verwenden

Die Lösung wäre (wieder sind Elemente in eckigen Klammern optional):

Beispiel  13

# hier sind keine Kommentare erlaubt; jede Zeile außer der letzten muss enden mit \
[result = \]
cond_1 ? \
Import("block1.avs") : [(] \
cond_2 ? \
Import("block2.avs") : [(] \
...
cond_n ? \
Import("blockn.avs") \
 : \
Import("block-else.avs") \
)...)) # n-1 schließende Klammern, um die öffnenden auszugleichen

Funktionen verwenden (eine Funktion für jeden Block)

Die Lösung wäre (wieder sind Elemente in eckigen Klammern optional):

Beispiel  14

# hier sind keine Kommentare erlaubt; jede Zeile außer der letzten muss enden mit \
[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 schließende Klammern, um die öffnenden auszugleichen

Das for..next Blockstatement

Das Problem dabei ist, die for..next Schleife in einer Weise zu implementieren, bei der der Zugriff auf die lokalen Variablen möglich ist, so dass Änderungen in Variablen im lokalen Bereich der Schleife dem Aufrufer zugänglich gemacht werden können, wenn sie beendet ist. Denn auf diese  Weise arbeitet die  for..next Schleife in den meisten Programmiersprachen. Darüber hinaus muss es Wege geben, aus der Schleife auszusteigen, bevor sie beendet ist (d.h. Ausbruch aus der Schleife).

Natürlich gibt es die Alternative, die  for..next Schleife in einer Weise zu implementieren, die keinen Zugriff auf lokale Variablen hat. Dies ist  in AviSynth einfacher, weil es durch eine Funktion implementiert werden kann, aber es ist auch weniger nützlich. Doch in vielen Fällen wäre es angemessen, so ein Konstrukt zu verwenden und so wollen wir es hier zeigen.

For..Next Schleife mit Zugriff auf Variablen im lokalen Bereich

  1. Verwende eine ForNext(start, end, step, blocktext) -Funktion, um einen mehrzeiligen String (ein Skript) anzulegen, der in der Schleife dann eine Reihe von Anweisungen entrollt und dann
  2. verwende Eval(), um das Skript im aktuellen Bereich auszuführen.

Der blocktext ist ein Skript-Text, in der Regel ein mehrzeiliger String in dreifachen Anführungszeichen, der die Anweisungen enthält, die in jeder Schleife ausgeführt werden sollen, zusammen mit Sondervariablen (z.B.  ${i} für den Schleifenzähler), die tatsächlich durch die ForNext Funktion ersetzt werden durch den aktuellen Wert(e) in jeder Schleife. Die StrReplace() -Funktion eignet sich besonders für die Ersetzen-Aufgabe.

Ein wenig Tweaking ist erforderlich, um die break-Anweisung zu implementieren, der abgerollte String muss so konstruiert sein, dass, wenn das break -Flag gesetzt ist der Rest des Codes übersprungen wird.

Das folgende Proof-of-concept Beispiel demonstriert die Prozedur:

a = AviSource("c:\irgendwas.avi")
cnt = 12
b = a.Trim(0,-4)
cond = false

# hier wollen wir das Folgende machen
# for (i = 0; i < 6; i++) {
# b = b + a.Trim(i*cnt, -4)
# cond = b.Framecount() > 20 ? true : false
# if (cond)
# break
# }

return b

Um das in AviSynth umzusetzen, muss unser Skript mit der ForNext Schleife so aussehen:

a = AviSource("c:\irgendwas.avi")
cnt = 12
b = a.Trim(0,-4)
cond = false

block = ForNext(0, 5, 1, """
b = b + a.Trim(${i}*cnt, -4)
cond = b.Framecount() > 20 ? true : false
${break(cond)}
""")

void = Eval(block)
return b

und die Ausgabe von ForNext mit den obigen Argumenten solle so etwas sein wie dies hier (das einzige Problem ist, dass Stringliterale nicht innerhalb des Blocktextes geschrieben werden können):

"""
__break = false
dummy = __break ? NOP : Eval("
b = b + a.Trim(0*cnt, -4)
cond = b.Framecount() > 20 ? true : false
__break = cond ? true : false
")
dummy = __break ? NOP : Eval("
b = b + a.Trim(1*cnt, -4)
cond = b.Framecount() > 20 ? true : false
__break = cond ? true : false
")
dummy = __break ? NOP : Eval("
b = b + a.Trim(2*cnt, -4)
cond = b.Framecount() > 20 ? true : false
__break = cond ? true : false
")
dummy = __break ? NOP : Eval("
b = b + a.Trim(3*cnt, -4)
cond = b.Framecount() > 20 ? true : false
__break = cond ? true : false
")
dummy = __break ? NOP : Eval("
b = b + a.Trim(4*cnt, -4)
cond = b.Framecount() > 20 ? true : false
__break = cond ? true : false
")
dummy = __break ? NOP : Eval("
b = b + a.Trim(5*cnt, -4)
cond = b.Framecount() > 20 ? true : false
__break = cond ? true : false
")
"""

FORTSETZUNG FOLGT...

For..Next Schleife ohne Zugriff auf die Variablen im lokalen Bereich

Wenn wir auf die Variablen de lokalen Bereichs nicht zugreifen müssen, ist die Umsetzung einfach:

  1. Erzeuge ein AVSLib array mit den passenden Schleifenwerten.
  2. Definiere die benötigten globalen Variabelen (zum Beispiel ein boolesches Flag, um den Block umgehend zu verlassen, wenn true).
  3. Packe den Code des Blocks in eine Funktion.
  4. Verwende einen Array operator , um den Block mit jedem Schleifenwert auszuführen.

FORTSETZUNG FOLGT...

Das do..while und do..until Blockstatement

NOCH ZU ERLEDIGEN...

Entscheiden, welche Implementation zu verwenden ist

Um ehrlich zu sein, gibt es keine klare Antwort auf diese Frage. Es hängt von dem Zweck ab, dem das Skript dient, von deinen Coding-Fähigkeiten und von Gewohnheiten, ob schon vorgefertigte Komponenten zur Verfügung stehen und welcher Art sie sind (Skripte, Funktions-Bibliotheken , etc.) und ähnlichen Faktoren.

Daher werden hier nur einige allgemeine Richtlinien vorgestellt, gruppiert nach der Art der Block-Anweisung

Das if..else und if..elif..else Blockstatement

Das for..next Blockstatement

NOCH ZU ERLEDIGEN...

Das do..while und do..until Blockstatement

NOCH ZU ERLEDIGEN...

Referenzen

[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


Zurück zur Scriptreferenz.

$Date: 2008/12/21 09:23:02 $