Безумный конвертер GIF'ок в анимированные стикеры для Telegram

Вместо тысячи слов...


xZibit тоже рад, ведь здесь GIF вставлены в стикеры, чтобы быть вставлеными в GIF для КДПВ!

А теперь о подробностях реализации.

Всё началось с дискуссии в чатике Telegram-разработчиков о грядущей фиче:

Bohdan Horbeshko, [04.07.19 20:21] Мда, а бот наверняка будет принимать только gif, и потом конвертить... | Vitaly, [04.07.19 20:22] с гифа в джейсон? я бы посмотрел :))) | Bohdan Horbeshko, [04.07.19 20:22] А почему нет? Вон PlayCanvas'овский редактор модельки в JSON конвертит. | Vitaly, [04.07.19 20:23] а гиф как? попиксельно экспортировать? :) | Bohdan Horbeshko, [04.07.19 20:24] Конечно, мир IT и не такие извращения видал, стерпит.

Мужик сказал — мужик сделал! Первый прототип на Pillow и svgwrite, разбирающий GIF'ку на пиксели и преобразующий их в векторные квадратики с предпросмотром в SVG, был написан за один выходной.

Веселье началось дальше…

JSON — открытый формат, говорили они...


Доселе с форматами в Telegram то и дело хитрили. Сделали поддержку GIF-анимаций — на самом деле они конвертируются в MP4-видео. Сделали поддержку стикеров — выгружаются они в PNG, но преобразуются в WebP. В этот раз всё честнее: что на входе, то и на выходе.

Для анимированных стикеров в Telegram используется не GIF, не видео, и даже не какой-нибудь устоявшийся формат векторной графики типа SVG или — упаси Ктулху! — Flash. В нём задействован новомодный формат, вышедший из-под крыла Airbnb — Lottie. Доселе он имел некоторую известность в среде мобильных разработчиков, но благодаря Telegram, возможно, обретёт бо́льшую популярность.

По сути своей, файлы Lottie являются сериализованными в JSON проектами Adobe After Effects, по максимуму реализующими все возможности этой программы. С отображением, увы, всё не так радужно. Хотя готовых «официальных» реализаций библиотеки для рендеринга Lottie и много, как раз под покрываемые Telegram платформы: Android, iOS, Qt и Web — лишь часть из возможностей формата реализована во всех из них. В Telegram пошли ещё дальше и ограничили перечень поддерживаемых возможностей, а также «придумали» свой формат, который отличается от обычного Lottie всего лишь упаковкой в GZip и параметром "tgs": 1. Кажется, я знаю, где сейчас работает Денис Попов! :-)

И если с документацией на библиотеки для разных платформ всё довольно неплохо, то найти хоть какое-то описание устройства формата, увы, не удалось — только JSON-схему в исходниках lottie-web. Пришлось попутно ковыряться в существующих анимациях, дабы понять общие концепции формата. Также обнаружились расхождения реальных файлов со схемой: в частности, в слоях типа 4, согласно схеме, вложенные объекты хранятся в свойстве "it" — однако в реальных файлах ключ называется "shapes", а "it" не работает.

Выясненные нюансы формата:

  • Файл состоит из слоёв. В отличие от GIF, здесь у каждого слоя может быть произвольное время начала и конца отображения. К слою можно (точнее, нужно) применять различные трансформации: масштабирование, повороты, изменение прозрачности и т.д. Слои могут быть даже трёхмерными (запрещено для Telegram).
  • Слой состоит из «фигур» (shapes). Типов у них много, некоторе нельзя использовать в Telegram. На практике, чтобы слой отобразился, он должен включать три фигуры: контур (в готовых анимациях это обычно тип "sh" — кривые Безье; конвертер пока использует только тип "rc" — прямоугольники), заливка (тип "fl") и трансформация (тип "tr").
  • Можно даже включать растровые элементы, создавать текстовые слои, устанавливать взаимосвязи параметров слоёв и фигур через выражения. Вся эта вкуснотища также запрещена в Telegram.

Отсюда прямо следует первая проблема: избыточность. Хотя в JSON-схему недавно добавлены значения по умолчанию для параметров трансформаций — в библиотеках они не реализованы. Так что задавать их в явном виде всё равно нужно.

Казалось бы, это и не проблема вовсе? Даже простенький GZip неплохо справляется со сжатием вопиюще повторяющихся данных, и 1 МБ сырого JSON магическим образом превращается в пару десятков килобайт, которые спокойно пролезают в заявленное ограничение в 64 кБ. Не тут-то было!

Загружаю я, значит, пухленькую анимацию, которая спокойно отображается lottie-web, в Telegram — и тут вместо условно красивого пиксель-арта на меня смотрит статическое размазанное вот это:



Что такое?! А оказалось, на разжатые данные тоже есть явно не указанное ограничение в 1 МБ. Представитель команды Telegram оперативно подтвердил его и сообщил о грядущем поднятии лимита до 2 МБ.

