Самый эффективный способ поиска файла для байтовых паттернов в Elixir
Я ищу теги id3 в файле песни. Файл может иметь расширенные теги id3v1, id3v1 (расположены в конце файла), а также теги id3v2 (обычно расположены в начале). Для тегов id3v1 я могу использовать File.read (song_file) и извлечь последние 355 байтов (128 + 227 для расширенного тега). Однако для тегов id3v2 мне нужно с самого начала искать файл, ища 10-байтовый шаблон id3v2. Я хочу избежать дополнительных затрат на повторное открытие и закрытие одного и того же файла при поиске различных тегов, поэтому я подумал, что наилучшим способом будет использование File.stream!(Song_file) и отправка потока файлов в различные функции для поиска разные теги.
def parse(file_name) do
file_stream = File.stream!(file_name, [], 1)
id3v1_tags(file_stream)
|> add_tags(id3v2_tags(file_stream))
end
def id3v1_tags(file_stream) do
tags = Tags%{} #struct containing desired tags
<< id3_extended_tag :: binary-size(227), id3_tag :: binary-size(128) >> = Stream.take(file_stream, -355)
id3_tag = to_string(id3_tag)
if String.slice(id3_tag,0, 3) == "TAG" do
Map.put(tags, :title, String.slice(id3_tag, 3, 30))
Map.put(tags, :track_artist, String.slice(id3_tag, 33, 30))
...
end
if String.slice(id3_extended_tag, 0, 4) == "TAG+" do
Map.put(tags, :title, tags.title <> String.slice(id3_extended_tag, 4, 60))
Map.put(tags, :track_artist, tags.track_artist <> String.slice(id3_extended_tag, 64, 60))
...
end
end
def id3v2_tags(file_stream) do
search for pattern:
<<0x49, 0x44, 0x33, version1, version2, flags, size1, size2, size3, size4>>
end
1) Сохраняю ли я время выполнения, создавая File.stream! один раз и отправлю его различным функциям (я буду сканировать десятки тысяч файлов, поэтому важно сэкономить немного времени)? Или я должен просто использовать File.read для тегов id3v1 и File.stream! для тегов id3v2?
2) Я получаю сообщение об ошибке в строке:
<< id3_extended_tag :: binary-size(227), id3_tag :: binary-size(128) >> = Stream.take(file_stream, -355)
потому что Stream.take(file_stream, -355) является функцией, а не двоичным файлом. Как мне превратить его в двоичный файл, который я могу сопоставить с образцом?
1 ответ
Я считаю, что ваша реализация неоправданно сложна из-за зависимости от потока. Сделайте так, чтобы это работало, сделайте это красивым, а затем сделайте это быстро (но только при необходимости)
Для простоты я сначала загрузил бы все в память. Просто используйте File.read!/1
, Затем вы можете использовать функции из: двоичного модуля для поиска шаблонов (:binary.match/2
), раздели это (:binary.split/2
) или захватить определенную часть (:binary.part/3
). Нет необходимости смешивать File.stream и File.read, просто прочитайте его один раз и передайте тот же самый двоичный файл.
Кроме того, очень важно, не используйте модуль String. Строка предназначена для работы с двоичными файлами в кодировке UTF-8. Вы хотите использовать двоичный модуль: для всех операций на уровне байтов.
В заключение, Stream.take/2
всегда возвращает функции, так как это лениво. Вы хотите использовать Enum.take/2
вместо этого (он принимает потоки, так как потоки также являются перечисляемыми). Хотя, как я уже сказал, я бы вообще пропустил потоковое вещание.