+ 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;
+ }
+ dict_flags |= AV_DICT_DONT_STRDUP_KEY;
+ } else if (!*dst)
+ av_freep(&dst);
+
+ if (dst)
+ av_dict_set(&s->metadata, key, dst, dict_flags);
+}
+
+/**
+ * Parse GEOB tag into a ID3v2ExtraMetaGEOB struct.
+ */
+static void read_geobtag(AVFormatContext *s, AVIOContext *pb, int taglen,
+ char *tag, ID3v2ExtraMeta **extra_meta)
+{
+ ID3v2ExtraMetaGEOB *geob_data = NULL;
+ ID3v2ExtraMeta *new_extra = NULL;
+ char encoding;
+ unsigned int len;
+
+ if (taglen < 1)
+ return;
+
+ geob_data = av_mallocz(sizeof(ID3v2ExtraMetaGEOB));
+ if (!geob_data) {
+ av_log(s, AV_LOG_ERROR, "Failed to alloc %zu bytes\n",
+ sizeof(ID3v2ExtraMetaGEOB));
+ return;
+ }
+
+ new_extra = av_mallocz(sizeof(ID3v2ExtraMeta));
+ if (!new_extra) {
+ av_log(s, AV_LOG_ERROR, "Failed to alloc %zu bytes\n",
+ sizeof(ID3v2ExtraMeta));
+ goto fail;
+ }
+
+ /* read encoding type byte */
+ encoding = avio_r8(pb);
+ taglen--;
+
+ /* read MIME type (always ISO-8859) */
+ if (decode_str(s, pb, ID3v2_ENCODING_ISO8859, &geob_data->mime_type,
+ &taglen) < 0 ||
+ taglen <= 0)
+ goto fail;
+
+ /* read file name */
+ if (decode_str(s, pb, encoding, &geob_data->file_name, &taglen) < 0 ||
+ taglen <= 0)
+ goto fail;
+
+ /* read content description */
+ if (decode_str(s, pb, encoding, &geob_data->description, &taglen) < 0 ||
+ taglen < 0)
+ 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)
+ av_log(s, AV_LOG_WARNING,
+ "Error reading GEOB frame, data truncated.\n");
+ geob_data->datasize = len;
+ } else {
+ geob_data->data = NULL;
+ geob_data->datasize = 0;
+ }
+
+ /* add data to the list */
+ new_extra->tag = "GEOB";
+ new_extra->data = geob_data;
+ new_extra->next = *extra_meta;
+ *extra_meta = new_extra;
+
+ return;
+
+fail:
+ av_log(s, AV_LOG_ERROR, "Error reading frame %s, skipped\n", tag);
+ free_geobtag(geob_data);
+ av_free(new_extra);
+ return;
+}
+
+static int is_number(const char *str)
+{
+ while (*str >= '0' && *str <= '9')
+ str++;
+ return !*str;
+}
+
+static AVDictionaryEntry *get_date_tag(AVDictionary *m, const char *tag)
+{
+ AVDictionaryEntry *t;
+ if ((t = av_dict_get(m, tag, NULL, AV_DICT_MATCH_CASE)) &&
+ strlen(t->value) == 4 && is_number(t->value))
+ return t;
+ return NULL;
+}
+
+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;