суббота, 21 сентября 2013 г.

Основы Avisynth (часть 1)

Опубликовано в ЖЖ в апреле 2009, актуально для AviSynth 2.58. 
Оригинал

Если вы читали предыдущую статью, вы уже в общих чертах знаете, что такое AviSynth и как он работает. На всякий случай повторюсь. Это нелинейный видеоредактор, не имеющий собственного интерфейса. Результат его работы вы можете увидеть, только открыв соответствующий файл-сценарий в видеоприложении (редакторе или проигрывателе). Поскольку AviSynth только «отдает» кадры другим («конечным») видеоприложениям, он является фреймсервером.
Преимуществ, равно как и недостатков, сторонники и противники использования этой программы могут найти достаточно много. Например, возможность писать скрипты (а впоследствии использовать один и тот же скрипт – шаблон) для сторонников будет «преимуществом». В то же время, необходимость писать скрипты для противников будет «недостатком». Поэтому, насколько нужен AviSynth вам, вы поймете, только начав его осваивать.
В данном материале я не пытаюсь полностью изложить справочный материал «другими словами» или повторить статьи других авторов. В справочном материале есть неточности или моменты, которые рассмотрены поверхностно. Здесь будут подробно рассмотрены в том числе и такие моменты. Поэтому перед чтением данной статьи вы можете ознакомиться со справочным материалом (русский перевод) по адресу http://avisynth.org.ru. Также можно почитать вводную статью по AviSynth Александра Балахнина по адресу http://www.ixbt.com/divideo/avisynth1.shtml
Начнём с обзора вспомогательного софта, без которого в данном случае не обойтись.
1. Редактор скриптов. Во время установки AviSynth предлагает определить файловые ассоциации для двух типов открытия. Открывать (Open) скрипты предлагается через стандартный Блокнот, а проигрывать (Play) – через Media Player 6.4. Действием «по умолчанию» является Open. Я не вижу особых причин использовать в качестве редактора скриптов какое-то другое приложение, в том числе и с подсветкой синтаксиса.
2. Проигрыватель для скриптов AviSynth должен «уметь» открывать видео файлы с помощью DirectShow. Именно поэтому не годятся проигрыватели, «использующие внутренние кодеки», например VLC. Я предпочитаю использовать Virtual Dub.
Скрипты представляют собой обычные текстовые файлы – не надо пугаться такого необычного расширения. Кстати, показ этих самых расширений «для зарегистрированных типов файлов» лучше включить, что облегчит создание новый файлов *.avs.
Заранее создайте папку, в которой будете хранить скрипты, а в будущем, возможно, и шаблоны. Например, D:\AVS_projects
Если вы не знакомы ни с одним языком программирования, возможно, осваивать «программирование» на скрипт-языке AviSynth вам будет трудновато. Хотя при ближайшем рассмотрении язык сложен скорее для программистов, намеревающихся написать что-то сложное. «Прообразом» этого скрипт-языка явился язык программирования Си – здесь используются только похожие обозначения операций. Имена переменных и функций могут быть записаны в любом регистре.
Рассмотрим простейший скрипт, который приводится в справке по AviSynth. Создайте файл test1.avs следующего содержания:
Version()
Пара скобок не обязательна, однако этим подчеркивается тот факт, что Version является фунцией. Пустые скобки означают, что функции не передается никаких параметров. В большом скрипте таких функций без параметров может быть много. Если при этом весь скрипт состоит из двух-трех файлов, то понять, функция перед нами или переменная, может быть сложно. Поэтому для того, чтобы впоследствии скрипт легче читался, функции без параметров лучше всегда писать с парой скобок.
Откроем данный скрипт в Virtual Dub и посмотрим результат. Увидим беззвучный ролик с перечислением тех, кто принимал участие в разработке AviSynth. Легко догадаться, что видео было синтезировано функцией Version(). Таким образом, в простейшем случае скрипт представляет собой набор последовательно выполняющихся инструкций. Таким упрощением с небольшим успехом могут пользоваться новички, пока не начнут сталкиваться со странными ошибками. Например, запишем в test2.avs
Version()
Version()
Вопреки ожиданиям некоторых (а может быть, и многих), результат будет такой же, как и в случае test1.avs. Функция Version() возвращает результат. Результат в AviSynth должен быть присвоен переменной, хотя в данном скрипте мы никакого присвоения не видим, оно присутствует. Формальное правило такого присвоения формулируется следующим образом: если результат выполнения функции не присваивается явно какой-либо переменной, он присваивается неявно переменной last. Посмотрим, как будет выглядеть скрипт с учётом этого правила:
 last= Version()
 last= Version()
