Самый эффективный способ поиска файла для байтовых паттернов в 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 вместо этого (он принимает потоки, так как потоки также являются перечисляемыми). Хотя, как я уже сказал, я бы вообще пропустил потоковое вещание.

Другие вопросы по тегам