From a93b09cb45b86427d6e81fa51c660877d8d5fd17 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Sat, 25 Feb 2012 09:45:38 +0100 Subject: id3v2: read attached pictures and export them in ID3v2ExtraMeta. --- libavformat/id3v2.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'libavformat/id3v2.h') diff --git a/libavformat/id3v2.h b/libavformat/id3v2.h index a296e0315b..f358d02892 100644 --- a/libavformat/id3v2.h +++ b/libavformat/id3v2.h @@ -24,6 +24,7 @@ #include #include "avformat.h" +#include "internal.h" #include "metadata.h" #define ID3v2_HEADER_SIZE 10 @@ -59,6 +60,14 @@ typedef struct ID3v2ExtraMetaGEOB { uint8_t *data; } ID3v2ExtraMetaGEOB; +typedef struct ID3v2ExtraMetaAPIC { + uint8_t *data; + int len; + const char *type; + uint8_t *description; + enum CodecID id; +} ID3v2ExtraMetaAPIC; + /** * Detect ID3v2 Header. * @param buf must be ID3v2_HEADER_SIZE byte long @@ -120,4 +129,8 @@ extern const char ff_id3v2_4_tags[][4]; */ extern const char ff_id3v2_3_tags[][4]; +extern const CodecMime ff_id3v2_mime_tags[]; + +extern const char *ff_id3v2_picture_types[21]; + #endif /* AVFORMAT_ID3V2_H */ -- cgit v1.2.3 From 079ea6ca5f8f108ec328d3c2c1792e676fc30b9c Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Sat, 25 Feb 2012 09:53:35 +0100 Subject: lavf: export id3v2 attached pictures as streams. --- libavformat/id3v2.c | 34 ++++++++++++++++++++++++++++++++++ libavformat/id3v2.h | 6 ++++++ libavformat/utils.c | 9 ++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) (limited to 'libavformat/id3v2.h') diff --git a/libavformat/id3v2.c b/libavformat/id3v2.c index 853a04c78e..4170c853a6 100644 --- a/libavformat/id3v2.c +++ b/libavformat/id3v2.c @@ -702,3 +702,37 @@ void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta) current = next; } } + +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; + + apic->data = NULL; + apic->len = 0; + } + + return 0; +} diff --git a/libavformat/id3v2.h b/libavformat/id3v2.h index f358d02892..c3f08f5875 100644 --- a/libavformat/id3v2.h +++ b/libavformat/id3v2.h @@ -109,6 +109,12 @@ int ff_id3v2_write(struct AVFormatContext *s, int id3v2_version, const char *mag */ void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta); +/** + * Create a stream for each APIC (attached picture) extracted from the + * ID3v2 header. + */ +int ff_id3v2_parse_apic(AVFormatContext *s, ID3v2ExtraMeta **extra_meta); + extern const AVMetadataConv ff_id3v2_34_metadata_conv[]; extern const AVMetadataConv ff_id3v2_4_metadata_conv[]; diff --git a/libavformat/utils.c b/libavformat/utils.c index c8dd7f5868..e657362a38 100644 --- a/libavformat/utils.c +++ b/libavformat/utils.c @@ -520,6 +520,7 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputForma AVFormatContext *s = *ps; int i, ret = 0; AVDictionary *tmp = NULL; + ID3v2ExtraMeta *id3v2_extra_meta = NULL; if (!s && !(s = avformat_alloc_context())) return AVERROR(ENOMEM); @@ -562,12 +563,17 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputForma /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */ if (s->pb) - ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC); + ff_id3v2_read_all(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); if (s->iformat->read_header) if ((ret = s->iformat->read_header(s)) < 0) goto fail; + if (id3v2_extra_meta && + (ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) + goto fail; + ff_id3v2_free_extra_meta(&id3v2_extra_meta); + /* queue attached pictures */ for (i = 0; i < s->nb_streams; i++) if (s->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) { @@ -589,6 +595,7 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputForma return 0; fail: + ff_id3v2_free_extra_meta(&id3v2_extra_meta); av_dict_free(&tmp); if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO)) avio_close(s->pb); -- cgit v1.2.3 From 393fd0d89e6d0a1cf5bd39f309f6f10e0f1f8ac7 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Sat, 25 Feb 2012 09:53:59 +0100 Subject: id3v2: remove unused ff_id3v2_read(). Rename ff_id3v2_read_all to ff_id3v2_read(). --- libavformat/id3v2.c | 7 +------ libavformat/id3v2.h | 9 ++------- libavformat/omadec.c | 2 +- libavformat/utils.c | 2 +- 4 files changed, 5 insertions(+), 15 deletions(-) (limited to 'libavformat/id3v2.h') diff --git a/libavformat/id3v2.c b/libavformat/id3v2.c index 4170c853a6..1f2c3d5437 100644 --- a/libavformat/id3v2.c +++ b/libavformat/id3v2.c @@ -653,7 +653,7 @@ seek: 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]; @@ -684,11 +684,6 @@ void ff_id3v2_read_all(AVFormatContext *s, const char *magic, ID3v2ExtraMeta **e 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; diff --git a/libavformat/id3v2.h b/libavformat/id3v2.h index c3f08f5875..5f3ec1b3ca 100644 --- a/libavformat/id3v2.h +++ b/libavformat/id3v2.h @@ -84,16 +84,11 @@ int ff_id3v2_match(const uint8_t *buf, const char *magic); int ff_id3v2_tag_len(const uint8_t *buf); /** - * Read an ID3v2 tag (text tags only) - */ -void ff_id3v2_read(AVFormatContext *s, const char *magic); - -/** - * Read an ID3v2 tag, including supported extra metadata (currently only GEOB) + * Read an ID3v2 tag, including supported extra metadata * @param extra_meta If not NULL, extra metadata is parsed into a list of * ID3v2ExtraMeta structs and *extra_meta points to the head of the list */ -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); /** * Write an ID3v2 tag. diff --git a/libavformat/omadec.c b/libavformat/omadec.c index 7c54ffbc97..810e970c11 100644 --- a/libavformat/omadec.c +++ b/libavformat/omadec.c @@ -267,7 +267,7 @@ static int oma_read_header(AVFormatContext *s) ID3v2ExtraMeta *extra_meta = NULL; OMAContext *oc = s->priv_data; - ff_id3v2_read_all(s, ID3v2_EA3_MAGIC, &extra_meta); + ff_id3v2_read(s, ID3v2_EA3_MAGIC, &extra_meta); ret = avio_read(s->pb, buf, EA3_HEADER_SIZE); if (ret < EA3_HEADER_SIZE) return -1; diff --git a/libavformat/utils.c b/libavformat/utils.c index e657362a38..cf4392b581 100644 --- a/libavformat/utils.c +++ b/libavformat/utils.c @@ -563,7 +563,7 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputForma /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */ if (s->pb) - ff_id3v2_read_all(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); + ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); if (s->iformat->read_header) if ((ret = s->iformat->read_header(s)) < 0) -- cgit v1.2.3 From 411225aabce57411d1544a7bbc6f6bee6d8ef638 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Mon, 27 Feb 2012 22:08:50 +0100 Subject: id3v2enc: split ff_id3v2_write(). This will allow writing the tag in several steps, needed for writing attached pictures. --- libavformat/id3v2.h | 26 ++++++++++++++++++-- libavformat/id3v2enc.c | 64 +++++++++++++++++++++++++++++++++----------------- libavformat/mp3enc.c | 2 +- libavformat/omaenc.c | 2 +- 4 files changed, 68 insertions(+), 26 deletions(-) (limited to 'libavformat/id3v2.h') diff --git a/libavformat/id3v2.h b/libavformat/id3v2.h index 5f3ec1b3ca..f47abe9d25 100644 --- a/libavformat/id3v2.h +++ b/libavformat/id3v2.h @@ -46,6 +46,12 @@ enum ID3v2Encoding { ID3v2_ENCODING_UTF8 = 3, }; +typedef struct ID3v2EncContext { + int version; ///< ID3v2 minor version, either 3 or 4 + int64_t size_pos; ///< offset of the tag total size + int len; ///< size of the tag written so far +} ID3v2EncContext; + typedef struct ID3v2ExtraMeta { const char *tag; void *data; @@ -91,12 +97,28 @@ int ff_id3v2_tag_len(const uint8_t *buf); void ff_id3v2_read(AVFormatContext *s, const char *magic, ID3v2ExtraMeta **extra_meta); /** - * Write an ID3v2 tag. + * Initialize an ID3v2 tag. + */ +void ff_id3v2_start(ID3v2EncContext *id3, AVIOContext *pb, int id3v2_version, + const char *magic); + +/** + * Convert and write all global metadata from s into an ID3v2 tag. + */ +int ff_id3v2_write_metadata(AVFormatContext *s, ID3v2EncContext *id3); + +/** + * Finalize an opened ID3v2 tag. + */ +void ff_id3v2_finish(ID3v2EncContext *id3, AVIOContext *pb); + +/** + * Write an ID3v2 tag containing all global metadata from s. * @param id3v2_version Subversion of ID3v2; supported values are 3 and 4 * @param magic magic bytes to identify the header * If in doubt, use ID3v2_DEFAULT_MAGIC. */ -int ff_id3v2_write(struct AVFormatContext *s, int id3v2_version, const char *magic); +int ff_id3v2_write_simple(struct AVFormatContext *s, int id3v2_version, const char *magic); /** * Free memory allocated parsing special (non-text) metadata. diff --git a/libavformat/id3v2enc.c b/libavformat/id3v2enc.c index 3a4d229232..8666818128 100644 --- a/libavformat/id3v2enc.c +++ b/libavformat/id3v2enc.c @@ -97,50 +97,70 @@ static int id3v2_check_write_tag(AVFormatContext *s, AVDictionaryEntry *t, const return -1; } -int ff_id3v2_write(struct AVFormatContext *s, int id3v2_version, - const char *magic) +void ff_id3v2_start(ID3v2EncContext *id3, AVIOContext *pb, int id3v2_version, + const char *magic) { - int64_t size_pos, cur_pos; - AVDictionaryEntry *t = NULL; - - int totlen = 0, enc = id3v2_version == 3 ? ID3v2_ENCODING_UTF16BOM : - ID3v2_ENCODING_UTF8; + id3->version = id3v2_version; - - avio_wb32(s->pb, MKBETAG(magic[0], magic[1], magic[2], id3v2_version)); - avio_w8(s->pb, 0); - avio_w8(s->pb, 0); /* flags */ + avio_wb32(pb, MKBETAG(magic[0], magic[1], magic[2], id3v2_version)); + avio_w8(pb, 0); + avio_w8(pb, 0); /* flags */ /* reserve space for size */ - size_pos = avio_tell(s->pb); - avio_wb32(s->pb, 0); + id3->size_pos = avio_tell(pb); + avio_wb32(pb, 0); +} + +int ff_id3v2_write_metadata(AVFormatContext *s, ID3v2EncContext *id3) +{ + AVDictionaryEntry *t = NULL; + int enc = id3->version == 3 ? ID3v2_ENCODING_UTF16BOM : + ID3v2_ENCODING_UTF8; ff_metadata_conv(&s->metadata, ff_id3v2_34_metadata_conv, NULL); - if (id3v2_version == 4) + if (id3->version == 4) ff_metadata_conv(&s->metadata, ff_id3v2_4_metadata_conv, NULL); while ((t = av_dict_get(s->metadata, "", t, AV_DICT_IGNORE_SUFFIX))) { int ret; if ((ret = id3v2_check_write_tag(s, t, ff_id3v2_tags, enc)) > 0) { - totlen += ret; + id3->len += ret; continue; } - if ((ret = id3v2_check_write_tag(s, t, id3v2_version == 3 ? + if ((ret = id3v2_check_write_tag(s, t, id3->version == 3 ? ff_id3v2_3_tags : ff_id3v2_4_tags, enc)) > 0) { - totlen += ret; + id3->len += ret; continue; } /* unknown tag, write as TXXX frame */ if ((ret = id3v2_put_ttag(s, t->key, t->value, MKBETAG('T', 'X', 'X', 'X'), enc)) < 0) return ret; - totlen += ret; + id3->len += ret; } - cur_pos = avio_tell(s->pb); - avio_seek(s->pb, size_pos, SEEK_SET); - id3v2_put_size(s->pb, totlen); - avio_seek(s->pb, cur_pos, SEEK_SET); + return 0; +} + +void ff_id3v2_finish(ID3v2EncContext *id3, AVIOContext *pb) +{ + int64_t cur_pos = avio_tell(pb); + avio_seek(pb, id3->size_pos, SEEK_SET); + id3v2_put_size(pb, id3->len); + avio_seek(pb, cur_pos, SEEK_SET); +} + +int ff_id3v2_write_simple(struct AVFormatContext *s, int id3v2_version, + const char *magic) +{ + ID3v2EncContext id3 = { 0 }; + int ret; + + ff_id3v2_start(&id3, s->pb, id3v2_version, magic); + if ((ret = ff_id3v2_write_metadata(s, &id3)) < 0) + return ret; + ff_id3v2_finish(&id3, s->pb); + return 0; } diff --git a/libavformat/mp3enc.c b/libavformat/mp3enc.c index ce547ead38..ab20c299fe 100644 --- a/libavformat/mp3enc.c +++ b/libavformat/mp3enc.c @@ -187,7 +187,7 @@ static int mp3_write_header(struct AVFormatContext *s) MP3Context *mp3 = s->priv_data; int ret; - ret = ff_id3v2_write(s, mp3->id3v2_version, ID3v2_DEFAULT_MAGIC); + ret = ff_id3v2_write_simple(s, mp3->id3v2_version, ID3v2_DEFAULT_MAGIC); if (ret < 0) return ret; diff --git a/libavformat/omaenc.c b/libavformat/omaenc.c index e932b4bbc1..c3ee0e8de1 100644 --- a/libavformat/omaenc.c +++ b/libavformat/omaenc.c @@ -49,7 +49,7 @@ static av_cold int oma_write_header(AVFormatContext *s) } /* Metadata; OpenMG does not support ID3v2.4 */ - ff_id3v2_write(s, 3, ID3v2_EA3_MAGIC); + ff_id3v2_write_simple(s, 3, ID3v2_EA3_MAGIC); ffio_wfourcc(s->pb, "EA3\0"); avio_w8(s->pb, EA3_HEADER_SIZE >> 7); -- cgit v1.2.3 From ba445f5557bf16aeb807bb6c83d7de264f98f375 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Mon, 27 Feb 2012 22:51:28 +0100 Subject: id3v2enc: add a function for writing attached pictures. Unused so far. --- libavformat/id3v2.h | 5 +++ libavformat/id3v2enc.c | 91 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 87 insertions(+), 9 deletions(-) (limited to 'libavformat/id3v2.h') diff --git a/libavformat/id3v2.h b/libavformat/id3v2.h index f47abe9d25..88dcbdb812 100644 --- a/libavformat/id3v2.h +++ b/libavformat/id3v2.h @@ -107,6 +107,11 @@ void ff_id3v2_start(ID3v2EncContext *id3, AVIOContext *pb, int id3v2_version, */ int ff_id3v2_write_metadata(AVFormatContext *s, ID3v2EncContext *id3); +/** + * Write an attached picture from pkt into an ID3v2 tag. + */ +int ff_id3v2_write_apic(AVFormatContext *s, ID3v2EncContext *id3, AVPacket *pkt); + /** * Finalize an opened ID3v2 tag. */ diff --git a/libavformat/id3v2enc.c b/libavformat/id3v2enc.c index 58f77970ef..624333609b 100644 --- a/libavformat/id3v2enc.c +++ b/libavformat/id3v2enc.c @@ -19,6 +19,7 @@ */ #include +#include #include "libavutil/dict.h" #include "libavutil/intreadwrite.h" @@ -40,6 +41,20 @@ static int string_is_ascii(const uint8_t *str) return !*str; } +static void id3v2_encode_string(AVIOContext *pb, const uint8_t *str, + enum ID3v2Encoding enc) +{ + int (*put)(AVIOContext*, const char*); + + if (enc == ID3v2_ENCODING_UTF16BOM) { + avio_wl16(pb, 0xFEFF); /* BOM */ + put = avio_put_str16le; + } else + put = avio_put_str; + + put(pb, str); +} + /** * Write a text frame with one (normal frames) or two (TXXX frames) strings * according to encoding (only UTF-8 or UTF-16+BOM supported). @@ -50,7 +65,6 @@ static int id3v2_put_ttag(ID3v2EncContext *id3, AVIOContext *avioc, const char * { int len; uint8_t *pb; - int (*put)(AVIOContext*, const char*); AVIOContext *dyn_buf; if (avio_open_dyn_buf(&dyn_buf) < 0) return AVERROR(ENOMEM); @@ -62,15 +76,9 @@ static int id3v2_put_ttag(ID3v2EncContext *id3, AVIOContext *avioc, const char * enc = ID3v2_ENCODING_ISO8859; avio_w8(dyn_buf, enc); - if (enc == ID3v2_ENCODING_UTF16BOM) { - avio_wl16(dyn_buf, 0xFEFF); /* BOM */ - put = avio_put_str16le; - } else - put = avio_put_str; - - put(dyn_buf, str1); + id3v2_encode_string(dyn_buf, str1, enc); if (str2) - put(dyn_buf, str2); + id3v2_encode_string(dyn_buf, str2, enc); len = avio_close_dyn_buf(dyn_buf, &pb); avio_wb32(avioc, tag); @@ -147,6 +155,71 @@ int ff_id3v2_write_metadata(AVFormatContext *s, ID3v2EncContext *id3) return 0; } +int ff_id3v2_write_apic(AVFormatContext *s, ID3v2EncContext *id3, AVPacket *pkt) +{ + AVStream *st = s->streams[pkt->stream_index]; + AVDictionaryEntry *e; + + AVIOContext *dyn_buf; + uint8_t *buf; + const CodecMime *mime = ff_id3v2_mime_tags; + const char *mimetype = NULL, *desc = ""; + int enc = id3->version == 3 ? ID3v2_ENCODING_UTF16BOM : + ID3v2_ENCODING_UTF8; + int i, len, type = 0; + + /* get the mimetype*/ + while (mime->id != CODEC_ID_NONE) { + if (mime->id == st->codec->codec_id) { + mimetype = mime->str; + break; + } + mime++; + } + if (!mimetype) { + av_log(s, AV_LOG_ERROR, "No mimetype is known for stream %d, cannot " + "write an attached picture.\n", st->index); + return AVERROR(EINVAL); + } + + /* get the picture type */ + e = av_dict_get(st->metadata, "comment", NULL, 0); + for (i = 0; e && i < FF_ARRAY_ELEMS(ff_id3v2_picture_types); i++) { + if (strstr(ff_id3v2_picture_types[i], e->value) == ff_id3v2_picture_types[i]) { + type = i; + break; + } + } + + /* get the description */ + if ((e = av_dict_get(st->metadata, "title", NULL, 0))) + desc = e->value; + + /* start writing */ + if (avio_open_dyn_buf(&dyn_buf) < 0) + return AVERROR(ENOMEM); + + avio_w8(dyn_buf, enc); + avio_put_str(dyn_buf, mimetype); + avio_w8(dyn_buf, type); + id3v2_encode_string(dyn_buf, desc, enc); + avio_write(dyn_buf, pkt->data, pkt->size); + len = avio_close_dyn_buf(dyn_buf, &buf); + + avio_wb32(s->pb, MKBETAG('A', 'P', 'I', 'C')); + if (id3->version == 3) + avio_wb32(s->pb, len); + else + id3v2_put_size(s->pb, len); + avio_wb16(s->pb, 0); + avio_write(s->pb, buf, len); + av_freep(&buf); + + id3->len += len + ID3v2_HEADER_SIZE; + + return 0; +} + void ff_id3v2_finish(ID3v2EncContext *id3, AVIOContext *pb) { int64_t cur_pos = avio_tell(pb); -- cgit v1.2.3