Осенью я сделал несколько клипов посвященных использованию ffmpeg для создания видеороликов из статичного контента — картинок и текста. Идея в создании программного комплекса способного генерить видеролики из карточек товара в автоматическом режиме.
Мне показалось что тема не очень зашла, да и как то навалилось работы, поэтому продолжение забросил. Однако, периодически, ко мне прорывается фидбек из разряда когда будет дальше. Поэтому будущее наступило и сегодня покажу как можно используя ffmpeg собрать готовый ролик из статики. Чтобы с анимацией. И музычки накинем.
В предыдущих роликах я обходился командной строкой Windows и батниками, но для решения сегодняшней задачи это слишком многословно и не универсально. Поэтому я использовал ещё и Python на минималках.
Сам по себе выходной ролик в творческом плане ))) абсолютно убог, но задача показать как все то, что было в моих роликах раньше, собрать в единое целое. Код без защиты от дурака и вредоноса, в продакшен такое нельзя !
Думаю с вступлением достаточно. Поехали.
Входные данные
У меня есть папка data, где лежат несколько картинок с расширением jpg и текстовый файл с несколькими строками текста. Я использую все картинки, сколько бы их не было в папке. Я показываю все строки текста в файле.
Анимации изображений с помощью ffmpeg
Каждая картинка показывается с тремя анимациями одинаковой продолжительности.
- Картинка плавно въезжает в высоту экрана из большего размера
- Картинка показывается вписанной в экран
- Картинка увеличивается из вписанной, после чего заменяется новой картинкой.
Длина ролика разная. Зависит от количества картинок.
Анимация текста с помощью ffmpeg
Текст выкатывается справа налево, каждая строка текста из текстового файла размещается в видео под предыдущей строкой текста. Скорость выкатывания строк зависит от длины текста, количества строк текста и совокупной длины ролика (которая зависит от количества картинок в папке).
Музыка
Музыку взял из фотонотеки YouTube и наложил на готовый ролик. Она должна обрезаться под длину ролика.
В итоге получилось вот такое видео.
Код скрипта на Python
Python 3.8. Библиотека только одна os, чисто для работы с входными файлами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
import os lpi = 3 # прододжительность каждого эффекта effects_count = 3 # всего эффектов (w,h) = (1080,720) # высота ширина клипа # готовлюсь читать данные из директории sep = os.path.sep data_dir = f"{os.getcwd()}{sep}data{sep}" out_file = f"c:{sep}_tmp{sep}out.mp4" # текстовки with open(f"{data_dir}text.txt", 'r', encoding='utf8') as f: texts = f.read().splitlines() # картинки images = [f"{data_dir}{i}" for i in os.listdir(data_dir) if i.endswith(".jpg")] images_count = len(images) # количество картинок texts_count = len(texts) # количество строк текста clip_length = images_count * lpi * effects_count # общая длина клипа зависит от колва картинок, продолжительности каждого эффекта и колва эффектов text_length = clip_length // texts_count # продолжительность показа каждого куска текста ''' Основной цикл формирования анимации картинок ''' animation = [] overlays = "" for i in range(0,images_count): # timestamp старта анимации текущей картинки tst = i * (effects_count * lpi) # сначала в инпутах у меня белый фон, затем звуковая дорожка, соотв. картинки начинаются после них input_num = i + 2 # animation effects # увеличенную картинку вписываем в высоту animation.append(f"[{input_num}]scale=-1:'if(between(t, {tst}, {tst + lpi}), {h * lpi} - (t - {tst + 1})*{h}, 1)':eval=frame[i1{input_num}]") # показываем картинку вписанную в высоту экрана animation.append(f"[{input_num}]scale=-1:'if(between(t, {tst + lpi}, {tst + 2 * lpi}), {h},1)':eval=frame[i2{input_num}]") # увеличиваем картинку назад animation.append(f"[{input_num}]scale=-1:'if(between(t, {tst + 2 * lpi}, {tst + 3 * lpi}), {h} + {h *4/ lpi}*(t-{tst + 2 * lpi}) , 1)':eval=frame[i3{input_num}]") # объединяем анимации наложением overlays += ",[0]" if i == 0 else f"[b{input_num-1}],[b{input_num-1}]" overlays += f"[i1{input_num}]overlay=(W-w)/2:(H-h)/2[x{input_num}1]" overlays += f",[x{input_num}1][i2{input_num}]overlay=(W-w)/2:(H-h)/2[x{input_num}2]" overlays += f",[x{input_num}2][i3{input_num}]overlay=(W-w)/2:(H-h)/2" text_filters = "" # для каждой строки текста из файла будет свой drawtext со своей анимацией for i, text in enumerate(texts): # текст строки + стилизация drawtext = f",drawtext=text='{text}':font=Arial:fontsize=40:fontcolor=#DD0000:borderw=2:bordercolor=white" drawtext += f":y=H - {texts_count - i+1}*50" # прижимаем блок текста вниз, но каждая строка будет на новой строке # анимация выкатывания строки drawtext += f":x='if(between(t, {i*text_length}, {(i+1)*text_length}), (W - (t - {i*text_length})*( (w - 100)/{text_length})), 100)'" # скрываем текстовки пока не начали выкатывать drawtext += f":enable='between(t, {i*text_length}, {clip_length})'" # добавляем drawtext текущего текста к фильтрам text_filters += drawtext filters = ",".join(animation) + overlays + text_filters + "[end]" print(filters) #exit() # / с фильтрами закончили ''' Собираю ffmpeg ''' cmd = f"ffmpeg -f lavfi -i color=c=gray:s={w}x{h} " # белый фон cmd += f"-i {data_dir}music.mp3 " cmd += " ".join([f"-loop 1 -i {i}" for i in images]) + " " # массив входных картинок cmd += f"-t {clip_length} " cmd += f"-filter_complex \"{filters}\" " cmd += f"-map [end]:v -map 1:a -shortest " cmd += f"-y {out_file}" print(cmd) # вывод подготовленной команды os.system(cmd) # выполняю os.system(r'"C:\Program Files\VideoLAN\VLC\vlc.exe" ' + out_file) # для удобства под виндой |
Повторюсь, код грязный !!! Его нельзя в лоб использовать в живом проекте на автомате. Например он проверяет файлы картинок по расширению и берет только jpg. Это не безопасно, да и картинки могут быть не только jpg ))). А длинные текстовые строки просто не будут помещаться в ширину видео. Одним словом дорабатывать его ещё и дорабатывать.
Однако что-то подобное у меня успешно работает несколько месяцев на полном автомате, заливая короткие ролики-презентации по api в социалки.
На выходе для данных из трех картинок и текстового файла с 4 строками текста получается вот такая сборка для ffmpeg:
1 2 3 4 5 6 7 8 |
ffmpeg -f lavfi -i color=c=gray:s=1080x720 ^ -i C:\Drive\Programming\Azzrael_YT\ffmpeg\data\music.mp3 -loop 1 -i C:\Drive\Programming\Azzrael_YT\ffmpeg\data\kup1.jpg ^ -loop 1 -i C:\Drive\Programming\Azzrael_YT\ffmpeg\data\kup2.jpg ^ -loop 1 -i C:\Drive\Programming\Azzrael_YT\ffmpeg\data\kup3.jpg ^ -t 27 ^ -filter_complex "[2]scale=-1:'if(between(t, 0, 3), 2160 - (t - 1)*720, 1)':eval=frame[i12],[2]scale=-1:'if(between(t, 3, 6), 720,1)':eval=frame[i22],[2]scale=-1:'if(between(t, 6, 9), 720 + 960.0*(t-6) , 1)':eval=frame[i32],[3]scale=-1:'if(between(t, 9, 12), 2160 - (t - 10)*720, 1)':eval=frame[i13],[3]scale=-1:'if(between(t, 12, 15), 720,1)':eval=frame[i23],[3]scale=-1:'if(between(t, 15, 18), 720 + 960.0*(t-15) , 1)':eval=frame[i33],[4]scale=-1:'if(between(t, 18, 21), 2160 - (t - 19)*720, 1)':eval=frame[i14],[4]scale=-1:'if(between(t, 21, 24), 720,1)':eval=frame[i24],[4]scale=-1:'if(between(t, 24, 27), 720 + 960.0*(t-24) , 1)':eval=frame[i34],[0][i12]overlay=(W-w)/2:(H-h)/2[x21],[x21][i22]overlay=(W-w)/2:(H-h)/2[x22],[x22][i32]overlay=(W-w)/2:(H-h)/2[b2],[b2][i13]overlay=(W-w)/2:(H-h)/2[x31],[x31][i23]overlay=(W-w)/2:(H-h)/2[x32],[x32][i33]overlay=(W-w)/2:(H-h)/2[b3],[b3][i14]overlay=(W-w)/2:(H-h)/2[x41],[x41][i24]overlay=(W-w)/2:(H-h)/2[x42],[x42][i34]overlay=(W-w)/2:(H-h)/2,drawtext=text='2020 сексуальные женские бикини':font=Arial:fontsize=40:fontcolor=#DD0000:borderw=2:bordercolor=white:y=H - 5*50:x='if(between(t, 0, 6), (W - (t - 0)*( (w - 100)/6)), 100)':enable='between(t, 0, 27)',drawtext=text='687 руб.':font=Arial:fontsize=40:fontcolor=#DD0000:borderw=2:bordercolor=white:y=H - 4*50:x='if(between(t, 6, 12), (W - (t - 6)*( (w - 100)/6)), 100)':enable='between(t, 6, 27)',drawtext=text='бесплатная доставка':font=Arial:fontsize=40:fontcolor=#DD0000:borderw=2:bordercolor=white:y=H - 3*50:x='if(between(t, 12, 18), (W - (t - 12)*( (w - 100)/6)), 100)':enable='between(t, 12, 27)',drawtext=text='https\://aliexpress.ru/item/4000334756839.html':font=Arial:fontsize=40:fontcolor=#DD0000:borderw=2:bordercolor=white:y=H - 2*50:x='if(between(t, 18, 24), (W - (t - 18)*( (w - 100)/6)), 100)':enable='between(t, 18, 27)'[end]" ^ -map [end]:v -map 1:a -shortest -y c:\_tmp\out.mp4 |
Если дело дойдет до продолжения, то либо пойдем в сторону придумывания разных эффектов, либо в сторону парсинга источников статики и генерации из этой статики видео по схеме в этой статье. Ну или поговорим про использование api для заливки видео. Мне лично про эффекты более интересно. Если есть пожелания пишите или здесь (тут премодерация комментариев) или на Youtube.
Игорь 2021-12-12
Спасибо!