diff options
author | Daniel Stenberg <daniel@haxx.se> | 2002-04-23 08:43:04 +0000 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2002-04-23 08:43:04 +0000 |
commit | 6755f82a7002b98fc522dab216ab32bb62c289c2 (patch) | |
tree | d012a0f33434463d52cef740d41d687c64f5818c /firmware | |
parent | e7cc45929aec0f8c2b8300cebfea3b2ac9a51c29 (diff) | |
download | rockbox-6755f82a7002b98fc522dab216ab32bb62c289c2.tar.gz rockbox-6755f82a7002b98fc522dab216ab32bb62c289c2.zip |
id3 tag reading code, both v1 and v2. Still needs to be adjusted more to
remove the malloc()s and possible other stuff.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@185 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware')
-rw-r--r-- | firmware/id3.c | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/firmware/id3.c b/firmware/id3.c new file mode 100644 index 0000000000..825a54a718 --- /dev/null +++ b/firmware/id3.c | |||
@@ -0,0 +1,549 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Daniel Stenberg | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | |||
20 | /* | ||
21 | * Parts of this code has been stolen from the Ample project and was written | ||
22 | * by David Härdeman. | ||
23 | */ | ||
24 | |||
25 | #include <stdio.h> | ||
26 | #include <stdlib.h> | ||
27 | #include <unistd.h> | ||
28 | #include <string.h> | ||
29 | #include <errno.h> | ||
30 | |||
31 | struct mp3entry { | ||
32 | char *path; | ||
33 | char *title; | ||
34 | char *artist; | ||
35 | char *album; | ||
36 | int bitrate; | ||
37 | int frequency; | ||
38 | int id3v2len; | ||
39 | int id3v1len; | ||
40 | int filesize; /* in bytes */ | ||
41 | int length; /* song length */ | ||
42 | }; | ||
43 | |||
44 | typedef struct mp3entry mp3entry; | ||
45 | |||
46 | typedef unsigned char bool; | ||
47 | #define TRUE 1 | ||
48 | #define FALSE 0 | ||
49 | |||
50 | /* Some utility macros used in getsonglength() */ | ||
51 | #define CHECKSYNC(x) (((x >> 21) & 0x07FF) == 0x7FF) | ||
52 | #define BYTE0(x) ((x >> 24) & 0xFF) | ||
53 | #define BYTE1(x) ((x >> 16) & 0xFF) | ||
54 | #define BYTE2(x) ((x >> 8) & 0xFF) | ||
55 | #define BYTE3(x) ((x >> 0) & 0xFF) | ||
56 | |||
57 | #define UNSYNC(b1,b2,b3,b4) (((b1 & 0x7F) << (3*7)) + \ | ||
58 | ((b2 & 0x7F) << (2*7)) + \ | ||
59 | ((b3 & 0x7F) << (1*7)) + \ | ||
60 | ((b4 & 0x7F) << (0*7))) | ||
61 | |||
62 | #define HASID3V2(entry) entry->id3v2len > 0 | ||
63 | #define HASID3V1(entry) entry->id3v1len > 0 | ||
64 | |||
65 | /* Table of bitrates for MP3 files, all values in kilo. | ||
66 | * Indexed by version, layer and value of bit 15-12 in header. | ||
67 | */ | ||
68 | const int bitrate_table[2][3][16] = | ||
69 | { | ||
70 | { | ||
71 | {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, | ||
72 | {0,32,48,56, 64,80, 96, 112,128,160,192,224,256,320,384,0}, | ||
73 | {0,32,40,48, 56,64, 80, 96, 112,128,160,192,224,256,320,0} | ||
74 | }, | ||
75 | { | ||
76 | {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0}, | ||
77 | {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0}, | ||
78 | {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0} | ||
79 | } | ||
80 | }; | ||
81 | |||
82 | /* Table of samples per frame for MP3 files. | ||
83 | * Indexed by layer. Multiplied with 1000. | ||
84 | */ | ||
85 | const int bs[4] = {0, 384000, 1152000, 1152000}; | ||
86 | |||
87 | /* Table of sample frequency for MP3 files. | ||
88 | * Indexed by version and layer. | ||
89 | */ | ||
90 | const int freqtab[2][4] = | ||
91 | { | ||
92 | {44100, 48000, 32000, 0}, | ||
93 | {22050, 24000, 16000, 0}, | ||
94 | }; | ||
95 | |||
96 | /* | ||
97 | * Removes trailing spaces from a string. | ||
98 | * | ||
99 | * Arguments: buffer - the string to process | ||
100 | * | ||
101 | * Returns: void | ||
102 | */ | ||
103 | static void | ||
104 | stripspaces(char *buffer) | ||
105 | { | ||
106 | int i = 0; | ||
107 | while(*(buffer + i) != '\0') | ||
108 | i++; | ||
109 | |||
110 | for(;i >= 0; i--) { | ||
111 | if(*(buffer + i) == ' ') | ||
112 | *(buffer + i) = '\0'; | ||
113 | else if(*(buffer + i) == '\0') | ||
114 | continue; | ||
115 | else | ||
116 | break; | ||
117 | } | ||
118 | } | ||
119 | |||
120 | /* | ||
121 | * Sets the title of an MP3 entry based on its ID3v1 tag. | ||
122 | * | ||
123 | * Arguments: file - the MP3 file to scen for a ID3v1 tag | ||
124 | * entry - the entry to set the title in | ||
125 | * | ||
126 | * Returns: TRUE if a title was found and created, else FALSE | ||
127 | */ | ||
128 | static bool | ||
129 | setid3v1title(FILE *file, mp3entry *entry) | ||
130 | { | ||
131 | char buffer[31]; | ||
132 | int offsets[3] = {-95,-65,-125}; | ||
133 | int i; | ||
134 | char *result; | ||
135 | |||
136 | for(i=0;i<3;i++) { | ||
137 | if(fseek(file, offsets[i], SEEK_END) != 0) { | ||
138 | free(result); | ||
139 | return FALSE; | ||
140 | } | ||
141 | |||
142 | buffer[0]=0; | ||
143 | fgets(buffer, 31, file); | ||
144 | stripspaces(buffer); | ||
145 | |||
146 | if(buffer[0]) { | ||
147 | switch(i) { | ||
148 | case 0: | ||
149 | entry->artist = strdup(buffer); | ||
150 | break; | ||
151 | case 1: | ||
152 | entry->album = strdup(buffer); | ||
153 | break; | ||
154 | case 2: | ||
155 | entry->title = strdup(buffer); | ||
156 | break; | ||
157 | } | ||
158 | } | ||
159 | } | ||
160 | |||
161 | return TRUE; | ||
162 | } | ||
163 | |||
164 | |||
165 | /* | ||
166 | * Sets the title of an MP3 entry based on its ID3v2 tag. | ||
167 | * | ||
168 | * Arguments: file - the MP3 file to scen for a ID3v2 tag | ||
169 | * entry - the entry to set the title in | ||
170 | * | ||
171 | * Returns: TRUE if a title was found and created, else FALSE | ||
172 | */ | ||
173 | static void | ||
174 | setid3v2title(FILE *file, mp3entry *entry) | ||
175 | { | ||
176 | char *buffer; | ||
177 | int minframesize; | ||
178 | int size, readsize = 0, headerlen; | ||
179 | char *title = NULL; | ||
180 | char *artist = NULL; | ||
181 | char *album = NULL; | ||
182 | char header[10]; | ||
183 | unsigned short int version; | ||
184 | |||
185 | /* 10 = headerlength */ | ||
186 | if(entry->id3v2len < 10) | ||
187 | return; | ||
188 | |||
189 | /* Check version */ | ||
190 | fseek(file, 0, SEEK_SET); | ||
191 | fread(header, sizeof(char), 10, file); | ||
192 | version = (unsigned short int)header[3]; | ||
193 | |||
194 | /* Read all frames in the tag */ | ||
195 | size = entry->id3v2len - 10; | ||
196 | buffer = malloc(size + 1); | ||
197 | if(size != (int)fread(buffer, sizeof(char), size, file)) { | ||
198 | free(buffer); | ||
199 | return; | ||
200 | } | ||
201 | *(buffer + size) = '\0'; | ||
202 | |||
203 | /* Set minimun frame size according to ID3v2 version */ | ||
204 | if(version > 2) | ||
205 | minframesize = 12; | ||
206 | else | ||
207 | minframesize = 8; | ||
208 | |||
209 | /* | ||
210 | * We must have at least minframesize bytes left for the | ||
211 | * remaining frames to be interesting | ||
212 | */ | ||
213 | while(size - readsize > minframesize) { | ||
214 | |||
215 | /* Read frame header and check length */ | ||
216 | if(version > 2) { | ||
217 | memcpy(header, (buffer + readsize), 10); | ||
218 | readsize += 10; | ||
219 | headerlen = UNSYNC(header[4], header[5], | ||
220 | header[6], header[7]); | ||
221 | } else { | ||
222 | memcpy(header, (buffer + readsize), 6); | ||
223 | readsize += 6; | ||
224 | headerlen = (header[3] << 16) + | ||
225 | (header[4] << 8) + | ||
226 | (header[5]); | ||
227 | } | ||
228 | if(headerlen < 1) | ||
229 | continue; | ||
230 | |||
231 | /* Check for certain frame headers */ | ||
232 | if(!strncmp(header, "TPE1", strlen("TPE1")) || | ||
233 | !strncmp(header, "TP1", strlen("TP1"))) { | ||
234 | readsize++; | ||
235 | headerlen--; | ||
236 | if(headerlen > (size - readsize)) | ||
237 | headerlen = (size - readsize); | ||
238 | artist = malloc(headerlen + 1); | ||
239 | snprintf(artist, headerlen + 1, "%s", | ||
240 | (buffer + readsize)); | ||
241 | readsize += headerlen; | ||
242 | } | ||
243 | else if(!strncmp(header, "TIT2", strlen("TIT2")) || | ||
244 | !strncmp(header, "TT2", strlen("TT2"))) { | ||
245 | readsize++; | ||
246 | headerlen--; | ||
247 | if(headerlen > (size - readsize)) | ||
248 | headerlen = (size - readsize); | ||
249 | title = malloc(headerlen + 1); | ||
250 | snprintf(title, headerlen + 1, "%s", | ||
251 | (buffer + readsize)); | ||
252 | readsize += headerlen; | ||
253 | } | ||
254 | else if(!strncmp(header, "TALB", strlen("TALB"))) { | ||
255 | readsize++; | ||
256 | headerlen--; | ||
257 | if(headerlen > (size - readsize)) | ||
258 | headerlen = (size - readsize); | ||
259 | album = malloc(headerlen + 1); | ||
260 | snprintf(album, headerlen + 1, "%s", | ||
261 | (buffer + readsize)); | ||
262 | readsize += headerlen; | ||
263 | } | ||
264 | } | ||
265 | |||
266 | if(artist) | ||
267 | entry->artist = artist; | ||
268 | |||
269 | if(title) | ||
270 | entry->title = title; | ||
271 | |||
272 | if(album) | ||
273 | entry->album = album; | ||
274 | |||
275 | free(buffer); | ||
276 | } | ||
277 | |||
278 | /* | ||
279 | * Calculates the size of the ID3v2 tag. | ||
280 | * | ||
281 | * Arguments: file - the file to search for a tag. | ||
282 | * | ||
283 | * Returns: the size of the tag or 0 if none was found | ||
284 | */ | ||
285 | static int | ||
286 | getid3v2len(FILE *file) | ||
287 | { | ||
288 | char buf[6]; | ||
289 | int offset; | ||
290 | |||
291 | /* Make sure file has a ID3 tag */ | ||
292 | if((fseek(file, 0, SEEK_SET) != 0) || | ||
293 | (fread(buf, sizeof(char), 6, file) != 6) || | ||
294 | (strncmp(buf, "ID3", strlen("ID3")) != 0)) | ||
295 | offset = 0; | ||
296 | /* Now check what the ID3v2 size field says */ | ||
297 | else if(fread(buf, sizeof(char), 4, file) != 4) | ||
298 | offset = 0; | ||
299 | else | ||
300 | offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10; | ||
301 | |||
302 | return offset; | ||
303 | } | ||
304 | |||
305 | static int | ||
306 | getfilesize(FILE *file) | ||
307 | { | ||
308 | /* seek to the end of it */ | ||
309 | if(fseek(file, 0, SEEK_END)) | ||
310 | return 0; /* unknown */ | ||
311 | |||
312 | return ftell(file); | ||
313 | } | ||
314 | |||
315 | /* | ||
316 | * Calculates the size of the ID3v1 tag. | ||
317 | * | ||
318 | * Arguments: file - the file to search for a tag. | ||
319 | * | ||
320 | * Returns: the size of the tag or 0 if none was found | ||
321 | */ | ||
322 | static int | ||
323 | getid3v1len(FILE *file) | ||
324 | { | ||
325 | char buf[3]; | ||
326 | int offset; | ||
327 | |||
328 | /* Check if we find "TAG" 128 bytes from EOF */ | ||
329 | if((fseek(file, -128, SEEK_END) != 0) || | ||
330 | (fread(buf, sizeof(char), 3, file) != 3) || | ||
331 | (strncmp(buf, "TAG", 3) != 0)) | ||
332 | offset = 0; | ||
333 | else | ||
334 | offset = 128; | ||
335 | |||
336 | return offset; | ||
337 | } | ||
338 | |||
339 | /* | ||
340 | * Calculates the length (in milliseconds) of an MP3 file. Currently this code | ||
341 | * doesn't care about VBR (Variable BitRate) files since it would have to scan | ||
342 | * through the entire file but this should become a config option in the | ||
343 | * future. | ||
344 | * | ||
345 | * Modified to only use integers. | ||
346 | * | ||
347 | * Arguments: file - the file to calculate the length upon | ||
348 | * entry - the entry to update with the length | ||
349 | * | ||
350 | * Returns: the song length in milliseconds, | ||
351 | * -1 means that it couldn't be calculated | ||
352 | */ | ||
353 | static int | ||
354 | getsonglength(FILE *file, mp3entry *entry) | ||
355 | { | ||
356 | long header; | ||
357 | int version; | ||
358 | int layer; | ||
359 | int bitindex; | ||
360 | int bitrate; | ||
361 | int freqindex; | ||
362 | int frequency; | ||
363 | |||
364 | long bpf; | ||
365 | long tpf; | ||
366 | int i; | ||
367 | |||
368 | /* Start searching after ID3v2 header */ | ||
369 | if(fseek(file, entry->id3v2len, SEEK_SET)) | ||
370 | return -1; | ||
371 | |||
372 | /* Fill up header with first 24 bits */ | ||
373 | for(version = 0; version < 3; version++) { | ||
374 | header <<= 8; | ||
375 | if(!fread(&header, 1, 1, file)) | ||
376 | return -1; | ||
377 | } | ||
378 | |||
379 | /* Loop trough file until we find a frame header */ | ||
380 | restart: | ||
381 | do { | ||
382 | header <<= 8; | ||
383 | if(!fread(&header, 1, 1, file)) | ||
384 | return -1; | ||
385 | } while(!CHECKSYNC(header)); | ||
386 | |||
387 | /* | ||
388 | * Some files are filled with garbage in the beginning, | ||
389 | * if the bitrate index of the header is binary 1111 | ||
390 | * that is a good is a good indicator | ||
391 | */ | ||
392 | if((header & 0xF000) == 0xF000) | ||
393 | goto restart; | ||
394 | |||
395 | #ifdef DEBUG_STANDALONE | ||
396 | fprintf(stderr, | ||
397 | "We found %x-%x-%x-%x and checksync %i and test %x\n", | ||
398 | BYTE0(header), BYTE1(header), BYTE2(header), BYTE3(header), | ||
399 | CHECKSYNC(header), (header & 0xF000) == 0xF000); | ||
400 | #endif | ||
401 | /* MPEG Audio Version */ | ||
402 | switch((header & 0x180000) >> 19) { | ||
403 | case 2: | ||
404 | version = 2; | ||
405 | break; | ||
406 | case 3: | ||
407 | version = 1; | ||
408 | break; | ||
409 | default: | ||
410 | return -1; | ||
411 | } | ||
412 | |||
413 | /* Layer */ | ||
414 | switch((header & 0x060000) >> 17) { | ||
415 | case 1: | ||
416 | layer = 3; | ||
417 | break; | ||
418 | case 2: | ||
419 | layer = 2; | ||
420 | break; | ||
421 | case 3: | ||
422 | layer = 1; | ||
423 | break; | ||
424 | default: | ||
425 | return -1; | ||
426 | } | ||
427 | |||
428 | /* Bitrate */ | ||
429 | bitindex = (header & 0xF000) >> 12; | ||
430 | bitrate = bitrate_table[version-1][layer-1][bitindex]; | ||
431 | if(bitrate == 0) | ||
432 | return -1; | ||
433 | |||
434 | /* Sampling frequency */ | ||
435 | freqindex = (header & 0x0C00) >> 10; | ||
436 | frequency = freqtab[version-1][freqindex]; | ||
437 | if(frequency == 0) | ||
438 | return -1; | ||
439 | |||
440 | #ifdef DEBUG_STANDALONE | ||
441 | fprintf(stderr, | ||
442 | "Version %i, lay %i, biti %i, bitr %i, freqi %i, freq %i\n", | ||
443 | version, layer, bitindex, bitrate, freqindex, frequency); | ||
444 | #endif | ||
445 | entry->bitrate = bitrate; | ||
446 | entry->frequency = frequency; | ||
447 | |||
448 | /* Calculate bytes per frame, calculation depends on layer */ | ||
449 | switch(layer) { | ||
450 | case 1: | ||
451 | bpf = bitrate_table[version - 1][layer - 1][bitindex]; | ||
452 | bpf *= 12000.0 * 4.0; | ||
453 | bpf /= freqtab[version-1][freqindex] << (version - 1); | ||
454 | break; | ||
455 | case 2: | ||
456 | case 3: | ||
457 | bpf = bitrate_table[version - 1][layer - 1][bitindex]; | ||
458 | bpf *= 144000; | ||
459 | bpf /= freqtab[version-1][freqindex] << (version - 1); | ||
460 | break; | ||
461 | default: | ||
462 | bpf = 1.0; | ||
463 | } | ||
464 | |||
465 | /* Calculate time per frame */ | ||
466 | tpf = bs[layer] / freqtab[version-1][freqindex] << (version - 1); | ||
467 | |||
468 | /* | ||
469 | * Now song length is | ||
470 | * ((filesize)/(bytes per frame))*(time per frame) | ||
471 | */ | ||
472 | return entry->filesize*tpf/bpf; | ||
473 | } | ||
474 | |||
475 | |||
476 | /* | ||
477 | * Checks all relevant information (such as ID3v1 tag, ID3v2 tag, length etc) | ||
478 | * about an MP3 file and updates it's entry accordingly. | ||
479 | * | ||
480 | * Arguments: entry - the entry to check and update with the new information | ||
481 | * | ||
482 | * Returns: void | ||
483 | */ | ||
484 | bool | ||
485 | mp3info(mp3entry *entry, char *filename) | ||
486 | { | ||
487 | FILE *file; | ||
488 | char *copy; | ||
489 | char *title; | ||
490 | |||
491 | if((file = fopen(filename, "r")) == NULL) | ||
492 | return TRUE; | ||
493 | |||
494 | memset(entry, 0, sizeof(mp3entry)); | ||
495 | |||
496 | entry->path = filename; | ||
497 | |||
498 | entry->filesize = getfilesize(file); | ||
499 | entry->id3v2len = getid3v2len(file); | ||
500 | entry->id3v1len = getid3v1len(file); | ||
501 | entry->length = getsonglength(file, entry); | ||
502 | entry->title = NULL; | ||
503 | |||
504 | if(HASID3V2(entry)) | ||
505 | setid3v2title(file, entry); | ||
506 | |||
507 | if(HASID3V1(entry) && !entry->title) | ||
508 | setid3v1title(file, entry); | ||
509 | |||
510 | fclose(file); | ||
511 | |||
512 | return FALSE; | ||
513 | } | ||
514 | |||
515 | #ifdef DEBUG_STANDALONE | ||
516 | |||
517 | int main(int argc, char **argv) | ||
518 | { | ||
519 | if(argc > 1) { | ||
520 | mp3entry mp3; | ||
521 | if(mp3info(&mp3, argv[1])) { | ||
522 | printf("Failed\n"); | ||
523 | return 0; | ||
524 | } | ||
525 | |||
526 | printf("Title: %s\n" | ||
527 | "Artist: %s\n" | ||
528 | "Album: %s\n" | ||
529 | "Length: %.1f secs\n" | ||
530 | "Bitrate: %d\n" | ||
531 | "Frequency: %d\n", | ||
532 | mp3.title?mp3.title:"<blank>", | ||
533 | mp3.artist?mp3.artist:"<blank>", | ||
534 | mp3.album?mp3.album:"<blank>", | ||
535 | mp3.length/1000.0, | ||
536 | mp3.bitrate, | ||
537 | mp3.frequency); | ||
538 | } | ||
539 | |||
540 | return 0; | ||
541 | } | ||
542 | |||
543 | #endif | ||
544 | |||
545 | /* ----------------------------------------------------------------- | ||
546 | * local variables: | ||
547 | * eval: (load-file "rockbox-mode.el") | ||
548 | * end: | ||
549 | */ | ||