|
| 1 | +-module(mp3_manager). |
| 2 | + |
| 3 | +-export([start/0]). |
| 4 | + |
| 5 | +-import(lists, [map/2, reverse/1]). |
| 6 | + |
| 7 | +start() -> |
| 8 | + Files = lib_files_find:files(".", "*.mp3", true), |
| 9 | + V = map(fun handle/1, Files), |
| 10 | + lib_misc:dump("mp3data", V). |
| 11 | + |
| 12 | +handle(File) -> |
| 13 | + (catch read_id3_tag(File)). |
| 14 | + |
| 15 | +read_id3_tag(File) -> |
| 16 | + case file:open(File, [read, binary, raw]) of |
| 17 | + {ok, S} -> |
| 18 | + Size = filelib:file_size(File), |
| 19 | + Result = (catch analyse(S, Size)), |
| 20 | + file:close(S), |
| 21 | + {File, Result}; |
| 22 | + Error -> |
| 23 | + {File, Error} |
| 24 | + end. |
| 25 | + |
| 26 | +analyse(S, Size) -> |
| 27 | + {ok, B1} = file:pread(S, 0, 10), |
| 28 | + case parse_start_tag(B1) of |
| 29 | + {"ID3v2.3.0", {_Unsync, Extended, _Experimental, Len}} -> |
| 30 | + {ok, Data} = file:pread(S, 10, Len), |
| 31 | + case Extended of |
| 32 | + 1 -> |
| 33 | + {Extended, Data1} = split_binary(Data, 10), |
| 34 | + parse_frames(Data1); |
| 35 | + 0 -> |
| 36 | + parse_frames(Data) |
| 37 | + end; |
| 38 | + no -> |
| 39 | + {ok, B2} = file:pread(S, Size - 128, 128), |
| 40 | + parse_v1_tag(B2) |
| 41 | + end. |
| 42 | + |
| 43 | +parse_start_tag(<<$I, $D, $3, 3, 0, Unsync:1, Extended:1, Experimental:1, _:5, K:32>>) -> |
| 44 | + Tag = "ID3v2.3.0", |
| 45 | + Size = syncsafe2int(K), |
| 46 | + {Tag, {Unsync, Extended, Experimental, Size}}; |
| 47 | +parse_start_tag(_) -> |
| 48 | + no. |
| 49 | + |
| 50 | +parse_frames(B) -> |
| 51 | + F = gather_frames(B), |
| 52 | + G = map(fun parse_frame/1, F), |
| 53 | + H = [{I, J} || {I, J} <- G], |
| 54 | + {ok, H}. |
| 55 | +parse_frame({"T1T2", _, Txt}) -> {title, parse_txt(Txt)}; |
| 56 | +parse_frame({"TPE1", _, Txt}) -> {performer, parse_txt(Txt)}; |
| 57 | +parse_frame({"TALB", _, Txt}) -> {album, parse_txt(Txt)}; |
| 58 | +parse_frame({"TRCK", _, Txt}) -> {track, parse_txt(Txt)}; |
| 59 | +parse_frame(_) -> skipped. |
| 60 | + |
| 61 | +parse_txt(<<0:8, Txt/binary>>) -> Txt; |
| 62 | +parse_txt(<<1:8, 16#ff, 16#fe, Txt/binary>>) -> unicode_to_ascii(Txt). |
| 63 | + |
| 64 | +unicode_to_ascii(Bin) -> list_to_binary(uni_to_ascii(binary_to_list(Bin))). |
| 65 | + |
| 66 | +uni_to_ascii([X, _ | Y]) -> [X | uni_to_ascii(Y)]; |
| 67 | +uni_to_ascii([]) -> []. |
| 68 | + |
| 69 | +gather_frames(B) when size(B) < 10 -> []; |
| 70 | +gather_frames(<<0, 0, 0, 0, _/binary>>) -> []; |
| 71 | +gather_frames(<<$P, $R, $I, $V, _/binary>>) -> []; |
| 72 | +gather_frames(<<Id1, Id2, Id3, Id4, SafeN:32, Flags:16, Rest/binary>>) -> |
| 73 | + <<_A:1, _B:1, _C:1, _:5, I:1, J:1, _K:1, _:5>> = <<Flags:16>>, |
| 74 | + case {I, J} of |
| 75 | + {0, 0} -> |
| 76 | + Tag = {Id1, Id2, Id3, Id4}, |
| 77 | + case is_tag(Tag) of |
| 78 | + true -> |
| 79 | + Size = syncsafe2int(SafeN), |
| 80 | + {Data, Next} = split_binary(Rest, Size), |
| 81 | + [{Tag, Flags, Data} | gather_frames(Next)]; |
| 82 | + false -> |
| 83 | + [] |
| 84 | + end; |
| 85 | + _ -> |
| 86 | + [] |
| 87 | + end; |
| 88 | +gather_frames(CC) -> [{error, CC}]. |
| 89 | + |
| 90 | +is_tag([H | T]) when $A =< H, H =< $Z -> is_tag(T); |
| 91 | +is_tag([H | T]) when $0 =< H, H =< $9 -> is_tag(T); |
| 92 | +is_tag([]) -> true; |
| 93 | +is_tag(_) -> false. |
| 94 | + |
| 95 | +syncsafe2int(N) -> |
| 96 | + <<_:1, N1:7, _:1, N2:7, _:1, N3:7, _:1, N4:7>> = <<N:32>>, |
| 97 | + <<I:32>> = <<0:4, N1:7, N2:7, N3:7, N4:7>>, |
| 98 | + I. |
| 99 | + |
| 100 | +parse_v1_tag(<<$T, $A, $G, B/binary>>) -> |
| 101 | + {Title, B1} = split_binary(B, 30), |
| 102 | + {Artist, B2} = split_binary(B1, 30), |
| 103 | + {Album, B3} = split_binary(B2, 30), |
| 104 | + {_Year, B4} = split_binary(B3, 30), |
| 105 | + {_Comment, <<K, Track, _Gendre>>} = split_binary(B4, 28), |
| 106 | + L = [{title, trim(Title)}, {artist, trim(Artist)}, {album, trim(Album)}], |
| 107 | + case K of |
| 108 | + 0 -> |
| 109 | + {"ID3v1.1", [{track, Track} | L]}; |
| 110 | + _ -> |
| 111 | + {"ID3v1", L} |
| 112 | + end; |
| 113 | +parse_v1_tag(_) -> |
| 114 | + no. |
| 115 | + |
| 116 | +trim(Bin) -> |
| 117 | + list_to_binary(trim_blanks(binary_to_list(Bin))). |
| 118 | + |
| 119 | +trim_blanks(X) -> reverse(skip_blanks_and_zero(reverse(X))). |
| 120 | + |
| 121 | +skip_blanks_and_zero([$\s | T]) -> skip_blanks_and_zero(T); |
| 122 | +skip_blanks_and_zero([0 | T]) -> skip_blanks_and_zero(T); |
| 123 | +skip_blanks_and_zero(X) -> X. |
0 commit comments