diff options
author | Björn Stenberg <bjorn@haxx.se> | 2003-06-04 15:09:35 +0000 |
---|---|---|
committer | Björn Stenberg <bjorn@haxx.se> | 2003-06-04 15:09:35 +0000 |
commit | 8498a48496579aecc1645604f49d931d4f35ff7f (patch) | |
tree | 6caebfc5fe29e6c155d236cab5ceff17e1a05bf6 /firmware | |
parent | 7bc69aa084fe3d0577303fbf6cf1569a19cde390 (diff) | |
download | rockbox-8498a48496579aecc1645604f49d931d4f35ff7f.tar.gz rockbox-8498a48496579aecc1645604f49d931d4f35ff7f.zip |
Generalized id3v2 parsing code. Added support for the composer frame and free-form genre and tracknum frames. (Patch #706111 by Thomas Paul Diffenbach)
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@3727 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware')
-rw-r--r-- | firmware/export/id3.h | 4 | ||||
-rw-r--r-- | firmware/id3.c | 256 |
2 files changed, 167 insertions, 93 deletions
diff --git a/firmware/export/id3.h b/firmware/export/id3.h index a9ab9ef36f..48a56f231a 100644 --- a/firmware/export/id3.h +++ b/firmware/export/id3.h | |||
@@ -26,6 +26,10 @@ struct mp3entry { | |||
26 | char *title; | 26 | char *title; |
27 | char *artist; | 27 | char *artist; |
28 | char *album; | 28 | char *album; |
29 | char* genre_string ; | ||
30 | char* track_string ; | ||
31 | char* year_string ; | ||
32 | char* composer ; | ||
29 | int tracknum; | 33 | int tracknum; |
30 | int version; | 34 | int version; |
31 | int layer; | 35 | int layer; |
diff --git a/firmware/id3.c b/firmware/id3.c index f797ed891b..9205c17d8c 100644 --- a/firmware/id3.c +++ b/firmware/id3.c | |||
@@ -23,12 +23,16 @@ | |||
23 | * all sorts of friendly Rockbox people. | 23 | * all sorts of friendly Rockbox people. |
24 | * | 24 | * |
25 | */ | 25 | */ |
26 | |||
27 | /* tagResolver and associated code copyright 2003 Thomas Paul Diffenbach | ||
28 | */ | ||
26 | 29 | ||
27 | #include <stdio.h> | 30 | #include <stdio.h> |
28 | #include <stdlib.h> | 31 | #include <stdlib.h> |
29 | #include <string.h> | 32 | #include <string.h> |
30 | #include <errno.h> | 33 | #include <errno.h> |
31 | #include <stdbool.h> | 34 | #include <stdbool.h> |
35 | #include <stddef.h> | ||
32 | #include "file.h" | 36 | #include "file.h" |
33 | #include "debug.h" | 37 | #include "debug.h" |
34 | #include "atoi.h" | 38 | #include "atoi.h" |
@@ -46,6 +50,99 @@ | |||
46 | ((b2 & 0xFF) << (1*8)) | \ | 50 | ((b2 & 0xFF) << (1*8)) | \ |
47 | ((b3 & 0xFF) << (0*8))) | 51 | ((b3 & 0xFF) << (0*8))) |
48 | 52 | ||
53 | /* | ||
54 | HOW TO ADD ADDITIONAL ID3 VERSION 2 TAGS | ||
55 | Code and comments by Thomas Paul Diffenbach | ||
56 | |||
57 | To add another ID3v2 Tag, do the following: | ||
58 | 1. add a char* named for the tag to struct mp3entry in id3.h, | ||
59 | (I (tpd) prefer to use char* rather than ints, even for what seems like | ||
60 | numerical values, for cases where a number won't do, e.g., | ||
61 | YEAR: "circa 1765", "1790/1977" (composed/performed), "28 Feb 1969" | ||
62 | TRACK: "1/12", "1 of 12", GENRE: "Freeform genre name" | ||
63 | Text is more flexible, and as the main use of id3 data is to | ||
64 | display it, converting it to an int just means reconverting to | ||
65 | display it, at a runtime cost.) | ||
66 | |||
67 | 2. If any special processing beyond copying the tag value from the Id3 | ||
68 | block to the struct mp3entry is rrequired (such as converting to an | ||
69 | int), write a function to perform this special processing. | ||
70 | |||
71 | This function's prototype must match that of | ||
72 | typedef tagPostProcessFunc, that is it must be: | ||
73 | int func( struct mp3entry*, char* tag, int bufferpos ) | ||
74 | the first argument is a pointer to the current mp3entry structure the | ||
75 | second argument is a pointer to the null terminated string value of the | ||
76 | tag found the third argument is the offset of the next free byte in the | ||
77 | mp3entry's buffer your function should return the corrected offset; if | ||
78 | you don't lengthen or shorten the tag string, you can return the third | ||
79 | argument unchanged. | ||
80 | |||
81 | Unless you have a good reason no to, make the function static. | ||
82 | TO JUST COPY THE TAG NO SPECIAL PROCESSING FUNCTION IS NEEDED. | ||
83 | |||
84 | 3. add one or more entries to the tagList array, using the format: | ||
85 | char* ID3 Tag symbolic name -- see the ID3 specification for these, | ||
86 | sizeof() that name minus 1, | ||
87 | offsetof( struct mp3entry, variable_name_in_struct_mp3entry ), | ||
88 | pointer to your special processing function or NULL | ||
89 | if you need no special processing | ||
90 | Many ID3 symbolic names come in more than one form. You can add both | ||
91 | forms, each referencing the same variable in struct mp3entry. | ||
92 | If both forms are present, the last found will be used. | ||
93 | |||
94 | 4. Alternately, use the TAG_LIST_ENTRY macro with | ||
95 | ID3 tag symbolic name, | ||
96 | variable in struct mp3entry, | ||
97 | special processing function address | ||
98 | |||
99 | 5. Add code to wps-display.c function get_tag to assign a printf-like | ||
100 | format specifier for the tag */ | ||
101 | |||
102 | /* Structure for ID3 Tag extraction information */ | ||
103 | struct tag_resolver { | ||
104 | const char* tag; | ||
105 | int tag_length; | ||
106 | size_t offset; | ||
107 | int (*ppFunc)(struct mp3entry*, char* tag, int bufferpos); | ||
108 | }; | ||
109 | |||
110 | /* parse numeric value from string */ | ||
111 | static int parsenum( struct mp3entry* entry, char* tag, int bufferpos ) | ||
112 | { | ||
113 | entry->tracknum = atoi( tag ); | ||
114 | return bufferpos; | ||
115 | } | ||
116 | |||
117 | /* parse numeric genre from string */ | ||
118 | static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos ) | ||
119 | { | ||
120 | if( tag[ 1 ] == '(' && tag[ 2 ] != '(' ) { | ||
121 | entry->genre = atoi( tag + 2 ); | ||
122 | entry->genre_string = 0; | ||
123 | return tag - entry->id3v2buf; | ||
124 | } | ||
125 | else { | ||
126 | entry->genre = 0xFF; | ||
127 | return bufferpos; | ||
128 | } | ||
129 | } | ||
130 | |||
131 | static struct tag_resolver taglist[] = { | ||
132 | { "TPE1", 4, offsetof(struct mp3entry, artist), NULL }, | ||
133 | { "TP1", 3, offsetof(struct mp3entry, artist), NULL }, | ||
134 | { "TIT2", 4, offsetof(struct mp3entry, title), NULL }, | ||
135 | { "TT2", 3, offsetof(struct mp3entry, title), NULL }, | ||
136 | { "TALB", 4, offsetof(struct mp3entry, album), NULL }, | ||
137 | { "TRCK", 4, offsetof(struct mp3entry, track_string), &parsenum }, | ||
138 | { "TYER", 4, offsetof(struct mp3entry, year_string), &parsenum }, | ||
139 | { "TYR", 3, offsetof(struct mp3entry, year_string), &parsenum }, | ||
140 | { "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre }, | ||
141 | { "TCOM", 5, offsetof(struct mp3entry, composer), NULL } | ||
142 | }; | ||
143 | |||
144 | #define TAGLIST_SIZE ((int)(sizeof(taglist) / sizeof(taglist[0]))) | ||
145 | |||
49 | /* Checks to see if the passed in string is a 16-bit wide Unicode v2 | 146 | /* Checks to see if the passed in string is a 16-bit wide Unicode v2 |
50 | string. If it is, we attempt to convert it to a 8-bit ASCII string | 147 | string. If it is, we attempt to convert it to a 8-bit ASCII string |
51 | (for valid 8-bit ASCII characters). If it's not unicode, we leave | 148 | (for valid 8-bit ASCII characters). If it's not unicode, we leave |
@@ -186,24 +283,6 @@ static bool setid3v1title(int fd, struct mp3entry *entry) | |||
186 | return true; | 283 | return true; |
187 | } | 284 | } |
188 | 285 | ||
189 | static int read_frame(int fd, unsigned char *buf, char **destptr, int framelen) | ||
190 | { | ||
191 | int bytesread; | ||
192 | |||
193 | bytesread = read(fd, buf, framelen); | ||
194 | if(bytesread < 0) | ||
195 | return bytesread * 10 - 1; | ||
196 | |||
197 | if(bytesread < framelen) | ||
198 | return -1; | ||
199 | |||
200 | *destptr = buf; | ||
201 | if(unicode_munge(destptr, &bytesread) < 0) | ||
202 | return -2; | ||
203 | |||
204 | (*destptr)[bytesread] = '\0'; | ||
205 | return bytesread + 1; | ||
206 | } | ||
207 | 286 | ||
208 | /* | 287 | /* |
209 | * Sets the title of an MP3 entry based on its ID3v2 tag. | 288 | * Sets the title of an MP3 entry based on its ID3v2 tag. |
@@ -221,12 +300,12 @@ static void setid3v2title(int fd, struct mp3entry *entry) | |||
221 | char header[10]; | 300 | char header[10]; |
222 | unsigned char version; | 301 | unsigned char version; |
223 | char *buffer = entry->id3v2buf; | 302 | char *buffer = entry->id3v2buf; |
224 | char *tmp = NULL; | ||
225 | int bytesread = 0; | 303 | int bytesread = 0; |
226 | int buffersize = sizeof(entry->id3v2buf); | 304 | int buffersize = sizeof(entry->id3v2buf); |
227 | int flags; | 305 | int flags; |
228 | int skip; | 306 | int skip; |
229 | 307 | int i; | |
308 | |||
230 | /* Bail out if the tag is shorter than 10 bytes */ | 309 | /* Bail out if the tag is shorter than 10 bytes */ |
231 | if(entry->id3v2len < 10) | 310 | if(entry->id3v2len < 10) |
232 | return; | 311 | return; |
@@ -263,7 +342,7 @@ static void setid3v2title(int fd, struct mp3entry *entry) | |||
263 | entry->id3version = version; | 342 | entry->id3version = version; |
264 | entry->tracknum = entry->year = entry->genre = 0; | 343 | entry->tracknum = entry->year = entry->genre = 0; |
265 | entry->title = entry->artist = entry->album = NULL; | 344 | entry->title = entry->artist = entry->album = NULL; |
266 | 345 | ||
267 | /* Skip the extended header if it is present */ | 346 | /* Skip the extended header if it is present */ |
268 | if(version >= ID3_VER_2_4) { | 347 | if(version >= ID3_VER_2_4) { |
269 | if(header[5] & 0x40) { | 348 | if(header[5] & 0x40) { |
@@ -281,7 +360,7 @@ static void setid3v2title(int fd, struct mp3entry *entry) | |||
281 | * We must have at least minframesize bytes left for the | 360 | * We must have at least minframesize bytes left for the |
282 | * remaining frames to be interesting | 361 | * remaining frames to be interesting |
283 | */ | 362 | */ |
284 | while(size > minframesize) { | 363 | while(size > minframesize ) { |
285 | flags = 0; | 364 | flags = 0; |
286 | 365 | ||
287 | /* Read frame header and check length */ | 366 | /* Read frame header and check length */ |
@@ -353,78 +432,56 @@ static void setid3v2title(int fd, struct mp3entry *entry) | |||
353 | 432 | ||
354 | DEBUGF("id3v2 frame: %.4s\n", header); | 433 | DEBUGF("id3v2 frame: %.4s\n", header); |
355 | 434 | ||
356 | /* Check for certain frame headers */ | 435 | /* Check for certain frame headers |
357 | if (!entry->artist && | ||
358 | (!strncmp(header, "TPE1", strlen("TPE1")) || | ||
359 | !strncmp(header, "TP1", strlen("TP1")))) { | ||
360 | bytesread = read_frame(fd, buffer + bufferpos, | ||
361 | &entry->artist, framelen); | ||
362 | if(bytesread < 0) | ||
363 | return; | ||
364 | |||
365 | bufferpos += bytesread; | ||
366 | size -= framelen; | ||
367 | } | ||
368 | else if (!entry->title && | ||
369 | (!strncmp(header, "TIT2", strlen("TIT2")) || | ||
370 | !strncmp(header, "TT2", strlen("TT2")))) { | ||
371 | bytesread = read_frame(fd, buffer + bufferpos, | ||
372 | &entry->title, framelen); | ||
373 | if(bytesread < 0) | ||
374 | return; | ||
375 | |||
376 | bufferpos += bytesread; | ||
377 | size -= framelen; | ||
378 | } | ||
379 | else if( !entry->album && | ||
380 | !strncmp(header, "TALB", strlen("TALB"))) { | ||
381 | bytesread = read_frame(fd, buffer + bufferpos, | ||
382 | &entry->album, framelen); | ||
383 | if(bytesread < 0) | ||
384 | return; | ||
385 | |||
386 | bufferpos += bytesread; | ||
387 | size -= framelen; | ||
388 | } | ||
389 | else if (!entry->tracknum && | ||
390 | !strncmp(header, "TRCK", strlen("TRCK"))) { | ||
391 | bytesread = read_frame(fd, buffer + bufferpos, | ||
392 | &tmp, framelen); | ||
393 | if(bytesread < 0) | ||
394 | return; | ||
395 | |||
396 | entry->tracknum = atoi(tmp); | ||
397 | 436 | ||
398 | size -= framelen; | 437 | 'size' is the amount of frame bytes remaining. We decrement it by |
399 | } | 438 | the amount of bytes we read. If we fail to read as many bytes as |
400 | else if (!entry->year && | 439 | we expect, we assume that we can't read from this file, and bail |
401 | (!strncmp(header, "TYER", 4) || | 440 | out. |
402 | !strncmp(header, "TYR", 3))) { | 441 | |
403 | bytesread = read_frame(fd, buffer + bufferpos, | 442 | For each frame. we will iterate over the list of supported tags, |
404 | &tmp, framelen); | 443 | and read the tag into entry's buffer. All tags will be kept as |
405 | if(bytesread < 0) | 444 | strings, for cases where a number won't do, e.g., YEAR: "circa |
406 | return; | 445 | 1765", "1790/1977" (composed/performed), "28 Feb 1969" TRACK: |
407 | 446 | "1/12", "1 of 12", GENRE: "Freeform genre name" Text is more | |
408 | entry->year = atoi(tmp); | 447 | flexible, and as the main use of id3 data is to display it, |
409 | size -= bytesread; | 448 | converting it to an int just means reconverting to display it, at a |
410 | } | 449 | runtime cost. |
411 | else if (!entry->genre && | 450 | |
412 | !strncmp(header, "TCON", 4)) { | 451 | For tags that the current code does convert to ints, a post |
413 | char* ptr = buffer + bufferpos; | 452 | processing function will be called via a pointer to function. */ |
414 | bytesread = read(fd, ptr, framelen); | 453 | |
415 | if(bytesread < 0 || bytesread < framelen) | 454 | for (i=0; i<TAGLIST_SIZE; i++) { |
416 | return; | 455 | struct tag_resolver* tr = &taglist[i]; |
456 | char** ptag = (char**) (((char*)entry) + tr->offset); | ||
457 | char* tag; | ||
417 | 458 | ||
418 | if (ptr[1] == '(' && ptr[2] != '(') | 459 | if( !*ptag && !memcmp( header, tr->tag, tr->tag_length ) ) { |
419 | entry->genre = atoi(ptr+2); | 460 | |
420 | bufferpos += bytesread + 1; | 461 | /* found a tag matching one in tagList, and not yet filled */ |
421 | size -= bytesread; | 462 | bytesread = read(fd, buffer + bufferpos, framelen); |
463 | if( bytesread != framelen ) | ||
464 | return; | ||
465 | |||
466 | size -= bytesread; | ||
467 | *ptag = buffer + bufferpos; | ||
468 | unicode_munge( ptag, &bytesread ); | ||
469 | tag = *ptag; | ||
470 | tag[bytesread + 1] = 0; | ||
471 | bufferpos += bytesread + 2; | ||
472 | if( tr->ppFunc ) | ||
473 | bufferpos = tr->ppFunc(entry, tag, bufferpos); | ||
474 | break; | ||
475 | } | ||
422 | } | 476 | } |
423 | else { | 477 | |
424 | /* Unknown frame, skip it using the total size in case | 478 | if( i == TAGLIST_SIZE ) { |
425 | it was truncated */ | 479 | /* no tag in tagList was found, or it was a repeat. |
480 | skip it using the total size */ | ||
481 | |||
426 | size -= totframelen; | 482 | size -= totframelen; |
427 | lseek(fd, totframelen, SEEK_CUR); | 483 | if( lseek(fd, totframelen, SEEK_CUR) == -1 ) |
484 | return; | ||
428 | } | 485 | } |
429 | } | 486 | } |
430 | } | 487 | } |
@@ -448,10 +505,11 @@ static int getid3v2len(int fd) | |||
448 | offset = 0; | 505 | offset = 0; |
449 | 506 | ||
450 | /* Now check what the ID3v2 size field says */ | 507 | /* Now check what the ID3v2 size field says */ |
451 | else if(read(fd, buf, 4) != 4) | ||
452 | offset = 0; | ||
453 | else | 508 | else |
454 | offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10; | 509 | if(read(fd, buf, 4) != 4) |
510 | offset = 0; | ||
511 | else | ||
512 | offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10; | ||
455 | 513 | ||
456 | DEBUGF("ID3V2 Length: 0x%x\n", offset); | 514 | DEBUGF("ID3V2 Length: 0x%x\n", offset); |
457 | return offset; | 515 | return offset; |
@@ -588,6 +646,7 @@ int main(int argc, char **argv) | |||
588 | int i; | 646 | int i; |
589 | for(i=1; i<argc; i++) { | 647 | for(i=1; i<argc; i++) { |
590 | struct mp3entry mp3; | 648 | struct mp3entry mp3; |
649 | mp3.album = "Bogus"; | ||
591 | if(mp3info(&mp3, argv[i])) { | 650 | if(mp3info(&mp3, argv[i])) { |
592 | printf("Failed to get %s\n", argv[i]); | 651 | printf("Failed to get %s\n", argv[i]); |
593 | return 0; | 652 | return 0; |
@@ -597,6 +656,10 @@ int main(int argc, char **argv) | |||
597 | " Title: %s\n" | 656 | " Title: %s\n" |
598 | " Artist: %s\n" | 657 | " Artist: %s\n" |
599 | " Album: %s\n" | 658 | " Album: %s\n" |
659 | " Genre: %s (%d) \n" | ||
660 | " Composer: %s\n" | ||
661 | " Year: %s (%d)\n" | ||
662 | " Track: %s (%d)\n" | ||
600 | " Length: %s / %d s\n" | 663 | " Length: %s / %d s\n" |
601 | " Bitrate: %d\n" | 664 | " Bitrate: %d\n" |
602 | " Frequency: %d\n", | 665 | " Frequency: %d\n", |
@@ -604,6 +667,13 @@ int main(int argc, char **argv) | |||
604 | mp3.title?mp3.title:"<blank>", | 667 | mp3.title?mp3.title:"<blank>", |
605 | mp3.artist?mp3.artist:"<blank>", | 668 | mp3.artist?mp3.artist:"<blank>", |
606 | mp3.album?mp3.album:"<blank>", | 669 | mp3.album?mp3.album:"<blank>", |
670 | mp3.genre_string?mp3.genre_string:"<blank>", | ||
671 | mp3.genre, | ||
672 | mp3.composer?mp3.composer:"<blank>", | ||
673 | mp3.year_string?mp3.year_string:"<blank>", | ||
674 | mp3.year, | ||
675 | mp3.track_string?mp3.track_string:"<blank>", | ||
676 | mp3.tracknum, | ||
607 | secs2str(mp3.length), | 677 | secs2str(mp3.length), |
608 | mp3.length/1000, | 678 | mp3.length/1000, |
609 | mp3.bitrate, | 679 | mp3.bitrate, |