1 { *************************************************************************** }
2 {                                                                             }
3 { Audio Tools Library (Freeware)                                              }
4 { Class TID3v1 - for manipulating with ID3v1 tags                             }
5 {                                                                             }
6 { Copyright (c) 2001,2002 by Jurgen Faul                                      }
7 { E-mail: jfaul@gmx.de                                                        }
8 { http://jfaul.de/atl                                                         }
9 {                                                                             }
10 { Version 1.0 (25 July 2001)                                                  }
11 {   - Reading & writing support for ID3v1.x tags                              }
12 {   - Tag info: title, artist, album, track, year, genre, comment             }
13 {                                                                             }
14 { *************************************************************************** }
15 
16 unit ID3v1;
17 
18 interface
19 
20 uses
21   Classes, SysUtils;
22 
23 const
24   MAX_MUSIC_GENRES = 148;                       { Max. number of music genres }
25   DEFAULT_GENRE = 255;                              { Index for default genre }
26 
27   { Used with VersionID property }
28   TAG_VERSION_1_0 = 1;                                { Index for ID3v1.0 tag }
29   TAG_VERSION_1_1 = 2;                                { Index for ID3v1.1 tag }
30 
31 var
32   MusicGenre: array [0..MAX_MUSIC_GENRES - 1] of string;        { Genre names }
33 
34 type
35   { Used in TID3v1 class }
36   String04 = string[4];                          { String with max. 4 symbols }
37   String30 = string[30];                        { String with max. 30 symbols }
38 
39   { Class TID3v1 }
40   TID3v1 = class(TObject)
41     private
42       { Private declarations }
43       FExists: Boolean;
44       FVersionID: Byte;
45       FTitle: String30;
46       FArtist: String30;
47       FAlbum: String30;
48       FYear: String04;
49       FComment: String30;
50       FTrack: Byte;
51       FGenreID: Byte;
52       procedure FSetTitle(const NewTitle: String30);
53       procedure FSetArtist(const NewArtist: String30);
54       procedure FSetAlbum(const NewAlbum: String30);
55       procedure FSetYear(const NewYear: String04);
56       procedure FSetComment(const NewComment: String30);
57       procedure FSetTrack(const NewTrack: Byte);
58       procedure FSetGenreID(const NewGenreID: Byte);
FGetGenrenull59       function FGetGenre: string;
60     public
61       { Public declarations }
62       constructor Create;                                     { Create object }
63       procedure ResetData;                                   { Reset all data }
ReadFromFilenull64       function ReadFromFile(const FileName: string): Boolean;      { Load tag }
RemoveFromFilenull65       function RemoveFromFile(const FileName: string): Boolean;  { Delete tag }
SaveToFilenull66       function SaveToFile(const FileName: string): Boolean;        { Save tag }
67       property Exists: Boolean read FExists;              { True if tag found }
68       property VersionID: Byte read FVersionID;                { Version code }
69       property Title: String30 read FTitle write FSetTitle;      { Song title }
70       property Artist: String30 read FArtist write FSetArtist;  { Artist name }
71       property Album: String30 read FAlbum write FSetAlbum;      { Album name }
72       property Year: String04 read FYear write FSetYear;               { Year }
73       property Comment: String30 read FComment write FSetComment;   { Comment }
74       property Track: Byte read FTrack write FSetTrack;        { Track number }
75       property GenreID: Byte read FGenreID write FSetGenreID;    { Genre code }
76       property Genre: string read FGetGenre;                     { Genre name }
77   end;
78 
79 implementation
80 
81 type
82   { Real structure of ID3v1 tag }
83   TagRecord = record
84     Header: array [1..3] of Char;                { Tag header - must be "TAG" }
85     Title: array [1..30] of Char;                                { Title data }
86     Artist: array [1..30] of Char;                              { Artist data }
87     Album: array [1..30] of Char;                                { Album data }
88     Year: array [1..4] of Char;                                   { Year data }
89     Comment: array [1..30] of Char;                            { Comment data }
90     Genre: Byte;                                                 { Genre data }
91   end;
92 
93 { ********************* Auxiliary functions & procedures ******************** }
94 
ReadTagnull95 function ReadTag(const FileName: string; var TagData: TagRecord): Boolean;
96 var
97   SourceFile: file;
98 begin
99   try
100     Result := true;
101     { Set read-access and open file }
102     AssignFile(SourceFile, FileName);
103     FileMode := 0;
104     Reset(SourceFile, 1);
105     { Read tag }
106     Seek(SourceFile, FileSize(SourceFile) - 128);
107     BlockRead(SourceFile, TagData, 128);
108     CloseFile(SourceFile);
109   except
110     { Error }
111     Result := false;
112   end;
113 end;
114 
115 { --------------------------------------------------------------------------- }
116 
RemoveTagnull117 function RemoveTag(const FileName: string): Boolean;
118 var
119   SourceFile: file;
120 begin
121   try
122     Result := true;
123     { Allow write-access and open file }
124     FileSetAttr(FileName, 0);
125     AssignFile(SourceFile, FileName);
126     FileMode := 2;
127     Reset(SourceFile, 1);
128     { Delete tag }
129     Seek(SourceFile, FileSize(SourceFile) - 128);
130     Truncate(SourceFile);
131     CloseFile(SourceFile);
132   except
133     { Error }
134     Result := false;
135   end;
136 end;
137 
138 { --------------------------------------------------------------------------- }
139 
SaveTagnull140 function SaveTag(const FileName: string; TagData: TagRecord): Boolean;
141 var
142   SourceFile: file;
143 begin
144   try
145     Result := true;
146     { Allow write-access and open file }
147     FileSetAttr(FileName, 0);
148     AssignFile(SourceFile, FileName);
149     FileMode := 2;
150     Reset(SourceFile, 1);
151     { Write tag }
152     Seek(SourceFile, FileSize(SourceFile));
153     BlockWrite(SourceFile, TagData, SizeOf(TagData));
154     CloseFile(SourceFile);
155   except
156     { Error }
157     Result := false;
158   end;
159 end;
160 
161 { --------------------------------------------------------------------------- }
162 
GetTagVersionnull163 function GetTagVersion(const TagData: TagRecord): Byte;
164 begin
165   Result := TAG_VERSION_1_0;
166   { Terms for ID3v1.1 }
167   if ((TagData.Comment[29] = #0) and (TagData.Comment[30] <> #0)) or
168     ((TagData.Comment[29] = #32) and (TagData.Comment[30] <> #32)) then
169     Result := TAG_VERSION_1_1;
170 end;
171 
172 { ********************** Private functions & procedures ********************* }
173 
174 procedure TID3v1.FSetTitle(const NewTitle: String30);
175 begin
176   FTitle := TrimRight(NewTitle);
177 end;
178 
179 { --------------------------------------------------------------------------- }
180 
181 procedure TID3v1.FSetArtist(const NewArtist: String30);
182 begin
183   FArtist := TrimRight(NewArtist);
184 end;
185 
186 { --------------------------------------------------------------------------- }
187 
188 procedure TID3v1.FSetAlbum(const NewAlbum: String30);
189 begin
190   FAlbum := TrimRight(NewAlbum);
191 end;
192 
193 { --------------------------------------------------------------------------- }
194 
195 procedure TID3v1.FSetYear(const NewYear: String04);
196 begin
197   FYear := TrimRight(NewYear);
198 end;
199 
200 { --------------------------------------------------------------------------- }
201 
202 procedure TID3v1.FSetComment(const NewComment: String30);
203 begin
204   FComment := TrimRight(NewComment);
205 end;
206 
207 { --------------------------------------------------------------------------- }
208 
209 procedure TID3v1.FSetTrack(const NewTrack: Byte);
210 begin
211   FTrack := NewTrack;
212 end;
213 
214 { --------------------------------------------------------------------------- }
215 
216 procedure TID3v1.FSetGenreID(const NewGenreID: Byte);
217 begin
218   FGenreID := NewGenreID;
219 end;
220 
221 { --------------------------------------------------------------------------- }
222 
TID3v1.FGetGenrenull223 function TID3v1.FGetGenre: string;
224 begin
225   Result := '';
226   { Return an empty string if the current GenreID is not valid }
227   if FGenreID in [0..MAX_MUSIC_GENRES - 1] then Result := MusicGenre[FGenreID];
228 end;
229 
230 { ********************** Public functions & procedures ********************** }
231 
232 constructor TID3v1.Create;
233 begin
234   inherited;
235   ResetData;
236 end;
237 
238 { --------------------------------------------------------------------------- }
239 
240 procedure TID3v1.ResetData;
241 begin
242   FExists := false;
243   FVersionID := TAG_VERSION_1_0;
244   FTitle := '';
245   FArtist := '';
246   FAlbum := '';
247   FYear := '';
248   FComment := '';
249   FTrack := 0;
250   FGenreID := DEFAULT_GENRE;
251 end;
252 
253 { --------------------------------------------------------------------------- }
254 
ReadFromFilenull255 function TID3v1.ReadFromFile(const FileName: string): Boolean;
256 var
257   TagData: TagRecord;
258 begin
259   { Reset and load tag data from file to variable }
260   ResetData;
261   Result := ReadTag(FileName, TagData);
262   { Process data if loaded and tag header OK }
263   if (Result) and (TagData.Header = 'TAG') then
264   begin
265     FExists := true;
266     FVersionID := GetTagVersion(TagData);
267     { Fill properties with tag data }
268     FTitle := TrimRight(TagData.Title);
269     FArtist := TrimRight(TagData.Artist);
270     FAlbum := TrimRight(TagData.Album);
271     FYear := TrimRight(TagData.Year);
272     if FVersionID = TAG_VERSION_1_0 then
273       FComment := TrimRight(TagData.Comment)
274     else
275     begin
276       FComment := TrimRight(Copy(TagData.Comment, 1, 28));
277       FTrack := Ord(TagData.Comment[30]);
278     end;
279     FGenreID := TagData.Genre;
280   end;
281 end;
282 
283 { --------------------------------------------------------------------------- }
284 
RemoveFromFilenull285 function TID3v1.RemoveFromFile(const FileName: string): Boolean;
286 var
287   TagData: TagRecord;
288 begin
289   { Find tag }
290   Result := ReadTag(FileName, TagData);
291   { Delete tag if loaded and tag header OK }
292   if (Result) and (TagData.Header = 'TAG') then Result := RemoveTag(FileName);
293 end;
294 
295 { --------------------------------------------------------------------------- }
296 
SaveToFilenull297 function TID3v1.SaveToFile(const FileName: string): Boolean;
298 var
299   TagData: TagRecord;
300 begin
301   { Prepare tag record }
302   FillChar(TagData, SizeOf(TagData), 0);
303   TagData.Header := 'TAG';
304   Move(FTitle[1], TagData.Title, Length(FTitle));
305   Move(FArtist[1], TagData.Artist, Length(FArtist));
306   Move(FAlbum[1], TagData.Album, Length(FAlbum));
307   Move(FYear[1], TagData.Year, Length(FYear));
308   Move(FComment[1], TagData.Comment, Length(FComment));
309   if FTrack > 0 then
310   begin
311     TagData.Comment[29] := #0;
312     TagData.Comment[30] := Chr(FTrack);
313   end;
314   TagData.Genre := FGenreID;
315   { Delete old tag and write new tag }
316   Result := (RemoveFromFile(FileName)) and (SaveTag(FileName, TagData));
317 end;
318 
319 { ************************** Initialize music genres ************************ }
320 
321 initialization
322 begin
323   { Standard genres }
324   MusicGenre[0] := 'Blues';
325   MusicGenre[1] := 'Classic Rock';
326   MusicGenre[2] := 'Country';
327   MusicGenre[3] := 'Dance';
328   MusicGenre[4] := 'Disco';
329   MusicGenre[5] := 'Funk';
330   MusicGenre[6] := 'Grunge';
331   MusicGenre[7] := 'Hip-Hop';
332   MusicGenre[8] := 'Jazz';
333   MusicGenre[9] := 'Metal';
334   MusicGenre[10] := 'New Age';
335   MusicGenre[11] := 'Oldies';
336   MusicGenre[12] := 'Other';
337   MusicGenre[13] := 'Pop';
338   MusicGenre[14] := 'R&B';
339   MusicGenre[15] := 'Rap';
340   MusicGenre[16] := 'Reggae';
341   MusicGenre[17] := 'Rock';
342   MusicGenre[18] := 'Techno';
343   MusicGenre[19] := 'Industrial';
344   MusicGenre[20] := 'Alternative';
345   MusicGenre[21] := 'Ska';
346   MusicGenre[22] := 'Death Metal';
347   MusicGenre[23] := 'Pranks';
348   MusicGenre[24] := 'Soundtrack';
349   MusicGenre[25] := 'Euro-Techno';
350   MusicGenre[26] := 'Ambient';
351   MusicGenre[27] := 'Trip-Hop';
352   MusicGenre[28] := 'Vocal';
353   MusicGenre[29] := 'Jazz+Funk';
354   MusicGenre[30] := 'Fusion';
355   MusicGenre[31] := 'Trance';
356   MusicGenre[32] := 'Classical';
357   MusicGenre[33] := 'Instrumental';
358   MusicGenre[34] := 'Acid';
359   MusicGenre[35] := 'House';
360   MusicGenre[36] := 'Game';
361   MusicGenre[37] := 'Sound Clip';
362   MusicGenre[38] := 'Gospel';
363   MusicGenre[39] := 'Noise';
364   MusicGenre[40] := 'AlternRock';
365   MusicGenre[41] := 'Bass';
366   MusicGenre[42] := 'Soul';
367   MusicGenre[43] := 'Punk';
368   MusicGenre[44] := 'Space';
369   MusicGenre[45] := 'Meditative';
370   MusicGenre[46] := 'Instrumental Pop';
371   MusicGenre[47] := 'Instrumental Rock';
372   MusicGenre[48] := 'Ethnic';
373   MusicGenre[49] := 'Gothic';
374   MusicGenre[50] := 'Darkwave';
375   MusicGenre[51] := 'Techno-Industrial';
376   MusicGenre[52] := 'Electronic';
377   MusicGenre[53] := 'Pop-Folk';
378   MusicGenre[54] := 'Eurodance';
379   MusicGenre[55] := 'Dream';
380   MusicGenre[56] := 'Southern Rock';
381   MusicGenre[57] := 'Comedy';
382   MusicGenre[58] := 'Cult';
383   MusicGenre[59] := 'Gangsta';
384   MusicGenre[60] := 'Top 40';
385   MusicGenre[61] := 'Christian Rap';
386   MusicGenre[62] := 'Pop/Funk';
387   MusicGenre[63] := 'Jungle';
388   MusicGenre[64] := 'Native American';
389   MusicGenre[65] := 'Cabaret';
390   MusicGenre[66] := 'New Wave';
391   MusicGenre[67] := 'Psychadelic';
392   MusicGenre[68] := 'Rave';
393   MusicGenre[69] := 'Showtunes';
394   MusicGenre[70] := 'Trailer';
395   MusicGenre[71] := 'Lo-Fi';
396   MusicGenre[72] := 'Tribal';
397   MusicGenre[73] := 'Acid Punk';
398   MusicGenre[74] := 'Acid Jazz';
399   MusicGenre[75] := 'Polka';
400   MusicGenre[76] := 'Retro';
401   MusicGenre[77] := 'Musical';
402   MusicGenre[78] := 'Rock & Roll';
403   MusicGenre[79] := 'Hard Rock';
404   { Extended genres }
405   MusicGenre[80] := 'Folk';
406   MusicGenre[81] := 'Folk-Rock';
407   MusicGenre[82] := 'National Folk';
408   MusicGenre[83] := 'Swing';
409   MusicGenre[84] := 'Fast Fusion';
410   MusicGenre[85] := 'Bebob';
411   MusicGenre[86] := 'Latin';
412   MusicGenre[87] := 'Revival';
413   MusicGenre[88] := 'Celtic';
414   MusicGenre[89] := 'Bluegrass';
415   MusicGenre[90] := 'Avantgarde';
416   MusicGenre[91] := 'Gothic Rock';
417   MusicGenre[92] := 'Progessive Rock';
418   MusicGenre[93] := 'Psychedelic Rock';
419   MusicGenre[94] := 'Symphonic Rock';
420   MusicGenre[95] := 'Slow Rock';
421   MusicGenre[96] := 'Big Band';
422   MusicGenre[97] := 'Chorus';
423   MusicGenre[98] := 'Easy Listening';
424   MusicGenre[99] := 'Acoustic';
425   MusicGenre[100]:= 'Humour';
426   MusicGenre[101]:= 'Speech';
427   MusicGenre[102]:= 'Chanson';
428   MusicGenre[103]:= 'Opera';
429   MusicGenre[104]:= 'Chamber Music';
430   MusicGenre[105]:= 'Sonata';
431   MusicGenre[106]:= 'Symphony';
432   MusicGenre[107]:= 'Booty Bass';
433   MusicGenre[108]:= 'Primus';
434   MusicGenre[109]:= 'Porn Groove';
435   MusicGenre[110]:= 'Satire';
436   MusicGenre[111]:= 'Slow Jam';
437   MusicGenre[112]:= 'Club';
438   MusicGenre[113]:= 'Tango';
439   MusicGenre[114]:= 'Samba';
440   MusicGenre[115]:= 'Folklore';
441   MusicGenre[116]:= 'Ballad';
442   MusicGenre[117]:= 'Power Ballad';
443   MusicGenre[118]:= 'Rhythmic Soul';
444   MusicGenre[119]:= 'Freestyle';
445   MusicGenre[120]:= 'Duet';
446   MusicGenre[121]:= 'Punk Rock';
447   MusicGenre[122]:= 'Drum Solo';
448   MusicGenre[123]:= 'A capella';
449   MusicGenre[124]:= 'Euro-House';
450   MusicGenre[125]:= 'Dance Hall';
451   MusicGenre[126]:= 'Goa';
452   MusicGenre[127]:= 'Drum & Bass';
453   MusicGenre[128]:= 'Club-House';
454   MusicGenre[129]:= 'Hardcore';
455   MusicGenre[130]:= 'Terror';
456   MusicGenre[131]:= 'Indie';
457   MusicGenre[132]:= 'BritPop';
458   MusicGenre[133]:= 'Negerpunk';
459   MusicGenre[134]:= 'Polsk Punk';
460   MusicGenre[135]:= 'Beat';
461   MusicGenre[136]:= 'Christian Gangsta Rap';
462   MusicGenre[137]:= 'Heavy Metal';
463   MusicGenre[138]:= 'Black Metal';
464   MusicGenre[139]:= 'Crossover';
465   MusicGenre[140]:= 'Contemporary Christian';
466   MusicGenre[141]:= 'Christian Rock';
467   MusicGenre[142]:= 'Merengue';
468   MusicGenre[143]:= 'Salsa';
469   MusicGenre[144]:= 'Trash Metal';
470   MusicGenre[145]:= 'Anime';
471   MusicGenre[146]:= 'JPop';
472   MusicGenre[147]:= 'Synthpop';
473 end;
474 
475 end.
476