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

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;

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

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

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ć ;)

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

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

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