id3v2 reader
authorAndreas Öman <andreas@olebyn.nu>
Tue, 22 May 2007 08:23:45 +0000 (08:23 +0000)
committerBenoit Fouet <benoit.fouet@free.fr>
Tue, 22 May 2007 08:23:45 +0000 (08:23 +0000)
patch by Andreas Ã\96man andreas olebyn nu
original thread: [FFmpeg-devel] [Ffmpeg-devel] ID3v2

Originally committed as revision 9101 to svn://svn.ffmpeg.org/ffmpeg/trunk

libavformat/mp3.c

index bf03d8c..7daed9e 100644 (file)
@@ -169,6 +169,149 @@ static int id3v2_match(const uint8_t *buf)
             (buf[9] & 0x80) == 0);
 }
 
+static unsigned int id3v2_get_size(ByteIOContext *s, int len)
+{
+    int v=0;
+    while(len--)
+        v= (v<<7) + (get_byte(s)&0x7F);
+    return v;
+}
+
+static void id3v2_read_ttag(AVFormatContext *s, int taglen, char *dst, int dstlen)
+{
+    char *q;
+    int len;
+
+    if(taglen < 1)
+        return;
+
+    taglen--; /* account for encoding type byte */
+    dstlen--; /* Leave space for zero terminator */
+
+    switch(get_byte(&s->pb)) { /* encoding type */
+
+    case 0:  /* ISO-8859-1 (0 - 255 maps directly into unicode) */
+        q = dst;
+        while(taglen--) {
+            uint8_t tmp;
+            PUT_UTF8(get_byte(&s->pb), tmp, if (q - dst < dstlen - 1) *q++ = tmp;)
+        }
+        *q = '\0';
+        break;
+
+    case 3:  /* UTF-8 */
+        len = FFMIN(taglen, dstlen);
+        get_buffer(&s->pb, dst, len);
+        dst[len] = 0;
+        break;
+    }
+}
+
+/**
+ * ID3v2 parser
+ *
+ * Handles ID3v2.2, 2.3 and 2.4.
+ *
+ */
+
+static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t flags)
+{
+    int isv34, tlen;
+    uint32_t tag;
+    offset_t next;
+    char tmp[16];
+    int taghdrlen;
+    const char *reason;
+
+    switch(version) {
+    case 2:
+        if(flags & 0x40) {
+            reason = "compression";
+            goto error;
+        }
+        isv34 = 0;
+        taghdrlen = 6;
+        break;
+
+    case 3 ... 4:
+        isv34 = 1;
+        taghdrlen = 10;
+        break;
+
+    default:
+        reason = "version";
+        goto error;
+    }
+
+    if(flags & 0x80) {
+        reason = "unsynchronization";
+        goto error;
+    }
+
+    if(isv34 && flags & 0x40) /* Extended header present, just skip over it */
+        url_fskip(&s->pb, id3v2_get_size(&s->pb, 4));
+
+    while(len >= taghdrlen) {
+        if(isv34) {
+            tag  = get_be32(&s->pb);
+            tlen = id3v2_get_size(&s->pb, 4);
+            get_be16(&s->pb); /* flags */
+        } else {
+            tag  = get_be24(&s->pb);
+            tlen = id3v2_get_size(&s->pb, 3);
+        }
+        len -= taghdrlen + tlen;
+
+        if(len < 0)
+            break;
+
+        next = url_ftell(&s->pb) + tlen;
+
+        switch(tag) {
+        case MKBETAG('T', 'I', 'T', '2'):
+        case MKBETAG(0,   'T', 'T', '2'):
+            id3v2_read_ttag(s, tlen, s->title, sizeof(s->title));
+            break;
+        case MKBETAG('T', 'P', 'E', '1'):
+        case MKBETAG(0,   'T', 'P', '1'):
+            id3v2_read_ttag(s, tlen, s->author, sizeof(s->author));
+            break;
+        case MKBETAG('T', 'A', 'L', 'B'):
+        case MKBETAG(0,   'T', 'A', 'L'):
+            id3v2_read_ttag(s, tlen, s->album, sizeof(s->album));
+            break;
+        case MKBETAG('T', 'C', 'O', 'N'):
+        case MKBETAG(0,   'T', 'C', 'O'):
+            id3v2_read_ttag(s, tlen, s->genre, sizeof(s->genre));
+            break;
+        case MKBETAG('T', 'C', 'O', 'P'):
+        case MKBETAG(0,   'T', 'C', 'R'):
+            id3v2_read_ttag(s, tlen, s->copyright, sizeof(s->copyright));
+            break;
+        case MKBETAG('T', 'R', 'C', 'K'):
+        case MKBETAG(0,   'T', 'R', 'K'):
+            id3v2_read_ttag(s, tlen, tmp, sizeof(tmp));
+            s->track = atoi(tmp);
+            break;
+        case 0:
+            /* padding, skip to end */
+            url_fskip(&s->pb, len);
+            len = 0;
+            continue;
+        }
+        /* Skip to end of tag */
+        url_fseek(&s->pb, next, SEEK_SET);
+    }
+
+    if(version == 4 && flags & 0x10) /* Footer preset, always 10 bytes, skip over it */
+        url_fskip(&s->pb, 10);
+    return;
+
+  error:
+    av_log(s, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n", version, reason);
+    url_fskip(&s->pb, len);
+}
+
 static void id3v1_get_string(char *str, int str_size,
                              const uint8_t *buf, int buf_size)
 {
@@ -313,12 +456,12 @@ static int mp3_read_header(AVFormatContext *s,
     if (ret != ID3v2_HEADER_SIZE)
         return -1;
     if (id3v2_match(buf)) {
-        /* skip ID3v2 header */
+        /* parse ID3v2 header */
         len = ((buf[6] & 0x7f) << 21) |
             ((buf[7] & 0x7f) << 14) |
             ((buf[8] & 0x7f) << 7) |
             (buf[9] & 0x7f);
-        url_fskip(&s->pb, len);
+        id3v2_parse(s, len, buf[3], buf[5]);
     } else {
         url_fseek(&s->pb, 0, SEEK_SET);
     }