Теперь видно, что одной и той же переменной дважды присваивается результат выполнения функции Version(). Однако вы будете удивлены, что при попытке открыть такой скрипт будет выдана ошибка. Дело в том, что мы не учли второе формальное правило (а может быть и баг): вызывающему приложению возвращается клип, содержащийся в переменной last только в том случае, если последним действием было неявное присваивание значения last. В противном случае скрипт не будет воспроизведен. Можно явно указать, какую переменную мы хотим «увидеть» в результате. Для этого в конце скрипта можно указать return(x) (нельзя записать выражение last=x, поскольку здесь будет явное присваивание). Если в скрипте «последняя» переменная не last, то можно просто добавить строчку return(last), чтобы не думать куда «спрятать» ту переменную. В русском переводе справки по AviSynth, указано, что при отсутствии return() в конце скрипта, в любом случае возвращается last. Однако, как видим, это не всегда верно. С учетом вышесказанного перепишем скрипт:
 last= Version()
 last= Version()
 return(last)
Теперь проблем с открытием такого скрипта не возникнет.
Ознакомившись с этими двумя важными правилами (а будет еще одно), можно приступать к более глубокому знакомству с синтаксисом. Одной из важнейших характеристик любого языка, хотя бы отдаленно напоминающего язык программирования, является поддержка различных типов данных. В скрипте AviSynth можно встретить пять различных типов данных. Это clip (клип), string (строка символов), float (числа с плавающей запятой), int (целые числа), bool (булев тип). Пользовательских типов данных нет. Неявное преобразование типа не допускается, за редкими исключениями (допускается неявное преобразование между типами int и float). Поэтому в каждом конкретном случае лучше использовать соответствующие функции приведения. Рассмотрим пример:
 x=4/3
Казалось бы, результат должен быть 1.33(3). Однако результат будет равен 1, поскольку AviSynth полагает, что было деление двух целых чисел. Он также предположит, что х – также целое число. Если даже записать выражение с функцией приведения типа: х=float(4/3), в результате будет получено 1.0. Такая вот «неочевидность» роднит скрипт-язык AviSynth с Си. В данном случае верным будет один из следующих вариантов:
 x=float(4)/float(3)
 x=4.0/3.0
Как видите, чтобы указать факт, что число нужно рассматривать как float, достаточно записать его с десятичной точкой. Резонный вопрос, а что получится, если часть операндов будет типа int, а часть – float? Ответ прост: все они будут приведены к типу float. Например, выражение x=4/3.0 будет равносильно x=float(4)/3.0. Иногда целые числа удобнее записывать в шестнадцатеричной форме. Делается это с помощью префикса $, например:
 x=$10A
В этом примере переменной х было присвоено значение 266.
Меньше всего «подводных камней» – при работе с переменными типа string. Присутствует практически весь «привычный» набор операций, как то: конкатенация (сложение), поиск подстроки, копирование подстроки так далее. Однако нельзя обращаться к отдельным символам по их индексам.
С переменными типа clip вроде бы все понятно. Такие переменные не могут быть преобразованы к другому типу. В остальном эти переменные похожи на string, то есть определены операции присваивания, сложения и сравнения, а также есть функции позволяющие извлекать отдельные кадры и получать различные характеристики клипа. Разве что нет функции или операции, которая позволила бы получить «пустой» клип, подобно пустой строке.
Переменные булева типа должны равняться либо true, либо false. Вопреки ожиданиям, численные аргументы не будут преобразованы. Такие переменные вообще не могут быть преобразованы ни к одному типу. Хотя это и не страшно. При желании такое преобразование можно организовать самостоятельно. Например:
 x= (bx==true) ? 1 : 0
В этом выражении x присваивается единица в случае, если bx истинно (двойной знак «=» обозначает операцию сравнения). Сравнивать bx с true совсем не обязательно. Ведь стоящее в скобках выражение само по себе должно возвращать значение булева типа. Поэтому можно переписать:
 x= bx ? 1 : 0
Однако это не совсем преобразование. Мы всего лишь присвоили значение другой переменной. Разберём детально это выражение. Оператор «? :» опять же заимствован из Си и является единственной допустимой формой «if … then … else …» в скрипт-языке AviSynth. Стоящее до знака «?» выражение проверяется на истинность (вместо выражения может быть булева переменная). Если оно истинно, переменной х будет присвоено значение выражения после знака «?», в противном случае – значение выражения после знака «:». Наберемся наглости и напишем вот такое выражение:
 bx= bx ? 1 : 0
