Подсчёт md5-хэша для mp3-файла

в 2:43, , рубрики: hash, md5, mp3, Алгоритмы, Работа со звуком, метки: , ,

Прямой подсчёт хэш-суммы для файла не годится, так как он содержит метаинформацию — теги, которые имеют тенденцию со временем меняться, в отличии от аудио-содержимого.

Кстати, для форматов FLAC и APE данная проблема не существует, так как они обычно изначально содержат хэш-сумму аудио-данных, прописываемую кодировщиком. Для FLAC’а значение можно получить командой metaflac --show-md5sum.

Далее — достаточно надёжный способ подсчёта…

— Подход №1
— Подход №2
— Теги Xing и Lame
— Resync
— Надёжность подсчёта

Подход №1. Убираем ненужное

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

Структура mp3-файла:
— тег ID3v2
— mpeg-фреймы — собственно аудио-данные
— тег Lyrics
— тег APE
— тег ID3v1 (завершающий)

(Все теги опциональны.)

ID3v2, в отличии от своего предшественника, находится в начале файла, что даёт возможность клиенту сразу же прочитать мета-информацию если файл предаётся по сети, например. Начинается он с трёх ASCII-символов «ID3», затем идёт закодированная длина тега:

if buf[0:3] == 'ID3':
    id3_v2_len = 20 if ord(buf[5]) & 0x10 else 10
    id3_v2_len += ((ord(buf[6]) * 128 + ord(buf[7])) * 128 + ord(buf[8])) * 128 + ord(buf[9])
    audio_start = id3_v2_len

Дальше идут аудио-фреймы. Их начало можно визуально заметить если открыть файл в кодировке 1251 и найти символы «яы».

Теперь пойдём с конца. ID3v1 распознаётся как 128-байтный блок в конце файла, начинающийся с ASCII-строки «TAG». Если потом поискать с конца же «LYRICSBEGIN», то можно найти тег Lyrics3. А если «APETAGEX» — тег APEv2.

Если всё это вырезать, должны будут остаться только аудио-данные. Такой подход исповедует программа mp3tag.de, куча частных скриптов и tagging-библиотеки, значительная часть из которых ориентирована только на ID3, что, понятное дело, мешает.

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

В итоге программу написанную на такой манер мне пришлось выкинуть после столкновения с реальностью.

Подход №2. Оставляем нужное

MP3-плееры действуют наоборот — вычленяют то что им интересно — mpeg-фреймы, пропуская всё что на фреймы не похоже, причём делают это весьма успешно — «всхлипов» на «плохих» файлах обычно не услышишь. Разумно поступать так же.

Похоже так поступает плеер foobar2000, который по моей оценке работает идеально, но утилизировать его в данном случае, понятное дело, не получится.

MPlayer должен бы поступать так же, но сомнения возникают из-за того что по факту он иногда спотыкается на некорректных тегах, оставляя их. Команда очистки файла для него такая: mplayer in.mp3 -dumpaudio -dumpfile out.mp3.

Ещё есть медийные библиотеки — декодеры mp3. Это mad, gstreamer и libmpg123, которыми невозбранно пользуются разные пригрыватели. Первые два не пробовал, а вот libmpg123 пошла на ура — этот код проверен годами и множеством проектов, и качественный по результатам моих собственных исследований и сравнений. Там, в doc/examples есть исходный код микро-программы с говорящим названием extract_frames.c. Программа принимает на вход оригинальный mp3-файл и отправляет на выход чистые аудио-фреймы.

libmpg123 без проблем компилируется под cygwin и mingw (правда mingw-версия почему-то глючит с stdin/stdout, так что пришлось исправить исходник, открывая файл в бинарном режиме самому). Я слегка изменил программу чтобы вместо фреймов она сразу выдавала md5 и внёс пару изменений описанных ниже. Исходный код, кому интересно:

dl.dropbox.com/u/1883230/my/habr/mp3hash.zip

Теги Xing и Lame

Но в аудио-фреймах тоже может храниться мета-информация, от которой мы так хотим избавиться — это теги xing и lame, где закодирована лишняя для нас информация используемая для оптимизации перемещения по vbr-потоку, а также параметры использованные при кодировании. Вобщем-то ксинг с леймом можно и оставить так как мало кто умеет и будет их менять, но если вдруг в foobar2000 выполнить операцию «utilities / fix vbr mp3 header», то хэш-сумма для файла поменяется. Так что лучше будет выкинуть эту мету. Перестать учитывать данные теги при хэшировании можно передав libmpg123 следующий параметр:

// "remove ignore" несколько смущает, но это работает
ret = mpg123_param(m, MPG123_REMOVE_FLAGS, MPG123_IGNORE_INFOFRAME, 0.);

Resync

Также полезным оказалось убрать ограничение на resync limit. Если этого не сделать, программа «споткнётся» когда долго (4КБ) не встретит аудио-фреймов, что случается с файлами в которых, например, внутри ID3v2 содержится большое изображение. В моей версии программы хэш-сумма вычисляется та же, но появляющийся флаг ошибки всё портит и уже нельзя быть уверенным что результат получен без ошибок. А с этим параметром всё хорошо:

mpg123_param(m, MPG123_RESYNC_LIMIT, -1, 0.0);

Надёжность подсчёта

На мой ограниченный взгляд foobar2000 работает (избавляется от мета-информации) идеально. Патченная программа extract_frames.c не справляется с редкими файлами, но после операции «rebuild stream» в фубаре в 95% высчитывает уже нужный хэш (совместимый с фубаром). Далее и чуть хуже идёт mplayer — он почти всегда совместим с extract_frames (в режиме учёта lame/xing, понятное дело), но, как я уже писал, порой валится на мусорных тегах. Ещё далее идут различные теггеры, которые требуют достаточно корректных тегов, и вряд ли могут быть применимы задачи хэширования при наличии более стабильных альтернатив.

Вобщем после одной крупной неудачи и борьбы с парой аспектов, я лично остался доволен этим алгоритмом, проверив его на куче файлов.

Автор: zencd

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


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