Даже если эти проблемы решат — стикеры, выходящие за пределы 1 МБ несжатых данных и не содержащие трансформаций, окажутся недоступными для пользователей старых версий Telegram. Так что придётся, видимо, соблюдать ограничения и впредь.

Прозрачность — это важно


Pillow, наряду с OpenCV, можно назвать индустриальным стандартом для обработки изображений в Python. Мало того, он неплохо заточен и под особенности GIF: поддерживает индексированные цвета, даёт доступ к палитре. Поддерживает преобразование пиксельной карты в NumPy-массив, что важно для продуктивной обработки. Даже статистику по цветам собирает! Но обнаружились и минусы:

  1. Не нашлось задокументированного способа получить индекс прозрачного цвета. Пришлось в качестве временного решения подразумевать, что прозрачный цвет — самый распространённый, но в реальных GIF'ках это не всегда так.
  2. То же самое с задержкой между кадрами: Pillow отдаёт только сами кадры как последовательность изображений, без информации о задержках.
  3. Иногда некорректно накладываются частичные кадры.

Посему пришлось искать замену. В качестве неё выступил модуль gif2numpy. Он «заточен» под особенности GIF и предоставляет доступ ко всем техническим свойствам как изображения, так и отдельных кадров, в том числе GCE. Таким образом, проблему считывания задержек он решает.

Прозрачность, как оказалось, gif2numpy не поддерживает вообще: цвета сразу преобразуются в три канала с разрядностью в байт, без учёта разрядности и сохранения индексов цветов. Благо, модуль состоит из одного файла, так что не составило труда включить его в проект и доработать, зарезервировав под прозрачность цвет #FE00FE.

Проблему с частичными кадрами решить оказалось нетривиально. gif2numpy пытается накладывать такие кадры на предыдущий, однако не проверяет параметры наложения, из-за чего также не всегда выходит правильный результат. Дабы не возиться с флагами, добавлена предварительная обработка изображений с помощью gifsicle с ключом --unoptimize — он преобразует частичные кадры в полные. А заодно приводит их к использованию глобальной палитры, что устранило необходимость отдельным образом обрабатывать прозрачный цвет при использовании собственной палитры кадра.

Сожми меня сильнее


Квадратики — это хорошо, но с такими ограничениями нужно проявить больше фантазии, иначе в Telegram не «пролезают» даже миниатюрные GIF'ки.

Первым в ход пошло нечто похоже на RLE: соседние по горизонтали квадратики одного цвета объединяются в один прямоугольник.

Далее — черёд эксплуатации особенностей Lottie. Поскольку каждый слой имеет произвольное время начала и конца — можно применить технику, которая давным-давно используется видеокодеками, и отчасти в самом GIF: квадратики, которые остаются на одном месте в течение нескольких кадров, можно слить в один слой, во время отображения которого сменяется несколько других. Что и реализовано, пока только для пар соседних слоёв.

Планы по развитию


Идей, которые здесь можно применить, навалом:

  • Распознавать одноцветные области любого размера. Можно разбивать их на набор прямоугольников, для чего есть неплохой алгоритм. Также целесообразно преобразовывать их в контур, но это омрачается необходимостью указывать все точки кривых Безье в Lottie — прямоугольниками в некоторых случаях может быть выгоднее.
  • Распознавать движение. Техника, опять-таки, издревле применяющаяся в видеокодеках. Если один и тот же контур не меняет форму от кадра к кадру, но лишь координаты — стоит вместо дублирования на нескольких слоях поместить его на один слой с трансформацией.
  • Распознавать «накрытие» одних областей другими. Пример:

    ......
    .O..O.
    ......
    .OOOO.
    ......
    

    На прямоугольник одного цвета накладываются пиксели другого цвета. Вместо того, чтобы разбивать этот прямоугольник на кучу мелких, или пребразовывать в контур сложной формы — можно просто наложить их поверх целого прямоугольника.
  • Векторизация кривых и эллипсов, распознавание градиентов. Это испортит пиксельный шарм, зато на порядки улучшит сжимаемость некоторых GIF'ок. Градиенты есть даже в допотопных «колобках», я гарантирую это! :D
  • Сжатие с потерями. В первую очередь — устранение дизеринга, да и в излишне сглаженных картинках не помешает цвета поумерить. С этим наверняка справится вышеупомянутый gifsicle.

