avformat/id3v2: allow ID3 parsing without AVFormatContext

Add ff_id3v2_read_dict() for parsing without AVFormatContext, but
instead with AVIOContext and AVDictionary.

AVFormatContext is still used for logging, if available.

Chapter parsing is the only non-logging functionality that actually
needs AVFormatContext, and AFAICS it should be modified to write the
data to ID3v2ExtraMeta first, from where it can be implanted to
AVFormatContext by a separate function (like it is done with
read_apic() and ff_id3v2_parse_apic()). That is outside the scope of
this patch, though.

Signed-off-by: Anssi Hannula <anssi.hannula@iki.fi>
pull/50/merge
Anssi Hannula 11 years ago
parent d52882faef
commit 7fdf245ab9
  1. 104
      libavformat/id3v2.c
  2. 16
      libavformat/id3v2.h

@ -532,6 +532,14 @@ static void read_chapter(AVFormatContext *s, AVIOContext *pb, int len, char *tta
int taglen; int taglen;
char tag[5]; char tag[5];
if (!s) {
/* We should probably just put the chapter data to extra_meta here
* and do the AVFormatContext-needing part in a separate
* ff_id3v2_parse_apic()-like function. */
av_log(NULL, AV_LOG_DEBUG, "No AVFormatContext, skipped ID3 chapter data\n");
return;
}
if (decode_str(s, pb, 0, &dst, &len) < 0) if (decode_str(s, pb, 0, &dst, &len) < 0)
return; return;
if (len < 16) if (len < 16)
@ -650,16 +658,17 @@ static const ID3v2EMFunc *get_extra_meta_func(const char *tag, int isv34)
return NULL; return NULL;
} }
static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, static void id3v2_parse(AVIOContext *pb, AVDictionary **metadata,
AVFormatContext *s, int len, uint8_t version,
uint8_t flags, ID3v2ExtraMeta **extra_meta) uint8_t flags, ID3v2ExtraMeta **extra_meta)
{ {
int isv34, unsync; int isv34, unsync;
unsigned tlen; unsigned tlen;
char tag[5]; char tag[5];
int64_t next, end = avio_tell(s->pb) + len; int64_t next, end = avio_tell(pb) + len;
int taghdrlen; int taghdrlen;
const char *reason = NULL; const char *reason = NULL;
AVIOContext pb; AVIOContext pb_local;
AVIOContext *pbx; AVIOContext *pbx;
unsigned char *buffer = NULL; unsigned char *buffer = NULL;
int buffer_size = 0; int buffer_size = 0;
@ -693,7 +702,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
unsync = flags & 0x80; unsync = flags & 0x80;
if (isv34 && flags & 0x40) { /* Extended header present, just skip over it */ if (isv34 && flags & 0x40) { /* Extended header present, just skip over it */
int extlen = get_size(s->pb, 4); int extlen = get_size(pb, 4);
if (version == 4) if (version == 4)
/* In v2.4 the length includes the length field we just read. */ /* In v2.4 the length includes the length field we just read. */
extlen -= 4; extlen -= 4;
@ -702,7 +711,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
reason = "invalid extended header length"; reason = "invalid extended header length";
goto error; goto error;
} }
avio_skip(s->pb, extlen); avio_skip(pb, extlen);
len -= extlen + 4; len -= extlen + 4;
if (len < 0) { if (len < 0) {
reason = "extended header too long."; reason = "extended header too long.";
@ -718,20 +727,20 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
unsigned long dlen; unsigned long dlen;
if (isv34) { if (isv34) {
if (avio_read(s->pb, tag, 4) < 4) if (avio_read(pb, tag, 4) < 4)
break; break;
tag[4] = 0; tag[4] = 0;
if (version == 3) { if (version == 3) {
tlen = avio_rb32(s->pb); tlen = avio_rb32(pb);
} else } else
tlen = get_size(s->pb, 4); tlen = get_size(pb, 4);
tflags = avio_rb16(s->pb); tflags = avio_rb16(pb);
tunsync = tflags & ID3v2_FLAG_UNSYNCH; tunsync = tflags & ID3v2_FLAG_UNSYNCH;
} else { } else {
if (avio_read(s->pb, tag, 3) < 3) if (avio_read(pb, tag, 3) < 3)
break; break;
tag[3] = 0; tag[3] = 0;
tlen = avio_rb24(s->pb); tlen = avio_rb24(pb);
} }
if (tlen > (1<<28)) if (tlen > (1<<28))
break; break;
@ -740,7 +749,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
if (len < 0) if (len < 0)
break; break;
next = avio_tell(s->pb) + tlen; next = avio_tell(pb) + tlen;
if (!tlen) { if (!tlen) {
if (tag[0]) if (tag[0])
@ -752,7 +761,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
if (tflags & ID3v2_FLAG_DATALEN) { if (tflags & ID3v2_FLAG_DATALEN) {
if (tlen < 4) if (tlen < 4)
break; break;
dlen = avio_rb32(s->pb); dlen = avio_rb32(pb);
tlen -= 4; tlen -= 4;
} else } else
dlen = tlen; dlen = tlen;
@ -771,12 +780,12 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
type = "encrypted and compressed"; type = "encrypted and compressed";
av_log(s, AV_LOG_WARNING, "Skipping %s ID3v2 frame %s.\n", type, tag); av_log(s, AV_LOG_WARNING, "Skipping %s ID3v2 frame %s.\n", type, tag);
avio_skip(s->pb, tlen); avio_skip(pb, tlen);
/* check for text tag or supported special meta tag */ /* check for text tag or supported special meta tag */
} else if (tag[0] == 'T' || } else if (tag[0] == 'T' ||
(extra_meta && (extra_meta &&
(extra_func = get_extra_meta_func(tag, isv34)))) { (extra_func = get_extra_meta_func(tag, isv34)))) {
pbx = s->pb; pbx = pb;
if (unsync || tunsync || tcomp) { if (unsync || tunsync || tcomp) {
av_fast_malloc(&buffer, &buffer_size, tlen); av_fast_malloc(&buffer, &buffer_size, tlen);
@ -786,23 +795,23 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
} }
} }
if (unsync || tunsync) { if (unsync || tunsync) {
int64_t end = avio_tell(s->pb) + tlen; int64_t end = avio_tell(pb) + tlen;
uint8_t *b; uint8_t *b;
b = buffer; b = buffer;
while (avio_tell(s->pb) < end && b - buffer < tlen && !s->pb->eof_reached) { while (avio_tell(pb) < end && b - buffer < tlen && !pb->eof_reached) {
*b++ = avio_r8(s->pb); *b++ = avio_r8(pb);
if (*(b - 1) == 0xff && avio_tell(s->pb) < end - 1 && if (*(b - 1) == 0xff && avio_tell(pb) < end - 1 &&
b - buffer < tlen && b - buffer < tlen &&
!s->pb->eof_reached ) { !pb->eof_reached ) {
uint8_t val = avio_r8(s->pb); uint8_t val = avio_r8(pb);
*b++ = val ? val : avio_r8(s->pb); *b++ = val ? val : avio_r8(pb);
} }
} }
ffio_init_context(&pb, buffer, b - buffer, 0, NULL, NULL, NULL, ffio_init_context(&pb_local, buffer, b - buffer, 0, NULL, NULL, NULL,
NULL); NULL);
tlen = b - buffer; tlen = b - buffer;
pbx = &pb; // read from sync buffer pbx = &pb_local; // read from sync buffer
} }
#if CONFIG_ZLIB #if CONFIG_ZLIB
@ -818,7 +827,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
} }
if (!(unsync || tunsync)) { if (!(unsync || tunsync)) {
err = avio_read(s->pb, buffer, tlen); err = avio_read(pb, buffer, tlen);
if (err < 0) { if (err < 0) {
av_log(s, AV_LOG_ERROR, "Failed to read compressed tag\n"); av_log(s, AV_LOG_ERROR, "Failed to read compressed tag\n");
goto seek; goto seek;
@ -831,26 +840,26 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
av_log(s, AV_LOG_ERROR, "Failed to uncompress tag: %d\n", err); av_log(s, AV_LOG_ERROR, "Failed to uncompress tag: %d\n", err);
goto seek; goto seek;
} }
ffio_init_context(&pb, uncompressed_buffer, dlen, 0, NULL, NULL, NULL, NULL); ffio_init_context(&pb_local, uncompressed_buffer, dlen, 0, NULL, NULL, NULL, NULL);
tlen = dlen; tlen = dlen;
pbx = &pb; // read from sync buffer pbx = &pb_local; // read from sync buffer
} }
#endif #endif
if (tag[0] == 'T') if (tag[0] == 'T')
/* parse text tag */ /* parse text tag */
read_ttag(s, pbx, tlen, &s->metadata, tag); read_ttag(s, pbx, tlen, metadata, tag);
else else
/* parse special meta tag */ /* parse special meta tag */
extra_func->read(s, pbx, tlen, tag, extra_meta, isv34); extra_func->read(s, pbx, tlen, tag, extra_meta, isv34);
} else if (!tag[0]) { } else if (!tag[0]) {
if (tag[1]) if (tag[1])
av_log(s, AV_LOG_WARNING, "invalid frame id, assuming padding\n"); av_log(s, AV_LOG_WARNING, "invalid frame id, assuming padding\n");
avio_skip(s->pb, tlen); avio_skip(pb, tlen);
break; break;
} }
/* Skip to end of tag */ /* Skip to end of tag */
seek: seek:
avio_seek(s->pb, next, SEEK_SET); avio_seek(pb, next, SEEK_SET);
} }
/* Footer preset, always 10 bytes, skip over it */ /* Footer preset, always 10 bytes, skip over it */
@ -861,13 +870,14 @@ error:
if (reason) if (reason)
av_log(s, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n", av_log(s, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n",
version, reason); version, reason);
avio_seek(s->pb, end, SEEK_SET); avio_seek(pb, end, SEEK_SET);
av_free(buffer); av_free(buffer);
av_free(uncompressed_buffer); av_free(uncompressed_buffer);
return; return;
} }
void ff_id3v2_read(AVFormatContext *s, const char *magic, static void id3v2_read_internal(AVIOContext *pb, AVDictionary **metadata,
AVFormatContext *s, const char *magic,
ID3v2ExtraMeta **extra_meta) ID3v2ExtraMeta **extra_meta)
{ {
int len, ret; int len, ret;
@ -877,10 +887,10 @@ void ff_id3v2_read(AVFormatContext *s, const char *magic,
do { do {
/* save the current offset in case there's nothing to read/skip */ /* save the current offset in case there's nothing to read/skip */
off = avio_tell(s->pb); off = avio_tell(pb);
ret = avio_read(s->pb, buf, ID3v2_HEADER_SIZE); ret = avio_read(pb, buf, ID3v2_HEADER_SIZE);
if (ret != ID3v2_HEADER_SIZE) { if (ret != ID3v2_HEADER_SIZE) {
avio_seek(s->pb, off, SEEK_SET); avio_seek(pb, off, SEEK_SET);
break; break;
} }
found_header = ff_id3v2_match(buf, magic); found_header = ff_id3v2_match(buf, magic);
@ -890,15 +900,27 @@ void ff_id3v2_read(AVFormatContext *s, const char *magic,
((buf[7] & 0x7f) << 14) | ((buf[7] & 0x7f) << 14) |
((buf[8] & 0x7f) << 7) | ((buf[8] & 0x7f) << 7) |
(buf[9] & 0x7f); (buf[9] & 0x7f);
id3v2_parse(s, len, buf[3], buf[5], extra_meta); id3v2_parse(pb, metadata, s, len, buf[3], buf[5], extra_meta);
} else { } else {
avio_seek(s->pb, off, SEEK_SET); avio_seek(pb, off, SEEK_SET);
} }
} while (found_header); } while (found_header);
ff_metadata_conv(&s->metadata, NULL, ff_id3v2_34_metadata_conv); ff_metadata_conv(metadata, NULL, ff_id3v2_34_metadata_conv);
ff_metadata_conv(&s->metadata, NULL, id3v2_2_metadata_conv); ff_metadata_conv(metadata, NULL, id3v2_2_metadata_conv);
ff_metadata_conv(&s->metadata, NULL, ff_id3v2_4_metadata_conv); ff_metadata_conv(metadata, NULL, ff_id3v2_4_metadata_conv);
merge_date(&s->metadata); merge_date(metadata);
}
void ff_id3v2_read_dict(AVIOContext *pb, AVDictionary **metadata,
const char *magic, ID3v2ExtraMeta **extra_meta)
{
id3v2_read_internal(pb, metadata, NULL, magic, extra_meta);
}
void ff_id3v2_read(AVFormatContext *s, const char *magic,
ID3v2ExtraMeta **extra_meta)
{
id3v2_read_internal(s->pb, &s->metadata, s, magic, extra_meta);
} }
void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta) void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta)

@ -95,7 +95,21 @@ int ff_id3v2_match(const uint8_t *buf, const char *magic);
int ff_id3v2_tag_len(const uint8_t *buf); int ff_id3v2_tag_len(const uint8_t *buf);
/** /**
* Read an ID3v2 tag, including supported extra metadata * Read an ID3v2 tag into specified dictionary and retrieve supported extra metadata.
*
* Chapters are not currently read by this variant.
*
* @param metadata Parsed metadata is stored here
* @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_dict(AVIOContext *pb, AVDictionary **metadata, const char *magic, ID3v2ExtraMeta **extra_meta);
/**
* Read an ID3v2 tag, including supported extra metadata and chapters.
*
* Data is read from and stored to AVFormatContext.
*
* @param extra_meta If not NULL, extra metadata is parsed into a list of * @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 * ID3v2ExtraMeta structs and *extra_meta points to the head of the list
*/ */

Loading…
Cancel
Save