libavformat/id3v2.c
2ea512a6
 /*
  * Copyright (c) 2003 Fabrice Bellard
  *
  * This file is part of FFmpeg.
  *
  * FFmpeg is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
  * version 2.1 of the License, or (at your option) any later version.
  *
  * FFmpeg is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with FFmpeg; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
b69e5ee9
 /**
  * @file
  * ID3v2 header parser
  *
  * Specifications available at:
  * http://id3.org/Developer_Information
  */
 
7e09fe15
 #include "config.h"
 
 #if CONFIG_ZLIB
 #include <zlib.h>
 #endif
 
75411182
 #include "libavutil/avstring.h"
d2d67e42
 #include "libavutil/dict.h"
0671adbb
 #include "libavutil/intreadwrite.h"
ae99313a
 #include "avio_internal.h"
a93b09cb
 #include "internal.h"
0671adbb
 #include "id3v1.h"
 #include "id3v2.h"
2ea512a6
 
3b78c180
 const AVMetadataConv ff_id3v2_34_metadata_conv[] = {
0671adbb
     { "TALB", "album"        },
     { "TCOM", "composer"     },
     { "TCON", "genre"        },
     { "TCOP", "copyright"    },
     { "TENC", "encoded_by"   },
     { "TIT2", "title"        },
     { "TLAN", "language"     },
     { "TPE1", "artist"       },
     { "TPE2", "album_artist" },
     { "TPE3", "performer"    },
     { "TPOS", "disc"         },
     { "TPUB", "publisher"    },
     { "TRCK", "track"        },
     { "TSSE", "encoder"      },
3b78c180
     { 0 }
 };
 
 const AVMetadataConv ff_id3v2_4_metadata_conv[] = {
847d8af5
     { "TCMP", "compilation"   },
0671adbb
     { "TDRL", "date"          },
     { "TDRC", "date"          },
     { "TDEN", "creation_time" },
     { "TSOA", "album-sort"    },
     { "TSOP", "artist-sort"   },
     { "TSOT", "title-sort"    },
3b78c180
     { 0 }
 };
 
c4a37885
 static const AVMetadataConv id3v2_2_metadata_conv[] = {
0671adbb
     { "TAL", "album"        },
     { "TCO", "genre"        },
847d8af5
     { "TCP", "compilation"  },
0671adbb
     { "TT2", "title"        },
     { "TEN", "encoded_by"   },
     { "TP1", "artist"       },
     { "TP2", "album_artist" },
     { "TP3", "performer"    },
     { "TRK", "track"        },
3b78c180
     { 0 }
 };
 
 const char ff_id3v2_tags[][4] = {
0671adbb
     "TALB", "TBPM", "TCOM", "TCON", "TCOP", "TDLY", "TENC", "TEXT",
     "TFLT", "TIT1", "TIT2", "TIT3", "TKEY", "TLAN", "TLEN", "TMED",
     "TOAL", "TOFN", "TOLY", "TOPE", "TOWN", "TPE1", "TPE2", "TPE3",
     "TPE4", "TPOS", "TPUB", "TRCK", "TRSN", "TRSO", "TSRC", "TSSE",
     { 0 },
3b78c180
 };
 
 const char ff_id3v2_4_tags[][4] = {
0671adbb
     "TDEN", "TDOR", "TDRC", "TDRL", "TDTG", "TIPL", "TMCL", "TMOO",
     "TPRO", "TSOA", "TSOP", "TSOT", "TSST",
     { 0 },
3b78c180
 };
 
 const char ff_id3v2_3_tags[][4] = {
0671adbb
     "TDAT", "TIME", "TORY", "TRDA", "TSIZ", "TYER",
     { 0 },
3b78c180
 };
 
a93b09cb
 const char *ff_id3v2_picture_types[21] = {
     "Other",
     "32x32 pixels 'file icon'",
     "Other file icon",
     "Cover (front)",
     "Cover (back)",
     "Leaflet page",
     "Media (e.g. label side of CD)",
     "Lead artist/lead performer/soloist",
     "Artist/performer",
     "Conductor",
     "Band/Orchestra",
     "Composer",
     "Lyricist/text writer",
     "Recording Location",
     "During recording",
     "During performance",
     "Movie/video screen capture",
     "A bright coloured fish",
     "Illustration",
     "Band/artist logotype",
     "Publisher/Studio logotype",
 };
 
 const CodecMime ff_id3v2_mime_tags[] = {
0671adbb
     { "image/gif",  AV_CODEC_ID_GIF   },
     { "image/jpeg", AV_CODEC_ID_MJPEG },
     { "image/jpg",  AV_CODEC_ID_MJPEG },
     { "image/png",  AV_CODEC_ID_PNG   },
     { "image/tiff", AV_CODEC_ID_TIFF  },
     { "image/bmp",  AV_CODEC_ID_BMP   },
     { "JPG",        AV_CODEC_ID_MJPEG }, /* ID3v2.2  */
     { "PNG",        AV_CODEC_ID_PNG   }, /* ID3v2.2  */
     { "",           AV_CODEC_ID_NONE  },
a93b09cb
 };
 
