Odczyt ID3TAGv2 z pliku mp3

Juhas

Wiele razy byłem pytany o to, jak odczytać z pliku mp3 ID3TAGv2. Odczytanie ID3TAGv1 jest niezmiernie proste, a kod znajduje się gdzieś na 4p.
Natomiast z ID3TAGv2 jest trochę trudniej.
Najpierw należy się przyjrzeć ich budowie. W wersji pierwszej, jeżeli tag jest zapisany, wszystkie jego pola są zapisane, nawet jeżeli są puste. I tu łatwa sprawa... Natomiast w wersji drugiej, nie muszą występować wszystkie pola. A pola puste w ogóle nie występują...
Teraz co nieco o budowie wersji drugiej.

W pierwszych trzech bajtach pliku powinien być identyfikator: 'ID3'. Jeżeli go nie ma, znaczy to, że tag nie jest zapisany.
No więc tak. TAG jest, więc co dalej?

Ano nic prostszego, po prostu go odczytać :)
Po identyfikacji tagu jest 7 bajtów, które nas nie interesują.
Pierwsze pole tagu zaczyna się w 11 bajcie.

I teraz tak. Ze względu na to, że nie wszystkie pola tagu muszą istnieć, autorzy tagu postarali się jakby o identyfikatory pól. I teraz będzie o polach, jakie zawiera wersja druga:

Identyfikator| Nazwa

TENC | dekoder mp3
WXXX | adres www
TCOP | prawa autorskie
TOPE | oryginalny wykonawca
TCOM | kompozytor
COMM | komentarz
TYER | rok
TMED | nie mam pojęcia, do czego to jest, ale nigdy tego nie używałem :)
TCON | rodzaj muzyki
TRCK | numer ścieżki w playliście
TALB | album
TPE1 | artysta
TIT2 | tytuł

Jak widać PRAWIE wszystkie identyfikatory pól rozpoczynają się literką T, a wszystkie mają długość 4 bajtów. Więc skąd wiedzieć, co jest czym?

Po prostu musimy stworzyć sobie tablicę stałych, która może wyglądać np. tak:

const
id: array[0..13] of string = ('TENC', 'WXXX', 'TCOP', 'TOPE', 'TCOM',
'COMM', 'TYER', 'TMED', 'TCON', 'TLEN', 'TRCK', 'TALB', 'TPE1', 'TIT2');

I teraz jeszcze jedna ważna rzecz, o której muszę powiedzieć...
Budowa tego tagu wygląda mniej - więcej tak:

identyfikator tagu + 7 bajtów 'wolnych'+identyfikator pola+ 7(8, 11) bajtów wolnych+ identyfikator następnego pola + 7(12) bajtów wolnych....

Przykład takiego tagu: (kropką oznaczam wolne bajty)

ID3.......TENC.......DekoderWXXX........Adres wwwTCOP.......prawa autorskieTPE1.......ArtystaTIT2.......tytułTALB.......AlbumCOMM...........Komentarz

Więc tak jak widać, identyfikator kolejnego pola zaczyna się w następnym bajcie po ostatnim polu.

Teraz kolejna ważna rzecz...
Między niektórymi identyfikatorami pól, a ich 'zawartością' jest 7, 8 lub 11 bajtów wolnych. Teraz to zapiszę...

Między identyfikatorem TENC, a jego zawartością(czyli w tym wypadku 'Dekoder'), jest 7 bajtów wolnych.
Między identyfikatorem WXXX, a jego zawartością jest 8 bajtów wolnych.
Między identyfikatorem COMM, a jego zawartością jest 11 bajtów wolnych.
Między resztą identyfikatorów, a ich zawartością jest 7 bajtów wolnych.

Więc teraz krok drugi do odczytania tagu (krokiem pierwszym była ta tablica stałych)
Tworzymy typ rekordowy, np:

type
TId3TAGv2 = record
ID: string;
Tytul, Artysta, Album, Rok, Komentarz: string;
encoder, url, prawa, kompozytor, oryginalny: string;
end;

I następnie po prostu odczytujemy po kolei 4 bajty z pliku i sprawdzamy, czy ich zawartość znajduje się w naszej tablicy stałych...

Mam nadzieję, że wszyscy pokapowali o co chodzi.... A oto przykładowy kod autorstwa mojego do odczytu TAGUv2:


(*********************************************)
(*      Odczytu ID3TAGv2 z pliku mp3         *)
(*      autor: Adam 'Juhas' Jachocki         *)
(*         e-mail: [email protected]            *)
(*            www.juhas.glt.pl               *)
(*********************************************)

const
 id: array[0..13] of string = ('TENC', 'WXXX', 'TCOP', 'TOPE', 'TCOM',
'COMM', 'TYER', 'TMED', 'TCON', 'TLEN', 'TRCK', 'TALB', 'TPE1', 'TIT2');

type
    TId3TAGv2 = record
     ID: string;
     Tytul, Artysta, Album, Rok, Komentarz: string;
     encoder, url, prawa, kompozytor, oryginalny: string;
  end;

var
 tag2: TID3Tagv2;

procedure Tform1.TAG2Open(nazwaPliku: string);
var
 f: TfileStream;
 buffer: array[1..512] of char;
 buffer2: array[1..3] of char;
 enc, url, cop, ope, com, kom, yer, rck, alb, tit, tpe: string; //wartości
 SprId, sprId2, wartosc: string; //sprawdza, po odczytaniu 4 znaków, czy
SprID jest w ID
 p, d, wartosc1, wartosc2: integer;
//p - znak ewentualnego Identyfikatora
//d - drugi ewentualny identyfikator

begin
fileMode:=0;
f:=TFileStream.Create(nazwaPliku, fmOpenRead);
try
 f.readBuffer(buffer2, 3);
