10 сентября 2012 в 23:54

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

«Что позволило остаться 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
карма
264,0
рейтинг 0,0
Обо всём и ни о чём
Похожие публикации
Самое читаемое Разработка

Комментарии (39)

  • +70
    • +4
      У кого тогда колонки стояли, да ещё в офисе?
  • +9
    Это со спикера ))))
  • +3
    Из ума выпрыгнуть!
  • +17
    wave в gif совать — как-то совсем бесчеловечно )))
    • –7
      главное что не flac
      • +16
        Почему же? Всяко меньше весить будет :)
    • +4
      Аудио поток можно было вставить любой.

      Но, скрещивание старичка GIF с форматами которые в 2,5 раза младше (например Ogg Vorbis) это — нарушение гармонии.
  • +1
    не переживайте! для начала можно добавить ограниченную реализацию: двухбитный звук с дикретизацией, скажем, 5,5кГц :-))
  • +1
    Сайт с гифкой к сожалению лег.
    • +1
      У меня работает. Зашел вот сейчас вот.
  • –5
    Зачем?
    • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      Аналогичный вопрос. Ведь анимация со звуком — это видео. Берём OGM или WebM. Тег video сейчас поддерживается большинством браузеров. Но тут скорее был мотив — «а получится ли?».
      • +4
        Just 4 fun?
        • +1
          Ну, в общем-то «а получится ли?» и «just for fun» неразрывны.
    • +6
      Это мой асимметричный ответ Google. Он со своим WebM хочет занять нишу картинок (WebP), а тут его поджидают анимированные гифки со звуком)
      • –3
        а зачем минусовать? вполне логичный вопрос кажется задал
  • +3
    Если вы взяли браузеры, которые понимают CANVAS и AUDIO, то это браузеры, которые понимают и VIDEO. Не проще ли взять этот тег? Но извращение годное :)
    • 0
      Если представить, что мы говорим о анимированных гифках со звуком всерьез, то у них лучше обратная совместимость.

      Если использовать данный формат, то пользователи Internet Explorer 3 и Netscape Navigator 2 увидят просто о анимированные картинки, и не увидят ничего, если использовать тег video.
      • +1
        Делов-то :) Вкладываем внутрь тега VIDEO, тег EMBED.

        А проблему синхронизации звука и видео вы как-то решали?
        • 0
          С EMBED они тоже ничего не увидят, пока не загрузят гипотетический плагин.

          Синхронизацию делаю в лоб, если выводим первый кадр, то перезапускаем звук:
          if (curFrame == 1) {
            if (sound_element != null) {
              sound_element.currentTime = 0;
              sound_element.play();
            };
          };
          
          • 0
            В Виндах давно уже работает безо всякого плагина — через плеер, на Маке — через КвикТайм. Как на Линуксах не знаю.
          • 0
            Для отладки.
            Если на предпоследней секунде мышь резко вывести за границу кадра, звук продолжает звучать и похоже время звучания звука длиннее секвенции картинки.
            У меня в моем flash-плеер такая же беда, не знаю как лечить.

            А вообще идея хорошая.
            • 0
              Ой. Промахнулся.
  • +3
    Пожалуйста-пожалуйста скажите что Вы шутите — не хочу рекламные баннеры со звуком. Ох что-то будет…
    • +1
      Чего будет-то? Тег видео и флеш ещё пока почти везде работает.
      • +1
        Вот-вот, достаточно и там звука.
    • 0
      Будет GifBlock. Или FlashBlock расширят.
    • 0
      Их и так чуть более, чем достаточно, чёрт подери!
  • 0
    Да у вас пытливый ум, однако!
  • 0
    Плагины required!
    Идея — супер!
    Реализация тоже =)
  • 0
    <O_o>Я вздрагиваю когда представляю рекламные баннеры со звуком</O_o>
    • 0
      Вам повезло, вы их не видели.
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Странно, что раньше вы этого не делали
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Да… Не заходил
  • 0
    Кстати, я знаю один хороший хостинг анимированных «GIF» со звуком, созданный компанией Google
    Youtube называется

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