#include "libavutil/intreadwrite.h"
#include "libavutil/dict.h"
#include "avio_internal.h"
+#include "internal.h"
const AVMetadataConv ff_id3v2_34_metadata_conv[] = {
{ "TALB", "album"},
{ 0 },
};
+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[] = {
+ {"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},
+};
+
int ff_id3v2_match(const uint8_t *buf, const char * magic)
{
return buf[0] == magic[0] &&
static void read_ttag(AVFormatContext *s, AVIOContext *pb, int taglen, const char *key)
{
uint8_t *dst;
- int encoding, dict_flags = AV_DICT_DONT_OVERWRITE;
+ int encoding, dict_flags = AV_DICT_DONT_OVERWRITE | AV_DICT_DONT_STRDUP_VAL;
unsigned genre;
if (taglen < 1)
&& (sscanf(dst, "(%d)", &genre) == 1 || sscanf(dst, "%d", &genre) == 1)
&& genre <= ID3v1_GENRE_MAX) {
av_freep(&dst);
- dst = ff_id3v1_genre_str[genre];
+ dst = av_strdup(ff_id3v1_genre_str[genre]);
} else if (!(strcmp(key, "TXXX") && strcmp(key, "TXX"))) {
/* dst now contains the key, need to get value */
key = dst;
av_freep(&key);
return;
}
- dict_flags |= AV_DICT_DONT_STRDUP_VAL | AV_DICT_DONT_STRDUP_KEY;
- }
- else if (*dst)
- dict_flags |= AV_DICT_DONT_STRDUP_VAL;
+ dict_flags |= AV_DICT_DONT_STRDUP_KEY;
+ } else if (!*dst)
+ av_freep(&dst);
if (dst)
av_dict_set(&s->metadata, key, dst, dict_flags);
av_dict_set(m, "date", date, 0);
}
+static void free_apic(void *obj)
+{
+ ID3v2ExtraMetaAPIC *apic = obj;
+ av_freep(&apic->data);
+ 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->len = taglen;
+ apic->data = av_malloc(taglen);
+ if (!apic->data || !apic->len || avio_read(pb, apic->data, taglen) != taglen)
+ goto fail;
+
+ 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);
+}
+
+static void read_chapter(AVFormatContext *s, AVIOContext *pb, int taglen, char *tag, ID3v2ExtraMeta **extra_meta)
+{
+ AVRational time_base = {1, 1000};
+ char title[1024];
+ uint32_t start, end;
+
+ taglen -= avio_get_str(pb, taglen, title, sizeof(title));
+ if (taglen < 16)
+ return;
+
+ start = avio_rb32(pb);
+ end = avio_rb32(pb);
+ taglen -= 27;
+ if (taglen > 0) {
+ char tag[4];
+
+ avio_skip(pb, 8);
+ avio_read(pb, tag, 4);
+ if (!memcmp(tag, "TIT2", 4)) {
+ taglen = FFMIN(taglen, avio_rb32(pb));
+ if (taglen < 0)
+ return;
+ avio_skip(pb, 3);
+ avio_get_str(pb, taglen, title, sizeof(title));
+ }
+ }
+
+ avpriv_new_chapter(s, s->nb_chapters + 1, time_base, start, end, title);
+}
+
typedef struct ID3v2EMFunc {
const char *tag3;
const char *tag4;
static const ID3v2EMFunc id3v2_extra_meta_funcs[] = {
{ "GEO", "GEOB", read_geobtag, free_geobtag },
+ { "PIC", "APIC", read_apic, free_apic },
+ { "CHAP","CHAP", read_chapter, NULL },
{ NULL }
};
unsigned char *buffer = NULL;
int buffer_size = 0;
const ID3v2EMFunc *extra_func = NULL;
- unsigned char *compressed_buffer = NULL;
- int compressed_buffer_size = 0;
+ unsigned char *uncompressed_buffer = NULL;
+ int uncompressed_buffer_size = 0;
+
+ av_log(s, AV_LOG_DEBUG, "id3v2 ver:%d flags:%02X len:%d\n", version, flags, len);
switch (version) {
case 2:
unsync = flags & 0x80;
- /* Extended header present, just skip over it */
- if (isv34 && flags & 0x40) {
- int size = get_size(s->pb, 4);
- if (size < 6) {
- reason = "extended header too short.";
+ if (isv34 && flags & 0x40) { /* Extended header present, just skip over it */
+ int extlen = get_size(s->pb, 4);
+ if (version == 4)
+ extlen -= 4; // in v2.4 the length includes the length field we just read
+
+ if (extlen < 0) {
+ reason = "invalid extended header length";
goto error;
}
- len -= size;
+ avio_skip(s->pb, extlen);
+ len -= extlen + 4;
if (len < 0) {
reason = "extended header too long.";
goto error;
}
- /* already seeked past size, skip the reset */
- size -= 4;
- avio_skip(s->pb, size);
}
while (len >= taghdrlen) {
avio_skip(s->pb, tlen);
/* check for text tag or supported special meta tag */
} else if (tag[0] == 'T' || (extra_meta && (extra_func = get_extra_meta_func(tag, isv34)))) {
- if (unsync || tunsync || tcomp) {
- int i, j;
+ pbx = s->pb;
- av_fast_malloc(&buffer, &buffer_size, dlen);
+ if (unsync || tunsync || tcomp) {
+ av_fast_malloc(&buffer, &buffer_size, tlen);
if (!buffer) {
- av_log(s, AV_LOG_ERROR, "Failed to alloc %ld bytes\n", dlen);
+ av_log(s, AV_LOG_ERROR, "Failed to alloc %d bytes\n", tlen);
goto seek;
}
+ }
+ if (unsync || tunsync) {
+ int64_t end = avio_tell(s->pb) + tlen;
+ uint8_t *b;
+
+ b = buffer;
+ while (avio_tell(s->pb) < end && b - buffer < tlen) {
+ *b++ = avio_r8(s->pb);
+ if (*(b - 1) == 0xff && avio_tell(s->pb) < end - 1 && b - buffer < tlen) {
+ uint8_t val = avio_r8(s->pb);
+ *b++ = val ? val : avio_r8(s->pb);
+ }
+ }
+ ffio_init_context(&pb, buffer, b - buffer, 0, NULL, NULL, NULL, NULL);
+ tlen = b - buffer;
+ pbx = &pb; // read from sync buffer
+ }
+
#if CONFIG_ZLIB
if (tcomp) {
- int n, err;
+ int err;
av_log(s, AV_LOG_DEBUG, "Compresssed frame %s tlen=%d dlen=%ld\n", tag, tlen, dlen);
- av_fast_malloc(&compressed_buffer, &compressed_buffer_size, tlen);
- if (!compressed_buffer) {
- av_log(s, AV_LOG_ERROR, "Failed to alloc %d bytes\n", tlen);
+ 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);
goto seek;
}
- n = avio_read(s->pb, compressed_buffer, tlen);
- if (n < 0) {
- av_log(s, AV_LOG_ERROR, "Failed to read compressed tag\n");
- goto seek;
+ if (!(unsync || tunsync)) {
+ err = avio_read(s->pb, buffer, tlen);
+ if (err < 0) {
+ av_log(s, AV_LOG_ERROR, "Failed to read compressed tag\n");
+ goto seek;
+ }
+ tlen = err;
}
- err = uncompress(buffer, &dlen, compressed_buffer, n);
+ err = uncompress(uncompressed_buffer, &dlen, buffer, tlen);
if (err != Z_OK) {
av_log(s, AV_LOG_ERROR, "Failed to uncompress tag: %d\n", err);
goto seek;
}
+ ffio_init_context(&pb, uncompressed_buffer, dlen, 0, NULL, NULL, NULL, NULL);
+ tlen = dlen;
+ pbx = &pb; // read from sync buffer
}
#endif
-
- for (i = 0, j = 0; i < dlen; i++, j++) {
- if (!tcomp)
- buffer[j] = avio_r8(s->pb);
- if (j > 0 && !buffer[j] && buffer[j - 1] == 0xff) {
- /* Unsynchronised byte, skip it */
- j--;
- }
- }
- ffio_init_context(&pb, buffer, j, 0, NULL, NULL, NULL, NULL);
- tlen = j;
- pbx = &pb; // read from sync buffer
- } else {
- pbx = s->pb; // read straight from input
- }
if (tag[0] == 'T')
/* parse text tag */
read_ttag(s, pbx, tlen, tag);
}
else if (!tag[0]) {
if (tag[1])
- av_log(s, AV_LOG_WARNING, "invalid frame id, assuming padding");
+ av_log(s, AV_LOG_WARNING, "invalid frame id, assuming padding\n");
avio_skip(s->pb, tlen);
break;
}
av_log(s, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n", version, reason);
avio_seek(s->pb, end, SEEK_SET);
av_free(buffer);
- av_free(compressed_buffer);
+ av_free(uncompressed_buffer);
return;
}
-void ff_id3v2_read_all(AVFormatContext *s, const char *magic, ID3v2ExtraMeta **extra_meta)
+void ff_id3v2_read(AVFormatContext *s, const char *magic, ID3v2ExtraMeta **extra_meta)
{
int len, ret;
uint8_t buf[ID3v2_HEADER_SIZE];
/* save the current offset in case there's nothing to read/skip */
off = avio_tell(s->pb);
ret = avio_read(s->pb, buf, ID3v2_HEADER_SIZE);
- if (ret != ID3v2_HEADER_SIZE)
+ if (ret != ID3v2_HEADER_SIZE) {
+ avio_seek(s->pb, off, SEEK_SET);
break;
- found_header = ff_id3v2_match(buf, magic);
- if (found_header) {
+ }
+ found_header = ff_id3v2_match(buf, magic);
+ if (found_header) {
/* parse ID3v2 header */
len = ((buf[6] & 0x7f) << 21) |
((buf[7] & 0x7f) << 14) |
merge_date(&s->metadata);
}
-void ff_id3v2_read(AVFormatContext *s, const char *magic)
-{
- ff_id3v2_read_all(s, magic, NULL);
-}
-
void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta)
{
ID3v2ExtraMeta *current = *extra_meta, *next;
av_freep(¤t);
current = next;
}
+
+ *extra_meta = NULL;
+}
+
+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);
+ st->attached_pic.data = apic->data;
+ st->attached_pic.size = apic->len;
+ st->attached_pic.destruct = av_destruct_packet;
+ st->attached_pic.stream_index = st->index;
+ st->attached_pic.flags |= AV_PKT_FLAG_KEY;
+
+ apic->data = NULL;
+ apic->len = 0;
+ }
+
+ return 0;
}