0671adbb
 int ff_id3v2_match(const uint8_t *buf, const char *magic)
2ea512a6
 {
3a1350e8
     return  buf[0]         == magic[0] &&
             buf[1]         == magic[1] &&
             buf[2]         == magic[2] &&
0671adbb
             buf[3]         != 0xff     &&
             buf[4]         != 0xff     &&
            (buf[6] & 0x80) == 0        &&
            (buf[7] & 0x80) == 0        &&
            (buf[8] & 0x80) == 0        &&
            (buf[9] & 0x80) == 0;
2ea512a6
 }
ac3ef4a4
 
0671adbb
 int ff_id3v2_tag_len(const uint8_t *buf)
ac3ef4a4
 {
     int len = ((buf[6] & 0x7f) << 21) +
7d7b8c32
               ((buf[7] & 0x7f) << 14) +
               ((buf[8] & 0x7f) << 7) +
0671adbb
               (buf[9] & 0x7f) +
7d7b8c32
               ID3v2_HEADER_SIZE;
ac3ef4a4
     if (buf[5] & 0x10)
         len += ID3v2_HEADER_SIZE;
     return len;
 }
75411182
 
471fe57e
 static unsigned int get_size(AVIOContext *s, int len)
75411182
 {
7d7b8c32
     int v = 0;
     while (len--)
e63a3628
         v = (v << 7) + (avio_r8(s) & 0x7F);
75411182
     return v;
 }
 
7a019dff
 /**
  * Free GEOB type extra metadata.
  */
a1526cd7
 static void free_geobtag(void *obj)
75411182
 {
a1526cd7
     ID3v2ExtraMetaGEOB *geob = obj;
7a019dff
     av_free(geob->mime_type);
     av_free(geob->file_name);
     av_free(geob->description);
     av_free(geob->data);
     av_free(geob);
 }
75411182
 
7a019dff
 /**
  * Decode characters to UTF-8 according to encoding type. The decoded buffer is
d2961e4e
  * always null terminated. Stop reading when either *maxread bytes are read from
  * pb or U+0000 character is found.
7a019dff
  *
  * @param dst Pointer where the address of the buffer with the decoded bytes is
  * stored. Buffer must be freed by caller.
  * @param maxread Pointer to maximum number of characters to read from the
  * AVIOContext. After execution the value is decremented by the number of bytes
  * actually read.
da9cea77
  * @returns 0 if no error occurred, dst is uninitialized on error
7a019dff
  */
 static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
d2961e4e
                       uint8_t **dst, int *maxread)
7a019dff
 {
d2961e4e
     int ret;
7a019dff
     uint8_t tmp;
     uint32_t ch = 1;
     int left = *maxread;
     unsigned int (*get)(AVIOContext*) = avio_rb16;
     AVIOContext *dynbuf;
75411182
 
7a019dff
     if ((ret = avio_open_dyn_buf(&dynbuf)) < 0) {
         av_log(s, AV_LOG_ERROR, "Error opening memory stream\n");
         return ret;
     }
75411182
 
7a019dff
     switch (encoding) {
eeb0b893
     case ID3v2_ENCODING_ISO8859:
d2961e4e
         while (left && ch) {
7a019dff
             ch = avio_r8(pb);
             PUT_UTF8(ch, tmp, avio_w8(dynbuf, tmp);)
             left--;
75411182
         }
         break;
 
eeb0b893
     case ID3v2_ENCODING_UTF16BOM:
7a019dff
         if ((left -= 2) < 0) {
             av_log(s, AV_LOG_ERROR, "Cannot read BOM value, input too short\n");
60df6b00
             avio_close_dyn_buf(dynbuf, dst);
7a019dff
             av_freep(dst);
             return AVERROR_INVALIDDATA;
         }
e63a3628
         switch (avio_rb16(pb)) {
20c68378
         case 0xfffe:
e63a3628
             get = avio_rl16;
20c68378
         case 0xfeff:
             break;
         default:
7a019dff
             av_log(s, AV_LOG_ERROR, "Incorrect BOM value\n");
60df6b00
             avio_close_dyn_buf(dynbuf, dst);
7a019dff
             av_freep(dst);
             *maxread = left;
             return AVERROR_INVALIDDATA;
20c68378
         }
         // fall-through
 
eeb0b893
     case ID3v2_ENCODING_UTF16BE:
d2961e4e
         while ((left > 1) && ch) {
7a019dff
             GET_UTF16(ch, ((left -= 2) >= 0 ? get(pb) : 0), break;)
             PUT_UTF8(ch, tmp, avio_w8(dynbuf, tmp);)
20c68378
         }
7a019dff
         if (left < 0)
0671adbb
             left += 2;  /* did not read last char from pb */
20c68378
         break;
 
eeb0b893
     case ID3v2_ENCODING_UTF8:
d2961e4e
         while (left && ch) {
7a019dff
             ch = avio_r8(pb);
             avio_w8(dynbuf, ch);
             left--;
         }
75411182
         break;
20c68378
     default:
7a019dff
         av_log(s, AV_LOG_WARNING, "Unknown encoding\n");
     }
 
     if (ch)
         avio_w8(dynbuf, 0);
 
60df6b00
     avio_close_dyn_buf(dynbuf, dst);
7a019dff
     *maxread = left;
 
     return 0;
 }
 
 /**
  * Parse a text tag.
  */
