diff options
-rw-r--r-- | apps/metadata.c | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/apps/metadata.c b/apps/metadata.c index 2e39ab1685..540e62c4aa 100644 --- a/apps/metadata.c +++ b/apps/metadata.c | |||
@@ -25,6 +25,7 @@ | |||
25 | #include "mp3_playback.h" | 25 | #include "mp3_playback.h" |
26 | #include "mp3data.h" | 26 | #include "mp3data.h" |
27 | #include "logf.h" | 27 | #include "logf.h" |
28 | #include "atoi.h" | ||
28 | 29 | ||
29 | /* Simple file type probing by looking filename extension. */ | 30 | /* Simple file type probing by looking filename extension. */ |
30 | int probe_file_format(const char *filename) | 31 | int probe_file_format(const char *filename) |
@@ -85,6 +86,8 @@ unsigned short a52_441framesizes[]= | |||
85 | /* Get metadata for track - return false if parsing showed problems with the | 86 | /* Get metadata for track - return false if parsing showed problems with the |
86 | file that would prevent playback. */ | 87 | file that would prevent playback. */ |
87 | 88 | ||
89 | static bool get_apetag_info (struct mp3entry *entry, int fd); | ||
90 | |||
88 | bool get_metadata(struct track_info* track, int fd, const char* trackname, | 91 | bool get_metadata(struct track_info* track, int fd, const char* trackname, |
89 | bool v1first) { | 92 | bool v1first) { |
90 | unsigned long totalsamples,bytespersample,channels,bitspersample,numbytes; | 93 | unsigned long totalsamples,bytespersample,channels,bitspersample,numbytes; |
@@ -341,6 +344,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname, | |||
341 | (track->id3.length / 8); | 344 | (track->id3.length / 8); |
342 | } | 345 | } |
343 | 346 | ||
347 | get_apetag_info (&track->id3, fd); /* use any apetag info we find */ | ||
344 | lseek (fd, 0, SEEK_SET); | 348 | lseek (fd, 0, SEEK_SET); |
345 | strncpy (track->id3.path, trackname, sizeof (track->id3.path)); | 349 | strncpy (track->id3.path, trackname, sizeof (track->id3.path)); |
346 | track->taginfo_ready = true; | 350 | track->taginfo_ready = true; |
@@ -411,3 +415,291 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname, | |||
411 | 415 | ||
412 | return true; | 416 | return true; |
413 | } | 417 | } |
418 | |||
419 | /************************* APE TAG HANDLING CODE ****************************/ | ||
420 | |||
421 | /* | ||
422 | * This is a first pass at APEv2 tag handling. I'm not sure if this should | ||
423 | * reside here, but I wanted to modify as little as possible since I don't | ||
424 | * have a feel for the complete system. It may be that APEv2 tags should be | ||
425 | * added to the ID3 handling code in the firmware directory. APEv2 tags are | ||
426 | * used in WavPack files and Musepack files by default, however they are | ||
427 | * also used in MP3 files sometimes (by Foobar2000). Also, WavPack files can | ||
428 | * also use ID3v1 tags (but not ID3v2), so it seems like some universal tag | ||
429 | * handler might be a reasonable approach. | ||
430 | * | ||
431 | * This code does not currently handle APEv1 tags, but I believe that this | ||
432 | * is not a problem because they were only used in Monkey's Audio files which | ||
433 | * will probably never be playable in RockBox (and certainly not by this CPU). | ||
434 | */ | ||
435 | |||
436 | #define APETAG_HEADER_FORMAT "8LLLL" | ||
437 | #define APETAG_HEADER_LENGTH 32 | ||
438 | #define APETAG_DATA_LIMIT 4096 | ||
439 | |||
440 | struct apetag_header { | ||
441 | char id [8]; | ||
442 | long version, length, item_count, flags; | ||
443 | char res [8]; | ||
444 | }; | ||
445 | |||
446 | static struct apetag { | ||
447 | struct apetag_header header; | ||
448 | char data [APETAG_DATA_LIMIT]; | ||
449 | } temp_apetag; | ||
450 | |||
451 | static int get_apetag_item (struct apetag *tag, | ||
452 | const char *item, | ||
453 | char *value, | ||
454 | int size); | ||
455 | |||
456 | static int load_apetag (int fd, struct apetag *tag); | ||
457 | static void UTF8ToAnsi (unsigned char *pUTF8); | ||
458 | |||
459 | /* | ||
460 | * This function searches the specified file for an APEv2 tag and uses any | ||
461 | * information found there to populate the appropriate fields in the specified | ||
462 | * mp3entry structure. A temporary buffer is used to hold the tag during this | ||
463 | * operation. For now, the actual string data that needs to be held during the | ||
464 | * life of the track entry is stored in the "id3v2buf" field (which should not | ||
465 | * be used for any file that has an APEv2 tag). This limits the total space | ||
466 | * for the artist, title, album, composer and genre strings to 300 characters. | ||
467 | */ | ||
468 | |||
469 | static bool get_apetag_info (struct mp3entry *entry, int fd) | ||
470 | { | ||
471 | int rem_space = sizeof (entry->id3v2buf), str_space; | ||
472 | char *temp_buffer = entry->id3v2buf; | ||
473 | |||
474 | if (rem_space <= 1 || !load_apetag (fd, &temp_apetag)) | ||
475 | return false; | ||
476 | |||
477 | if (get_apetag_item (&temp_apetag, "year", temp_buffer, rem_space)) | ||
478 | entry->year = atoi (temp_buffer); | ||
479 | |||
480 | if (get_apetag_item (&temp_apetag, "track", temp_buffer, rem_space)) | ||
481 | entry->tracknum = atoi (temp_buffer); | ||
482 | |||
483 | if (get_apetag_item (&temp_apetag, "artist", temp_buffer, rem_space)) { | ||
484 | UTF8ToAnsi (entry->artist = temp_buffer); | ||
485 | str_space = strlen (temp_buffer) + 1; | ||
486 | temp_buffer += str_space; | ||
487 | rem_space -= str_space; | ||
488 | } | ||
489 | |||
490 | if (rem_space > 1 && | ||
491 | get_apetag_item (&temp_apetag, "title", temp_buffer, rem_space)) { | ||
492 | UTF8ToAnsi (entry->title = temp_buffer); | ||
493 | str_space = strlen (temp_buffer) + 1; | ||
494 | temp_buffer += str_space; | ||
495 | rem_space -= str_space; | ||
496 | } | ||
497 | |||
498 | if (rem_space > 1 && | ||
499 | get_apetag_item (&temp_apetag, "album", temp_buffer, rem_space)) { | ||
500 | UTF8ToAnsi (entry->album = temp_buffer); | ||
501 | str_space = strlen (temp_buffer) + 1; | ||
502 | temp_buffer += str_space; | ||
503 | rem_space -= str_space; | ||
504 | } | ||
505 | |||
506 | if (rem_space > 1 && | ||
507 | get_apetag_item (&temp_apetag, "genre", temp_buffer, rem_space)) { | ||
508 | UTF8ToAnsi (entry->genre_string = temp_buffer); | ||
509 | str_space = strlen (temp_buffer) + 1; | ||
510 | temp_buffer += str_space; | ||
511 | rem_space -= str_space; | ||
512 | } | ||
513 | |||
514 | if (rem_space > 1 && | ||
515 | get_apetag_item (&temp_apetag, "composer", temp_buffer, rem_space)) | ||
516 | UTF8ToAnsi (entry->composer = temp_buffer); | ||
517 | |||
518 | return true; | ||
519 | } | ||
520 | |||
521 | /* | ||
522 | * Helper function to convert little-endian structures to easily usable native | ||
523 | * format using a format string (this does nothing on a little-endian machine). | ||
524 | */ | ||
525 | |||
526 | static void little_endian_to_native (void *data, char *format) | ||
527 | { | ||
528 | unsigned char *cp = (unsigned char *) data; | ||
529 | long temp; | ||
530 | |||
531 | while (*format) { | ||
532 | switch (*format) { | ||
533 | case 'L': | ||
534 | temp = cp [0] + ((long) cp [1] << 8) + ((long) cp [2] << 16) + ((long) cp [3] << 24); | ||
535 | * (long *) cp = temp; | ||
536 | cp += 4; | ||
537 | break; | ||
538 | |||
539 | case 'S': | ||
540 | temp = cp [0] + (cp [1] << 8); | ||
541 | * (short *) cp = (short) temp; | ||
542 | cp += 2; | ||
543 | break; | ||
544 | |||
545 | default: | ||
546 | if (*format >= '0' && *format <= '9') | ||
547 | cp += *format - '0'; | ||
548 | |||
549 | break; | ||
550 | } | ||
551 | |||
552 | format++; | ||
553 | } | ||
554 | } | ||
555 | |||
556 | /* | ||
557 | * Attempt to obtain the named string-type item from the specified APEv2 tag. | ||
558 | * The tag value will be copied to "value" (including an appended terminating | ||
559 | * NULL) and the length of the string (including the NULL) will be returned. | ||
560 | * If the data will not fit in the specified "size" then it will be truncated | ||
561 | * early (but still terminated). If the specified item is not found then 0 is | ||
562 | * returned and written to the first character of "value". If "value" is | ||
563 | * passed in as NULL, then the specified size is ignored and the actual size | ||
564 | * required to store the value is returned. | ||
565 | * | ||
566 | * Note that this function does not work on binary tag data; only UTF-8 | ||
567 | * encoded strings. However, numeric data (like ReplayGain) is usually stored | ||
568 | * as strings. | ||
569 | * | ||
570 | * Also, APEv2 tags may have multiple values for a given item and these will | ||
571 | * all be copied to "value" with NULL separators (this is why the total data | ||
572 | * size is returned). Of course, it is possible to ignore any additional | ||
573 | * values by simply using up to the first NULL. | ||
574 | */ | ||
575 | |||
576 | static int get_apetag_item (struct apetag *tag, | ||
577 | const char *item, | ||
578 | char *value, | ||
579 | int size) | ||
580 | { | ||
581 | if (value && size) | ||
582 | *value = 0; | ||
583 | |||
584 | if (tag->header.id [0] == 'A') { | ||
585 | char *p = tag->data; | ||
586 | char *q = p + tag->header.length - APETAG_HEADER_LENGTH; | ||
587 | int i; | ||
588 | |||
589 | for (i = 0; i < tag->header.item_count; ++i) { | ||
590 | int vsize, flags, isize; | ||
591 | |||
592 | vsize = * (long *) p; p += 4; | ||
593 | flags = * (long *) p; p += 4; | ||
594 | isize = strlen (p); | ||
595 | |||
596 | little_endian_to_native (&vsize, "L"); | ||
597 | little_endian_to_native (&flags, "L"); | ||
598 | |||
599 | if (p + isize + vsize + 1 > q) | ||
600 | break; | ||
601 | |||
602 | if (isize && vsize && !stricmp (item, p) && !(flags & 6)) { | ||
603 | |||
604 | if (value) { | ||
605 | if (vsize + 1 > size) | ||
606 | vsize = size - 1; | ||
607 | |||
608 | memcpy (value, p + isize + 1, vsize); | ||
609 | value [vsize] = 0; | ||
610 | } | ||
611 | |||
612 | return vsize + 1; | ||
613 | } | ||
614 | else | ||
615 | p += isize + vsize + 1; | ||
616 | } | ||
617 | } | ||
618 | |||
619 | return 0; | ||
620 | } | ||
621 | |||
622 | /* | ||
623 | * Attempt to load an APEv2 tag from the specified file into the specified | ||
624 | * structure. If the APEv2 tag will not fit into the predefined data size, | ||
625 | * then the tag is not loaded. A return value of TRUE indicates success. | ||
626 | */ | ||
627 | |||
628 | static int load_apetag (int fd, struct apetag *tag) | ||
629 | { | ||
630 | if (lseek (fd, -APETAG_HEADER_LENGTH, SEEK_END) == -1 || | ||
631 | read (fd, &tag->header, APETAG_HEADER_LENGTH) != APETAG_HEADER_LENGTH || | ||
632 | strncmp (tag->header.id, "APETAGEX", 8)) { | ||
633 | tag->header.id [0] = 0; | ||
634 | return false; | ||
635 | } | ||
636 | |||
637 | little_endian_to_native (&tag->header, APETAG_HEADER_FORMAT); | ||
638 | |||
639 | if (tag->header.version == 2000 && tag->header.item_count && | ||
640 | tag->header.length > APETAG_HEADER_LENGTH && | ||
641 | tag->header.length < APETAG_DATA_LIMIT) { | ||
642 | |||
643 | int data_size = tag->header.length - APETAG_HEADER_LENGTH; | ||
644 | |||
645 | if (lseek (fd, -tag->header.length, SEEK_END) == -1 || | ||
646 | read (fd, tag->data, data_size) != data_size) { | ||
647 | tag->header.id [0] = 0; | ||
648 | return false; | ||
649 | } | ||
650 | else | ||
651 | return true; | ||
652 | } | ||
653 | |||
654 | tag->header.id [0] = 0; | ||
655 | return false; | ||
656 | } | ||
657 | |||
658 | /* | ||
659 | * This is a *VERY* boneheaded attempt to convert UTF-8 unicode character | ||
660 | * strings to ANSI. It simply maps the 16-bit Unicode characters that are | ||
661 | * less than 0x100 directly to an 8-bit value, and turns all the rest into | ||
662 | * question marks. This can be done "in-place" because the resulting string | ||
663 | * can only get smaller. | ||
664 | */ | ||
665 | |||
666 | static void UTF8ToAnsi (unsigned char *pUTF8) | ||
667 | { | ||
668 | unsigned char *pAnsi = pUTF8; | ||
669 | unsigned short widechar = 0; | ||
670 | int trail_bytes = 0; | ||
671 | |||
672 | while (*pUTF8) { | ||
673 | if (*pUTF8 & 0x80) { | ||
674 | if (*pUTF8 & 0x40) { | ||
675 | if (trail_bytes) { | ||
676 | trail_bytes = 0; | ||
677 | *pAnsi++ = widechar < 0x100 ? widechar : '?'; | ||
678 | } | ||
679 | else { | ||
680 | char temp = *pUTF8; | ||
681 | |||
682 | while (temp & 0x80) { | ||
683 | trail_bytes++; | ||
684 | temp <<= 1; | ||
685 | } | ||
686 | |||
687 | widechar = temp >> trail_bytes--; | ||
688 | } | ||
689 | } | ||
690 | else if (trail_bytes) { | ||
691 | widechar = (widechar << 6) | (*pUTF8 & 0x3f); | ||
692 | |||
693 | if (!--trail_bytes) | ||
694 | *pAnsi++ = widechar < 0x100 ? widechar : '?'; | ||
695 | } | ||
696 | } | ||
697 | else | ||
698 | *pAnsi++ = *pUTF8; | ||
699 | |||
700 | pUTF8++; | ||
701 | } | ||
702 | |||
703 | *pAnsi = 0; | ||
704 | } | ||
705 | |||