«Что позволило остаться GIF, это — циклическое проигрывание анимации, которое добавил Netscape. Если бы Netscape не добавил поддержку GIF в свой браузер, GIF умер бы в 1998»
— Александр Тревор (Alexander Trevor),
руководитель команды по созданию GIF в CompuServe
Формат GIF в июне этого года отпраздновал свое 25-летие, и является сегодня самым старым графическим форматом, который распространен в интернете. Посвящая выходные просмотру смешных анимированных гифок понимаешь, что некоторые из них были бы в разы лучше со звуком. Все текущие решения для циклической анимации со звуком (например: coub.com, gifsound.com) предлагают отказаться от GIF, но это — не выход. И я решил пожертвовать просмотром гифок на выходных для решения этой крайне важной проблемы.
Первая в интернете гифка со звуком по ссылке. Надо нажать на синюю кнопку, а потом на гифке. Плеер должен работать во всех современных браузерах (тестировался в последнем Firefox и Chrome).
Гифок под катом не будет, а будет процесс создания расширения для стандарта, написания конвертера и плеера.
С 1987 формат GIF пережил всего два существенных изменения:
- В 89 году вышла вторая версия формата (названная GIF 89a). Стало возможным указывать задержку между картинками (нескольких картинок в одном файле было в первом формате GIF 87a). Сторонние разработчики теперь смогли добавлять свои собственные блоки в файл (Application Extension Block).
- В 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