%% ---
%%  Excerpted from "Programming Erlang",
%%  published by The Pragmatic Bookshelf.
%%  Copyrights apply to this code. It may not be used to create training material,
%%  courses, books, articles, and the like. Contact us if you are in doubt.
%%  We make no guarantees that this code is fit for any purpose.
%%  Visit http://www.pragmaticprogrammer.com/titles/jaerlang for more book information.
%%  
%%  日本語版の『プログラミングErlang』、およびサンプルコードについては、
%%   http://ssl.ohmsha.co.jp/cgi-bin/menu.cgi?ISBN=978-4-274-06714-3 を確認してください。
%% ---
-module(id3_tag_lengths).

%% Warning the code in this module uses a rather
%% crude way of finding the start and stiop pointers
%% into the music - for this reason it is not listed inm the book
%% anybody who finds out how to do this correctly is
%% welcome to tell the author how to do it.
%% 注意：このモジュールのコードはかなり乱暴な方法を使って曲の開始箇所と
%% 終了箇所を見付け出している。そのため、本にはコードを載せていない。
%% これを正しくやる方法を知っている人がいれば、筆者にぜひ教えてほしい。

%% 筆者の戦略はあまり柔軟性がない：
%%   mp3_sync:find_sync(Bin, 1)を使って同期マークを見つける
%%   失敗したらあきらめる
%%   最後の128バイトを調べてTAGならばOK
%%   そうでなければあきらめる
%%   最初にあるID3タグをいいかげんに解析してみる
%%   find_syncの結果と違っていれば、同期マークが見付かったとみなす
%%   

%% 要は、結果の{ok, Start, Stop} はデータがStartとStopの間にある
%% ことを意味していて、筆者はその値を信用しているということだ。
-compile(export_all).
-import(lists, [foreach/2, map/2]).

test() ->
    Files = lib_find:files("/home/joe/music_keep", "/*.mp3", true),
    map(fun(F) -> file(F) end, Files).

file(File) ->
    read_id3_tag(File).

read_id3_tag(File) ->
    case file:open(File, [read,binary,raw]) of
        {ok, S} ->
	    Size = filelib:file_size(File),
            Result = analyse(S, Size),
	    file:close(S),
	    Result;
        _Error ->
            error
    end.

analyse(S, Size) ->
    case (catch analyse1(S, Size)) of
	{'EXIT', _Why} ->
	    io:format("_Why=~p~n",[_Why]),
	    error;
	StartStop ->
	    StartStop
    end.

analyse1(S, Size) ->
    {ok, Bin}  = file:pread(S, 0, 10000),
    {ok, StartTrust} = mp3_sync:find_sync(Bin, 1),
    {Type, StartUntrust} = parse_start_tag(Bin),
    Stop = parse_end_tag(S, Size),
    if 
	StartTrust == StartUntrust ->
	    true;
	true ->
	    io:format("** error in header code:  real=~p Type=~p Val=~p~n",
		      [StartTrust, Type, StartUntrust])
    end,
    {StartTrust, Stop}.


parse_start_tag(<<$I,$D,$3,3,0,_Unsync:1,_Extended:1,_Experimental:1,
		 _:5,K:32,_/binary>>) ->
    Tag = "ID3v2.3.0",
    Size = syncsafe2int(K),
    {Tag, Size+10};
parse_start_tag(<<$I,$D,$3,4,0,_Unsync:1,_Extended:1,_Experimental:1,
		 Footer:1,_:4,K:32,_/binary>>) ->
    Tag = "ID3v2.3.0",
    Size = syncsafe2int(K),
    Size1 = case Footer of 
		1 -> 10 + Size;
		0 -> Size
	    end,
    {Tag, Size1+1};
parse_start_tag(<<X:10/binary,_/binary>>) ->
    io:format("strange start tag~p~n",[X]),
    {error, 1}.

parse_end_tag(S, Size) ->
    {ok, B2} = file:pread(S, Size-128, 128),
    parse_v1_tag(B2, Size).
    
parse_v1_tag(<<$T,$A,$G,_/binary>>, Size) ->
    Size - 128;
parse_v1_tag(_, Size) ->
    Size.

syncsafe2int(N) ->
    <<_:1,N1:7,_:1,N2:7,_:1,N3:7,_:1,N4:7>> = <<N:32>>,
    <<I:32>> = <<0:4,N1:7,N2:7,N3:7,N4:7>>,
    I.
