|
|
@@ -532,6 +532,14 @@ static void read_chapter(AVFormatContext *s, AVIOContext *pb, int len, char *tta |
|
|
|
int taglen; |
|
|
|
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) |
|
|
|
return; |
|
|
|
if (len < 16) |
|
|
@@ -650,16 +658,17 @@ static const ID3v2EMFunc *get_extra_meta_func(const char *tag, int isv34) |
|
|
|
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) |
|
|
|
{ |
|
|
|
int isv34, unsync; |
|
|
|
unsigned tlen; |
|
|
|
char tag[5]; |
|
|
|
int64_t next, end = avio_tell(s->pb) + len; |
|
|
|
int64_t next, end = avio_tell(pb) + len; |
|
|
|
int taghdrlen; |
|
|
|
const char *reason = NULL; |
|
|
|
AVIOContext pb; |
|
|
|
AVIOContext pb_local; |
|
|
|
AVIOContext *pbx; |
|
|
|
unsigned char *buffer = NULL; |
|
|
|
int buffer_size = 0; |
|
|
@@ -693,7 +702,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, |
|
|
|
unsync = flags & 0x80; |
|
|
|
|
|
|
|
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) |
|
|
|
/* In v2.4 the length includes the length field we just read. */ |
|
|
|
extlen -= 4; |
|
|
@@ -702,7 +711,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, |
|
|
|
reason = "invalid extended header length"; |
|
|
|
goto error; |
|
|
|
} |
|
|
|
avio_skip(s->pb, extlen); |
|
|
|
avio_skip(pb, extlen); |
|
|
|
len -= extlen + 4; |
|
|
|
if (len < 0) { |
|
|
|
reason = "extended header too long."; |
|
|
@@ -718,20 +727,20 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, |
|
|
|
unsigned long dlen; |
|
|
|
|
|
|
|
if (isv34) { |
|
|
|
if (avio_read(s->pb, tag, 4) < 4) |
|
|
|
if (avio_read(pb, tag, 4) < 4) |
|
|
|
break; |
|
|
|
tag[4] = 0; |
|
|
|
if (version == 3) { |
|
|
|
tlen = avio_rb32(s->pb); |
|
|
|
tlen = avio_rb32(pb); |
|
|
|
} else |
|
|
|
tlen = get_size(s->pb, 4); |
|
|
|
tflags = avio_rb16(s->pb); |
|
|
|
tlen = get_size(pb, 4); |
|
|
|
tflags = avio_rb16(pb); |
|
|
|
tunsync = tflags & ID3v2_FLAG_UNSYNCH; |
|
|
|
} else { |
|
|
|
if (avio_read(s->pb, tag, 3) < 3) |
|
|
|
if (avio_read(pb, tag, 3) < 3) |
|
|
|
break; |
|
|
|
tag[3] = 0; |
|
|
|
tlen = avio_rb24(s->pb); |
|
|
|
tlen = avio_rb24(pb); |
|
|
|
} |
|
|
|
if (tlen > (1<<28)) |
|
|
|
break; |
|
|
@@ -740,7 +749,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, |
|
|
|
if (len < 0) |
|
|
|
break; |
|
|
|
|
|
|
|
next = avio_tell(s->pb) + tlen; |
|
|
|
next = avio_tell(pb) + tlen; |
|
|
|
|
|
|
|
if (!tlen) { |
|
|
|
if (tag[0]) |
|
|
@@ -752,7 +761,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, |
|
|
|
if (tflags & ID3v2_FLAG_DATALEN) { |
|
|
|
if (tlen < 4) |
|
|
|
break; |
|
|
|
dlen = avio_rb32(s->pb); |
|
|
|
dlen = avio_rb32(pb); |
|
|
|
tlen -= 4; |
|
|
|
} else |
|
|
|
dlen = tlen; |
|
|
@@ -771,12 +780,12 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, |
|
|
|
type = "encrypted and compressed"; |
|
|
|
|
|
|
|
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 */ |
|
|
|
} else if (tag[0] == 'T' || |
|
|
|
(extra_meta && |
|
|
|
(extra_func = get_extra_meta_func(tag, isv34)))) { |
|
|
|
pbx = s->pb; |
|
|
|
pbx = pb; |
|
|
|
|
|
|
|
if (unsync || tunsync || tcomp) { |
|
|
|
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) { |
|
|
|
int64_t end = avio_tell(s->pb) + tlen; |
|
|
|
int64_t end = avio_tell(pb) + tlen; |
|
|
|
uint8_t *b; |
|
|
|
|
|
|
|
b = buffer; |
|
|
|
while (avio_tell(s->pb) < end && b - buffer < tlen && !s->pb->eof_reached) { |
|
|
|
*b++ = avio_r8(s->pb); |
|
|
|
if (*(b - 1) == 0xff && avio_tell(s->pb) < end - 1 && |
|
|
|
while (avio_tell(pb) < end && b - buffer < tlen && !pb->eof_reached) { |
|
|
|
*b++ = avio_r8(pb); |
|
|
|
if (*(b - 1) == 0xff && avio_tell(pb) < end - 1 && |
|
|
|
b - buffer < tlen && |
|
|
|
!s->pb->eof_reached ) { |
|
|
|
uint8_t val = avio_r8(s->pb); |
|
|
|
*b++ = val ? val : avio_r8(s->pb); |
|
|
|
!pb->eof_reached ) { |
|
|
|
uint8_t val = avio_r8(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); |
|
|
|
tlen = b - buffer; |
|
|
|
pbx = &pb; // read from sync buffer |
|
|
|
pbx = &pb_local; // read from sync buffer |
|
|
|
} |
|
|
|
|
|
|
|
#if CONFIG_ZLIB |
|
|
@@ -818,7 +827,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version, |
|
|
|
} |
|
|
|
|
|
|
|
if (!(unsync || tunsync)) { |
|
|
|
err = avio_read(s->pb, buffer, tlen); |
|
|
|
err = avio_read(pb, buffer, tlen); |
|
|
|
if (err < 0) { |
|
|
|
av_log(s, AV_LOG_ERROR, "Failed to read compressed tag\n"); |
|
|
|
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); |
|
|
|
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; |
|
|
|
pbx = &pb; // read from sync buffer |
|
|
|
pbx = &pb_local; // read from sync buffer |
|
|
|
} |
|
|
|
#endif |
|
|
|
if (tag[0] == 'T') |
|
|
|
/* parse text tag */ |
|
|
|
read_ttag(s, pbx, tlen, &s->metadata, tag); |
|
|
|
read_ttag(s, pbx, tlen, metadata, tag); |
|
|
|
else |
|
|
|
/* parse special meta tag */ |
|
|
|
extra_func->read(s, pbx, tlen, tag, extra_meta, isv34); |
|
|
|
} else if (!tag[0]) { |
|
|
|
if (tag[1]) |
|
|
|
av_log(s, AV_LOG_WARNING, "invalid frame id, assuming padding\n"); |
|
|
|
avio_skip(s->pb, tlen); |
|
|
|
avio_skip(pb, tlen); |
|
|
|
break; |
|
|
|
} |
|
|
|
/* Skip to end of tag */ |
|
|
|
seek: |
|
|
|
avio_seek(s->pb, next, SEEK_SET); |
|
|
|
avio_seek(pb, next, SEEK_SET); |
|
|
|
} |
|
|
|
|
|
|
|
/* Footer preset, always 10 bytes, skip over it */ |
|
|
@@ -861,14 +870,15 @@ error: |
|
|
|
if (reason) |
|
|
|
av_log(s, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n", |
|
|
|
version, reason); |
|
|
|
avio_seek(s->pb, end, SEEK_SET); |
|
|
|
avio_seek(pb, end, SEEK_SET); |
|
|
|
av_free(buffer); |
|
|
|
av_free(uncompressed_buffer); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
void ff_id3v2_read(AVFormatContext *s, const char *magic, |
|
|
|
ID3v2ExtraMeta **extra_meta) |
|
|
|
static void id3v2_read_internal(AVIOContext *pb, AVDictionary **metadata, |
|
|
|
AVFormatContext *s, const char *magic, |
|
|
|
ID3v2ExtraMeta **extra_meta) |
|
|
|
{ |
|
|
|
int len, ret; |
|
|
|
uint8_t buf[ID3v2_HEADER_SIZE]; |
|
|
@@ -877,10 +887,10 @@ void ff_id3v2_read(AVFormatContext *s, const char *magic, |
|
|
|
|
|
|
|
do { |
|
|
|
/* 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); |
|
|
|
off = avio_tell(pb); |
|
|
|
ret = avio_read(pb, buf, ID3v2_HEADER_SIZE); |
|
|
|
if (ret != ID3v2_HEADER_SIZE) { |
|
|
|
avio_seek(s->pb, off, SEEK_SET); |
|
|
|
avio_seek(pb, off, SEEK_SET); |
|
|
|
break; |
|
|
|
} |
|
|
|
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[8] & 0x7f) << 7) | |
|
|
|
(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 { |
|
|
|
avio_seek(s->pb, off, SEEK_SET); |
|
|
|
avio_seek(pb, off, SEEK_SET); |
|
|
|
} |
|
|
|
} while (found_header); |
|
|
|
ff_metadata_conv(&s->metadata, NULL, ff_id3v2_34_metadata_conv); |
|
|
|
ff_metadata_conv(&s->metadata, NULL, id3v2_2_metadata_conv); |
|
|
|
ff_metadata_conv(&s->metadata, NULL, ff_id3v2_4_metadata_conv); |
|
|
|
merge_date(&s->metadata); |
|
|
|
ff_metadata_conv(metadata, NULL, ff_id3v2_34_metadata_conv); |
|
|
|
ff_metadata_conv(metadata, NULL, id3v2_2_metadata_conv); |
|
|
|
ff_metadata_conv(metadata, NULL, ff_id3v2_4_metadata_conv); |
|
|
|
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) |
|
|
|