0671adbb
 static void read_ttag(AVFormatContext *s, AVIOContext *pb, int taglen,
379fcc49
                       AVDictionary **metadata, const char *key)
7a019dff
 {
     uint8_t *dst;
e5fcf364
     int encoding, dict_flags = AV_DICT_DONT_OVERWRITE | AV_DICT_DONT_STRDUP_VAL;
7a019dff
     unsigned genre;
 
     if (taglen < 1)
         return;
 
d2961e4e
     encoding = avio_r8(pb);
7a019dff
     taglen--; /* account for encoding type byte */
 
d2961e4e
     if (decode_str(s, pb, encoding, &dst, &taglen) < 0) {
7a019dff
         av_log(s, AV_LOG_ERROR, "Error reading frame %s, skipped\n", key);
         return;
75411182
     }
 
0671adbb
     if (!(strcmp(key, "TCON") && strcmp(key, "TCO"))                         &&
         (sscanf(dst, "(%d)", &genre) == 1 || sscanf(dst, "%d", &genre) == 1) &&
         genre <= ID3v1_GENRE_MAX) {
d2961e4e
         av_freep(&dst);
e5fcf364
         dst = av_strdup(ff_id3v1_genre_str[genre]);
d2961e4e
     } else if (!(strcmp(key, "TXXX") && strcmp(key, "TXX"))) {
         /* dst now contains the key, need to get value */
41770abf
         key = dst;
d2961e4e
         if (decode_str(s, pb, encoding, &dst, &taglen) < 0) {
             av_log(s, AV_LOG_ERROR, "Error reading frame %s, skipped\n", key);
             av_freep(&key);
             return;
         }
e5fcf364
         dict_flags |= AV_DICT_DONT_STRDUP_KEY;
     } else if (!*dst)
6ad974ae
         av_freep(&dst);
75411182
 
d2961e4e
     if (dst)
379fcc49
         av_dict_set(metadata, key, dst, dict_flags);
7a019dff
 }
 
 /**
  * Parse GEOB tag into a ID3v2ExtraMetaGEOB struct.
  */
0671adbb
 static void read_geobtag(AVFormatContext *s, AVIOContext *pb, int taglen,
05c3c568
                          char *tag, ID3v2ExtraMeta **extra_meta, int isv34)
7a019dff
 {
     ID3v2ExtraMetaGEOB *geob_data = NULL;
0671adbb
     ID3v2ExtraMeta *new_extra     = NULL;
7a019dff
     char encoding;
     unsigned int len;
 
     if (taglen < 1)
         return;
 
     geob_data = av_mallocz(sizeof(ID3v2ExtraMetaGEOB));
     if (!geob_data) {
ced0d6c1
         av_log(s, AV_LOG_ERROR, "Failed to alloc %"SIZE_SPECIFIER" bytes\n",
0671adbb
                sizeof(ID3v2ExtraMetaGEOB));
7a019dff
         return;
     }
 
     new_extra = av_mallocz(sizeof(ID3v2ExtraMeta));
     if (!new_extra) {
ced0d6c1
         av_log(s, AV_LOG_ERROR, "Failed to alloc %"SIZE_SPECIFIER" bytes\n",
0671adbb
                sizeof(ID3v2ExtraMeta));
7a019dff
         goto fail;
     }
 
     /* read encoding type byte */
     encoding = avio_r8(pb);
     taglen--;
 
     /* read MIME type (always ISO-8859) */
0671adbb
     if (decode_str(s, pb, ID3v2_ENCODING_ISO8859, &geob_data->mime_type,
                    &taglen) < 0 ||
         taglen <= 0)
7a019dff
         goto fail;
 
     /* read file name */
0671adbb
     if (decode_str(s, pb, encoding, &geob_data->file_name, &taglen) < 0 ||
         taglen <= 0)
7a019dff
         goto fail;
 
     /* read content description */
0671adbb
     if (decode_str(s, pb, encoding, &geob_data->description, &taglen) < 0 ||
         taglen < 0)
7a019dff
         goto fail;
 
     if (taglen) {
         /* save encapsulated binary data */
         geob_data->data = av_malloc(taglen);
         if (!geob_data->data) {
             av_log(s, AV_LOG_ERROR, "Failed to alloc %d bytes\n", taglen);
             goto fail;
         }
         if ((len = avio_read(pb, geob_data->data, taglen)) < taglen)
0671adbb
             av_log(s, AV_LOG_WARNING,
                    "Error reading GEOB frame, data truncated.\n");
7a019dff
         geob_data->datasize = len;
     } else {
0671adbb
         geob_data->data     = NULL;
7a019dff
         geob_data->datasize = 0;
     }
 
     /* add data to the list */
0671adbb
     new_extra->tag  = "GEOB";
7a019dff
     new_extra->data = geob_data;
     new_extra->next = *extra_meta;
0671adbb
     *extra_meta     = new_extra;
7a019dff
 
     return;
 
 fail:
     av_log(s, AV_LOG_ERROR, "Error reading frame %s, skipped\n", tag);
     free_geobtag(geob_data);
     av_free(new_extra);
     return;
75411182
 }
 
