在Elixir中搜索文件中字节模式的最有效方法

在Elixir中搜索文件中字节模式的最有效方法,elixir,id3,Elixir,Id3,我正在歌曲文件中搜索id3标签。文件可以有id3v1、id3v1扩展标记(位于文件末尾)以及id3v2标记(通常位于开头)。对于id3v1标记,我可以使用File.read(song_文件)并提取最后355个字节(扩展标记为128+227)。但是,对于id3v2标记,我需要从一开始就在文件中搜索10字节的id3v2模式。我希望在搜索不同的标记时避免重复打开和关闭同一文件的任何开销,因此我认为最好的方法是使用file.stream!(song_文件)并将文件流发送到不同的函数以搜索不同的标记 de

我正在歌曲文件中搜索id3标签。文件可以有id3v1、id3v1扩展标记(位于文件末尾)以及id3v2标记(通常位于开头)。对于id3v1标记,我可以使用File.read(song_文件)并提取最后355个字节(扩展标记为128+227)。但是,对于id3v2标记,我需要从一开始就在文件中搜索10字节的id3v2模式。我希望在搜索不同的标记时避免重复打开和关闭同一文件的任何开销,因此我认为最好的方法是使用file.stream!(song_文件)并将文件流发送到不同的函数以搜索不同的标记

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
def parse(文件名)do
file\u stream=file.stream!(文件名,[],1)
id3v1_标记(文件流)
|>添加标签(id3v2标签(文件流))
结束
def id3v1_标记(文件流)do
tags=tags%{}#包含所需标记的结构
>=Stream.take(文件\u Stream,-355)
id3_标记=到_字符串(id3_标记)
如果String.slice(id3_标记,0,3)=“标记”do
Map.put(标记,:title,String.slice(id3_标记,3,30))
Map.put(标签:track\u artist,String.slice(id3\u标签,33,30))
...
结束
如果String.slice(id3_extended_标记,0,4)=“tag+”do
Map.put(tags,:title,tags.title String.slice(id3_extended_tag,4,60))
Map.put(tags,:track\u artist,tags.track\u artist String.slice(id3\u extended\u tag,64,60))
...
结束
结束
def id3v2_标记(文件流)do
搜索模式:
结束
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(文件\u Stream,-355)

因为Stream.take(file_Stream,-355)是一个函数,而不是二进制文件。如何将其转换为模式匹配的二进制文件?

我认为由于对流的依赖,您的实现过于复杂。让它工作,让它漂亮,然后让它快(但只有在必要时)

为了简单起见,我会首先将所有内容加载到内存中。只需使用
File.read/1
。然后,您可以使用:binary模块中的函数来搜索模式(
:binary.match/2
),拆分模式(
:binary.split/2
),或者获取某个部分(
:binary.part/3
)。也不需要混合使用File.stream和File.read,只需读取一次并传递相同的二进制文件即可

另外,非常重要的是,不要使用字符串模块。字符串用于处理UTF-8编码的二进制文件。您希望对所有字节级操作使用:二进制模块


最后,
Stream.take/2
总是返回懒惰的函数。您希望改用
Enum.take/2
(它接受流,因为流也是可枚举的)。虽然,正如我所说,我会完全跳过流的内容。

我没有答案给你,但如果我是你,我真的会避免使用神奇的数字。我假设-355是128和227的和?如果我是你,我会将一个名为
@id\u extended\u tag\u binsize
的模块属性设置为227,将
@id3\u tag\u binsize
设置为128,然后在流中使用这些属性更不容易出错,更易于阅读和理解。首先,需要告诉File.stream使用语法File.stream读取字节,而不是行!(路径,[:读取],:字节)。接下来,我不认为-355可以用于Stream.take,因为Stream不是一个列表,而是一个潜在的列表,需要通过Stream.to_list调用来最终确定。感谢您的建议。我已经相应地更改了代码。我感谢您的回答。我会接受你的建议并使用File.read/1.我不知道关于字符串模块。我认为如果字节是字符,那么可以将它们视为二进制文件或字符串。