Read/Write ID3 Tag information from/to MP3 files
To read or write MP3Info you can use the code bellow from Rok Krulec
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
{ 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 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 ((ID > 255) or (ID > Genres.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 vGenreID > Genres.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 IOResult <> 0 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 IOResult <> 0 then vSaved := false; end else begin {I-} assignfile(dat, vFilename); reset(dat); seek(dat, FileSize(dat)); blockwrite(dat, id3, 128); closefile(dat); {I+} if IOResult <> 0 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 30-2
Songname 30 3-32
Artist 30 33-62
Album 30 63-92
Year 4 93-96
Comment 30 97-126
Genre 1 127