DelphiFAQ Home Search:

How to read/write ID3 tags in MP3 files

 

comments5 comments. Current rating: 4 stars (2 votes). Leave comments and/ or rate it.

The thing that makes MPEG Layer 3 files good (besides their size:) are ID3 tags. Thanks to them you can save information about the song. Here's the ID3 tag structure and information on reading/modifying them. Enjoy!

The ID3 tag is saved in the last 128 bytes of a MPEG Layer 3 file. It starts with a 'TAG' string. If this string is absent that means that ID3 information has been removed. But don't worry - all you have to do is append it to the file. In newer versions of Delphi if you are going to use this code you may want to use the packed keyword with arrays and records.

// The MPEG Layer 3 ID3 tag structure:
type
  ID3Struct = record
    Signature: array [0..2] of Char; { Should be: 'TAG' }
    Title, Artist, Album: array [0..29] of Char;
    Year: array [0..3] of Char;
    Comment: array [0..29] of Char;
    Genre: Byte;
  end;

// Here's the genre (Max. 256 entries).
const
  ID3Genre : array [0..126] of string = ('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', 'Psychadelic',
    'Rave', 'Showtunes', 'Trailer', 'Lo-Fi', 'Tribal', 'Acid Punk',
    'Acid Jazz', 'Polka', 'Retro', 'Musical', 'Rock & Roll',
    'Hard Rock', 'Folk', 'Folk-Rock', 'National Folk', 'Swing',
    'Fast Fusion', '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',
    'Ballad', 'Power Ballad', 'Rhythmic Soul', 'Freestyle', 'Duet',
    'Punk Rock', 'Drum Solo', 'Acapella', 'Euro-House', 'Dance Hall' );

// This is the ID3 Tag read code:
procedure ReadID3Tag;
var
  fMP3: file of Byte;
  Tag: ID3Struct;
