Commit 1fd80106 authored by Lukas Stabe's avatar Lukas Stabe Committed by wm4

avformat: fix id3 chapters

These changes store id3 chapter data in ID3v2ExtraMeta and introduce
ff_id3v2_parse_chapters to parse them into the format context if needed.

Encoders using ff_id3v2_read, which previously parsed chapters into the
format context automatically, were adjusted to call
ff_id3v2_parse_chapters.
Signed-off-by: 's avatarwm4 <nfxjfg@googlemail.com>
parent cafd9d66
...@@ -258,7 +258,8 @@ static int aiff_read_header(AVFormatContext *s) ...@@ -258,7 +258,8 @@ static int aiff_read_header(AVFormatContext *s)
position = avio_tell(pb); position = avio_tell(pb);
ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, size); ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, size);
if (id3v2_extra_meta) if (id3v2_extra_meta)
if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) { if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0 ||
(ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0) {
ff_id3v2_free_extra_meta(&id3v2_extra_meta); ff_id3v2_free_extra_meta(&id3v2_extra_meta);
return ret; return ret;
} }
......
...@@ -307,8 +307,10 @@ static void get_id3_tag(AVFormatContext *s, int len) ...@@ -307,8 +307,10 @@ static void get_id3_tag(AVFormatContext *s, int len)
ID3v2ExtraMeta *id3v2_extra_meta = NULL; ID3v2ExtraMeta *id3v2_extra_meta = NULL;
ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, len); ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, len);
if (id3v2_extra_meta) if (id3v2_extra_meta) {
ff_id3v2_parse_apic(s, &id3v2_extra_meta); ff_id3v2_parse_apic(s, &id3v2_extra_meta);
ff_id3v2_parse_chapters(s, &id3v2_extra_meta);
}
ff_id3v2_free_extra_meta(&id3v2_extra_meta); ff_id3v2_free_extra_meta(&id3v2_extra_meta);
} }
......
...@@ -460,8 +460,10 @@ static void get_id3_tag(AVFormatContext *s, int len) ...@@ -460,8 +460,10 @@ static void get_id3_tag(AVFormatContext *s, int len)
ID3v2ExtraMeta *id3v2_extra_meta = NULL; ID3v2ExtraMeta *id3v2_extra_meta = NULL;
ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, len); ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, len);
if (id3v2_extra_meta) if (id3v2_extra_meta) {
ff_id3v2_parse_apic(s, &id3v2_extra_meta); ff_id3v2_parse_apic(s, &id3v2_extra_meta);
ff_id3v2_parse_chapters(s, &id3v2_extra_meta);
}
ff_id3v2_free_extra_meta(&id3v2_extra_meta); ff_id3v2_free_extra_meta(&id3v2_extra_meta);
} }
......
...@@ -53,8 +53,10 @@ static void read_id3(AVFormatContext *s, uint64_t id3pos) ...@@ -53,8 +53,10 @@ static void read_id3(AVFormatContext *s, uint64_t id3pos)
return; return;
ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, 0); ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, 0);
if (id3v2_extra_meta) if (id3v2_extra_meta) {
ff_id3v2_parse_apic(s, &id3v2_extra_meta); ff_id3v2_parse_apic(s, &id3v2_extra_meta);
ff_id3v2_parse_chapters(s, &id3v2_extra_meta);
}
ff_id3v2_free_extra_meta(&id3v2_extra_meta); ff_id3v2_free_extra_meta(&id3v2_extra_meta);
} }
......
...@@ -670,59 +670,68 @@ fail: ...@@ -670,59 +670,68 @@ fail:
avio_seek(pb, end, SEEK_SET); avio_seek(pb, end, SEEK_SET);
} }
static void free_chapter(void *obj)
{
ID3v2ExtraMetaCHAP *chap = obj;
av_freep(&chap->element_id);
av_dict_free(&chap->meta);
av_freep(&chap);
}
static void read_chapter(AVFormatContext *s, AVIOContext *pb, int len, const char *ttag, ID3v2ExtraMeta **extra_meta, int isv34) static void read_chapter(AVFormatContext *s, AVIOContext *pb, int len, const char *ttag, ID3v2ExtraMeta **extra_meta, int isv34)
{ {
AVRational time_base = {1, 1000};
uint32_t start, end;
AVChapter *chapter;
uint8_t *dst = NULL;
int taglen; int taglen;
char tag[5]; char tag[5];
ID3v2ExtraMeta *new_extra = NULL;
ID3v2ExtraMetaCHAP *chap = NULL;
if (!s) { new_extra = av_mallocz(sizeof(*new_extra));
/* We should probably just put the chapter data to extra_meta here chap = av_mallocz(sizeof(*chap));
* and do the AVFormatContext-needing part in a separate
* ff_id3v2_parse_apic()-like function. */ if (!new_extra || !chap)
av_log(NULL, AV_LOG_DEBUG, "No AVFormatContext, skipped ID3 chapter data\n"); goto fail;
return;
} if (decode_str(s, pb, 0, &chap->element_id, &len) < 0)
goto fail;
if (decode_str(s, pb, 0, &dst, &len) < 0)
return;
if (len < 16) if (len < 16)
return; goto fail;
start = avio_rb32(pb); chap->start = avio_rb32(pb);
end = avio_rb32(pb); chap->end = avio_rb32(pb);
avio_skip(pb, 8); avio_skip(pb, 8);
chapter = avpriv_new_chapter(s, s->nb_chapters + 1, time_base, start, end, dst);
if (!chapter) {
av_free(dst);
return;
}
len -= 16; len -= 16;
while (len > 10) { while (len > 10) {
if (avio_read(pb, tag, 4) < 4) if (avio_read(pb, tag, 4) < 4)
goto end; goto fail;
tag[4] = 0; tag[4] = 0;
taglen = avio_rb32(pb); taglen = avio_rb32(pb);
avio_skip(pb, 2); avio_skip(pb, 2);
len -= 10; len -= 10;
if (taglen < 0 || taglen > len) if (taglen < 0 || taglen > len)
goto end; goto fail;
if (tag[0] == 'T') if (tag[0] == 'T')
read_ttag(s, pb, taglen, &chapter->metadata, tag); read_ttag(s, pb, taglen, &chap->meta, tag);
else else
avio_skip(pb, taglen); avio_skip(pb, taglen);
len -= taglen; len -= taglen;
} }
ff_metadata_conv(&chapter->metadata, NULL, ff_id3v2_34_metadata_conv); ff_metadata_conv(&chap->meta, NULL, ff_id3v2_34_metadata_conv);
ff_metadata_conv(&chapter->metadata, NULL, ff_id3v2_4_metadata_conv); ff_metadata_conv(&chap->meta, NULL, ff_id3v2_4_metadata_conv);
end:
av_free(dst); new_extra->tag = "CHAP";
new_extra->data = chap;
new_extra->next = *extra_meta;
*extra_meta = new_extra;
return;
fail:
if (chap)
free_chapter(chap);
av_freep(&new_extra);
} }
static void free_priv(void *obj) static void free_priv(void *obj)
...@@ -782,7 +791,7 @@ typedef struct ID3v2EMFunc { ...@@ -782,7 +791,7 @@ typedef struct ID3v2EMFunc {
static const ID3v2EMFunc id3v2_extra_meta_funcs[] = { static const ID3v2EMFunc id3v2_extra_meta_funcs[] = {
{ "GEO", "GEOB", read_geobtag, free_geobtag }, { "GEO", "GEOB", read_geobtag, free_geobtag },
{ "PIC", "APIC", read_apic, free_apic }, { "PIC", "APIC", read_apic, free_apic },
{ "CHAP","CHAP", read_chapter, NULL }, { "CHAP","CHAP", read_chapter, free_chapter },
{ "PRIV","PRIV", read_priv, free_priv }, { "PRIV","PRIV", read_priv, free_priv },
{ NULL } { NULL }
}; };
...@@ -1164,3 +1173,54 @@ int ff_id3v2_parse_apic(AVFormatContext *s, ID3v2ExtraMeta **extra_meta) ...@@ -1164,3 +1173,54 @@ int ff_id3v2_parse_apic(AVFormatContext *s, ID3v2ExtraMeta **extra_meta)
return 0; return 0;
} }
int ff_id3v2_parse_chapters(AVFormatContext *s, ID3v2ExtraMeta **extra_meta)
{
int ret = 0;
ID3v2ExtraMeta *cur;
AVRational time_base = {1, 1000};
ID3v2ExtraMetaCHAP **chapters = NULL;
int num_chapters = 0;
int i;
// since extra_meta is a linked list where elements are prepended,
// we need to reverse the order of chapters
for (cur = *extra_meta; cur; cur = cur->next) {
ID3v2ExtraMetaCHAP *chap;
if (strcmp(cur->tag, "CHAP"))
continue;
chap = cur->data;
if ((ret = av_dynarray_add_nofree(&chapters, &num_chapters, chap)) < 0)
goto end;
}
for (i = 0; i < (num_chapters / 2); i++) {
ID3v2ExtraMetaCHAP *right;
int right_index;
right_index = (num_chapters - 1) - i;
right = chapters[right_index];
chapters[right_index] = chapters[i];
chapters[i] = right;
}
for (i = 0; i < num_chapters; i++) {
ID3v2ExtraMetaCHAP *chap;
AVChapter *chapter;
chap = chapters[i];
chapter = avpriv_new_chapter(s, i, time_base, chap->start, chap->end, chap->element_id);
if (!chapter)
continue;
if ((ret = av_dict_copy(&chapter->metadata, chap->meta, 0)) < 0)
goto end;
}
end:
av_freep(&chapters);
return ret;
}
...@@ -79,6 +79,12 @@ typedef struct ID3v2ExtraMetaPRIV { ...@@ -79,6 +79,12 @@ typedef struct ID3v2ExtraMetaPRIV {
uint32_t datasize; uint32_t datasize;
} ID3v2ExtraMetaPRIV; } ID3v2ExtraMetaPRIV;
typedef struct ID3v2ExtraMetaCHAP {
uint8_t *element_id;
uint32_t start, end;
AVDictionary *meta;
} ID3v2ExtraMetaCHAP;
/** /**
* Detect ID3v2 Header. * Detect ID3v2 Header.
* @param buf must be ID3v2_HEADER_SIZE byte long * @param buf must be ID3v2_HEADER_SIZE byte long
...@@ -97,8 +103,6 @@ int ff_id3v2_tag_len(const uint8_t *buf); ...@@ -97,8 +103,6 @@ int ff_id3v2_tag_len(const uint8_t *buf);
/** /**
* Read an ID3v2 tag into specified dictionary and retrieve 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 metadata Parsed metadata is stored here
* @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
...@@ -106,7 +110,7 @@ int ff_id3v2_tag_len(const uint8_t *buf); ...@@ -106,7 +110,7 @@ int ff_id3v2_tag_len(const uint8_t *buf);
void ff_id3v2_read_dict(AVIOContext *pb, AVDictionary **metadata, const char *magic, ID3v2ExtraMeta **extra_meta); 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. * Read an ID3v2 tag, including supported extra metadata.
* *
* Data is read from and stored to AVFormatContext. * Data is read from and stored to AVFormatContext.
* *
...@@ -158,6 +162,11 @@ void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta); ...@@ -158,6 +162,11 @@ void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta);
*/ */
int ff_id3v2_parse_apic(AVFormatContext *s, ID3v2ExtraMeta **extra_meta); int ff_id3v2_parse_apic(AVFormatContext *s, ID3v2ExtraMeta **extra_meta);
/**
* Create chapters for all CHAP tags found in the ID3v2 header.
*/
int ff_id3v2_parse_chapters(AVFormatContext *s, ID3v2ExtraMeta **extra_meta);
extern const AVMetadataConv ff_id3v2_34_metadata_conv[]; extern const AVMetadataConv ff_id3v2_34_metadata_conv[];
extern const AVMetadataConv ff_id3v2_4_metadata_conv[]; extern const AVMetadataConv ff_id3v2_4_metadata_conv[];
......
...@@ -312,7 +312,8 @@ static int parse_dsd_prop(AVFormatContext *s, AVStream *st, uint64_t eof) ...@@ -312,7 +312,8 @@ static int parse_dsd_prop(AVFormatContext *s, AVStream *st, uint64_t eof)
id3v2_extra_meta = NULL; id3v2_extra_meta = NULL;
ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, size); ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, size);
if (id3v2_extra_meta) { if (id3v2_extra_meta) {
if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) { if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0 ||
(ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0) {
ff_id3v2_free_extra_meta(&id3v2_extra_meta); ff_id3v2_free_extra_meta(&id3v2_extra_meta);
return ret; return ret;
} }
......
...@@ -397,6 +397,11 @@ static int oma_read_header(AVFormatContext *s) ...@@ -397,6 +397,11 @@ static int oma_read_header(AVFormatContext *s)
OMAContext *oc = s->priv_data; OMAContext *oc = s->priv_data;
ff_id3v2_read(s, ID3v2_EA3_MAGIC, &extra_meta, 0); ff_id3v2_read(s, ID3v2_EA3_MAGIC, &extra_meta, 0);
if ((ret = ff_id3v2_parse_chapters(s, &extra_meta)) < 0) {
ff_id3v2_free_extra_meta(&extra_meta);
return ret;
}
ret = avio_read(s->pb, buf, EA3_HEADER_SIZE); ret = avio_read(s->pb, buf, EA3_HEADER_SIZE);
if (ret < EA3_HEADER_SIZE) if (ret < EA3_HEADER_SIZE)
return -1; return -1;
......
...@@ -613,6 +613,8 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, ...@@ -613,6 +613,8 @@ int avformat_open_input(AVFormatContext **ps, const char *filename,
!strcmp(s->iformat->name, "tta")) { !strcmp(s->iformat->name, "tta")) {
if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0)
goto fail; goto fail;
if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0)
goto fail;
} else } else
av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n"); av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n");
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment