diff --git a/libavformat/mp3.c b/libavformat/mp3.c index bf03d8c14a..7daed9ec55 100644 --- a/libavformat/mp3.c +++ b/libavformat/mp3.c @@ -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); }