+static void merge_date(AVDictionary **m)
+{
+ AVDictionaryEntry *t;
+ char date[17] = { 0 }; // YYYY-MM-DD hh:mm
+
+ if (!(t = get_date_tag(*m, "TYER")) &&
+ !(t = get_date_tag(*m, "TYE")))
+ return;
+ av_strlcpy(date, t->value, 5);
+ av_dict_set(m, "TYER", NULL, 0);
+ av_dict_set(m, "TYE", NULL, 0);
+
+ 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);
+ av_dict_set(m, "TDAT", NULL, 0);
+ av_dict_set(m, "TDA", NULL, 0);
+
+ if (!(t = get_date_tag(*m, "TIME")) &&
+ !(t = get_date_tag(*m, "TIM")))
+ goto finish;
+ snprintf(date + 10, sizeof(date) - 10,
+ " %.2s:%.2s", t->value, t->value + 2);
+ av_dict_set(m, "TIME", NULL, 0);
+ av_dict_set(m, "TIM", NULL, 0);
+
+finish:
+ if (date[0])
+ av_dict_set(m, "date", date, 0);
+}
+
+static void free_apic(void *obj)
+{
+ ID3v2ExtraMetaAPIC *apic = obj;
+ av_buffer_unref(&apic->buf);
+ av_freep(&apic->description);
+ av_freep(&apic);
+}
+
+static void read_apic(AVFormatContext *s, AVIOContext *pb, int taglen,
+ char *tag, ID3v2ExtraMeta **extra_meta)
+{
+ int enc, pic_type;
+ char mimetype[64];
+ const CodecMime *mime = ff_id3v2_mime_tags;
+ enum AVCodecID id = AV_CODEC_ID_NONE;
+ ID3v2ExtraMetaAPIC *apic = NULL;
+ ID3v2ExtraMeta *new_extra = NULL;
+ int64_t end = avio_tell(pb) + taglen;
+
+ 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 */
+ taglen -= avio_get_str(pb, taglen, mimetype, sizeof(mimetype));
+ while (mime->id != AV_CODEC_ID_NONE) {
+ if (!av_strncasecmp(mime->str, mimetype, sizeof(mimetype))) {
+ id = mime->id;
+ break;
+ }
+ mime++;
+ }
+ if (id == AV_CODEC_ID_NONE) {
+ av_log(s, AV_LOG_WARNING,
+ "Unknown attached picture mimetype: %s, skipping.\n", mimetype);
+ 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)) {
+ av_log(s, AV_LOG_WARNING, "Unknown attached picture type %d.\n",
+ pic_type);
+ 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) {
+ av_log(s, AV_LOG_ERROR,
+ "Error decoding attached picture description.\n");
+ goto fail;
+ }
+
+ apic->buf = av_buffer_alloc(taglen + FF_INPUT_BUFFER_PADDING_SIZE);
+ if (!apic->buf || avio_read(pb, apic->buf->data, taglen) != taglen)
+ goto fail;
+ memset(apic->buf->data + taglen, 0, FF_INPUT_BUFFER_PADDING_SIZE);
+
+ new_extra->tag = "APIC";
+ new_extra->data = apic;
+ new_extra->next = *extra_meta;
+ *extra_meta = new_extra;
+
+ return;
+
+fail:
+ if (apic)
+ free_apic(apic);
+ av_freep(&new_extra);
+ avio_seek(pb, end, SEEK_SET);
+}
+
+typedef struct ID3v2EMFunc {
+ const char *tag3;
+ const char *tag4;
+ void (*read)(AVFormatContext *, AVIOContext *, int, char *,
+ ID3v2ExtraMeta **);
+ void (*free)(void *obj);
+} ID3v2EMFunc;
+
+static const ID3v2EMFunc id3v2_extra_meta_funcs[] = {
+ { "GEO", "GEOB", read_geobtag, free_geobtag },
+ { "PIC", "APIC", read_apic, free_apic },
+ { NULL }
+};
+
+/**
+ * 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;
+ while (id3v2_extra_meta_funcs[i].tag3) {
+ if (!memcmp(tag,
+ (isv34 ? id3v2_extra_meta_funcs[i].tag4 :
+ id3v2_extra_meta_funcs[i].tag3),
+ (isv34 ? 4 : 3)))
+ return &id3v2_extra_meta_funcs[i];
+ i++;
+ }
+ return NULL;
+}
+
+static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
+ uint8_t flags, ID3v2ExtraMeta **extra_meta)