56e2ac6b
 static int is_number(const char *str)
 {
0671adbb
     while (*str >= '0' && *str <= '9')
         str++;
56e2ac6b
     return !*str;
 }
 
0671adbb
 static AVDictionaryEntry *get_date_tag(AVDictionary *m, const char *tag)
56e2ac6b
 {
d2d67e42
     AVDictionaryEntry *t;
     if ((t = av_dict_get(m, tag, NULL, AV_DICT_MATCH_CASE)) &&
56e2ac6b
         strlen(t->value) == 4 && is_number(t->value))
         return t;
     return NULL;
 }
 
d2d67e42
 static void merge_date(AVDictionary **m)
56e2ac6b
 {
d2d67e42
     AVDictionaryEntry *t;
0671adbb
     char date[17] = { 0 };      // YYYY-MM-DD hh:mm
56e2ac6b
 
     if (!(t = get_date_tag(*m, "TYER")) &&
         !(t = get_date_tag(*m, "TYE")))
         return;
     av_strlcpy(date, t->value, 5);
d2d67e42
     av_dict_set(m, "TYER", NULL, 0);
0671adbb
     av_dict_set(m, "TYE", NULL, 0);
56e2ac6b
 
     if (!(t = get_date_tag(*m, "TDAT")) &&
         !(t = get_date_tag(*m, "TDA")))
         goto finish;
     snprintf(date + 4, sizeof(date) - 4, "-%.2s-%.2s", t->value + 2, t->value);
d2d67e42
     av_dict_set(m, "TDAT", NULL, 0);
0671adbb
     av_dict_set(m, "TDA", NULL, 0);
56e2ac6b
 
     if (!(t = get_date_tag(*m, "TIME")) &&
         !(t = get_date_tag(*m, "TIM")))
         goto finish;
0671adbb
     snprintf(date + 10, sizeof(date) - 10,
              " %.2s:%.2s", t->value, t->value + 2);
d2d67e42
     av_dict_set(m, "TIME", NULL, 0);
0671adbb
     av_dict_set(m, "TIM", NULL, 0);
56e2ac6b
 
 finish:
     if (date[0])
d2d67e42
         av_dict_set(m, "date", date, 0);
56e2ac6b
 }
 
a93b09cb
 static void free_apic(void *obj)
 {
     ID3v2ExtraMetaAPIC *apic = obj;
1afddbe5
     av_buffer_unref(&apic->buf);
a93b09cb
     av_freep(&apic->description);
     av_freep(&apic);
 }
 
0671adbb
 static void read_apic(AVFormatContext *s, AVIOContext *pb, int taglen,
05c3c568
                       char *tag, ID3v2ExtraMeta **extra_meta, int isv34)
a93b09cb
 {
     int enc, pic_type;
0671adbb
     char mimetype[64];
     const CodecMime *mime     = ff_id3v2_mime_tags;
     enum AVCodecID id         = AV_CODEC_ID_NONE;
     ID3v2ExtraMetaAPIC *apic  = NULL;
a93b09cb
     ID3v2ExtraMeta *new_extra = NULL;
0671adbb
     int64_t end               = avio_tell(pb) + taglen;
a93b09cb
 
     if (taglen <= 4)
         goto fail;
 
     new_extra = av_mallocz(sizeof(*new_extra));
     apic      = av_mallocz(sizeof(*apic));
     if (!new_extra || !apic)
         goto fail;
 
     enc = avio_r8(pb);
     taglen--;
 
     /* mimetype */
05c3c568
     if (isv34) {
a93b09cb
     taglen -= avio_get_str(pb, taglen, mimetype, sizeof(mimetype));
05c3c568
     } else {
         avio_read(pb, mimetype, 3);
         mimetype[3] = 0;
     }
36ef5369
     while (mime->id != AV_CODEC_ID_NONE) {
6e9bbc65
         if (!av_strncasecmp(mime->str, mimetype, sizeof(mimetype))) {
a93b09cb
             id = mime->id;
             break;
         }
         mime++;
     }
36ef5369
     if (id == AV_CODEC_ID_NONE) {
0671adbb
         av_log(s, AV_LOG_WARNING,
                "Unknown attached picture mimetype: %s, skipping.\n", mimetype);
a93b09cb
         goto fail;
     }
     apic->id = id;
 
     /* picture type */
     pic_type = avio_r8(pb);
     taglen--;
     if (pic_type < 0 || pic_type >= FF_ARRAY_ELEMS(ff_id3v2_picture_types)) {
0671adbb
         av_log(s, AV_LOG_WARNING, "Unknown attached picture type %d.\n",
                pic_type);
a93b09cb
         pic_type = 0;
     }
     apic->type = ff_id3v2_picture_types[pic_type];
 
     /* description and picture data */
     if (decode_str(s, pb, enc, &apic->description, &taglen) < 0) {
0671adbb
         av_log(s, AV_LOG_ERROR,
                "Error decoding attached picture description.\n");
a93b09cb
         goto fail;
     }
 
24cfe91a
     apic->buf = av_buffer_alloc(taglen + FF_INPUT_BUFFER_PADDING_SIZE);
2653e125
     if (!apic->buf || !taglen || avio_read(pb, apic->buf->data, taglen) != taglen)
a93b09cb
         goto fail;
8d617b11
     memset(apic->buf->data + taglen, 0, FF_INPUT_BUFFER_PADDING_SIZE);
a93b09cb
 
0671adbb
     new_extra->tag  = "APIC";
     new_extra->data = apic;
     new_extra->next = *extra_meta;
     *extra_meta     = new_extra;
a93b09cb
 
     return;
 
 fail:
     if (apic)
         free_apic(apic);
     av_freep(&new_extra);
     avio_seek(pb, end, SEEK_SET);
 }
 
05c3c568
 static void read_chapter(AVFormatContext *s, AVIOContext *pb, int len, char *ttag, ID3v2ExtraMeta **extra_meta, int isv34)
07ed191b
 {
     AVRational time_base = {1, 1000};
     uint32_t start, end;
379fcc49
     AVChapter *chapter;
f5846dc9
     uint8_t *dst = NULL;
379fcc49
     int taglen;
     char tag[5];
07ed191b
 
7fdf245a
     if (!s) {
         /* We should probably just put the chapter data to extra_meta here
          * and do the AVFormatContext-needing part in a separate
          * ff_id3v2_parse_apic()-like function. */
         av_log(NULL, AV_LOG_DEBUG, "No AVFormatContext, skipped ID3 chapter data\n");
         return;
     }
 
6241e8a3
     if (decode_str(s, pb, 0, &dst, &len) < 0)
         return;
379fcc49
     if (len < 16)
07ed191b
         return;
 
     start = avio_rb32(pb);
     end   = avio_rb32(pb);
379fcc49
     avio_skip(pb, 8);
 
     chapter = avpriv_new_chapter(s, s->nb_chapters + 1, time_base, start, end, dst);
     if (!chapter) {
         av_free(dst);
         return;
     }
07ed191b
 
379fcc49
     len -= 16;
     while (len > 10) {
ffbcb1c6
         if (avio_read(pb, tag, 4) < 4)
             goto end;
379fcc49
         tag[4] = 0;
         taglen = avio_rb32(pb);
         avio_skip(pb, 2);
         len -= 10;
460f8fca
         if (taglen < 0 || taglen > len)
             goto end;
379fcc49
         if (tag[0] == 'T')
             read_ttag(s, pb, taglen, &chapter->metadata, tag);
         else
             avio_skip(pb, taglen);
         len -= taglen;
07ed191b
     }
 
379fcc49
     ff_metadata_conv(&chapter->metadata, NULL, ff_id3v2_34_metadata_conv);
     ff_metadata_conv(&chapter->metadata, NULL, ff_id3v2_4_metadata_conv);
460f8fca
 end:
f5846dc9
     av_free(dst);
07ed191b
 }
 
53765ae3
 static void free_priv(void *obj)
 {
     ID3v2ExtraMetaPRIV *priv = obj;
     av_freep(&priv->owner);
     av_freep(&priv->data);
     av_freep(&priv);
 }
 
 static void read_priv(AVFormatContext *s, AVIOContext *pb, int taglen,
                       char *tag, ID3v2ExtraMeta **extra_meta, int isv34)
 {
     ID3v2ExtraMeta *meta;
     ID3v2ExtraMetaPRIV *priv;
 
     meta = av_mallocz(sizeof(*meta));
     priv = av_mallocz(sizeof(*priv));
 
     if (!meta || !priv)
         goto fail;
 
     if (decode_str(s, pb, ID3v2_ENCODING_ISO8859, &priv->owner, &taglen) < 0)
         goto fail;
 
     priv->data = av_malloc(taglen);
     if (!priv->data)
         goto fail;
 
     priv->datasize = taglen;
 
     if (avio_read(pb, priv->data, priv->datasize) != priv->datasize)
         goto fail;
 
     meta->tag   = "PRIV";
     meta->data  = priv;
     meta->next  = *extra_meta;
     *extra_meta = meta;
 
     return;
 
 fail:
     if (priv)
         free_priv(priv);
     av_freep(&meta);
 }
 
c4a37885
 typedef struct ID3v2EMFunc {
     const char *tag3;
     const char *tag4;
0671adbb
     void (*read)(AVFormatContext *, AVIOContext *, int, char *,
05c3c568
                  ID3v2ExtraMeta **, int isv34);
ec22979a
     void (*free)(void *obj);
c4a37885
 } ID3v2EMFunc;
 
 static const ID3v2EMFunc id3v2_extra_meta_funcs[] = {
3b78c180
     { "GEO", "GEOB", read_geobtag, free_geobtag },
0671adbb
     { "PIC", "APIC", read_apic,    free_apic    },
0beff428
     { "CHAP","CHAP", read_chapter, NULL         },
53765ae3
     { "PRIV","PRIV", read_priv,    free_priv    },
3b78c180
     { NULL }
 };
 
7a019dff
 /**
  * Get the corresponding ID3v2EMFunc struct for a tag.
  * @param isv34 Determines if v2.2 or v2.3/4 strings are used
  * @return A pointer to the ID3v2EMFunc struct if found, NULL otherwise.
  */
 static const ID3v2EMFunc *get_extra_meta_func(const char *tag, int isv34)
 {
     int i = 0;
c4a37885
     while (id3v2_extra_meta_funcs[i].tag3) {
aedd30b6
         if (tag && !memcmp(tag,
c4a37885
                     (isv34 ? id3v2_extra_meta_funcs[i].tag4 :
                              id3v2_extra_meta_funcs[i].tag3),
7a019dff
                     (isv34 ? 4 : 3)))
c4a37885
             return &id3v2_extra_meta_funcs[i];
7a019dff
         i++;
     }
aedd30b6
     return NULL;
7a019dff
 }
 
7fdf245a
 static void id3v2_parse(AVIOContext *pb, AVDictionary **metadata,
                         AVFormatContext *s, int len, uint8_t version,
e926b5ce
                         uint8_t flags, ID3v2ExtraMeta **extra_meta)
75411182
 {
ac533ac4
     int isv34, unsync;
     unsigned tlen;
41770abf
     char tag[5];
7fdf245a
     int64_t next, end = avio_tell(pb) + len;
75411182
     int taghdrlen;
eb1e7f78
     const char *reason = NULL;
7fdf245a
     AVIOContext pb_local;
7a019dff
     AVIOContext *pbx;
18bbe9df
     unsigned char *buffer = NULL;
0671adbb
     int buffer_size       = 0;
acc88f07
     const ID3v2EMFunc *extra_func = NULL;
e9c37236
     unsigned char *uncompressed_buffer = NULL;
     int uncompressed_buffer_size = 0;
75411182
 
08e26e7f
     av_log(s, AV_LOG_DEBUG, "id3v2 ver:%d flags:%02X len:%d\n", version, flags, len);
 
7d7b8c32
     switch (version) {
75411182
     case 2:
7d7b8c32
         if (flags & 0x40) {
75411182
             reason = "compression";
             goto error;
         }
0671adbb
         isv34     = 0;
75411182
         taghdrlen = 6;
         break;
 
     case 3:
     case 4:
0671adbb
         isv34     = 1;
75411182
         taghdrlen = 10;
         break;
 
     default:
         reason = "version";
         goto error;
     }
 
18bbe9df
     unsync = flags & 0x80;
75411182
 
ddb44312
     if (isv34 && flags & 0x40) { /* Extended header present, just skip over it */
7fdf245a
         int extlen = get_size(pb, 4);
ddb44312
         if (version == 4)
0671adbb
             /* In v2.4 the length includes the length field we just read. */
             extlen -= 4;
ddb44312
 
         if (extlen < 0) {
             reason = "invalid extended header length";
dd7453a2
             goto error;
         }
7fdf245a
         avio_skip(pb, extlen);
49891784
         len -= extlen + 4;
dd7453a2
         if (len < 0) {
             reason = "extended header too long.";
             goto error;
         }
     }
75411182
 
7d7b8c32
     while (len >= taghdrlen) {
40a5dd2f
         unsigned int tflags = 0;
0671adbb
         int tunsync         = 0;
0beff428
         int tcomp           = 0;
         int tencr           = 0;
084bd109
         unsigned long dlen;
18bbe9df
 
7d7b8c32
         if (isv34) {
7fdf245a
             if (avio_read(pb, tag, 4) < 4)
0e0f6bd4
                 break;
41770abf
             tag[4] = 0;
0671adbb
             if (version == 3) {
7fdf245a
                 tlen = avio_rb32(pb);
0671adbb
             } else
7fdf245a
                 tlen = get_size(pb, 4);
             tflags  = avio_rb16(pb);
7a07d158
             tunsync = tflags & ID3v2_FLAG_UNSYNCH;
75411182
         } else {
7fdf245a
             if (avio_read(pb, tag, 3) < 3)
0e0f6bd4
                 break;
41770abf
             tag[3] = 0;
7fdf245a
             tlen   = avio_rb24(pb);
75411182
         }
2f9d6ffd
         if (tlen > (1<<28))
ac533ac4
             break;
75411182
         len -= taghdrlen + tlen;
 
7d7b8c32
         if (len < 0)
75411182
             break;
 
7fdf245a
         next = avio_tell(pb) + tlen;
75411182
 
1e18d32d
         if (!tlen) {
             if (tag[0])
0671adbb
                 av_log(s, AV_LOG_DEBUG, "Invalid empty frame %s, skipping.\n",
                        tag);
1e18d32d
             continue;
         }
 
a152c77f
         if (tflags & ID3v2_FLAG_DATALEN) {
64be0d1e
             if (tlen < 4)
                 break;
7fdf245a
             dlen = avio_rb32(pb);
a152c77f
             tlen -= 4;
7e09fe15
         } else
             dlen = tlen;
 
         tcomp = tflags & ID3v2_FLAG_COMPRESSION;
         tencr = tflags & ID3v2_FLAG_ENCRYPTION;
 
         /* skip encrypted tags and, if no zlib, compressed tags */
         if (tencr || (!CONFIG_ZLIB && tcomp)) {
             const char *type;
             if (!tcomp)
                 type = "encrypted";
             else if (!tencr)
                 type = "compressed";
             else
                 type = "encrypted and compressed";
a152c77f
 
7e09fe15
             av_log(s, AV_LOG_WARNING, "Skipping %s ID3v2 frame %s.\n", type, tag);
7fdf245a
             avio_skip(pb, tlen);
7a019dff
         /* check for text tag or supported special meta tag */
0671adbb
         } else if (tag[0] == 'T' ||
                    (extra_meta &&
                     (extra_func = get_extra_meta_func(tag, isv34)))) {
7fdf245a
             pbx = pb;
7e09fe15
 
e9c37236
             if (unsync || tunsync || tcomp) {
                 av_fast_malloc(&buffer, &buffer_size, tlen);
86f86877
                 if (!buffer) {
e9c37236
                     av_log(s, AV_LOG_ERROR, "Failed to alloc %d bytes\n", tlen);
86f86877
                     goto seek;
                 }
e9c37236
             }
             if (unsync || tunsync) {
7fdf245a
                 int64_t end = avio_tell(pb) + tlen;
e9c37236
                 uint8_t *b;
 
9ae80e6a
                 b = buffer;
7fdf245a
                 while (avio_tell(pb) < end && b - buffer < tlen && !pb->eof_reached) {
                     *b++ = avio_r8(pb);
                     if (*(b - 1) == 0xff && avio_tell(pb) < end - 1 &&
760f7d3a
                         b - buffer < tlen &&
7fdf245a
                         !pb->eof_reached ) {
                         uint8_t val = avio_r8(pb);
                         *b++ = val ? val : avio_r8(pb);
e9c37236
                     }
                 }
7fdf245a
                 ffio_init_context(&pb_local, buffer, b - buffer, 0, NULL, NULL, NULL,
0671adbb
                                   NULL);
e9c37236
                 tlen = b - buffer;
7fdf245a
                 pbx  = &pb_local; // read from sync buffer
e9c37236
             }
 
7e09fe15
 #if CONFIG_ZLIB
                 if (tcomp) {
e9c37236
                     int err;
7e09fe15
 
084bd109
                     av_log(s, AV_LOG_DEBUG, "Compresssed frame %s tlen=%d dlen=%ld\n", tag, tlen, dlen);
7e09fe15
 
e9c37236
                     av_fast_malloc(&uncompressed_buffer, &uncompressed_buffer_size, dlen);
                     if (!uncompressed_buffer) {
                         av_log(s, AV_LOG_ERROR, "Failed to alloc %ld bytes\n", dlen);
7e09fe15
                         goto seek;
                     }
 
e9c37236
                     if (!(unsync || tunsync)) {
7fdf245a
                         err = avio_read(pb, buffer, tlen);
e9c37236
                         if (err < 0) {
                             av_log(s, AV_LOG_ERROR, "Failed to read compressed tag\n");
                             goto seek;
                         }
                         tlen = err;
7e09fe15
                     }
 
e9c37236
                     err = uncompress(uncompressed_buffer, &dlen, buffer, tlen);
7e09fe15
                     if (err != Z_OK) {
                         av_log(s, AV_LOG_ERROR, "Failed to uncompress tag: %d\n", err);
                         goto seek;
                     }
7fdf245a
                     ffio_init_context(&pb_local, uncompressed_buffer, dlen, 0, NULL, NULL, NULL, NULL);
e9c37236
                     tlen = dlen;
7fdf245a
                     pbx = &pb_local; // read from sync buffer
7e09fe15
                 }
 #endif
7a019dff
             if (tag[0] == 'T')
                 /* parse text tag */
7fdf245a
                 read_ttag(s, pbx, tlen, metadata, tag);
7a019dff
             else
                 /* parse special meta tag */
05c3c568
                 extra_func->read(s, pbx, tlen, tag, extra_meta, isv34);
0671adbb
         } else if (!tag[0]) {
2e3ca1ff
             if (tag[1])
5081310b
                 av_log(s, AV_LOG_WARNING, "invalid frame id, assuming padding\n");
7fdf245a
             avio_skip(pb, tlen);
2e3ca1ff
             break;
         }
75411182
         /* Skip to end of tag */
86f86877
 seek:
7fdf245a
         avio_seek(pb, next, SEEK_SET);
75411182
     }
 
0671adbb
     /* Footer preset, always 10 bytes, skip over it */
     if (version == 4 && flags & 0x10)
bca6dee3
         end += 10;
75411182
 
0671adbb
 error:
eb1e7f78
     if (reason)
0671adbb
         av_log(s, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n",
                version, reason);
7fdf245a
     avio_seek(pb, end, SEEK_SET);
18bbe9df
     av_free(buffer);
e9c37236
     av_free(uncompressed_buffer);
75411182
     return;
 }
6378b062
 
7fdf245a
 static void id3v2_read_internal(AVIOContext *pb, AVDictionary **metadata,
                                 AVFormatContext *s, const char *magic,
5331773c
                                 ID3v2ExtraMeta **extra_meta, int64_t max_search_size)
b3158f7a
 {
     int len, ret;
     uint8_t buf[ID3v2_HEADER_SIZE];
0671adbb
     int found_header;
5331773c
     int64_t start, off;
b3158f7a
 
5331773c
     if (max_search_size && max_search_size < ID3v2_HEADER_SIZE)
         return;
 
     start = avio_tell(pb);
b3158f7a
     do {
         /* save the current offset in case there's nothing to read/skip */
7fdf245a
         off = avio_tell(pb);
5331773c
         if (max_search_size && off - start >= max_search_size - ID3v2_HEADER_SIZE) {
             avio_seek(pb, off, SEEK_SET);
             break;
         }
 
7fdf245a
         ret = avio_read(pb, buf, ID3v2_HEADER_SIZE);
b3b456b2
         if (ret != ID3v2_HEADER_SIZE) {
7fdf245a
             avio_seek(pb, off, SEEK_SET);
189665d9
             break;
b3b456b2
         }
61625565
         found_header = ff_id3v2_match(buf, magic);
         if (found_header) {
b3158f7a
             /* parse ID3v2 header */
             len = ((buf[6] & 0x7f) << 21) |
                   ((buf[7] & 0x7f) << 14) |
                   ((buf[8] & 0x7f) << 7) |
                    (buf[9] & 0x7f);
7fdf245a
             id3v2_parse(pb, metadata, s, len, buf[3], buf[5], extra_meta);
b3158f7a
         } else {
7fdf245a
             avio_seek(pb, off, SEEK_SET);
b3158f7a
         }
     } while (found_header);
7fdf245a
     ff_metadata_conv(metadata, NULL, ff_id3v2_34_metadata_conv);
     ff_metadata_conv(metadata, NULL, id3v2_2_metadata_conv);
     ff_metadata_conv(metadata, NULL, ff_id3v2_4_metadata_conv);
     merge_date(metadata);
 }
 
 void ff_id3v2_read_dict(AVIOContext *pb, AVDictionary **metadata,
                         const char *magic, ID3v2ExtraMeta **extra_meta)
 {
5331773c
     id3v2_read_internal(pb, metadata, NULL, magic, extra_meta, 0);
7fdf245a
 }
 
 void ff_id3v2_read(AVFormatContext *s, const char *magic,
5331773c
                    ID3v2ExtraMeta **extra_meta, unsigned int max_search_size)
