{"id":14277,"url":"\/distributions\/14277\/click?bit=1&hash=17ce698c744183890278e5e72fb5473eaa8dd0a28fac1d357bd91d8537b18c22","title":"\u041e\u0446\u0438\u0444\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u0442\u0440\u044b \u0431\u0435\u043d\u0437\u0438\u043d\u0430 \u0438\u043b\u0438 \u0437\u043e\u043b\u043e\u0442\u044b\u0435 \u0443\u043a\u0440\u0430\u0448\u0435\u043d\u0438\u044f","buttonText":"\u041a\u0430\u043a?","imageUuid":"771ad34a-9f50-5b0b-bc84-204d36a20025"}

Увидел в ТГ видеокружок - винил-пластинку с аудио, и захотел также. И сделал своего бота

  • Реальная история полета мысли и рождения продукта
  • Примеры создания бота с нуля
  • Готовый скрипт для рендера кружочков с музыкой (ну почти)
  • Готовый бот с неприлично простым функционалом: t.me/Wjooh_bot

В одном телеграм канале с мемами увидел такой вот видео-нот, и так мне зашла эта идея что сразу захотелось сделать такой же. И делать вообще регулярно по настроению, когда наткнулся на подходящую картинку или захотел подсадить друга на топовую песню. Я начал прощупывать карманные ресурсы. Карманные — потому что делать по настроению значит делать с телефона

Мой фаворит в видео редакторах YouCut, который обычно позволяет создать кат по любой ситуативной идеи, и быстро, в этот раз немного подвел: всё что можно от него добиться это использовать эфект вращения с одной заданной скоростью, а эта скорость задана весьма выше чем у вайба винила.