begin { ReadID3Tag }
  try
    (* sFileName - (string) The full file name with path. *)
    AssignFile(fMP3, sFileName);
    Reset(fMP3);
    try
      Seek(fMP3, FileSize(fMP3) - 128);
      BlockRead(fMP3, Tag, SizeOf(Tag))
    finally

    end; { try }

  finally
    CloseFile(fMP3)
  end; { try }
  if fMP3.Signature<>'TAG' then
  begin

    { Doesn't have an ID3 tag }
  end { fMP3.Signature<>'TAG' }
  else
  begin

    { do something with the tag }
  end; { not (fMP3.Signature<>'TAG') }
end; { ReadID3Tag }


(* WriteID3Tag() function
  **
  ** Copyright (c) 2000 Jacob Dybala (m3Rlin)
  ** Freeware.
  **
  ** Created : January 7 2000
  ** Modified: January 7 2000
  ** Please leave this copyright notice.
  *)
procedure WriteID3Tag(id3NewTag: ID3Struct; sFileName: string);
var
  fMP3: file of Byte;
  Tag: ID3Struct;
begin { WriteID3Tag }
  try
    AssignFile(fMP3, sFileName);
    Reset(fMP3);
    try
      Seek(fMP3, FileSize(fMP3) - ID3OffsetFromEnd);
      BlockRead(fMP3, Tag, SizeOf(Tag));
      if fMP3.Signature='TAG' then
        { Replace old tag }
        Seek(fMP3, FileSize(fMP3) - ID3OffsetFromEnd)
      else
        { Append tag to file because it doesn't exist.
          Cannot use Append() function. It's only for text files. }
        Seek(fMP3, FileSize(fMP3));
      BlockWrite(fMP3, id3NewTag, SizeOf(id3NewTag))
    finally

    end; { try }

  finally
    CloseFile(fMP3)
  end; { try }
end; { WriteID3Tag }
You don't like the formatting? Check out SourceCoder then!
Content-type: text/html

Comments:

2006-01-15, 15:55:56
anonymous from France  
rating
Lots of errors ...
This code works :
procedure WriteID3Tag(NewTag: ID3Struct; sFileName: string);
var
fMP3 : file;
Tag : ID3Struct;
begin { WriteID3Tag }
try
FileSetAttr(sFileName, 0);
AssignFile(fMP3, sFileName);
FileMode := 2;
Reset(fMP3, 1);
try
Seek(fMP3, FileSize(fMP3) - 128);
BlockRead(fMP3, Tag, SizeOf(Tag));

if Tag.Signature='TAG' then
{ Replace old tag }
Seek(fMP3, FileSize(fMP3) - 128)
else
{ Append tag to file because it doesn't exist.
Cannot use Append() function. It's only for text files. }
Seek(fMP3, FileSize(fMP3));

BlockWrite(fMP3, NewTag, SizeOf(NewTag))
finally

end; { try }

finally
CloseFile(fMP3)
end; { try }
end; { WriteID3Tag }

2006-06-09, 02:12:25
anonymous from China  
2006-12-23, 06:22:15
anonymous from Italy  
//----------------------------------------------
//error fixed, and put everything inounit:
//----------------------------------------------

un
unit ulfmp3;

interface

// The MPEG Layer 3 ID3 tag structure:
type
ID3Struct = record
Signature: array [0..2] of Char; { Should be: 'TAG' }
Title, Artist, Album: array [0..29] of Char;
Year: array [0..3] of Char;
Comment: array [0..29] of Char;
Genre: Byte;
end;

// Here's the genre (Max. 256 entries).
const
ID3Genre : array [0..125] of string = ('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', 'Psychadelic',
'Rave', 'Showtunes', 'Trailer', 'Lo-Fi', 'Tribal', 'Acid Punk',
'Acid Jazz', 'Polka', 'Retro', 'Musical', 'Rock & Roll',
'Hard Rock', 'Folk', 'Folk-Rock', 'National Folk', 'Swing',
'Fast Fusion', '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',
'Ballad', 'Power Ballad', 'Rhythmic Soul', 'Freestyle', 'Duet',
'Punk Rock', 'Drum Solo', 'Acapella', 'Euro-House', 'Dance Hall' );

function ReadID3Tag(sFileName:String):ID3Struct;
procedure WriteID3Tag(NewTag: ID3Struct; sFileName: string);

implementation


// This is the ID3 Tag read code:
function ReadID3Tag(sFileName:String):ID3Struct;
var
fMP3: file of Byte;
Tag: ID3Struct;
begin { ReadID3Tag }
try
(* sFileName - (string) The full file name with path. *)
AssignFile(fMP3, sFileName);
Reset(fMP3);
try
Seek(fMP3, FileSize(fMP3) - 128);
BlockRead(fMP3, Tag, SizeOf(Tag))
finally

end; { try }

finally
CloseFile(fMP3)
end; { try }

if tag.Signature<>'TAG' then
begin

{ Doesn't have an ID3 tag }
end { fMP3.Signature<>'TAG' }
else
begin

{ do something with the tag }
end; { not (fMP3.Signature<>'TAG') }
result:=tag;
end; { ReadID3Tag }


(* WriteID3Tag() function
**
** Copyright (c) 2000 Jacob Dybala (m3Rlin)
** Freeware.
**
** Created : January 7 2000
** Modified: January 7 2000
** Please leave this copyright notice.
*)

procedure WriteID3Tag(NewTag: ID3Struct; sFileName: string);
var
fMP3 : file;
Tag : ID3Struct;
begin { WriteID3Tag }
try
//FileSetAttr(sFileName, 0);
AssignFile(fMP3, sFileName);
FileMode := 2;
Reset(fMP3, 1);
try
Seek(fMP3, FileSize(fMP3) - 128);
BlockRead(fMP3, Tag, SizeOf(Tag));

if Tag.Signature='TAG' then
{ Replace old tag }
Seek(fMP3, FileSize(fMP3) - 128)
else
{ Append tag to file because it doesn't exist.
Cannot use Append() function. It's only for text files. }
Seek(fMP3, FileSize(fMP3));

BlockWrite(fMP3, NewTag, SizeOf(NewTag))
finally

end; { try }

finally
CloseFile(fMP3)
end; { try }
end; { WriteID3Tag }


end.

2012-07-23, 22:41:03
anonymous from United States  
2013-01-27, 04:49:14
anonymous from Belgium  
rating
Used above code to develop a component for it (in lazarus: but should also be useable in Delphi):

unit id3;

interface

uses
Classes, SysUtils;

type
TCharArray3 = packed array[0..2] of Char;
TCharArray4 = packed array[0..3] of Char;
TCharArray30 = packed array[0..29] of Char;
TByteFile = file of byte;
TID3Struct = packed record
Signature: TCharArray3; // Must be 'TAG'
Title: TCharArray30;
Artist: TCharArray30;
Album: TCharArray30;
Year: TCharArray4;
Comment: TCharArray30;
Genre: Byte;
end;
TID3Genre = (id3gBlues, id3gClassicRock, id3gCountry, id3gDance, id3gDisco,
id3gFunk, id3gGrunge, id3gHipHop, id3gJazz, id3gMetal, id3gNewAge,
id3gOldies, id3gOther, id3gPop, id3gRB, id3gRap, id3gReggae, id3gRock,
id3gTechno, id3gIndustrial, id3gAlternative, id3gSka, id3gDeathMetal,
id3gPranks, id3gSoundtrack, id3gEuroTechno, id3gAmbient, id3gTripHop,
id3gVocal, id3gJazzFunk, id3gFusion, id3gTrance, id3gClassical,
id3gInstrumental, id3gAcid, id3gHouse, id3gGame, id3gSoundClip, id3gGospel,
id3gNoise, id3gAlternRock, id3gBass, id3gSoul, id3gPunk, id3gSpace,
id3gMeditative, id3gInstrumentalPop, id3gInstrumentalRock, id3gEthnic,
id3gGothic, id3gDarkwave, id3gTechnoIndustrial, id3gElectronic, id3gPopFolk,
id3gEurodance, id3gDream, id3gSouthernRock, id3gComedy, id3gCult,
id3gGangsta, id3gTop40, id3gChristianRap, id3gPopFunk, id3gJungle,
id3gNativeAmerican, id3gCabaret, id3gNewWave, id3gPsychadelic, id3gRave,
id3gShowtunes, id3gTrailer, id3gLoFi, id3gTribal, id3gAcidPunk,
id3gAcidJazz, id3gPolka, id3gRetro, id3gMusical, id3gRockRoll, id3gHardRock,
id3gFolk, id3gFolkRock, id3gNationalFolk, id3gSwing, id3gFastFusion,
id3gBebob, id3gLatin, id3gRevival, id3gCeltic, id3gBluegrass,
id3gAvantgarde, id3gGothicRock, id3gProgressiveRock, id3gPsychedelicRock,
id3gSymphonicRock, id3gSlowRock, id3gBigBand, id3gChorus, id3gEasyListening,
id3gAcoustic, id3gHumour, id3gSpeech, id3gChanson, id3gOpera,
id3gChamberMusic, id3gSonata, id3gSymphony, id3gBootyBass, id3gPrimus,
id3gPorn, id3gGroove, id3gSatire, id3gSlowJam, id3gClub, id3gTango,
id3gSamba, id3gFolklore, id3gBallad, id3gPowerBallad, id3gRhythmicSoul,
id3gFreestyle, id3gDuet, id3gPunkRock, id3gDrumSolo, id3gAcapella,
id3gEuroHouse, id3gDanceHall);
TReadResult = (rrOK, rrFileNotExists, rrNoTag, rrNOK);
TWriteResult = (wrOK, wrEmptyFileName, wrNOK);

{ TID3 }

TID3 = class(TComponent)
private
FFileExist: Boolean;
FFileName: TFileName;
FID3Struct: TID3Struct;
FMP3File: TByteFile;
function GetAlbum: string;
function GetArtist: string;
function GetComment: string;
function GetHasTag: Boolean;
function GetID3Genre: TID3Genre;
function GetSignature: string;
function GetTitle: string;
function GetYear: SmallInt;
procedure SetAlbum(AValue: string);
procedure SetArtist(AValue: string);
procedure SetComment(AValue: string);
procedure SetFileName(AValue: TFileName);
procedure SetID3Genre(AValue: TID3Genre);
procedure SetSignature(AValue: string);
procedure SetTitle(AValue: string);
procedure SetYear(AValue: SmallInt);
protected
property ID3Struct: TID3Struct read FID3Struct;
property MP3File: TByteFile read FMP3File;
public
constructor Create(AOwner: TComponent); override;
function ReadID3Tag: TReadResult;
procedure ResetTagFields;
function WriteID3Tag: TWriteResult;
published
property Album: string read GetAlbum write SetAlbum;
property Artist: string read GetArtist write SetArtist;
property Comment: string read GetComment write SetComment;
property FileExist: Boolean read FFileExist;
property FileName: TFileName read FFileName write SetFileName;
property Genre: TID3Genre read GetID3Genre write SetID3Genre;
property HasTag: Boolean read GetHasTag;
property Signature: string read GetSignature;
property Title: string read GetTitle write SetTitle;
property Year: SmallInt read GetYear write SetYear;
end;

procedure register;

implementation

const
cTAG = 'TAG';

procedure Register;
begin
RegisterComponents('ID3',[TID3]);
end;

{ TID3 }

function TID3.GetAlbum: string;
begin
Result := FID3Struct.Album;
end;

function TID3.GetArtist: string;
begin
Result := FID3Struct.Artist;
end;

function TID3.GetComment: string;
begin
Result := FID3Struct.Comment;
end;

function TID3.GetHasTag: Boolean;
begin
Result := Signature = cTAG;
end;

function TID3.GetID3Genre: TID3Genre;
begin
Result := TID3Genre(FID3Struct.Genre);
end;

function TID3.GetSignature: string;
begin
Result := ID3Struct.Signature;
end;

function TID3.GetTitle: string;
begin
Result := FID3Struct.Title;
end;

function TID3.GetYear: SmallInt;
var
ErrorPos: Integer;
begin
Val(FID3Struct.Year, Result, ErrorPos);
end;

procedure TID3.SetAlbum(AValue: string);
begin
FID3Struct.Album := Trim(Copy(AValue, 1, SizeOf(FID3Struct.Album)));
end;

procedure TID3.SetArtist(AValue: string);
begin
FID3Struct.Artist := Trim(Copy(AValue, 1, SizeOf(FID3Struct.Artist)));
end;

procedure TID3.SetComment(AValue: string);
begin
FID3Struct.Comment := Trim(Copy(AValue, 1, SizeOf(FID3Struct.Comment)));
end;

procedure TID3.SetFileName(AValue: TFileName);
begin
FFileExist := FileExists(Trim(AValue));
if FFileName = Trim(AValue) then Exit;
FFileName := Trim(AValue);
if FFileExist then
begin
AssignFile(FMP3File, FFileName);
ReadID3Tag;
end;
end;

procedure TID3.SetID3Genre(AValue: TID3Genre);
begin
FID3Struct.Genre := Ord(AValue);
end;

procedure TID3.SetSignature(AValue: string);
begin
FID3Struct.Signature := Trim(Copy(AValue, 1, SizeOf(FID3Struct.Signature)));
end;

procedure TID3.SetTitle(AValue: string);
begin
FID3Struct.Title := Trim(Copy(AValue, 1, SizeOf(FID3Struct.Title)));
end;

procedure TID3.SetYear(AValue: SmallInt);
begin
FID3Struct.Year := Trim(Copy(IntToStr(AValue), 1, SizeOf(FID3Struct.Year)));
end;

constructor TID3.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FileName := EmptyStr;
end;

function TID3.ReadID3Tag: TReadResult;
begin
if not FFileExist then
Result := rrFileNotExists
else
begin
try
Reset(FMP3File);
try
Seek(FMP3File, FileSize(FMP3File) - 128);
BlockRead(FMP3File, FID3Struct, SizeOf(FID3Struct));
finally
CloseFile(FMP3File);
end;
if not HasTag then
Result := rrNoTag
else
Result := rrOK;
except
Result := rrNOK;
end;
end;
end;

procedure TID3.ResetTagFields;
begin
Album := EmptyStr;
Artist := EmptyStr;
Comment := EmptyStr;
Genre := id3gOther;
SetSignature(EmptyStr);
Title := EmptyStr;
Year := 0;
end;

function TID3.WriteID3Tag: TWriteResult;
var
Tag: TID3Struct;
HadTag: Boolean;
begin
if (FFileName = EmptyStr) then
Result := wrEmptyFileName
else
begin
try
FileMode := 2;
Reset(FMP3File, 1);
try
Seek(FMP3File, FileSize(FMP3File) - 128);
BlockRead(FMP3File, Tag, SizeOf(Tag));
if Tag.Signature = cTAG then
Seek(FMP3File, FileSize(FMP3File) - 128)
else
Seek(FMP3File, FileSize(FMP3File));
HadTag := HasTag;
try
if not HasTag then SetSignature(cTAG);
BlockWrite(FMP3File, FID3Struct, SizeOf(FID3Struct));
except
if not HadTag then SetSignature(EmptyStr);
raise;
end;
finally
CloseFile(FMP3File);
end;
Result := wrOK;
except
Result := wrNOK;
end;
end;
end;

end.

 

 

NEW: Optional: Register   Login
Email address (not necessary):

Rate as
Hide my email when showing my comment.
Please notify me once a day about new comments on this topic.
Please provide a valid email address if you select this option, or post under a registered account.
 

Show city and country
Show country only
Hide my location
You can mark text as 'quoted' by putting [quote] .. [/quote] around it.
Please type in the code:

Please do not post inappropriate pictures. Inappropriate pictures include pictures of minors and nudity.
The owner of this web site reserves the right to delete such material.

photo Add a picture: