Index: apps/metadata.c
===================================================================
RCS file: /cvsroot/rockbox/apps/metadata.c,v
retrieving revision 1.42
diff -r1.42 metadata.c
24a25
> #include "errno.h"
900a902,1314
> #if 1
> #define MP4_ID(a, b, c, d)  (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
> 
> #define MP4_alac MP4_ID('a', 'l', 'a', 'c')
> #define MP4_calb MP4_ID(0xa9, 'a', 'l', 'b')
> #define MP4_cART MP4_ID(0xa9, 'A', 'R', 'T')
> #define MP4_cnam MP4_ID(0xa9, 'n', 'a', 'm')
> #define MP4_ftyp MP4_ID('f', 't', 'y', 'p')
> #define MP4_gnre MP4_ID('g', 'n', 'r', 'e')
> #define MP4_hdlr MP4_ID('h', 'd', 'l', 'r')
> #define MP4_ilst MP4_ID('i', 'l', 's', 't')
> #define MP4_M4A  MP4_ID('M', '4', 'A', ' ')
> #define MP4_mdat MP4_ID('m', 'd', 'a', 't')
> #define MP4_mdia MP4_ID('m', 'd', 'i', 'a')
> #define MP4_mdir MP4_ID('m', 'd', 'i', 'r')
> #define MP4_meta MP4_ID('m', 'e', 't', 'a')
> #define MP4_minf MP4_ID('m', 'i', 'n', 'f')
> #define MP4_moov MP4_ID('m', 'o', 'o', 'v')
> #define MP4_mp4a MP4_ID('m', 'p', '4', 'a')
> #define MP4_mp42 MP4_ID('m', 'p', '4', '2')
> #define MP4_soun MP4_ID('s', 'o', 'u', 'n')
> #define MP4_stbl MP4_ID('s', 't', 'b', 'l')
> #define MP4_stsd MP4_ID('s', 't', 's', 'd')
> #define MP4_stts MP4_ID('s', 't', 't', 's')
> #define MP4_trak MP4_ID('t', 'r', 'a', 'k')
> #define MP4_trkn MP4_ID('t', 'r', 'k', 'n')
> #define MP4_udta MP4_ID('u', 'd', 't', 'a')
> #define MP4_extra MP4_ID('-', '-', '-', '-')
> 
> /* Read an unsigned 16-bit integer from a big-endian file. */
> #ifdef ROCKBOX_BIG_ENDIAN
> #define read_uint16be(fd, buf) read((fd), (buf), 2)
> #else
> int read_uint16be(int fd, unsigned short* buf)
> {
>     size_t n;
>     
>     n = read(fd, (char*) buf, 4);
>     *buf = betoh16(*buf);
>     return n;
> }
> #endif
> 
> /* TODO: More error handling would be a good idea... :) */
> 
> /* Read the tag data from an MP4 file, storing up to buffer_size bytes in
>  * buffer.
>  */
> unsigned long read_mp4_tag(int fd, unsigned int size_left, char* buffer, 
>     unsigned int buffer_left)
> {
>     unsigned int bytes_read = 0;
>     
>     if (buffer_left == 0)
>     {
>         lseek(fd, size_left, SEEK_CUR);     /* Skip everything */
>     } 
>     else 
>     {
>         /* Skip the data tag header - maybe we should parse it properly? */
>         lseek(fd, 16, SEEK_CUR); 
>         size_left -= 16;
> 
>         if (size_left > buffer_left)
>         {
>             read(fd, buffer, buffer_left);
>             lseek(fd, size_left - buffer_left, SEEK_CUR);
>             bytes_read = buffer_left;
>         } 
>         else
>         {
>             read(fd, buffer, size_left);
>             bytes_read = size_left;
>         }
>     }
>     
>     return bytes_read;
> }
> 
> /* Read a string tag from an M4A file */
> unsigned int read_mp4_tag_string(int fd, int size_left, char** buffer, 
>     unsigned int* buffer_left, char** dest)
> {
>     unsigned int bytes_read = read_mp4_tag(fd, size_left, *buffer,
>         *buffer_left - 1);
>     unsigned int length = 0;
> 
>     if (bytes_read)
>     {
>         (*buffer)[bytes_read] = 0;
>         *dest = *buffer;
>         length = strlen(*buffer) + 1;
>         *buffer_left -= length;
>         *buffer += length;
>     }
>     else
>     {
>         *dest = NULL;
>     }
>     
>     return length;
> }
> 
> static unsigned int read_mp4_atom(int fd, unsigned int* size, 
>     unsigned int* type, unsigned int size_left)
> {
>     read_uint32be(fd, size);
>     read_uint32be(fd, type);
>         
>     if (*size == 1)
>     {
>         errno = EFBIG;
>         *type = 0;
>         return 0;   /* TODO: Support atoms > 4GB? */
>     }
> 
>     if (*size > 0)
>     {
>         if (*size > size_left)
>         {
>             size_left = 0;
>         }
>         else
>         {
>             size_left -= *size;
>         }
>         
>         *size -= 8;
>     }
>     else
>     {
>         *size = size_left;
>         size_left = 0;
>     }
>     
>     return size_left;
> }
> 
> static bool read_mp4_tags(int fd, struct mp3entry* id3, 
>     unsigned int size_left)
> {
>     unsigned int size;
>     unsigned int type;
>     unsigned int buffer_left = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
>     char* buffer = id3->id3v2buf;
> 
>     do
>     {
>         size_left = read_mp4_atom(fd, &size, &type, size_left);
> 
>         DEBUGF("Tag atom: %c%c%c%c (%d bytes left)\n", type >> 24 & 0xff, 
>             type >> 16 & 0xff, type >> 8 & 0xff, type & 0xff, size);
> 
>         switch (type)
>         {
>         case MP4_cnam:
>             read_mp4_tag_string(fd, size, &buffer, &buffer_left, 
>                 &id3->title);
>             break;
> 
>         case MP4_cART:
>             read_mp4_tag_string(fd, size, &buffer, &buffer_left, 
>                 &id3->artist);
>             break;
> 
>         case MP4_calb:
>             read_mp4_tag_string(fd, size, &buffer, &buffer_left,
>                 &id3->album);
>             break;
>         
>         case MP4_gnre:
>             {
>                 unsigned short genre;
>                 
>                 read_mp4_tag(fd, size, (char*) &genre, sizeof(genre));
>                 id3->genre = betoh16(genre);
>             }
>             break;
> 
>         case MP4_trkn:
>             {
>                 unsigned short n[2];
>                 
>                 read_mp4_tag(fd, size, (char*) &n, sizeof(n));
>                 id3->tracknum = betoh16(n[1]);
>             }
>             break;
> 
>         case MP4_extra:
>             {
>                 char tag_name[TAG_NAME_LENGTH];
>                 unsigned int sub_size;
>                 
>                 /* "mean" atom */
>                 read_uint32be(fd, &sub_size);
>                 size -= sub_size;
>                 lseek(fd, sub_size - 4, SEEK_CUR);
>                 /* "name" atom */
>                 read_uint32be(fd, &sub_size);
>                 size -= sub_size;
>                 lseek(fd, 8, SEEK_CUR);
>                 sub_size -= 12;
>                 
>                 if (sub_size > sizeof(tag_name) - 1)
>                 {
>                     read(fd, tag_name, sizeof(tag_name) - 1);
>                     lseek(fd, sub_size - sizeof(tag_name) - 1, SEEK_CUR);
>                     tag_name[sizeof(tag_name) - 1] = 0;
>                 }
>                 else
>                 {
>                     read(fd, tag_name, sub_size);
>                     tag_name[sub_size] = 0;
>                 }
>                 
>                 if (strcasecmp(tag_name, "composer") == 0)
>                 {
>                     read_mp4_tag_string(fd, size, &buffer, &buffer_left, 
>                         &id3->composer);
>                 }
>                 else
>                 {
>                     char* any;
>                     unsigned int length = read_mp4_tag_string(fd, size,
>                         &buffer, &buffer_left, &any);
>                     
>                     if (length > 0)
>                     {
>                         /* Re-use the read buffer as the dest buffer... */
>                         buffer -= length;
>                         buffer_left += length;
> 
>                         parse_replaygain(tag_name, buffer, id3, buffer, 
>                             buffer_left);
>                     }
>                 }
>             }
>             break;
>         
>         default:
>             lseek(fd, size, SEEK_CUR);
>             break;
>         }
>     }
>     while ((size_left > 0) && (errno == 0));
> 
>     return true;
> }
> 
> static bool read_mp4_container(int fd, struct mp3entry* id3, 
>     unsigned int size_left)
> {
>     unsigned int size;
>     unsigned int type;
>     unsigned int handler = 0;
>     bool rc = true;
>     
>     do
>     {
>         size_left = read_mp4_atom(fd, &size, &type, size_left);
>         
>         DEBUGF("Atom: %c%c%c%c (0x%08x, %d bytes left)\n", 
>             type >> 24 & 0xff, type >> 16 & 0xff, type >> 8 & 0xff, 
>             type & 0xff, type, size);
>         
>         switch (type)
>         {
>         case MP4_ftyp:
>             {
>                 unsigned int id;
>                 
>                 read_uint32be(fd, &id);
>                 size -= 4;
>                 
>                 if ((id != MP4_M4A) && (id != MP4_mp42))
>                 {
>                     DEBUGF("Unknown MP4 file type: %c%c%c%c\n", 
>                         id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff,
>                         id & 0xff);
>                     return false;
>                 }
>             }
>             break;
> 
>         case MP4_meta:
>             lseek(fd, 4, SEEK_CUR);  /* Skip version */
>             size -= 4;
>             /* Fall through */
> 
>         case MP4_moov:
>         case MP4_udta:
>         case MP4_mdia:
>         case MP4_stbl:
>         case MP4_trak:
>             rc = read_mp4_container(fd, id3, size);
>             size = 0;
>             break;
>         
>         case MP4_ilst:
>             if (handler == MP4_mdir)
>             {
>                 rc = read_mp4_tags(fd, id3, size);
>                 size = 0;
>             }
>             break;
>         
>         case MP4_minf:
>             if (handler == MP4_soun)
>             {
>                 rc = read_mp4_container(fd, id3, size);
>                 size = 0;
>             }
>             break;
>         
>         case MP4_stsd:
>             lseek(fd, 8, SEEK_CUR);
>             size -= 8;
>             rc = read_mp4_container(fd, id3, size);
>             size = 0;
>             break;
>         
>         case MP4_hdlr:
>             lseek(fd, 8, SEEK_CUR);
>             read_uint32be(fd, &handler);
>             DEBUGF("    Handler %c%c%c%c\n", handler >> 24 & 0xff, 
>                 handler >> 16 & 0xff, handler >> 8 & 0xff,handler & 0xff);
>             size -= 12;
>             break;
>         
>         case MP4_stts:
>             {
>                 unsigned int entries;
>                 unsigned int i;
>                 
>                 lseek(fd, 4, SEEK_CUR);
>                 read_uint32be(fd, &entries);
>                 id3->samples = 0;
> 
>                 for (i = 0; i < entries; i++)
>                 {
>                     unsigned int n;
>                     unsigned int l;
> 
>                     read_uint32be(fd, &n);
>                     read_uint32be(fd, &l);
>                     id3->samples += n * l;
>                 }
>                 
>                 DEBUGF("    samples: %d\n", id3->samples);
>                 size = 0;
>             }
>             break;
> 
>         case MP4_mp4a:
>         case MP4_alac:
>             {
>                 unsigned int frequency;
> 
>                 id3->codectype = (type == MP4_mp4a) ? AFMT_AAC : AFMT_ALAC;
>                 lseek(fd, 22, SEEK_CUR);
>                 read_uint32be(fd, &frequency);
>                 size -= 26;
>                 id3->frequency = frequency;
>                 /* There're some child atoms here, but we don't need them */
>             }
>             break;
> 
>         case MP4_mdat:
>             DEBUGF("    mdat size: %d (%d)\n", size, size_left);
>             id3->filesize = size;
>             break;
> 
>         default:
>             break;
>         }
>         
>         lseek(fd, size, SEEK_CUR);
>     }
>     while ((size_left > 0) && (errno == 0) && rc);
>     
>     return rc;
> }
> 
> static bool get_mp4_metadata(int fd, struct mp3entry* id3)
> {
>     id3->codectype = AFMT_UNKNOWN;
>     id3->genre = 255;
>     errno = 0;
> 
>     if (read_mp4_container(fd, id3, filesize(fd)) && (errno == 0) 
>         && (id3->samples > 0) && (id3->frequency > 0))
>     {
>         id3->length = (id3->samples / id3->frequency) * 1000;
>         id3->bitrate = ((int64_t) id3->filesize * 8) / id3->length;
>         DEBUGF("Bitrate %d, length %d ms, samples %d, frequency %d\n",
>             id3->bitrate, id3->length, id3->samples, id3->frequency);
> 
>         if (id3->codectype == AFMT_UNKNOWN)
>         {
>             logf("Not an ALAC or AAC file\n");
>             return false;
>         }
>     }
>     else
>     {
>         DEBUGF("MP4 metadata error. errno %d, length %d, frequency\n",
>             errno, id3->length, id3->frequency);
>         return false;
>     }
>     
>     return true;
> }
> #else
1104c1518
< 
---
>     
1175a1590,1595
>   
>   if ((id3->frequency < 100) || (id3->length == 0))
>   {
>     // Guard against files with metadata in unsupported places.
>     return false;
>   }
1184a1605
> #endif
1513c1934
<         if (!get_m4a_metadata(fd, &(track->id3)))
---
>         if (!get_mp4_metadata(fd, &(track->id3)))