Ссылки


  • Исходники. Местами страшные.
  • Канал, на котором я выкладываю паки успешно сконвертированных GIF'ок.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 24

    0
    соседние по горизонтали квадратики одного цвета объединяются в один прямоугольник.

    А если вместо разбития на квадраты использовать триангуляцию? Готовых библиотек навалом.

    — Разбиваем кадр на группы цветов (простой рекурсивный перебор соседей, пока совпадает цвет).
    — Строим контуры групп
    — Триангулируем контуры
      0
      А смысл дробить именно на треугольники есть? Это ведь не для видеокарт, тут и примитива-то такого нет; звёзды в Telegram запрещены, а кривыми Безье выйдет по 9 чисел на каждый треугольник, против 4-х у прямоугольников — многовато.

      Если уж задействовать контуры, то лучше охватывать как можно бо́льшую площадь, и дробить вообще не придётся. Тут главный вопрос в том, можно ли делать в контурах дырки; маски запрещены точно, но есть ещё некие фигуры типа "trim". Если нельзя — придётся контуры с дырками таки дробить, либо соединять дырки с внешней границей по типу фагосомы.
      –2
      Навеяло Гифкой в клипе :)
        0
        Хорошо хоть, что не «Banned from Equestria» :-) (лучше не ищите, что это — развидеть не получится).
        +3

        Отличная затея! Телеграмму очень не хватает старых добрых колобков

          0
          Кстати, автор «колобков» Иван Манцуров — соавтор проекта «Колобанга» (мультфильм про колобков), а проект, в свою очередь, сделал стикеры — правда, пока не анимированные. telegram.me/addstickers/Kolobanga
          +1
          Буханка и троллейбус. Но ведь смог!
          Проверил. Работает. Ограничение, кстати уже 128 Кб а не 64 Кб на .tgs
          И ещё, если просто прикрепить .tgs к сообщению, то, если файл проходит ограничения, то он вставляется как анимация (с виду, как стикер), и его можно при желании сохранить в .tgs
          К сожалению, всё это годится только к ооочень оптимизированным и маленьким .gif
          Но всё равно, спасибо.
            0
            Ограничение, кстати уже 128 Кб а не 64 Кб на .tgs
            Это радует, но с текущей энтропией особой роли не играет: в 64 кБ GZip влезает около 3 МБ несжатого JSON.

            А от 1-го до 2-х несжатых МБ уже влезают? Такой, например. Мне обновление telegram-desktop прилетело сегодня, но больше 1 МБ по-прежнему не отображаются.
            (с виду, как стикер)
            Со статическими было так же: можно загрузить WebP-изображение — и оно отображается как стикер, только пак по клику не открывается (потому что его нет, собственно).
              0
              Ваш стикер в моём клиенте не захотел отображаться. При добавлении в стикерпак он виден статичным и размытым. Так что да, ограничение при разворачивании…
            0
            А кто-нибудь уже сделал анимированный стикерпак со старыми скайповыми смайлами?
            очень скучаю по старым друзьям
            image

              0
              Не только ты.
              image
              Жду пака с Колобками. Может даже перерисованными
                0
                t.me/addstickers/KOLOBOKSmiles
                Только смотрятся не очень, крупноваты пиксели.
                  0
                  Этот у меня давно стоит, но он не анимированный.

                  Жаль, кстати, что нельзя в одном паке комбинировать статические стикеры и анимированные. Так автор пака мог бы в него анимированные версии добавить, чтобы новинка сразу дошла до пользователей.
                    0
                    Эти давно знаю, но они не анимированные.
                0
                Для этого существовал Flash со своим swf.
                И не нужно было бы тогда заново изобретать велосипед.
                Ну а если TGS, то логично было бы и экспорт делать из него
                  0
                  Ух, я вернулся в свои 90-е. Убогие анимации, проприетарные сервера, куча разных программ выполняющих одно и тоже.
                    0
                    А чем можно открыть готовые tgs? Наконвертил пачку гифок, что-то работает норм, а какие-то статичные и прям мыльцо. Из 20 штук, только 5 получились. Понятно, что не проходят в максимальный размер файла.

                    i.imgur.com/4XdB6VS.png

                    Просто если предлагают изначально делать в AE, то как открыть их в AE для редактирования?
                      0
                      Редактировать вроде эта штука умеет. Но она только под макось.
                        0
                        И это её фатальный недостаток
                          0
                          Ну редактировать такую кучу фигур — в любом случае плохая затея. Уж проще тогда перерисовать, если вручную лезть. И не обязательно в AE — есть плагин экспорта для Synfig. Там, кстати, и куча скриптов для конверсии есть, даже векторизатор уже прикрутили.
                            0
                            Мне кажется, что проще будет все таки поправить некоторые мелочи и сохранить изначальный авторский колорит этих колобков. Много кто пытался рисовать новых (и перерисовывать старых, включая всякие HD версии), но мало у кого получалось. Из какого-нибудь тематического пака в 50 смайлов, оставались востребованными пара штук от силы.
                      0
                      Скажите, а какое время конвертации было у вас?
                      Просто у меня гифка весом 3 МБ больше часа конвертируется уже. Я понимаю, что ресурсы компа у всех свои, но в целом не должно быть уж очень большого расхождения.
                        0
                        i7 4770/16Gb Ram/SSD — не больше 15 секунд. Одну гифку которую не смог проглотить телеграмм, примерно с минуту делалась
                          0

                          А смысл такую огромную гифку конвертировать? Она же в лимиты не влезет однозначно.

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое