Title: How to read infos in MP3 files
Question: How to read infos in MP3 files
Answer:
{
Description:
Component MP3Info extracts/saves any ID3 Tag into/from it's
properties from/to MP3 file.
Keywords: MP3 ID3 TAG INFO
Author: Rok Krulec (Rok@fpp.edu) (http://Rok.fpp.edu)
Primary site: ftp://ftp.fpp.edu/pub/people/Rok_Krulec/mp3i-v12.zip
If you use this component, I would like to get 1 American dollar to Rok Krulec, Zagrad 6, 3000 Celje, Slovenia, Europe.
Description of Variables, Properties, Methods and Events:
Genres: TStrings; - List of Genres
constructor Create(AOwner: TComponent); - Creates an instance
destructor Destroy; override; - Destroys an instance
method Save; - Saves ID3 Tag to file
method RemoveID3; - Removes ID3 Tag form file
property Filename: TFilename; - Filename of MP3 file, when changed it opens a new MP3 file
property Artist: String; - Artist (30 Chars)
property Title: String; - Title (30 Chars)
property Album: String; - Album (30 Chars)
property Year: String; - Year ( 4 chars)
property Comment: String; - Comment (30 Chars)
property Genre: String; - Genre [Read Only]
Property GenreID: Byte; - Genre ID
property Valid: Boolean; - Is ID3 valid [Read Only]
property Saved: Boolean; - Save success [Read Only]
property Error: String; - Error Message [Read Only]
property onChangeFile:TNotifyEvent; - Triggers when other file is openned
property onChange:TNotifyEvent; - Triggers when one of propertis is changed (Artist, Title, Album, Year, Comment, GenreID)
property onError:TNotifyEvent; - Triggers when errors ocure (Wrong filename, Frong fileformat)
}
unit MP3Info;
interface
uses
SysUtils, Classes;
const
TAGLEN = 127;
type
TMP3Info = class(TComponent)
private
{ Private declarations }
vFilename: TFilename;
vMP3Tag, vArtist, vTitle, vAlbum, vComment, vYear, vGenre, vError: String;
vGenreID: Byte;
vValid: Boolean;
vSaved: Boolean;
vChangeFileEvent, vChangeEvent, vErrorEvent: TNotifyEvent;
procedure SetFilename(Filename: TFilename);
procedure SetArtist(Artist: String);
procedure SetTitle(Title: String);
procedure SetAlbum(Album: String);
procedure SetYear(Year: String);
procedure SetComment(Comment: String);
procedure SetGenreID(ID: Byte);
procedure Open;
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
Genres: TStrings;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Save;
procedure RemoveID3;
property Filename: TFilename read vFilename write SetFilename;
property Artist: String read vArtist write SetArtist;
property Title: String read vTitle write SetTitle;
property Album: String read vAlbum write SetAlbum;
property Year: String read vYear write SetYear;
property Comment: String read vComment write SetComment;
property Genre: String read vGenre;
Property GenreID: Byte read vGenreID write SetGenreID;
property Valid: Boolean read vValid;
property Saved: Boolean read vSaved;
property Error: String read vError;
property onChangeFile:TNotifyEvent read vChangeFileEvent write vChangeFileEvent;
property onChange:TNotifyEvent read vChangeEvent write vChangeEvent;
property onError:TNotifyEvent read vErrorEvent write vErrorEvent;
end;
procedure Register;
implementation
constructor TMP3Info.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Genres := TStringList.Create;
vGenreID := 12;
vValid := false;
vSaved := false;
{ Fill the Genres String List so one can use it combo boxes e.t.c. Example: ComboBox.Items.Assign(MP3Info.Genres) }
Genres.CommaText :=
'"Blues","Classic Rock","Country","Dance","Disco","Funk","Grunge","Hip-Hop","Jazz","Metal","New Age","Oldies",'
+'"Other","Pop","R&B","Rap","Reggae","Rock","Techno","Industrial","Alternative","Ska","Death Metal","Pranks",'
+'"Soundtrack","Euro-Techno","Ambient","Trip-Hop","Vocal","Jazz+Funk","Fusion","Trance","Classical","Instrumental",'
+'"Acid","House","Game","Sound Clip","Gospel","Noise","AlternRock","Bass","Soul","Punk","Space","Meditative",'
+'"Instrumental Pop","Instrumental Rock","Ethnic","Gothic","Darkwave","Techno-Industrial","Electronic","Pop-Folk",'
+'"Eurodance","Dream","Southern Rock","Comedy","Cult","Gangsta","Top 40","Christian Rap","Pop/Funk","Jungle",'
+'"Native American","Cabaret","New Wave","Psychedelic","Rave","Showtunes","Trailer","Lo-Fi","Tribal","Acid Punk",'
+'"Acid Jazz","Polka","Retro","Musical","Rock & Roll","Hard Rock","Folk","Folk/Rock","National Folk","Swing","Bebob",'
+'"Latin","Revival","Celtic","Bluegrass","Avantgarde","Gothic Rock","Progressive Rock","Psychedelic Rock","Symphonic Rock",'
+'"Slow Rock","Big Band","Chorus","Easy Listening","Acoustic","Humour","Speech","Chanson","Opera","Chamber Music","Sonata",'
+'"Symphony","Booty Bass","Primus","Porn Groove","Satire","Slow Jam","Club","Tango","Samba","Folklore"'
end;
destructor TMP3Info.Destroy;
begin
inherited Destroy;
end;
{ Procedure to run when Filename property is changed }
procedure TMP3Info.SetFilename(Filename: TFilename);
begin
vFilename:=Filename;
Open;
end;
procedure TMP3Info.SetArtist(Artist: String);
begin
vArtist:=Copy(Artist,0, 30);
if Assigned(onChange) then onChange(Self);
end;
procedure TMP3Info.SetTitle(Title: String);
begin
vTitle:=Copy(Title, 0, 30);
if Assigned(onChange) then onChange(Self);
end;
procedure TMP3Info.SetAlbum(Album: String);
begin
vAlbum:=Copy(Album, 0, 30);
if Assigned(onChange) then onChange(Self);
end;
procedure TMP3Info.SetYear(Year: String);
begin
vYear:=Copy(Year, 0, 4);
if Assigned(onChange) then onChange(Self);
end;
procedure TMP3Info.SetComment(Comment: String);
begin
vComment:=Copy(Comment, 0, 30);
if Assigned(onChange) then onChange(Self);
end;
procedure TMP3Info.SetGenreID(ID: Byte);
begin
if ((ID255) or (IDGenres.Count-1)) then ID:=12;
vGenreID:=ID;
vGenre:=Genres[vGenreID]; //this line is important because after changing
//vGenreID whitout it vGenre will be the same like before !!!
if Assigned(onChange) then onChange(Self);
end;
{ Opens file with Filename property, reads ID3 Tag and sets properties }
procedure TMP3Info.Open;
{ Strips empty spaces at the end of word }
function Strip(WordToStrip:String; CharToStripAway: Char):String;
var i: Integer;
begin
for i:=length(WordToStrip) downto 1 do begin
if WordToStrip[i]' ' then begin
Strip:=Copy(WordToStrip, 0, i);
exit;
end;
end;
Strip:='';
end;
var dat:file of char;
id3:array [0..TAGLEN] of char;
begin
vSaved:=false;
vValid:=True;
if FileExists(vFilename) then begin
assignfile(dat,vFilename);
reset(dat);
seek(dat,FileSize(dat)-128);
blockread(dat,id3,128);
closefile(dat);
vMP3tag:=copy(id3, 1, 3);
if vMP3Tag='TAG' then begin
vTitle:=strip(copy(id3, 4, 30),' ');
vArtist:=strip(copy(id3, 34, 30), ' ');
vAlbum:=strip(copy(id3, 64, 30), ' ');
vComment:=strip(copy(id3, 98, 30), ' ');
vYear:=strip(copy(id3, 94, 4), ' ');
vGenreID:=ord(id3[127]);
if vGenreIDGenres.Count then vGenreID:=12;
vGenre:=Genres[vGenreID];
{ Trigger OnChange Event }
if Assigned(onChangeFile) then onChangeFile(Self);
end else begin
vValid:=False;
vTitle:='';
vArtist:='';
vAlbum:='';
vComment:='';
vYear:='';
vGenreID:=12;
vError:='Wrong file format or no ID3 Tag !';
if Assigned(onError) then onError(Self);
end;
end else begin
vValid:=False;
vError:='File doesn`t exist !';
if Assigned(onError) then onError(Self);
end;
end;
{ Removes the ID3-tag from currently open file }
procedure TMP3Info.RemoveID3;
var dat : file of char;
begin
// does the file exist ?
if Not FileExists(vFilename) then
begin
vError:='File doesn`t exist !';
if Assigned(onError) then onError(Self);
exit;
end;
// is the file already untagged ?
if (vValid=false) then
begin
vError:='File is already untagged !';
if Assigned(onError) then onError(Self);
exit;
end;
// remove readonly-attribute
If (FileGetAttr(vFilename) and faReadOnly 0) then
FileSetAttr(vFileName,FileGetAttr(vFilename)-faReadOnly);
// if readonly attr. already exists it cannot be removed to cut ID3 Tag
If (FileGetAttr(vFilename) and faReadOnly 0) then
begin
vError:='Cant write ID3 tag information !';
if Assigned(onError) then onError(Self);
exit;
end;
// open current mp3 file if ID3 tag exists
If (vValid=true) then
begin
{I-}
assignfile(dat,vFilename);
reset(dat);
{I+}
If IOResult0 then
begin
vError:='Could not open file !';
if Assigned(onError) then onError(Self);
exit;
end;
seek(dat,FileSize(dat)-128);
truncate(dat); // cut all 128 bytes of file
closefile(dat);
vValid:=false; // set vValid to false because the tag has been removed
end;
end;
{ Saves ID3 Tag to currently opened file }
procedure TMP3Info.Save;
{ Empties 128 character array } { Don't tell me that there is a function for this in Pascal }
procedure EmptyArray(var Destination: array of char);
var i: Integer;
begin
for i:=0 to TAGLEN do begin
Destination[i]:=' ';
end;
end;
{ Insert a substring into character array at index position of array }
procedure InsertToArray(Source: String; var Destination: array of char; Index: Integer);
var i: Integer;
begin
for i:=0 to length(Source)-1 do begin
Destination[Index+i]:=Source[i+1];
end;
end;
var dat:file of char;
id3:array [0..TAGLEN] of char;
begin
vSaved:=true;
// does the filename exist ?
if FileExists(vFilename) then begin
// fill 128 bytes long array with ID3 Tag information
EmptyArray(id3);
InsertToArray('TAG', id3, 0);
InsertToArray(vTitle, id3, 3);
InsertToArray(vArtist, id3, 33);
InsertToArray(vAlbum, id3, 63);
InsertToArray(vComment, id3, 97);
InsertToArray(vYear, id3, 93);
id3[127]:=chr(vGenreID);
// remove readonly-attribute
If (FileGetAttr(vFilename) and faReadOnly 0) then
FileSetAttr(vFileName,FileGetAttr(vFilename)-faReadOnly);
// if readonly attr. already exists it cannot be removed to write ID3
If (FileGetAttr(vFilename) and faReadOnly 0) then
begin
vSaved:=False;
vError:='Cant write ID3 tag information !';
if Assigned(onError) then onError(Self);
exit;
end;
// if valid then overwrite existing ID3 Tag, else append to file
if (vValid=True) then begin
{I-}
assignfile(dat,vFilename);
reset(dat);
seek(dat,FileSize(dat)-128);
blockwrite(dat,id3,128);
closefile(dat);
{I+}
If IOResult0 then vSaved:=false;
end else begin
{I-}
assignfile(dat,vFilename);
reset(dat);
seek(dat,FileSize(dat));
blockwrite(dat,id3,128);
closefile(dat);
{I+}
If IOResult0 then vSaved:=false;
end
end else begin
vValid:=False;
vSaved:=False;
vError:='File doesn`t exist or is not valid !';
if Assigned(onError) then onError(Self);
end;
end;
procedure Register;
begin
RegisterComponents('Samples', [TMP3Info]);
end;
end.
{
The ID3 Information is stored in the last 128 bytes of an MP3 file.
The ID3 has the following fields, and the offsets given here, are from 0-127
Field Length offsets
-------------------------------------
Tag 3 0-2
Songname 30 3-32
Artist 30 33-62
Album 30 63-92
Year 4 93-96
Comment 30 97-126
Genre 1 127
}