7fdf245a
 {
5331773c
     id3v2_read_internal(s->pb, &s->metadata, s, magic, extra_meta, max_search_size);
b3158f7a
 }
 
7a019dff
 void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta)
 {
     ID3v2ExtraMeta *current = *extra_meta, *next;
c780b543
     const ID3v2EMFunc *extra_func;
7a019dff
 
     while (current) {
c780b543
         if ((extra_func = get_extra_meta_func(current->tag, 1)))
             extra_func->free(current->data);
7a019dff
         next = current->next;
         av_freep(&current);
         current = next;
     }
c94305ae
 
     *extra_meta = NULL;
7a019dff
 }
079ea6ca
 
 int ff_id3v2_parse_apic(AVFormatContext *s, ID3v2ExtraMeta **extra_meta)
 {
     ID3v2ExtraMeta *cur;
 
     for (cur = *extra_meta; cur; cur = cur->next) {
         ID3v2ExtraMetaAPIC *apic;
         AVStream *st;
 
         if (strcmp(cur->tag, "APIC"))
             continue;
         apic = cur->data;
 
         if (!(st = avformat_new_stream(s, NULL)))
             return AVERROR(ENOMEM);
 
         st->disposition      |= AV_DISPOSITION_ATTACHED_PIC;
         st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
         st->codec->codec_id   = apic->id;
         av_dict_set(&st->metadata, "title",   apic->description, 0);
         av_dict_set(&st->metadata, "comment", apic->type, 0);
 
         av_init_packet(&st->attached_pic);
1afddbe5
         st->attached_pic.buf          = apic->buf;
         st->attached_pic.data         = apic->buf->data;
8d617b11
         st->attached_pic.size         = apic->buf->size - FF_INPUT_BUFFER_PADDING_SIZE;
079ea6ca
         st->attached_pic.stream_index = st->index;
713f3062
         st->attached_pic.flags       |= AV_PKT_FLAG_KEY;
079ea6ca
 
1afddbe5
         apic->buf = NULL;
079ea6ca
     }
 
     return 0;
 }