Программистов возмутит тот факт, что такое выражение с точки зрения AviSynth верное. Теперь переменная bx имеет тип int. В скрипте любой переменной может быть присвоено значение другого типа. Переменная, таким образом, как бы создается заново.
Следует обратить внимание, что выражения до и после знака «:» должны присутствовать. Но как же тогда быть, если нам нужно только одно из двух действий? «Бездействие» в данном случае рассматривать как действие, которое не изменяет переменной. То есть если переменная была уже определена ранее, выражение условного присваивания будет выглядеть следующим образом:
 x=10
 …
 x= (z==0) ? 5 : x
Значение х изменится только в том случае, если z равно нулю. В противном случае оно не изменится. Если х не было определено заранее, можно использовать конструкцию вида:
 x= (z==0) ? 5 : NOP()
Функция NOP() возвращает «ничего» – переменную, которая ничего не означает, иначе называемую NULL. Так написано в справке. В действительности такой встроенной переменной не существует. Проведя нехитрые эксперименты, можно обнаружить, что NOP() возвращает число 0. Отсюда следует, что смысла у данной функции нету. Так что лучше заранее определить значение переменной «по умолчанию».
В случае, когда в условном выражении отсутствует переменная, которой присваивается значение (в данном случае x=), значение будет присвоено переменной last.
Если условный оператор (хоть и неполноценный – он используется только для присваивания значений переменных, а не для перехода к определенным меткам) присутствует, то никаких операторов для организации цикла в AviSynth нету. Несмотря на это, циклы в скрипте организовывать можно. Как это делать, мы рассмотрим позже.
Таким образом, скрипт представляет собой последовательность операций присваивания без возможности перехода в произвольную точку, что, как кажется на первый взгляд, является серьёзным препятствием для построения циклических и разветвляющихся алгоритмов обработки. Этим cкрипт AviSynth очень напоминает последовательность фильтров Virtual Dub.
Как вы заметили, в скрипт-языке AviSynth нет разделителей, подобных «;» в Си. Как быть, если я захочу записать несколько переменных в одну строку? Например, в шаблоне скрипта мне нужно записать четыре переменные (число пикселей, на которое нужно «подрезать» каждую сторону кадра) таким образом, чтобы при взгляде на них я понимал, какую из сторон обозначает каждая из них. Запись, при которой переменные расположились бы согласно сторонам, будет самой удобной. И оказывается, такое возможно. Разделителем между переменными в одной строке может выступать пробел или символ табуляции. Таким образом, можно спокойно записать в скрипте-шаблоне:
  ct=0
 cl=0 cr=0
  cb=0
В скрипт-языке AviSynth существует так называемая ООП-форма записи (ООП – объектно-ориентированное программирование), наличие которой может показаться излишеством. В чем суть этой записи, сейчас разберём на примере. Пусть есть такая последовательность операций (из справки AviSynth):
 Version()
 ReduceBy2()
 FadeOut(10)
Результатом будет клип, площадь которого уменьшена в четыре раза с плавным «затуханием» в конце в течение 10 кадров. ООП-форма записи будет выглядеть следующим образом:
 Version().ReduceBy2().FadeOut(10)
Такая форма записи более компактна. Впрочем, мы могли бы записать и так:
 Version() ReduceBy2() FadeOut(10)
ООП-форма записи кажется излишеством, пока речь идёт о неявном присваивании переменной last. Если мы захотим явно присвоить результат какой-то переменной, преимущество ООП-формы записи станет очевидным. Рассмотрим еще раз тот же пример:
 с1=Version()
 с1=ReduceBy2(с1)
 с1=FadeOut(с1,10)
Здесь видно, что каждой функции теперь нужно ещё и сообщать о том, какой клип мы модифицируем. Это будет серьёзным источником ошибок. Ничего не изменится и при записи с разделяющими пробелами:
 с1=Version() с1=ReduceBy2(с1) с1=FadeOut(с1,10)
А вот ООП-форма записи практически не изменится
 с1=Version().ReduceBy2().FadeOut(10)
Здесь только для первой функции в последовательности нужно указать, над каким клипом будут производиться действия (первой функцией является Version(), для которой клип указать нельзя, поэтому в данном примере с1 в параметрах функций вовсе отсутствует). Когда значение присваивается с использованием условных операторов, то без ООП формы записи и вовсе не обойтись. Конечно, это отчасти лукавство. Данный пример можно переписать вот так:
 c1=FadeOut(ReduceBy2(Version()),10)
Но такая вложенность не добавляет скрипту читаемости (скорее, наоборот), и будет более вероятным источником ошибок.
Рассмотренные выше примеры с использованием переменной last и «нашей» переменной c1 «нуждаются» в формулировании третьего формального правила, касающегося неявной передачи переменной last функциям: переменная last неявно передается любой функции. Даже если функция и не ожидает аргумента типа clip, переменная last неявно передается. В этом случае такой переменной просто нельзя будет воспользоваться.

Комментариев нет: