Author: Herbert Poltnik
How to read infos in MP3 files
Answer:
1 {
2 Description:
3 Component MP3Info extracts/saves any ID3 Tag into/from it's
4 properties from/to MP3 file.
5 Keywords: MP3 ID3 TAG INFO
6
7 Author: Rok Krulec (Rok@fpp.edu) (http://Rok.fpp.edu)
8 Primary site: ftp://ftp.fpp.edu/pub/people/Rok_Krulec/mp3i-v12.zip
9
10 If you use this component, I would like to get 1 American dollar to Rok Krulec,
11 Zagrad 6, 3000 Celje, Slovenia, Europe.
12
13 Description of Variables, Properties, Methods and Events:
14 Genres: TStrings; - List of Genres
15 constructor Create(AOwner: TComponent); - Creates an instance
16 destructor Destroy; override; - Destroys an instance
17 method Save; - Saves ID3 Tag to file
18 method RemoveID3; - Removes ID3 Tag form file
19 property Filename: TFilename; - Filename of MP3 file, when changed
20 it opens a new MP3 file
21 property Artist: String; - Artist (30 Chars)
22 property Title: String; - Title (30 Chars)
23 property Album: String; - Album (30 Chars)
24 property Year: String; - Year ( 4 chars)
25 property Comment: String; - Comment (30 Chars)
26 property Genre: String; - Genre [Read Only]
27 Property GenreID: Byte; - Genre ID
28 property Valid: Boolean; - Is ID3 valid [Read Only]
29 property Saved: Boolean; - Save success [Read Only]
30 property Error: String; - Error Message [Read Only]
31 property onChangeFile:TNotifyEvent; - Triggers when other file is openned
32 property onChange:TNotifyEvent; - Triggers when one of propertis is
33 changed (Artist, Title, Album, Year, Comment, GenreID)
34 property onError:TNotifyEvent; - Triggers when errors ocure (Wrong
35 filename, Frong fileformat)
36 }
37
38 unit MP3Info;
39
40 interface
41
42 uses
43 SysUtils, Classes;
44
45 const
46 TAGLEN = 127;
47
48 type
49 TMP3Info = class(TComponent)
50 private
51 { Private declarations }
52 vFilename: TFilename;
53 vMP3Tag, vArtist, vTitle, vAlbum, vComment, vYear, vGenre, vError: string;
54 vGenreID: Byte;
55 vValid: Boolean;
56 vSaved: Boolean;
57 vChangeFileEvent, vChangeEvent, vErrorEvent: TNotifyEvent;
58 procedure SetFilename(Filename: TFilename);
59 procedure SetArtist(Artist: string);
60 procedure SetTitle(Title: string);
61 procedure SetAlbum(Album: string);
62 procedure SetYear(Year: string);
63 procedure SetComment(Comment: string);
64 procedure SetGenreID(ID: Byte);
65 procedure Open;
66 protected
67 { Protected declarations }
68 public
69 { Public declarations }
70 published
71 { Published declarations }
72 Genres: TStrings;
73 constructor Create(AOwner: TComponent); override;
74 destructor Destroy; override;
75 procedure Save;
76 procedure RemoveID3;
77 property Filename: TFilename read vFilename write SetFilename;
78 property Artist: string read vArtist write SetArtist;
79 property Title: string read vTitle write SetTitle;
80 property Album: string read vAlbum write SetAlbum;
81 property Year: string read vYear write SetYear;
82 property Comment: string read vComment write SetComment;
83 property Genre: string read vGenre;
84 property GenreID: Byte read vGenreID write SetGenreID;
85 property Valid: Boolean read vValid;
86 property Saved: Boolean read vSaved;
87 property Error: string read vError;
88 property onChangeFile: TNotifyEvent read vChangeFileEvent write
89 vChangeFileEvent;
90 property onChange: TNotifyEvent read vChangeEvent write vChangeEvent;
91 property onError: TNotifyEvent read vErrorEvent write vErrorEvent;
92 end;
93
94 procedure register;
95
96 implementation
97
98 constructor TMP3Info.Create(AOwner: TComponent);
99 begin
100 inherited Create(AOwner);
101 Genres := TStringList.Create;
102 vGenreID := 12;
103 vValid := false;
104 vSaved := false;
105
106 { Fill the Genres String List so one can use it combo boxes e.t.c. Example:
107 ComboBox.Items.Assign(MP3Info.Genres) }
108 Genres.CommaText :=
109 '"Blues","Classic
110 Rock","Country","Dance","Disco","Funk","Grunge","Hip-Hop","Jazz","Metal","New
111 Age","Oldies",'
112 +
113 '"Other","Pop","R&B","Rap","Reggae","Rock","Techno","Industrial","Alternative","Ska"
114 ,"Death Metal","Pranks",'
115 +
116 '"Soundtrack","Euro-Techno","Ambient","Trip-Hop","Vocal","Jazz+Funk","Fusion","Tranc
117 e","Classical","Instrumental",'
118 + '"Acid","House","Game","Sound
119 Clip","Gospel","Noise","AlternRock","Bass","Soul","Punk","Space","Meditative",'
120 + '"Instrumental Pop","Instrumental
121 Rock","Ethnic","Gothic","Darkwave","Techno-Industrial","Electronic","Pop-Folk",'
122 + '"Eurodance","Dream","Southern Rock","Comedy","Cult","Gangsta","Top
123 40","Christian Rap","Pop/Funk","Jungle",'
124 + '"Native American","Cabaret","New
125 Wave","Psychedelic","Rave","Showtunes","Trailer","Lo-Fi","Tribal","Acid Punk",'
126 + '"Acid Jazz","Polka","Retro","Musical","Rock & Roll","Hard
127 Rock","Folk","Folk/Rock","National Folk","Swing","Bebob",'
128 + '"Latin","Revival","Celtic","Bluegrass","Avantgarde","Gothic
129 Rock","Progressive Rock","Psychedelic Rock","Symphonic Rock",'
130 + '"Slow Rock","Big Band","Chorus","Easy
131 Listening","Acoustic","Humour","Speech","Chanson","Opera","Chamber Music","Sonata",'
132 + '"Symphony","Booty Bass","Primus","Porn Groove","Satire","Slow
133 Jam","Club","Tango","Samba","Folklore"'
134
135 end;
136
137 destructor TMP3Info.Destroy;
138 begin
139 inherited Destroy;
140 end;
141
142 { Procedure to run when Filename property is changed }
143
144 procedure TMP3Info.SetFilename(Filename: TFilename);
145 begin
146 vFilename := Filename;
147 Open;
148 end;
149
150 procedure TMP3Info.SetArtist(Artist: string);
151 begin
152 vArtist := Copy(Artist, 0, 30);
153 if Assigned(onChange) then
154 onChange(Self);
155 end;
156
157 procedure TMP3Info.SetTitle(Title: string);
158 begin
159 vTitle := Copy(Title, 0, 30);
160 if Assigned(onChange) then
161 onChange(Self);
162 end;
163
164 procedure TMP3Info.SetAlbum(Album: string);
165 begin
166 vAlbum := Copy(Album, 0, 30);
167 if Assigned(onChange) then
168 onChange(Self);
169 end;
170
171 procedure TMP3Info.SetYear(Year: string);
172 begin
173 vYear := Copy(Year, 0, 4);
174 if Assigned(onChange) then
175 onChange(Self);
176 end;
177
178 procedure TMP3Info.SetComment(Comment: string);
179 begin
180 vComment := Copy(Comment, 0, 30);
181 if Assigned(onChange) then
182 onChange(Self);
183 end;
184
185 procedure TMP3Info.SetGenreID(ID: Byte);
186 begin
187 if ((ID > 255) or (ID > Genres.Count - 1)) then
188 ID := 12;
189 vGenreID := ID;
190 vGenre := Genres[vGenreID]; //this line is important because after changing
191 //vGenreID whitout it vGenre will be the same like before !!!
192 if Assigned(onChange) then
193 onChange(Self);
194 end;
195
196 { Opens file with Filename property, reads ID3 Tag and sets properties }
197
198 procedure TMP3Info.Open;
199
200 { Strips empty spaces at the end of word }
201 function Strip(WordToStrip: string; CharToStripAway: Char): string;
202 var
203 i: Integer;
204 begin
205 for i := length(WordToStrip) downto 1 do
206 begin
207 if WordToStrip[i] <> ' ' then
208 begin
209 Strip := Copy(WordToStrip, 0, i);
210 exit;
211 end;
212 end;
213 Strip := '';
214 end;
215
216 var
217 dat: file of char;
218 id3: array[0..TAGLEN] of char;
219 begin
220 vSaved := false;
221 vValid := True;
222 if FileExists(vFilename) then
223 begin
224 assignfile(dat, vFilename);
225 reset(dat);
226 seek(dat, FileSize(dat) - 128);
227 blockread(dat, id3, 128);
228 closefile(dat);
229 vMP3tag := copy(id3, 1, 3);
230 if vMP3Tag = 'TAG' then
231 begin
232 vTitle := strip(copy(id3, 4, 30), ' ');
233 vArtist := strip(copy(id3, 34, 30), ' ');
234 vAlbum := strip(copy(id3, 64, 30), ' ');
235 vComment := strip(copy(id3, 98, 30), ' ');
236 vYear := strip(copy(id3, 94, 4), ' ');
237 vGenreID := ord(id3[127]);
238 if vGenreID > Genres.Count then
239 vGenreID := 12;
240 vGenre := Genres[vGenreID];
241 { Trigger OnChange Event }
242 if Assigned(onChangeFile) then
243 onChangeFile(Self);
244 end
245 else
246 begin
247 vValid := False;
248 vTitle := '';
249 vArtist := '';
250 vAlbum := '';
251 vComment := '';
252 vYear := '';
253 vGenreID := 12;
254 vError := 'Wrong file format or no ID3 Tag !';
255 if Assigned(onError) then
256 onError(Self);
257 end;
258 end
259 else
260 begin
261 vValid := False;
262 vError := 'File doesn`t exist !';
263 if Assigned(onError) then
264 onError(Self);
265 end;
266 end;
267
268 { Removes the ID3-tag from currently open file }
269
270 procedure TMP3Info.RemoveID3;
271 var
272 dat: file of char;
273 begin
274 // does the file exist ?
275 if not FileExists(vFilename) then
276 begin
277 vError := 'File doesn`t exist !';
278 if Assigned(onError) then
279 onError(Self);
280 exit;
281 end;
282 // is the file already untagged ?
283 if (vValid = false) then
284 begin
285 vError := 'File is already untagged !';
286 if Assigned(onError) then
287 onError(Self);
288 exit;
289 end;
290 // remove readonly-attribute
291 if (FileGetAttr(vFilename) and faReadOnly > 0) then
292 FileSetAttr(vFileName, FileGetAttr(vFilename) - faReadOnly);
293 // if readonly attr. already exists it cannot be removed to cut ID3 Tag
294 if (FileGetAttr(vFilename) and faReadOnly > 0) then
295 begin
296 vError := 'Canīt write ID3 tag information !';
297 if Assigned(onError) then
298 onError(Self);
299 exit;
300 end;
301 // open current mp3 file if ID3 tag exists
302 if (vValid = true) then
303 begin
304 {I-}
305 assignfile(dat, vFilename);
306 reset(dat);
307 {I+}
308 if IOResult <> 0 then
309 begin
310 vError := 'Could not open file !';
311 if Assigned(onError) then
312 onError(Self);
313 exit;
314 end;
315 seek(dat, FileSize(dat) - 128);
316 truncate(dat); // cut all 128 bytes of file
317 closefile(dat);
318 vValid := false; // set vValid to false because the tag has been removed
319 end;
320 end;
321
322 { Saves ID3 Tag to currently opened file }
323
324 procedure TMP3Info.Save;
325
326 { Empties 128 character array }{ Don't tell me that there is a function for this in
327 Pascal }
328 procedure EmptyArray(var Destination: array of char);
329 var
330 i: Integer;
331 begin
332 for i := 0 to TAGLEN do
333 begin
334 Destination[i] := ' ';
335 end;
336 end;
337
338 { Insert a substring into character array at index position of array }
339 procedure InsertToArray(Source: string; var Destination: array of char; Index:
340 Integer);
341 var
342 i: Integer;
343 begin
344 for i := 0 to length(Source) - 1 do
345 begin
346 Destination[Index + i] := Source[i + 1];
347 end;
348 end;
349
350 var
351 dat: file of char;
352 id3: array[0..TAGLEN] of char;
353 begin
354 vSaved := true;
355 // does the filename exist ?
356 if FileExists(vFilename) then
357 begin
358 // fill 128 bytes long array with ID3 Tag information
359 EmptyArray(id3);
360 InsertToArray('TAG', id3, 0);
361 InsertToArray(vTitle, id3, 3);
362 InsertToArray(vArtist, id3, 33);
363 InsertToArray(vAlbum, id3, 63);
364 InsertToArray(vComment, id3, 97);
365 InsertToArray(vYear, id3, 93);
366 id3[127] := chr(vGenreID);
367 // remove readonly-attribute
368 if (FileGetAttr(vFilename) and faReadOnly > 0) then
369 FileSetAttr(vFileName, FileGetAttr(vFilename) - faReadOnly);
370 // if readonly attr. already exists it cannot be removed to write ID3
371 if (FileGetAttr(vFilename) and faReadOnly > 0) then
372 begin
373 vSaved := False;
374 vError := 'Canīt write ID3 tag information !';
375 if Assigned(onError) then
376 onError(Self);
377 exit;
378 end;
379 // if valid then overwrite existing ID3 Tag, else append to file
380 if (vValid = True) then
381 begin
382 {I-}
383 assignfile(dat, vFilename);
384 reset(dat);
385 seek(dat, FileSize(dat) - 128);
386 blockwrite(dat, id3, 128);
387 closefile(dat);
388 {I+}
389 if IOResult <> 0 then
390 vSaved := false;
391 end
392 else
393 begin
394 {I-}
395 assignfile(dat, vFilename);
396 reset(dat);
397 seek(dat, FileSize(dat));
398 blockwrite(dat, id3, 128);
399 closefile(dat);
400 {I+}
401 if IOResult <> 0 then
402 vSaved := false;
403 end
404 end
405 else
406 begin
407 vValid := False;
408 vSaved := False;
409 vError := 'File doesn`t exist or is not valid !';
410 if Assigned(onError) then
411 onError(Self);
412 end;
413 end;
414
415 procedure register;
416 begin
417 RegisterComponents('Samples', [TMP3Info]);
418 end;
419
420 end.
421
422 {
423 The ID3 Information is stored in the last 128 bytes of an MP3 file.
424 The ID3 has the following fields, and the offsets given here, are from 0-127
425
426 Field Length offsets
427 -------------------------------------
428 Tag 3 0-2
429 Songname 30 3-32
430 Artist 30 33-62
431 Album 30 63-92
432 Year 4 93-96
433 Comment 30 97-126
434 Genre 1 127
435 }
|