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>
This commit is contained in:
@@ -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,14 +870,15 @@ 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,
|
||||||
ID3v2ExtraMeta **extra_meta)
|
AVFormatContext *s, const char *magic,
|
||||||
|
ID3v2ExtraMeta **extra_meta)
|
||||||
{
|
{
|
||||||
int len, ret;
|
int len, ret;
|
||||||
uint8_t buf[ID3v2_HEADER_SIZE];
|
uint8_t buf[ID3v2_HEADER_SIZE];
|
||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user