summaryrefslogtreecommitdiff
path: root/libavformat/id3v2.c
diff options
context:
space:
mode:
authorDavid Goldwich <david.goldwich@gmail.com>2011-09-18 18:04:34 +0200
committerAnton Khirnov <anton@khirnov.net>2011-09-21 08:28:21 +0200
commit7a019dffe568e4d58cf1b8b2216f8678e45066b9 (patch)
tree0d4fc7fd11fa7db315c36fff7a79d617ab87c979 /libavformat/id3v2.c
parentb3e144a7dfc6953df3b3e478eb8b8521ab5c6ecc (diff)
id3v2: add support for non-text and GEOB type tag frames
This extends the ID3v2 parser to allow for reading of non-text (i.e. other than T***) meta tag frames providing a ff_id3v2_read_all() function. An additional data structure 'ID3v2ExtraMeta' is introduced for these tags since AVDictionary is string oriented and unsuitable for binary data. A parser for tag frames of type GEOB is implemented, which is needed to extract keyring information from encrypted OMA files. GEOB data is parsed into 'ID3v2ExtraMetaGEOB' data structures. The routine to decode characters from different encodings to UTF-8, formerly part of the read_ttag() function, is moved to its own function. Because some tag frames contain subparts of unknown length, the function is now also able to read until a null character is found. In addition, the function now takes care of allocating a buffer long enough to hold the decoded characters. Signed-off-by: David Goldwich <david.goldwich@gmail.com> Signed-off-by: Anton Khirnov <anton@khirnov.net>
Diffstat (limited to 'libavformat/id3v2.c')
-rw-r--r--libavformat/id3v2.c270
1 files changed, 233 insertions, 37 deletions
diff --git a/libavformat/id3v2.c b/libavformat/id3v2.c
index be6c03bbe5..68c1709b93 100644
--- a/libavformat/id3v2.c
+++ b/libavformat/id3v2.c
@@ -59,63 +59,129 @@ static unsigned int get_size(AVIOContext *s, int len)
return v;
}
-static void read_ttag(AVFormatContext *s, AVIOContext *pb, int taglen, const char *key)
+/**
+ * Free GEOB type extra metadata.
+ */
+static void free_geobtag(ID3v2ExtraMetaGEOB *geob)
{
- char *q, dst[512];
- const char *val = NULL;
- int len, dstlen = sizeof(dst) - 1;
- unsigned genre;
- unsigned int (*get)(AVIOContext*) = avio_rb16;
+ av_free(geob->mime_type);
+ av_free(geob->file_name);
+ av_free(geob->description);
+ av_free(geob->data);
+ av_free(geob);
+}
- dst[0] = 0;
- if (taglen < 1)
- return;
+/**
+ * Decode characters to UTF-8 according to encoding type. The decoded buffer is
+ * always null terminated.
+ *
+ * @param dst Pointer where the address of the buffer with the decoded bytes is
+ * stored. Buffer must be freed by caller.
+ * @param dstlen Pointer to an int where the length of the decoded string
+ * is stored (in bytes, incl. null termination)
+ * @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.
+ * @seeknull If true, decoding stops after the first U+0000 character found, if
+ * there is any before maxread is reached
+ * @returns 0 if no error occured, dst is uninitialized on error
+ */
+static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
+ uint8_t **dst, int *dstlen, int *maxread, const int seeknull)
+{
+ int len, ret;
+ uint8_t tmp;
+ uint32_t ch = 1;
+ int left = *maxread;
+ unsigned int (*get)(AVIOContext*) = avio_rb16;
+ AVIOContext *dynbuf;
- taglen--; /* account for encoding type byte */
+ if ((ret = avio_open_dyn_buf(&dynbuf)) < 0) {
+ av_log(s, AV_LOG_ERROR, "Error opening memory stream\n");
+ return ret;
+ }
- switch (avio_r8(pb)) { /* encoding type */
+ switch (encoding) {
case ID3v2_ENCODING_ISO8859:
- q = dst;
- while (taglen-- && q - dst < dstlen - 7) {
- uint8_t tmp;
- PUT_UTF8(avio_r8(pb), tmp, *q++ = tmp;)
+ while (left && (!seeknull || ch)) {
+ ch = avio_r8(pb);
+ PUT_UTF8(ch, tmp, avio_w8(dynbuf, tmp);)
+ left--;
}
- *q = 0;
break;
case ID3v2_ENCODING_UTF16BOM:
- taglen -= 2;
+ if ((left -= 2) < 0) {
+ av_log(s, AV_LOG_ERROR, "Cannot read BOM value, input too short\n");
+ avio_close_dyn_buf(dynbuf, (uint8_t **)dst);
+ av_freep(dst);
+ return AVERROR_INVALIDDATA;
+ }
switch (avio_rb16(pb)) {
case 0xfffe:
get = avio_rl16;
case 0xfeff:
break;
default:
- av_log(s, AV_LOG_ERROR, "Incorrect BOM value in tag %s.\n", key);
- return;
+ av_log(s, AV_LOG_ERROR, "Incorrect BOM value\n");
+ avio_close_dyn_buf(dynbuf, (uint8_t **)dst);
+ av_freep(dst);
+ *maxread = left;
+ return AVERROR_INVALIDDATA;
}
// fall-through
case ID3v2_ENCODING_UTF16BE:
- q = dst;
- while (taglen > 1 && q - dst < dstlen - 7) {
- uint32_t ch;
- uint8_t tmp;
-
- GET_UTF16(ch, ((taglen -= 2) >= 0 ? get(pb) : 0), break;)
- PUT_UTF8(ch, tmp, *q++ = tmp;)
+ while ((left > 1) && (!seeknull || ch)) {
+ GET_UTF16(ch, ((left -= 2) >= 0 ? get(pb) : 0), break;)
+ PUT_UTF8(ch, tmp, avio_w8(dynbuf, tmp);)
}
- *q = 0;
+ if (left < 0)
+ left += 2; /* did not read last char from pb */
break;
case ID3v2_ENCODING_UTF8:
- len = FFMIN(taglen, dstlen);
- avio_read(pb, dst, len);
- dst[len] = 0;
+ while (left && (!seeknull || ch)) {
+ ch = avio_r8(pb);
+ avio_w8(dynbuf, ch);
+ left--;
+ }
break;
default:
- av_log(s, AV_LOG_WARNING, "Unknown encoding in tag %s.\n", key);
+ av_log(s, AV_LOG_WARNING, "Unknown encoding\n");
+ }
+
+ if (ch)
+ avio_w8(dynbuf, 0);
+
+ len = avio_close_dyn_buf(dynbuf, (uint8_t **)dst);
+ if (dstlen)
+ *dstlen = len;
+
+ *maxread = left;
+
+ return 0;
+}
+
+/**
+ * Parse a text tag.
+ */
+static void read_ttag(AVFormatContext *s, AVIOContext *pb, int taglen, const char *key)
+{
+ uint8_t *dst;
+ const char *val = NULL;
+ int len, dstlen;
+ unsigned genre;
+
+ if (taglen < 1)
+ return;
+
+ taglen--; /* account for encoding type byte */
+
+ if (decode_str(s, pb, avio_r8(pb), &dst, &dstlen, &taglen, 0) < 0) {
+ av_log(s, AV_LOG_ERROR, "Error reading frame %s, skipped\n", key);
+ return;
}
if (!(strcmp(key, "TCON") && strcmp(key, "TCO"))
@@ -134,6 +200,82 @@ static void read_ttag(AVFormatContext *s, AVIOContext *pb, int taglen, const cha
if (val)
av_dict_set(&s->metadata, key, val, AV_DICT_DONT_OVERWRITE);
+
+ av_free(dst);
+}
+
+/**
+ * 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, NULL, &taglen, 1) < 0
+ || taglen <= 0)
+ goto fail;
+
+ /* read file name */
+ if (decode_str(s, pb, encoding, &geob_data->file_name, NULL, &taglen, 1) < 0
+ || taglen <= 0)
+ goto fail;
+
+ /* read content description */
+ if (decode_str(s, pb, encoding, &geob_data->description, NULL, &taglen, 1) < 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)
@@ -182,7 +324,27 @@ finish:
av_dict_set(m, "date", date, 0);
}
-static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t flags)
+/**
+ * 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 (ff_id3v2_extra_meta_funcs[i].tag3) {
+ if (!memcmp(tag,
+ (isv34 ?
+ ff_id3v2_extra_meta_funcs[i].tag4 :
+ ff_id3v2_extra_meta_funcs[i].tag3),
+ (isv34 ? 4 : 3)))
+ return &ff_id3v2_extra_meta_funcs[i];
+ i++;
+ }
+ return NULL;
+}
+
+static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t flags, ID3v2ExtraMeta **extra_meta)
{
int isv34, tlen, unsync;
char tag[5];
@@ -190,8 +352,10 @@ static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t
int taghdrlen;
const char *reason = NULL;
AVIOContext pb;
+ AVIOContext *pbx;
unsigned char *buffer = NULL;
int buffer_size = 0;
+ void (*extra_func)(AVFormatContext*, AVIOContext*, int, char*, ID3v2ExtraMeta**) = NULL;
switch (version) {
case 2:
@@ -252,7 +416,8 @@ static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t
if (tflags & (ID3v2_FLAG_ENCRYPTION | ID3v2_FLAG_COMPRESSION)) {
av_log(s, AV_LOG_WARNING, "Skipping encrypted/compressed ID3v2 frame %s.\n", tag);
avio_skip(s->pb, tlen);
- } else if (tag[0] == 'T') {
+ /* check for text tag or supported special meta tag */
+ } else if (tag[0] == 'T' || (extra_meta && (extra_func = get_extra_meta_func(tag, isv34)->read))) {
if (unsync || tunsync) {
int i, j;
av_fast_malloc(&buffer, &buffer_size, tlen);
@@ -268,10 +433,17 @@ static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t
}
}
ffio_init_context(&pb, buffer, j, 0, NULL, NULL, NULL, NULL);
- read_ttag(s, &pb, j, tag);
+ tlen = j;
+ pbx = &pb; // read from sync buffer
} else {
- read_ttag(s, s->pb, tlen, tag);
+ pbx = s->pb; // read straight from input
}
+ if (tag[0] == 'T')
+ /* parse text tag */
+ read_ttag(s, pbx, tlen, tag);
+ else
+ /* parse special meta tag */
+ extra_func(s, pbx, tlen, tag, extra_meta);
}
else if (!tag[0]) {
if (tag[1])
@@ -295,7 +467,7 @@ seek:
return;
}
-void ff_id3v2_read(AVFormatContext *s, const char *magic)
+void ff_id3v2_read_all(AVFormatContext *s, const char *magic, ID3v2ExtraMeta **extra_meta)
{
int len, ret;
uint8_t buf[ID3v2_HEADER_SIZE];
@@ -315,7 +487,7 @@ void ff_id3v2_read(AVFormatContext *s, const char *magic)
((buf[7] & 0x7f) << 14) |
((buf[8] & 0x7f) << 7) |
(buf[9] & 0x7f);
- ff_id3v2_parse(s, len, buf[3], buf[5]);
+ ff_id3v2_parse(s, len, buf[3], buf[5], extra_meta);
} else {
avio_seek(s->pb, off, SEEK_SET);
}
@@ -326,6 +498,30 @@ void ff_id3v2_read(AVFormatContext *s, const char *magic)
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;
+ void (*free_func)(ID3v2ExtraMeta*);
+
+ while (current) {
+ if ((free_func = get_extra_meta_func(current->tag, 1)->free))
+ free_func(current->data);
+ next = current->next;
+ av_freep(&current);
+ current = next;
+ }
+}
+
+const ID3v2EMFunc ff_id3v2_extra_meta_funcs[] = {
+ { "GEO", "GEOB", read_geobtag, free_geobtag },
+ { NULL }
+};
+
const AVMetadataConv ff_id3v2_34_metadata_conv[] = {
{ "TALB", "album"},
{ "TCOM", "composer"},