finally
f.Free;
end;

//tutaj trzeba te pola 'wyzerować'

tit:='';
tpe:='';
alb:='';
yer:='';
kom:='';
enc:='';
url:='';
cop:='';
com:='';
ope:='';

tag2.ID:=buffer2;

if buffer2='ID3' then begin

f:=TfileStream.Create(nazwaPliku, fmOpenRead);
try
 f.ReadBuffer(buffer, 512);
finally
f.Free;
end;

p:=1;
d:=0;
for bufor:=1 to 512 do begin
  if bufor=512 then begin
                      wartosc1:=p+5;
                      wartosc:=copy(buffer, wartosc1, bufor);
                      if sprID='TENC' then enc:=wartosc;
                      if sprID='WXXX' then url:=wartosc;
                      if sprID='TCOP' then cop:=wartosc;
                      if sprID='TOPE' then ope:=wartosc;
                      if sprID='TCOM' then com:=wartosc;
                      if sprID='COMM' then kom:=wartosc;
                      if sprID='TYER' then yer:=wartosc;
                      if sprID='TALB' then alb:=wartosc;
                      if sprID='TIT2' then tit:=wartosc;
                      if sprID='TPE1' then tpe:=wartosc;
                     end;

 if (buffer[bufor]='T') or (buffer[bufor]='C') or (buffer[bufor]='W') then
begin
 if p=1 then p:=bufor
        else d:=bufor;

if p=bufor then begin

sprId:=buffer[bufor]+buffer[bufor+1]+buffer[bufor+2]+buffer[bufor+3];
              if (sprId<>id[0]) and (sprID<>id[1]) and (sprId<>id[2]) and
(sprID<>id[3]) And (sprID<>id[4]) and (sprID<>id[5]) AND (sprID<>id[6]) And
(sprID<>id[7]) and (sprID<>id[8]) and (sprID<>id[9]) and (sprID<>id[10]) and
(sprID<>id[11]) and (sprID<>id[12]) and (sprID<>id[13]) then begin
              d:=0;
              p:=1;

              end else sprID:=sprID;
             end;

 if p<>bufor then begin

sprId2:=buffer[bufor]+buffer[bufor+1]+buffer[bufor+2]+buffer[bufor+3];
                 if (sprId2=id[0]) or (sprID2=id[1]) or (sprId2=id[2]) or
(sprID2=id[3]) or (sprID2=id[4]) or (sprID2=id[5]) or (sprID2=id[6]) or
(sprID2=id[7]) or (sprID2=id[8]) or (sprID2=id[9]) or (sprID2=id[10]) or
(sprID2=id[11]) or (sprID2=id[12]) or (sprID2=id[13]) then begin
                                               wartosc1:=p+11;
                                               wartosc:=copy(buffer,
wartosc1, d-wartosc1);
                                               if sprID='TENC' then
enc:=wartosc;
                                               if sprID='WXXX' then
url:=wartosc;
                                               if sprID='TCOP' then
cop:=wartosc;
                                               if sprID='TOPE' then
ope:=wartosc;
                                               if sprID='TCOM' then
com:=wartosc;
                                               if sprID='COMM' then
kom:=wartosc;
                                               if sprID='TYER' then
yer:=wartosc;
                                               if sprID='TALB' then
alb:=wartosc;
                                               if sprID='TIT2' then
tit:=wartosc;
                                               if sprID='TPE1' then
tpe:=wartosc;
                                               sprID:=sprID2;
                                               p:=d;
                                               d:=0;
                                              end else d:=0;

             end;

end;

end;

end;
tag2.Tytul:=trim(tit);
tag2.Artysta:=tpe;
tag2.Album:=alb;
tag2.Rok:=yer;
tag2.Komentarz:=trim(kom);
tag2.encoder:=enc;
tag2.url:=trim(url);
tag2.prawa:=cop;
tag2.kompozytor:=com;
tag2.oryginalny:=ope;

end;

Ten kod może być nieco niezrozumiały, ale działa :)
Wszelkie pytania kierujcie do mnie.

7 komentarzy

Dopiero teraz to zamieszczasz. A ja już zrobiłem program
www.4programmers.net/download.php?id=889
i będę musiał przerobić.

Ten kod działa, ale na ID3v2 0.2, czyli z resztą jest nie kompatybilny (choć może czasem) należy równierz pamietać że ID3v2 może być wielojęzykowy, czyli WideChar oraz może mieć maksymalnie 256MB! objętości nie licząc samej zawartości plików mp3

< delphi > i </ delphi >
za to masz - 3

Niedługo pewnie umieszczę zupełnie inny kod już taki bardziej profesjonalny(zapisywanie też będzie uwzględnione). Jak tylko będzie mi się chciało go dokończyć ;)

a to wejdzie na ID3TAGv1 i na ID3TAGv2 czy tylko na ID3TAGv2??

W kodzie programu w artykule sa bledy ... i program nalezy poprawic zeby dzialal.

Zamiast zawilej procedury poszukiwania taga proponuje sprawe uproscic:
type TTagBuffer= array[1..1024] of char; // sam nie wiem jaka jest poprawny
//rozmiar ale to powinno wystarczyc

procedure TForm1.FindTagPos(AName: string; ABuffer: TTagBuffer; var AStart, ALength: cardinal);
var k: integer;
begin
AStart:=Pos(AName, ABuffer);
if AStart>0 then
begin
inc(AStart,length(AName));
ALength:= byte(ABuffer[AStart]); //odczytaj dlugosc taga
k:=AStart+3;
while AStart<k do
begin
inc(AStart);
ALength:= ALength shl 8 or byte(ABuffer[AStart]);
end;
inc(AStart,4);
dec(ALength);
if AName='WXXX' then inc(AStart);
end;
end;