Анимированный GIF со звуком

в 19:54, , рубрики: gif, javascript, python, wave, Алгоритмы, Анимация и 3D графика, ненормальное программирование, метки: , , ,

«Что позволило остаться GIF, это — циклическое проигрывание анимации, которое добавил Netscape. Если бы Netscape не добавил поддержку GIF в свой браузер, GIF умер бы в 1998»

— Александр Тревор (Alexander Trevor),
руководитель команды по созданию GIF в CompuServe

Формат GIF в июне этого года отпраздновал свое 25-летие, и является сегодня самым старым графическим форматом, который распространен в интернете. Посвящая выходные просмотру смешных анимированных гифок понимаешь, что некоторые из них были бы в разы лучше со звуком. Все текущие решения для циклической анимации со звуком (например: coub.com, gifsound.com) предлагают отказаться от GIF, но это — не выход. И я решил пожертвовать просмотром гифок на выходных для решения этой крайне важной проблемы.

Первая в интернете гифка со звуком по ссылке. Надо нажать на синюю кнопку, а потом на гифке. Плеер должен работать во всех современных браузерах (тестировался в последнем Firefox и Chrome).

Гифок под катом не будет, а будет процесс создания расширения для стандарта, написания конвертера и плеера.

С 1987 формат GIF пережил всего два существенных изменения:

  1. В 89 году вышла вторая версия формата (названная GIF 89a). Стало возможным указывать задержку между картинками (нескольких картинок в одном файле было в первом формате GIF 87a). Сторонние разработчики теперь смогли добавлять свои собственные блоки в файл (Application Extension Block).
  2. В 90 году компания Netscape добавила свой блок, который позволял указывать сколько раз будет повторяться анимация.

Разработка расширения формата

Как было сказано выше, стандарт GIF 89a позволяет приложениям размещать в GIF файле свои данные. Формат блока расширения для приложений:

Размер, байт Содержание
2 Заголовок блока расширения приложения (всегда 0x21, 0xFF)
1 Размер блока (всегда 11)
8 Идентификатор приложения
3 Код аутентификации приложения (может использоваться для проверки, что блок создан конкретным приложением)
* Вложенные блоки данных
1 Указатель конца блока (0x00)

Попробуем уместить сюда заголовка WAVE файла:

Размер, байт Содержание
4 Заголовок файла (всегда «RIFF»)
4 Размер данных
4 Формат данных (для WAVE фалов — «WAVE»)

Так как размер блока контролируется форматом GIF, выкинем из заголовка поле с размером данных, а в идентификатор приложения запишем «RIFFWAVE». Остаток WAVE файла запишем как вложенные GIF блоки.

Блок со звуком будем вставлять прямо перед первым блоком с изображением (на самом деле можно его вставить в любом месте).

Разработка конвертера

Конвертер на входе принимает GIF и WAVE файлы, а на выходе выдает GIF с RIFFWAVE блоком. Исходный код можно посмотреть посмотреть на google code.

Код довольно простой, читаем WAVE файл, создаем из него GIF блок. Потом читаем гиф файл и записываем все блоки, как только находим первый блок с картинкой, перед ним вставляем блок со звуком. Самый важный участок кода — конвертация WAVE файла в GIF блок:

def get_wav_block(file): 
     # читаем данные из фала
     (signature, 
      size, 
      format) = unpack('4sI4s', file) 
     if signature != 'RIFF': 
        raise Exception('Not a RIFF file') 
     if format != 'WAVE': 
        raise Exception('Not a WAVE file') 
     data = file.read(size - 4) 

     # Готовим заголовок блока
     wave_block_header = struct.pack('BBB8sBBB', 0x21, 0xff, 11, 'RIFFWAVE', 0, 0, 0) 
     data_subblocks = [wave_block_header]; 

     # разбиваем данные на блоки из 255 байт
     for i in range(0, len(data) // 255): 
          data_subblocks.append(chr(0xff)) 
          data_subblocks.append(data[i*255:(i + 1)*255]) 
     if (len(data) % 255) > 0: 
          rest = len(data) % 255 
          data_subblocks.append(chr(rest)) 
          data_subblocks.append(data[-rest:])          

     # добавляем сивол окончания блока
     data_subblocks.append(chr(0)) 

     return ''.join(data_subblocks) 

Делаем гифку со звуком

Делаем анимированный гиф:

# ffmpeg получаем отдельные кадры 
ffmpeg -ss 0:00:9.73 -t 2.86 -i warlus.webm -s 500x280 -r 10 frames/image%03d.png 
# делаем анимированный  GIF
convert -delay 10 -loop 0 frames/*.png source.gif

Получаем звук и проверяем, что все выглядит (и звучит), как мы запланировали:

mplayer -ss 0:00:9.73 -endpos 2.86 warlus.webm -ao pcm:file="source.wav" -vo null 
mplayer -loop 0 -audiofile source.wav source.gif 

Конвертируем в GIF со звуком

python wave2gif.py example/source.gif example/source.wav result.gif

Делаем плеер

За основу плеера возьмем jsgif — плеер для анимированных GIF на JavaScript. jsgif разбирает gif и проигрывает рисуя каждый кадр на Canvas. Добавим в него функцию, которая при обнаружении «RIFFWAVE» блока:

  • преобразовываем данные из блока обратно в формат WAVE файла;
  • конвертируем полученный файл в data: URL и передаем в элемент Audio.

var doSound = function(sound) { 
          // Header 
          var size = sound.data.length + 4 
          var size_text = String.fromCharCode(size  & 255, (size  >> 8) & 255, (size  >> 16) & 255, (size  >> 24) & 255); 
          var header = [ 
              "RIFF", 
              size_text, // length 
              "WAVE" 
          ].join(''); 
          
          var out = [header, sound.data].join(''); 
          var dataURI = "data:audio/wav;base64," + escape(window.btoa(out)); 
          
          sound_element = new Audio(); 
          sound_element.src = dataURI; 
    };

jsgif работает не быстро, а с добавлением звука стал еще медленнее. Так же, чтобы получить данные файла, плеер вызывает XMLHttpRequest, поэтому плеер работает только с картинками с одного домена. Но разве это помехи для искусства.

Что дальше?

Радоваться гифкам со звуком. Можно реализовать плагин для браузеров, который бы позволял проигрывать такие гифки без дополнительного XMLHttpRequest, и который возможно будет быстрей работать. Если кто-то сталкивался с подобной задачей, буду признателен за указание в какую сторону смотреть, чтобы написать плагин который обрабатывает определенные типы файлов.

Источники

GIF89a Specification
WAVE PCM soundfile format
jsgif: A GIF player in JavaScript

Автор: PatapSmile

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js