Pull to refresh

Снегопад с помощью фильтров FFmpeg

Reading time 6 min
Views 18K
FFmpeg — мощное ПО со большим набором возможностей. В статье я постараюсь рассказать о немного необычном применении фильтров ffmpeg и о том что можно сделать используя исключительно их. Видео ниже сделано с помощью 1 команды ffmpeg (ни один графический редактор не пострадал).


Я буду использовать последнюю версию ffmpeg, собранную из git. Так что, если вы хотите чтобы мои примеры работали, рекомендую собрать из ветки master. Сборка и установка тривиальна и описана на сайте ffmpeg.org, при ./configure не забудьте указать "--enable-libfreetype".

Для начала нам нужно создать фон для нашего видео и мы будем использовать средства ffmpeg. Дело в том что ffmpeg позволяет использовать в качестве входного источника не только реальные устройства (например, видео-камеры или sdi-карты) и файлы, но и может использовать собственное виртуальное устройство, которое называется lavfi.

FFmpeg содержит в себе несколько готовых фильтров, которые генерируют видеоизображение. Среди них:
  • testsrc — тестовый экран с таймером и меняющуйся строкой
  • cellauto — заполнение экрана случайными черно-белыми ячейками
  • life — заполнение экрана алгоритмом типа игры life
  • mandelbrot — фрактальное множество Мандельброта
  • rgbtestsrc — 3 цветных полосы
  • smptebars — несколько цветных полос

Полный список фильтров можно посмотреть командой
ffmpeg -filters

Вот как они выглядят:


Поскольку я указал что буду использовать исключительно ffmpeg, то и это изображение я сгенерировал с помощью 1 команды ffmpeg. Вот она:
ffmpeg \ 
-f lavfi -i "testsrc=s=320x240" \ #Указываем все входные lavfi источники и ставим им размер в 320x240
-f lavfi -i "cellauto=s=320x240" \
-f lavfi -i "life=s=320x240" \
-f lavfi -i "mandelbrot=s=320x240" \
-f lavfi -i "rgbtestsrc=s=320x240" \
-f lavfi -i "smptebars=s=320x240" \
-filter_complex \ 
"[0:0]pad=iw*6:ih[a];\ #Делаем падинг изображений с помощью фильтра pad
[1:0]pad=iw:ih[b];\
[2:0]pad=iw*2:ih[c];\
[3:0]pad=iw*3:ih[d];\
[4:0]pad=iw*4:ih[e];\
[5:0]pad=iw*5:ih[f];\
[a][b]overlay=w[x];\ # Склеиваем изображения с помощью фильтра overlay
[x][c]overlay=w[y];\
[y][d]overlay=w[z];\
[z][e]overlay=w[v];\
[v][f]overlay=w" -vframes 1 all_filters.jpg

Хотя я сделал картинку, с помощью этой команды можно также объединить 6 видео в ряд или сделать «мозайку», нужно лишь подставить в качестве входных источников видео-файлы и изменить параметры для фильтров pad и overlay. Пробежимся по опциям:
  • -f lavfi — входной формат для ffmpeg — виртуальное устройство lavfi
  • -i "<filter-name>=<filter-options>" — использовать в качестве входных файлов фильтр с этими опциями
  • -filter_complex — далее мы укажем граф фильтров
  • [0:0]pad=<pad-options> — используем входной источник 0, поток 0 и применяем к нему фильтр pad
  • iw/ih — внутренние алиасы ffmpeg, которые указывают ширину и высоту входного изображения
  • iw*6:ih[a] — увеличиваем ширину изображения в 6 раз и присваиваем этому потоку имя «a»
  • [a][b]overlay=w[x] — применяем фильтр overlay к потокам «a» и «b» и присваиваем этому потоку имя «x»
  • vframes 1 — количество видеокадров 1, т.к. хотим получить картинку, а не видео
  • all_filters.jpg — имя файла для сохранения картинки, расширения ffmpeg понимает, так что если укажите .mp4, ffmpeg выдаст вам mp4 контейнер

И так, создадим фон для нашего видео:
ffmpeg2 -f lavfi -i "color=lightblue" -t 30 -y blue.ts 

Тут все тоже самое что и в примере с картинками, за исключением того, что здесь мы создаем видео и вместо фильтров-генераторов, используем простой фильтр цвета. Помимо количества кадров, вы можете задать продолжительность видео через ключ "-t". По умолчанию ffmpeg задаст fps выходного потока равным 25. Поскольку я указал расширение выходному файлу .ts, то ffmpeg будет использовать контейнер MPEG2TS, а кодек mpeg2video.

Теперь добавим снежинку. Для этого будем использовать фильтр drawtext. Да, да, мы нарисуем снежинку текстом, вернее специальным юникод символом "" (U+2744).

Команда, которая это делает:
ffmpeg -f lavfi -i "color=lightblue" \
-vf "drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=50:text='':fontcolor=white:x=sin(n/10)*30+w/2:y=n" \
-t 5 -y snow.gif

Опции фильтра drawtext:
  • fontfile — путь до файла шрифта
  • fontsize — размер шрифта
  • text — отображаемый текст
  • fontcolor — цвет шрифта
  • x,y — координаты текста


Как можно заметить, вся соль в координатах. Дело в том, что ffmpeg позволяет менять параметры некоторых своих фильтров «на лету», например, в зависимости от номера кадра или таймстемпа. В данном примере, помимо уже знакомых w и h, я использовал очередной внутренний алиас ffmpegа «n» — номер текущего кадра и функцию синуса чтобы придать снежинке движение из стороны в сторону. В фильтрах ffmpeg вы можете использовать тригонометрические и арифметические функции, такие как trunc и random. Полный список функций можно посмотреть вот здесь.

Теперь осталось только добавить еще фильтров (нужно больше фильтров!) для создания других снежинок и изменить их размер и движения.
Итоговая строчка запуска ffmpeg
ffmpeg -f lavfi -i "color=lightblue" \
-vf "drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=10:text='':fontcolor=white:x=sin(n/10)*30:y=3*n,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=16:text='':fontcolor=white:x=cos(n/9)*3+w/3:y=n/2-h/16,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=19:text='':fontcolor=white:x=sin(n/7)*10+w/9:y=n/3-h/20,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=15:text='':fontcolor=white:x=cos(n/25)*20+w/2:y=n-h/9,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=20:text='':fontcolor=white:x=sin(n/19)*4+w-w/3:y=n-h/20,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=17:text='':fontcolor=white:x=cos(n/10)*9+w/7:y=n/2-h/18,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=16:text='':fontcolor=white:x=sin(n/20)*23+w-w/7:y=2*n-h/15,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=19:text='':fontcolor=white:x=cos(n/15)*15+w/5:y=n-h/6,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=20:text='':fontcolor=white:x=sin(n/15)*50+w-w/9:y=n-h/10,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=12:text='':fontcolor=white:x=cos(n/30)+w/2:y=n*2,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=16:text='':fontcolor=white:x=cos(n/9)*3+w/3:y=n/2-h/6,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=19:text='':fontcolor=white:x=sin(n/7)*10+w/9:y=n/4-h/2,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=15:text='':fontcolor=white:x=cos(n/25)*20+w/2:y=n/5-h/2,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=20:text='':fontcolor=white:x=sin(n/19)*4+w-w/3:y=n-h/2,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=17:text='':fontcolor=white:x=cos(n/10)*9+w/7:y=n/2-h/2,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=16:text='':fontcolor=white:x=sin(n/20)*23+w-w/7:y=2*n-h/3,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=19:text='':fontcolor=white:x=cos(n/15)*15+w/5:y=n/6-h/2,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=20:text='':fontcolor=white:x=sin(n/15)*50+w-w/9:y=2*n-h/2,\
drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:fontsize=12:text='':fontcolor=white:x=cos(n/22)+w/2:y=2+h"\
 -qscale 1 -s 720x560 -t 12 -y snow.mp4



На видео выше результат выполнения этой команды. К сожалению, мне не удалось заставить параметр fontsize принимать в качестве значения результат функции random, скорее всего это пока не поддерживается. Напоследок укажу еще один нюанс с которым я с столкнулся — параметр text тоже не принимает значения вида text=n. Для того чтобы отображать номер кадра используйте конструкцию «text=%{expr\\:n}». На этом у меня все, надеюсь вы узнали для себя что нибудь новое.
Tags:
Hubs:
+25
Comments 12
Comments Comments 12

Articles