=(

Второй кочкой, на которой споткнулся полет моей идеи, стала сама механика видеокружочков. Юзер не может загрузить их с устройства — только записать.. Тут я вспомнил что разрабатываю ботов на Python, и в сущностях сообщений явно видел поле VideoNote — видеокружок. И стало ясно что загружать их из галереи нельзя только в реализации приложения — само API телеграмма естественно не против.

Для надежности загуглил «как делают видео-сообщения из обычных видео», и нашел кучу примеров зреющей идеи — телеграмм боты на всяко-разных условиях предлагают сделать кружочек из любого вашего видео. А раз такое делает бот, почему бы боту и не закручивать его, и не накладывать аудио самому? Вот и ни почему.

Поехали

Начинаем с центральной функции — монтажа и рендера.

  • Входные данные — картинка и аудио файл.
  • На выходе нужно mp4 видео с вращающейся картинкой под музыку.

Сразу как то интуитивно было, что вращение проще оформить на этапе подготовки картинки. Использовать самый банальный инструмент, встроенный в Python — PIL Image, создать раскадровку будущего видео и сохранить на диске.

Делаем цикл, оставляем на перспективу множитель скорости(им также можно менять направление вращения знаком +-), поворачиваем картинку на шаг*скорость, и сохраняем в массив кадров

def rotate_set(f_imgpath, f_speed,f_id): f_step = int(360 / f_speed) f_res = [] # умножаем на минус потому что интуитивнее когда плюс крутит по часовой f_speed = -f_speed f_img = Image.open(f_imgpath) for i in range(0, f_step): q_img = f_img.rotate(i * f_speed) f_res.append(q_img) return f_res

Сразу скажу, получится урод. Надо сначала сделать из картинки квадрат: считаем точки краев картинки и получаем новую

def crop_img(f_imgpath): img = Image.open(f_imgpath) f_size = min(img.size) f_crop_size = (max(img.size)) f_dif = int((f_crop_size - f_size) / 2) if img.height >= img.width: f_crop_img = img.crop((0,f_dif,img.width,img.height - f_dif)) else: f_crop_img = img.crop((f_dif, 0, img.width - f_dif,img.height)) return f_crop_img

Готовый набор картинок собираем в контейнер видео-либы, накладываем аудио и го рендерить на старом офисном ноуте это дело адски долгое.

def spin_imag(f_len=59, f_speed=2, f_img='low.jpg'): j = 0 clips = [] f_img_obj = crop_img(f_img) f_frames = rotate_set(f_img_obj, f_speed) for i in range(0, f_len * 24): # тут гоняем массив кадров полного вращения f_frames # пока не получим массив на всю длинну видео f_len * 24 clips.append(ImageSequenceClip(f_frames[j])) j += 1 if j >= len(f_frames): j = 0 result_clip = concatenate_videoclips(clips, method="compose") audio_clip = AudioFileClip(f_audio) result_clip.audio = new_audioclip f_result_file = f'{s_files_path}.mp4' result_clip.write_videofile(f_result_file, fps=24, ) return f_result_file

Сразу тестим на VPS с убунтой и 300mb ОЗУ: Процесс убивается еще на закручивании картинок

:D

Оптимизируем

Ладно, если не торопиться, то во первых надо делать входные картинки одного небольшого размера, всё-таки в кружочке нет приоритета на ХайРес, а видео рендерится по размеру большего из слоев. Заодно внимательнее следим за закрытием ненужных файлов/потоков

def crop_img(f_imgpath): ... else: f_crop_img = img.crop((f_dif, 0, img.width - f_dif, img.height)) f_crop_img = f_crop_img.resize((s_img_size, s_img_size)) img.close() return f_crop_img

Из любопытства глядим на нагрузку системы

Бедная озу, где то я не там свернул.

Немного вчитавшись в доки находим метод как раз для склейки картинок кадров в клип, в который надо только отправить адреса файлов, а он сам их по умному загрузит и склеит.

# сохраняем каждый кадр в файл, возвращаем путь к файлу def rotate(f_img,f_angle,f_result_path): f_res_path = f_result_path rotate_img = f_img.rotate(f_angle) rotate_img.save(f_result_path) return f_res_path def rotate_set(f_img, f_speed, f_id): ... for i in range(0, f_step): q_img = rotate(f_img, i * f_speed, f'{s_work_dir}{i}_rotate.jpg') f_res.append(q_img) return f_res def spin_image(f_id=0, f_len=59, f_speed=2, f_img='low.jpg'): ... # теперь здесь мы получаем массив адресов файлов f_frames = rotate_set(f_img_obj, f_speed, f_id) f_img_obj.close() for i in range(0, f_len * 24): # гоняем массив адресов файлов так же как раньше картинки clips.append(f_frames[j]) j += 1 if j >= len(f_frames): j = 0 # этот объект будет загружать кадры из файлов только когда они # потребуются на рендере result_clip = ImageSequenceClip(clips, fps=24) ...

Ощутимая оптимизации. Надо больше читать доки

Где то в это время под тест попадет кейс когда входное аудио короче минуты, и хочется эту ситуацию быстро пусть топорно решить.

Применяем вуду-программирование:

def spin_image(f_id=0, f_len=59, f_speed=2, f_img='low.jpg'): ... audio_clip = AudioFileClip(f_audio) if audio_clip.duration < result_clip.duration: f_count = int(result_clip.duration / audio_clip.duration) + 1 f_clip_list = [] for i in range(0, f_count): f_clip_list.append(audio_clip.copy().set_start(audio_clip.duration * i)) f_clip_list[f_count - 1] = f_clip_list[f_count - 1].copy().set_duration( result_clip.duration - ((f_count - 1) * audio_clip.duration)) audio_clip = f_clip_list else: audio_clip = [audio_clip.set_duration(result_clip.duration)] new_audioclip = CompositeAudioClip(audio_clip) ...

С божей помощью оно работает с первого раза, едем дальше.

Друг (ссылка: Разработка кроссплатформенных приложений, интерактивных экскурсий, презентаций, AR, VR, MR для выставок, музеев, рекламы и веба) советует для оптимизации потыкаться в кодеки рендера и битрейт, так что мы снижаем битрейт аудио до 100k, меняем кодек на новомодный h264 видео до 200k (дефолт выдавал 400, дефолт х254 вобще 4500! sic), врубаем режим оптимизации ultrafast, и все ради бедной убунты-300-озу, дай ей бог сил.

def spin_image(f_id=0, f_len=59, f_speed=2, f_img='low.jpg'): ... result_clip.write_videofile(f_result_file, fps=24, codec='libx264', preset='ultrafast', bitrate='200k' audio_bitrate='100k') ...

Убунта справляется. Но такое качество даже в кружочке неприемлемо

Если использовать как посоветовал мой друг (ссылка: Разработка кроссплатформенных приложений, интерактивных экскурсий, презентаций, AR, VR, MR для выставок, музеев, рекламы и веба) кодек h265, который ещё более современный и оптимизированный, то результирующее видео вообще не воспроизводится на мобильном ТГ, на десктопе выглядит выразительно(скорее всего проблема в моем невежестве, но тратить на это время не целесообразно, ИДЕЯ ГОРИТ)

Видимо h265 делает такой клевый эфект всегда при низком битрейте.

Молодец друг, если что он занимается Разработка кроссплатформенных приложений, интерактивных экскурсий, презентаций, AR, VR, MR для выставок, музеев, рекламы и веба и вот его профиль ссылка

Дальше начинаем подбирать разрешение и битрейт что бы железо тянуло, картинка удовлетворяла и самое интересное — какие критерии для видео существуют что бы ТГ его сделал кружочком. Дело в том что загрузка VideoNote работает загадочным образом, она отправляет на сервер ТГ любое твое видео, но они там его каким то образом оценивают и решают — выдать в чат каноничный кружок, или если что то не понравилось — вкинуть его как обычное видео. В документации написаны требования, только забыли упомянуть разрешение:

  • Разрешение видео не более 640р
  • Расширение файла .mp4
  • Длинна не более минуты
  • Квадратное

Кружок начинает выглядеть прекрасно уже на 600к битрейте

Наконец наложим сверху тестурку пластинки.

def get_mask(f_name, f_size=s_img_size): f_path = f'{f_name}{f_size}.png' if not os.path.exists(f_path): with Image.open(f_name) as og: with og.resize((f_size, f_size)) as rs: rs.save(f_path) return f_path def spin_image(): ... result_clip = ImageSequenceClip(clips,fps=24) # сначала загружаем пнг как обычный кадр logo = ImageClip(get_mask(mask.png,f_img_size),duration=result_clip.duration) # потом хитро загружаем его же но фильтром маской, и накладываем на предыдущий f_mask = ImageClip(get_mask(mask.png,f_img_size),ismask=True).to_mask() logo = logo.set_mask(f_mask) result_clip = CompositeVideoClip([result_clip,logo]) ...

Конечный код скрипта доступен на

Я до последнего не верил что такого бота никто еще не делал, и недавно нашел одну реализацию такой идеи, можете найти его по слову винилайзер, у него я в итоге и подглядел макс разрешение для кружочка. Видеть чужую реализацию твоей тайной идеи конечно больно, но я сразу решил что у моего бота будет фича в простоте, так чик-хоп и готов кружок. А у конкурента фишка в сложности (:

Короче немного магии Aiogram, про него контента итак хватает, придумал социальный элемент — показывать юзерам чужие кружочки! и оценивать! и формировать топ!!! Добавил выбор маски-пластинки и запустил на копеечном VPS

Я назвал своего бота Вжух

0
4 комментария
Илья О.

Ничего не понятно, так как я не прогаю, но ты крутышка

Ответить
Развернуть ветку
Фам

Бесполезная хрень, но идея с пластинками просто топ ! Выглядит так будто в телеге так и задумывалось, что это не видео, а винил крутится.

Ответить
Развернуть ветку
Максим Лунин

Только что проверил. Не работает бот( После /start ничего не происходит.

Хотя идея классная.

Ответить
Развернуть ветку
Yellow Astronaut
Автор

Возможны небольшие застывания из за огРоМНОГО НАПЛЫВА ЮЗЕРОВ БОГМНЕ СВИДЕТЕЛЬ Я НЕ БЫЛ ГОТОВ К ТАкомУ АЖИОТАЖУ

Ответить
Развернуть ветку
1 комментарий
Раскрывать всегда