Index: apps/metadata.c
===================================================================
RCS file: /cvsroot/rockbox/apps/metadata.c,v
retrieving revision 1.43
diff -d -u -b -r1.43 metadata.c
--- apps/metadata.c	1 May 2006 13:42:35 -0000	1.43
+++ apps/metadata.c	8 May 2006 18:22:15 -0000
@@ -22,6 +22,7 @@
 #include <ctype.h>
 #include <inttypes.h>
 
+#include "errno.h"
 #include "metadata.h"
 #include "mp3_playback.h"
 #include "logf.h"
@@ -905,6 +906,429 @@
     return true;
 }
 
+#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_cwrt MP4_ID(0xa9, 'w', 'r', 't')
+#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)
+    {
+        /* 64-bit size field */
+        errno = EFBIG;
+        *type = 0;
+        return 0;
+    }
+
+    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;
+    bool cwrt = false;
+
+    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_cwrt:
+            read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+                &id3->composer);
+            cwrt = false;
+            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) && !cwrt)
+                {
+                    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 %d\n",
+            errno, id3->length, id3->frequency);
+        return false;
+    }
+    
+    return true;
+}
+
+#else
 
 static bool get_m4a_metadata(int fd, struct mp3entry* id3)
 {
@@ -1181,6 +1605,12 @@
     }
   }
 
+  if ((id3->frequency < 100) || (id3->length == 0))
+  {
+    // Guard against files with metadata in unsupported places.
+    return false;
+  }
+
   id3->vbr=true; /* All ALAC files are VBR */
   id3->filesize=filesize(fd);
   id3->samples=totalsamples;
@@ -1189,6 +1619,7 @@
 
   return true;
 }    
+#endif
 
 static bool get_musepack_metadata(int fd, struct mp3entry *id3)
 {
@@ -1517,7 +1948,7 @@
 
     case AFMT_ALAC:
     case AFMT_AAC:
-        if (!get_m4a_metadata(fd, &(track->id3)))
+        if (!get_mp4_metadata(fd, &(track->id3)))
         {
             return false;
         }
