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