zsugabubus@national.shitposting.agency: > Metadata names now recognized in the following format: > > [ [TargetTypeValue][TargetType] "/" ] TagName [ "/" TagName ]... [ "@" [ > "-" ] [TagLanguage] ] > > Language designators separated with "@" instead of "-" to avoid future > ambiguity about IETF language tags, like "en-US" and similar. > "-" designates TagDefault := 0. > > Signed-off-by: zsugabubus <zsugabubus@national.shitposting.agency> > --- > libavformat/matroska.c | 56 ++++++- > libavformat/matroska.h | 9 ++ > libavformat/matroskadec.c | 286 +++++++++++++++++++++-------------- > libavformat/matroskaenc.c | 308 ++++++++++++++++++++++++++------------ > 4 files changed, 449 insertions(+), 210 deletions(-) > > diff --git a/libavformat/matroska.c b/libavformat/matroska.c > index 7c56aba..4fcade2 100644 > --- a/libavformat/matroska.c > +++ b/libavformat/matroska.c > @@ -120,11 +120,63 @@ const CodecTags ff_webm_codec_tags[] = { > }; > > const AVMetadataConv ff_mkv_metadata_conv[] = { > - { "LEAD_PERFORMER", "performer" }, > - { "PART_NUMBER" , "track" }, > + { "TRACK/ARTIST", "artist" }, > + { "ALBUM/ARTIST", "album_artist" }, > + { "ALBUM/TITLE", "album" }, > + { "ALBUM/DATE_RELEASED", "date" }, > + { "TRACK/TITLE", "title" }, > + { "ALBUM/PART_NUMBER", "track" }, > + { "ALBUM/TOTAL_PARTS", "track_total" }, > + { "VOLUME/PART_NUMBER", "disc" }, > + { "VOLUME/TOTAL_PARTS", "disc_total" }, > + { "TRACK/GENRE", "genre" }, > + > + { "50/COMMENT", "comment" }, > + { "ALBUM/COMMENT", "comment" }, > + { "EPISODE/COMMENT", "comment" }, > { 0 } > }; > > +#define A (1 << AVMEDIA_TYPE_AUDIO) > +#define V (1 << AVMEDIA_TYPE_VIDEO) > +#define AV A | V > + > +/* Note: When converting from typevalues -> strings first matching entry is > used. */ > +const TypeValueConv ff_mkv_typevalue_conv[] = { > + { "COLLECTION", 70, AV }, > + > + { "SEASON", 60, V }, > + { "VOLUME", 60, AV }, > + { "EDITION", 60, A }, > + { "ISSUE", 60, A }, > + { "OPUS", 60, A }, > + { "SEQUEL", 60, V }, > + > + { "ALBUM", 50, A }, > + { "EPISODE", 50, V }, > + { "OPERA", 50, A }, > + { "MOVIE", 50, V }, > + { "CONCERT", 50, AV }, > + > + { "PART", 40, AV }, > + { "SESSION", 40, AV }, > + > + { "TRACK", 30, A }, > + { "CHAPTER", 30, V }, > + { "SONG", 30, A }, > + > + { "SUBTRACK", 20, A }, > + { "MOVEMENT", 20, A }, > + { "SCENE", 20, V }, > + > + { "SHOT", 10, V }, > + { "", 0, -1 } > +}; > + > +#undef A > +#undef V > +#undef AV > + > const char * const > ff_matroska_video_stereo_mode[MATROSKA_VIDEO_STEREOMODE_TYPE_NB] = { > "mono", > "left_right", > diff --git a/libavformat/matroska.h b/libavformat/matroska.h > index 6f198f0..6dc9582 100644 > --- a/libavformat/matroska.h > +++ b/libavformat/matroska.h > @@ -355,14 +355,23 @@ typedef struct CodecTags{ > enum AVCodecID id; > }CodecTags; > > +typedef struct { > + char str[sizeof "COLLECTION"]; > + uint8_t typevalue; > + uint8_t codec_types; /* (1 << AVMEDIA_TYPE_*)... */ > +} TypeValueConv; > + > /* max. depth in the EBML tree structure */ > #define EBML_MAX_DEPTH 16 > > #define MATROSKA_VIDEO_STEREO_PLANE_COUNT 3 > > +enum { MATROSKA_DEFAULT_TAGTARGETS_TYPEVALUE = 50 }; > + > extern const CodecTags ff_mkv_codec_tags[]; > extern const CodecTags ff_webm_codec_tags[]; > extern const AVMetadataConv ff_mkv_metadata_conv[]; > +extern const TypeValueConv ff_mkv_typevalue_conv[]; > extern const char * const > ff_matroska_video_stereo_mode[MATROSKA_VIDEO_STEREOMODE_TYPE_NB]; > extern const char * const > ff_matroska_video_stereo_plane[MATROSKA_VIDEO_STEREO_PLANE_COUNT]; > > diff --git a/libavformat/matroskadec.c b/libavformat/matroskadec.c > index 5455594..1c0478d 100644 > --- a/libavformat/matroskadec.c > +++ b/libavformat/matroskadec.c > @@ -98,8 +98,8 @@ typedef enum { > typedef const struct EbmlSyntax { > uint32_t id; > EbmlType type; > - size_t list_elem_size; > - size_t data_offset; > + unsigned list_elem_size; > + unsigned data_offset; > union { > int64_t i; > uint64_t u; > @@ -228,8 +228,8 @@ typedef struct MatroskaTrackOperation { > } MatroskaTrackOperation; > > typedef struct MatroskaTrack { > + uint64_t uid; /* Must be the first field. */ > uint64_t num; > - uint64_t uid; > uint64_t type; > char *name; > char *codec_id; > @@ -258,7 +258,7 @@ typedef struct MatroskaTrack { > } MatroskaTrack; > > typedef struct MatroskaAttachment { > - uint64_t uid; > + uint64_t uid; /* Must be the first field. */ > char *filename; > char *description; > char *mime; > @@ -268,9 +268,9 @@ typedef struct MatroskaAttachment { > } MatroskaAttachment; > > typedef struct MatroskaChapter { > + uint64_t uid; /* Must be the first field. */ > uint64_t start; > uint64_t end; > - uint64_t uid; > char *title; > > AVChapter *chapter; > @@ -286,26 +286,26 @@ typedef struct MatroskaIndex { > EbmlList pos; > } MatroskaIndex; > > -typedef struct MatroskaTag { > +typedef struct MatroskaSimpleTag { > char *name; > char *string; > char *lang; > uint64_t def; > EbmlList sub; > -} MatroskaTag; > +} MatroskaSimpleTag; > > typedef struct MatroskaTagTarget { > char *type; > uint64_t typevalue; > - uint64_t trackuid; > - uint64_t chapteruid; > - uint64_t attachuid; > + EbmlList trackuids; > + EbmlList chapteruids; > + EbmlList attachuids; > } MatroskaTagTarget; > > -typedef struct MatroskaTags { > +typedef struct MatroskaTag { > MatroskaTagTarget target; > - EbmlList tag; > -} MatroskaTags; > + EbmlList simpletags; > +} MatroskaTag; > > typedef struct MatroskaSeekhead { > uint64_t id; > @@ -652,32 +652,32 @@ static EbmlSyntax matroska_index[] = { > }; > > static EbmlSyntax matroska_simpletag[] = { > - { MATROSKA_ID_TAGNAME, EBML_UTF8, 0, > offsetof(MatroskaTag, name) }, > - { MATROSKA_ID_TAGSTRING, EBML_UTF8, 0, > offsetof(MatroskaTag, string) }, > - { MATROSKA_ID_TAGLANG, EBML_STR, 0, > offsetof(MatroskaTag, lang), { .s = "und" } }, > - { MATROSKA_ID_TAGDEFAULT, EBML_UINT, 0, > offsetof(MatroskaTag, def) }, > - { MATROSKA_ID_TAGDEFAULT_BUG, EBML_UINT, 0, > offsetof(MatroskaTag, def) }, > - { MATROSKA_ID_SIMPLETAG, EBML_NEST, sizeof(MatroskaTag), > offsetof(MatroskaTag, sub), { .n = matroska_simpletag } }, > + { MATROSKA_ID_TAGNAME, EBML_UTF8, 0, > offsetof(MatroskaSimpleTag, name) }, > + { MATROSKA_ID_TAGSTRING, EBML_UTF8, 0, > offsetof(MatroskaSimpleTag, string) }, > + { MATROSKA_ID_TAGLANG, EBML_STR, 0, > offsetof(MatroskaSimpleTag, lang), { .s = "und" } }, > + { MATROSKA_ID_TAGDEFAULT, EBML_UINT, 0, > offsetof(MatroskaSimpleTag, def), { .u = 1 } }, > + { MATROSKA_ID_TAGDEFAULT_BUG, EBML_UINT, 0, > offsetof(MatroskaSimpleTag, def), { .u = 1 } }, > + { MATROSKA_ID_SIMPLETAG, EBML_NEST, sizeof(MatroskaSimpleTag), > offsetof(MatroskaSimpleTag, sub), { .n = matroska_simpletag } }, > CHILD_OF(matroska_tag) > }; > > static EbmlSyntax matroska_tagtargets[] = { > - { MATROSKA_ID_TAGTARGETS_TYPE, EBML_STR, 0, > offsetof(MatroskaTagTarget, type) }, > - { MATROSKA_ID_TAGTARGETS_TYPEVALUE, EBML_UINT, 0, > offsetof(MatroskaTagTarget, typevalue), { .u = 50 } }, > - { MATROSKA_ID_TAGTARGETS_TRACKUID, EBML_UINT, 0, > offsetof(MatroskaTagTarget, trackuid) }, > - { MATROSKA_ID_TAGTARGETS_CHAPTERUID, EBML_UINT, 0, > offsetof(MatroskaTagTarget, chapteruid) }, > - { MATROSKA_ID_TAGTARGETS_ATTACHUID, EBML_UINT, 0, > offsetof(MatroskaTagTarget, attachuid) }, > + { MATROSKA_ID_TAGTARGETS_TYPE, EBML_STR, 0, > offsetof(MatroskaTagTarget, type) }, > + { MATROSKA_ID_TAGTARGETS_TYPEVALUE, EBML_UINT, 0, > offsetof(MatroskaTagTarget, typevalue), { .u = > MATROSKA_DEFAULT_TAGTARGETS_TYPEVALUE } }, > + { MATROSKA_ID_TAGTARGETS_TRACKUID, EBML_UINT, sizeof(uint64_t), > offsetof(MatroskaTagTarget, trackuids) }, > + { MATROSKA_ID_TAGTARGETS_CHAPTERUID, EBML_UINT, sizeof(uint64_t), > offsetof(MatroskaTagTarget, chapteruids) }, > + { MATROSKA_ID_TAGTARGETS_ATTACHUID, EBML_UINT, sizeof(uint64_t), > offsetof(MatroskaTagTarget, attachuids) }, > CHILD_OF(matroska_tag) > }; > > static EbmlSyntax matroska_tag[] = { > - { MATROSKA_ID_SIMPLETAG, EBML_NEST, sizeof(MatroskaTag), > offsetof(MatroskaTags, tag), { .n = matroska_simpletag } }, > - { MATROSKA_ID_TAGTARGETS, EBML_NEST, 0, > offsetof(MatroskaTags, target), { .n = matroska_tagtargets } }, > + { MATROSKA_ID_SIMPLETAG, EBML_NEST, sizeof(MatroskaSimpleTag), > offsetof(MatroskaTag, simpletags), { .n = matroska_simpletag } }, > + { MATROSKA_ID_TAGTARGETS, EBML_NEST, 0, > offsetof(MatroskaTag, target), { .n = matroska_tagtargets } }, > CHILD_OF(matroska_tags) > }; > > static EbmlSyntax matroska_tags[] = { > - { MATROSKA_ID_TAG, EBML_NEST, sizeof(MatroskaTags), > offsetof(MatroskaDemuxContext, tags), { .n = matroska_tag } }, > + { MATROSKA_ID_TAG, EBML_NEST, sizeof(MatroskaTag), > offsetof(MatroskaDemuxContext, tags), { .n = matroska_tag } }, > CHILD_OF(matroska_segment) > }; > > @@ -1719,103 +1719,169 @@ failed: > return result; > } > > -static void matroska_convert_tag(AVFormatContext *s, EbmlList *list, > - AVDictionary **metadata, char *prefix) > +static void matroska_get_targettype_string(char *key, unsigned *key_ptr, > unsigned key_size, > + MatroskaTag const *tag, enum > AVMediaType codec_type) > { > - MatroskaTag *tags = list->elem; > - char key[1024]; > - int i; > + for (const TypeValueConv *conv = ff_mkv_typevalue_conv; > + tag->target.typevalue <= (uint64_t)conv->typevalue; > + ++conv) > + if (tag->target.typevalue == (uint64_t)conv->typevalue && > + (codec_type == AVMEDIA_TYPE_UNKNOWN || (conv->codec_types & (1 > << codec_type))) && > + (!tag->target.type || !av_strcasecmp(tag->target.type, > conv->str))) > + { > + *key_ptr = strlen(conv->str); > + memcpy(key, conv->str, sizeof conv->str); > + return; > + } > > - for (i = 0; i < list->nb_elem; i++) { > - const char *lang = tags[i].lang && > - strcmp(tags[i].lang, "und") ? tags[i].lang : NULL; > + *key_ptr = snprintf(key, key_size, "%"PRIu64"%s", > + tag->target.typevalue, tag->target.type); > + if (key_size < *key_ptr) > + *key_ptr = key_size; > +} > > - if (!tags[i].name) { > - av_log(s, AV_LOG_WARNING, "Skipping invalid tag with no > TagName.\n"); > +static void matroska_convert_simpletags(AVFormatContext *s, const EbmlList > *simpletag_list, > + AVDictionary **metadata, > + char *key, unsigned base_key_ptr, > unsigned key_size) > +{ > + MatroskaSimpleTag *simpletags = simpletag_list->elem; > + > + for (int i = 0; i < simpletag_list->nb_elem; i++) { > + MatroskaSimpleTag *simpletag = &simpletags[i]; > + const char *lang = simpletag->lang && > + strcmp(simpletag->lang, "und") ? simpletag->lang > : NULL; > + unsigned key_ptr; > + > + if (!simpletag->name) { > + av_log(s, AV_LOG_WARNING, > + "Skipping invalid Tag with no TagName.\n"); > continue; > } > - if (prefix) > - snprintf(key, sizeof(key), "%s/%s", prefix, tags[i].name); > - else > - av_strlcpy(key, tags[i].name, sizeof(key)); > - if (tags[i].def || !lang) { > - av_dict_set(metadata, key, tags[i].string, 0); > - if (tags[i].sub.nb_elem) > - matroska_convert_tag(s, &tags[i].sub, metadata, key); > - } > - if (lang) { > - av_strlcat(key, "-", sizeof(key)); > - av_strlcat(key, lang, sizeof(key)); > - av_dict_set(metadata, key, tags[i].string, 0); > - if (tags[i].sub.nb_elem) > - matroska_convert_tag(s, &tags[i].sub, metadata, key); > + > + key_ptr = base_key_ptr + > + snprintf(key + base_key_ptr, key_size - base_key_ptr, "%s%s", > + base_key_ptr ? "/" : "", simpletag->name); > + if (key_size <= key_ptr) { > + av_log(s, AV_LOG_WARNING, > + "Skipping SimpleTag with path larger than %u bytes.\n", > + key_size); > + return; > } > + > + if (lang) > + snprintf(key + key_ptr, key_size - key_ptr, "@%s%s", > + !simpletag->def ? "-" : "", lang); > + av_dict_set(metadata, key, simpletag->string, AV_DICT_MULTIKEY); > + > + matroska_convert_simpletags(s, &simpletag->sub, metadata, > + key, key_ptr, key_size); > } > +} > + > +static void matroska_convert_tag(AVFormatContext *s, const MatroskaTag *tag, > + AVDictionary **metadata, enum AVMediaType > codec_type) > +{ > + char key[1024]; > + unsigned key_ptr; > + > + matroska_get_targettype_string(key, &key_ptr, sizeof(key), > + tag, codec_type); > + matroska_convert_simpletags(s, &tag->simpletags, metadata, > + key, key_ptr, sizeof(key)); > + > ff_metadata_conv(metadata, NULL, ff_mkv_metadata_conv); > } > > -static void matroska_convert_tags(AVFormatContext *s) > +static void matroska_convert_tags_for_attachment_cb(void *data, > + AVDictionary ***metadata, > + enum AVMediaType > *codec_type) > { > - MatroskaDemuxContext *matroska = s->priv_data; > - MatroskaTags *tags = matroska->tags.elem; > - int i, j; > + MatroskaAttachment *attachment = data; > + if (attachment->stream) { > + *metadata = &attachment->stream->metadata; > + *codec_type = attachment->stream->codecpar->codec_type; > + } > +} > > - for (i = 0; i < matroska->tags.nb_elem; i++) { > - if (tags[i].target.attachuid) { > - MatroskaAttachment *attachment = matroska->attachments.elem; > - int found = 0; > - for (j = 0; j < matroska->attachments.nb_elem; j++) { > - if (attachment[j].uid == tags[i].target.attachuid && > - attachment[j].stream) { > - matroska_convert_tag(s, &tags[i].tag, > - &attachment[j].stream->metadata, > NULL); > - found = 1; > - } > - } > - if (!found) { > - av_log(s, AV_LOG_WARNING, > - "The tags at index %d refer to a " > - "non-existent attachment %"PRId64".\n", > - i, tags[i].target.attachuid); > - } > - } else if (tags[i].target.chapteruid) { > - MatroskaChapter *chapter = matroska->chapters.elem; > - int found = 0; > - for (j = 0; j < matroska->chapters.nb_elem; j++) { > - if (chapter[j].uid == tags[i].target.chapteruid && > - chapter[j].chapter) { > - matroska_convert_tag(s, &tags[i].tag, > - &chapter[j].chapter->metadata, > NULL); > - found = 1; > - } > - } > - if (!found) { > - av_log(s, AV_LOG_WARNING, > - "The tags at index %d refer to a non-existent chapter > " > - "%"PRId64".\n", > - i, tags[i].target.chapteruid); > - } > - } else if (tags[i].target.trackuid) { > - MatroskaTrack *track = matroska->tracks.elem; > - int found = 0; > - for (j = 0; j < matroska->tracks.nb_elem; j++) { > - if (track[j].uid == tags[i].target.trackuid && > - track[j].stream) { > - matroska_convert_tag(s, &tags[i].tag, > - &track[j].stream->metadata, NULL); > - found = 1; > - } > - } > - if (!found) { > - av_log(s, AV_LOG_WARNING, > - "The tags at index %d refer to a non-existent track " > - "%"PRId64".\n", > - i, tags[i].target.trackuid); > +static void matroska_convert_tags_for_chapter_cb(void *data, > + AVDictionary ***metadata, > + enum AVMediaType > *codec_type) > +{ > + MatroskaChapter *chapter = data; > + if (chapter->chapter) > + *metadata = &chapter->chapter->metadata; > +} > + > +static void matroska_convert_tags_for_track_cb(void *data, > + AVDictionary ***metadata, > + enum AVMediaType *codec_type) > +{ > + MatroskaTrack *track = data; > + if (track->stream) { > + *metadata = &track->stream->metadata; > + *codec_type = track->stream->codecpar->codec_type; > + } > +} > + > +/* First field of elements in elem_list is expected to contain the uid. */ > +static int matroska_convert_tag_for_uids(AVFormatContext *s, const > MatroskaTag *tag, > + EbmlList *elem_list, unsigned > list_elem_size, > + EbmlList *uid_list, const char > *elem_name, > + void(*elem_cb)(void *data, > AVDictionary ***, enum AVMediaType *)) > +{ > + uint64_t *uids = uid_list->elem; > + > + for (int i = 0; i < uid_list->nb_elem; ++i) { > + uint64_t uid = uids[i]; > + int found = 0; > + > + void *data = elem_list->elem; > + for (int j = 0; j < elem_list->nb_elem; ++j, data = (char *)data + > list_elem_size) { > + uint64_t elem_uid = *(uint64_t *)data; > + > + if (uid == 0 /* Matches all. */ || uid == elem_uid) { > + AVDictionary **metadata = NULL; > + enum AVMediaType codec_type = AVMEDIA_TYPE_UNKNOWN; > + > + found = 1; > + elem_cb(data, &metadata, &codec_type); > + > + if (metadata) > + matroska_convert_tag(s, tag, metadata, codec_type); > } > - } else { > - matroska_convert_tag(s, &tags[i].tag, &s->metadata, > - tags[i].target.type); > } > + > + if (!found) > + av_log(s, AV_LOG_WARNING, > + "Tags element refer to a non-existent %s %"PRIu64".\n", > + elem_name, uid); > + } > + > + return uid_list->nb_elem; > +} > + > +static void matroska_convert_tags(AVFormatContext *s) > +{ > + MatroskaDemuxContext *matroska = s->priv_data; > + MatroskaTag *tags = matroska->tags.elem; > + > + for (int i = 0; i < matroska->tags.nb_elem; i++) { > + MatroskaTag *tag = &tags[i]; > + int any = 0; > + any |= matroska_convert_tag_for_uids(s, tag, > + &matroska->attachments, > sizeof(MatroskaAttachment), > + &tag->target.attachuids, > "attachment", > + > matroska_convert_tags_for_attachment_cb); > + any |= matroska_convert_tag_for_uids(s, tag, > + &matroska->chapters, > sizeof(MatroskaChapter), > + &tag->target.chapteruids, > "chapter", > + > matroska_convert_tags_for_chapter_cb); > + any |= matroska_convert_tag_for_uids(s, tag, > + &matroska->tracks, > sizeof(MatroskaTrack), > + &tag->target.trackuids, "track", > + > matroska_convert_tags_for_track_cb); > + if (!any) > + matroska_convert_tag(s, tag, &s->metadata, AVMEDIA_TYPE_UNKNOWN); > } > } > > diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c > index 233c472..2a098c1 100644 > --- a/libavformat/matroskaenc.c > +++ b/libavformat/matroskaenc.c > @@ -61,6 +61,10 @@ > #define IS_SEEKABLE(pb, mkv) (((pb)->seekable & AVIO_SEEKABLE_NORMAL) && \ > !(mkv)->is_live) > > +#define TAG_DURATION_PLACEHOLDER "00:00:00.000000000" > + > +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) > + > enum { > DEFAULT_MODE_INFER, > DEFAULT_MODE_INFER_NO_SUBS, > @@ -1433,58 +1437,185 @@ static int mkv_write_tracks(AVFormatContext *s) > MATROSKA_ID_TRACKS); > } > > -static int mkv_write_simpletag(AVIOContext *pb, const AVDictionaryEntry *t) > +typedef struct { > + uint64_t typevalue; > + uint64_t uid; > + uint32_t uid_elementid; > + const char *lang; > + unsigned def; > + char *data; > + /* (TargetValue, Tag), [ (TagName, SimpleTag)... ] */ > + unsigned depth; > + struct { > + char *name; > + ebml_master tag; > + } levels[EBML_MAX_DEPTH - 1 /* Tags */ - 1 /* Segment */]; > +} TagWriterContext; > + > +static int mkv_start_tag(MatroskaMuxContext *mkv, AVIOContext **pb, > + TagWriterContext *tw) > +{ > + ebml_master targets; > + int ret; > + const char *type = tw->levels[0].name; > + > + if (!*pb) { > + ret = start_ebml_master_crc32(pb, mkv); > + if (ret < 0) > + return ret; > + } > + > + tw->levels[0].tag = start_ebml_master(*pb, MATROSKA_ID_TAG, 0); > + targets = start_ebml_master(*pb, MATROSKA_ID_TAGTARGETS, 0); > + > + if (*type || tw->typevalue != MATROSKA_DEFAULT_TAGTARGETS_TYPEVALUE) > + put_ebml_uint(*pb, MATROSKA_ID_TAGTARGETS_TYPEVALUE, tw->typevalue); > + if (*type) > + put_ebml_string(*pb, MATROSKA_ID_TAGTARGETS_TYPE, type); > + if (tw->uid_elementid) > + put_ebml_uid(*pb, tw->uid_elementid, tw->uid); > + > + end_ebml_master(*pb, targets); > + return 0; > +} > + > +static uint64_t mkv_type2typevalue(const char *type) > { > + char type_buf[sizeof(((TypeValueConv *)0)->str)]; > + > + strncpy(type_buf, type, sizeof type_buf); > + > + for (const TypeValueConv *conv = ff_mkv_typevalue_conv; > + conv->typevalue; > + ++conv) > + if (!memcmp(type_buf, conv->str, sizeof(type_buf))) > + return conv->typevalue; > + > + return MATROSKA_DEFAULT_TAGTARGETS_TYPEVALUE; > +} > + > +static int mkv_tagwriter_init(TagWriterContext *tw, const AVDictionaryEntry > *t) > +{ > + enum { MAX_DEPTH = ARRAY_SIZE(tw->levels) }; > + > uint8_t *key = av_strdup(t->key); > - uint8_t *p = key; > - const uint8_t *lang = NULL; > - ebml_master tag; > + char *p = key; > + unsigned depth; > > if (!key) > return AVERROR(ENOMEM); > > - if ((p = strrchr(p, '-')) && > - (lang = ff_convert_lang_to(p + 1, AV_LANG_ISO639_2_BIBL))) > - *p = 0; > + /* TAG@-lang */ > + if ((p = strrchr(p, '@'))) { > + tw->def = p[1] != '-'; > + tw->lang = ff_convert_lang_to(p + 1 + !tw->def, > AV_LANG_ISO639_2_BIBL); > + *p = '\0'; > + } else { > + tw->def = 1; > + tw->lang = NULL; > + } > > - p = key; > - while (*p) { > + /* "hello world" -> "HELLO_WORLD" */ > + for (p = key; *p; ++p) > if (*p == ' ') > *p = '_'; > - else if (*p >= 'a' && *p <= 'z') > + else if ('a' <= *p && *p <= 'z') > *p -= 'a' - 'A'; > - p++; > - } > > - tag = start_ebml_master(pb, MATROSKA_ID_SIMPLETAG, 0); > - put_ebml_string(pb, MATROSKA_ID_TAGNAME, key); > - if (lang) > - put_ebml_string(pb, MATROSKA_ID_TAGLANG, lang); > - put_ebml_string(pb, MATROSKA_ID_TAGSTRING, t->value); > - end_ebml_master(pb, tag); > + tw->data = key; > + > + depth = 0; > + p = key; > + for (;;) { > + tw->levels[depth++].name = p; > + if (!(p = strchr(p, '/')) || MAX_DEPTH <= depth) > + break; > + *p++ = '\0'; > + } > + /* Use default TagTarget for simple tag names (no '/'). */ > + if (1 == depth) { > + tw->levels[1].name = tw->levels[0].name; > + tw->levels[0].name = (char *)""; > + depth++; > + tw->typevalue = MATROSKA_DEFAULT_TAGTARGETS_TYPEVALUE; > + } else { > + /* Try parse "123TARGETTYPE". */ > + p = tw->levels[0].name; > + if ('0' <= *p && *p <= '9') { > + tw->typevalue = 0; > + do > + tw->typevalue = tw->typevalue * 10 + (*p++ - '0'); > + while ('0' <= *p && *p <= '9'); > + tw->levels[0].name = *p ? p : (char *)""; > + } else { > + tw->typevalue = mkv_type2typevalue(p); > + } > + } > + tw->depth = depth; > > - av_freep(&key); > return 0; > } > > -static int mkv_write_tag_targets(MatroskaMuxContext *mkv, AVIOContext **pb, > - ebml_master *tag, uint32_t elementid, > uint64_t uid) > +static int mkv_write_simpletag(MatroskaMuxContext *mkv, AVIOContext **pb, > + TagWriterContext *ptw, const > AVDictionaryEntry *t, > + uint32_t uid_elementid, uint64_t uid) > { > - ebml_master targets; > + TagWriterContext tw; > + unsigned i = 0; > int ret; > > - if (!*pb) { > - ret = start_ebml_master_crc32(pb, mkv); > + if (t) { > + ret = mkv_tagwriter_init(&tw, t); > if (ret < 0) > - return ret; > + goto out; > + > + tw.uid = uid; > + tw.uid_elementid = uid_elementid; > + } > + > + /* 1. Find common levels. */ > + if (ptw) { > + if (t && > + ptw->uid == tw.uid && > + ptw->uid_elementid == tw.uid_elementid) > + for (; > + i < tw.depth && i < ptw->depth && > + !strcmp(tw.levels[i].name, ptw->levels[i].name); > + ++i) > + tw.levels[i].tag = ptw->levels[i].tag; > + > + /* 2. Close unneded tags. */ > + for (unsigned j = ptw->depth; i < j;) > + end_ebml_master(*pb, ptw->levels[--j].tag); > + } > + > + /* 3. Open new tags. */ > + if (t) { > + for (; i < tw.depth; ++i) { > + if (!i) { > + ret = mkv_start_tag(mkv, pb, &tw); > + if (ret < 0) > + return ret; > + } else { > + tw.levels[i].tag = start_ebml_master(*pb, > MATROSKA_ID_SIMPLETAG, 0); > + put_ebml_string(*pb, MATROSKA_ID_TAGNAME, tw.levels[i].name); > + } > + } > + > + /* 4. Write contents to the last one (intermediate tags will have > name only). */ > + if (tw.lang) > + put_ebml_string(*pb, MATROSKA_ID_TAGLANG, tw.lang); > + if (!tw.def) > + put_ebml_uint(*pb, MATROSKA_ID_TAGDEFAULT, tw.def); > + put_ebml_string(*pb, MATROSKA_ID_TAGSTRING, t->value); > } > > - *tag = start_ebml_master(*pb, MATROSKA_ID_TAG, 0); > - targets = start_ebml_master(*pb, MATROSKA_ID_TAGTARGETS, 4 + 1 + 8); > - if (elementid) > - put_ebml_uid(*pb, elementid, uid); > - end_ebml_master(*pb, targets); > - return 0; > +out: > + if (ptw) > + av_free(ptw->data); > + /* Save new state. */ > + memcpy(ptw, &tw, sizeof(tw)); > + return ret; > } > > static int mkv_check_tag_name(const char *name, uint32_t elementid) > @@ -1501,58 +1632,36 @@ static int mkv_check_tag_name(const char *name, > uint32_t elementid) > av_strcasecmp(name, "mimetype"))); > } > > -static int mkv_write_tag(MatroskaMuxContext *mkv, const AVDictionary *m, > - AVIOContext **pb, ebml_master *tag, > +static int mkv_write_tag(MatroskaMuxContext *mkv, const AVDictionary > *metadata, > + AVIOContext **pb, TagWriterContext *tw, > uint32_t elementid, uint64_t uid) > { > const AVDictionaryEntry *t = NULL; > - ebml_master tag2; > int ret; > > - ret = mkv_write_tag_targets(mkv, pb, tag ? tag : &tag2, elementid, uid); > - if (ret < 0) > - return ret; > - > - while ((t = av_dict_get(m, "", t, AV_DICT_IGNORE_SUFFIX))) { > + while ((t = av_dict_get(metadata, "", t, AV_DICT_IGNORE_SUFFIX))) { > if (mkv_check_tag_name(t->key, elementid)) { > - ret = mkv_write_simpletag(*pb, t); > + ret = mkv_write_simpletag(mkv, pb, tw, t, elementid, uid); > if (ret < 0) > return ret; > } > } > - > - if (!tag) > - end_ebml_master(*pb, tag2); > - > return 0; > } > > -static int mkv_check_tag(const AVDictionary *m, uint32_t elementid) > -{ > - const AVDictionaryEntry *t = NULL; > - > - while ((t = av_dict_get(m, "", t, AV_DICT_IGNORE_SUFFIX))) > - if (mkv_check_tag_name(t->key, elementid)) > - return 1; > - > - return 0; > -} > - > -static int mkv_write_tags(AVFormatContext *s) > +static int mkv_write_tags(AVFormatContext *s, TagWriterContext *tw) > { > MatroskaMuxContext *mkv = s->priv_data; > - ebml_master tag, *tagp = IS_SEEKABLE(s->pb, mkv) ? &tag : NULL; > int i, ret; > + AVIOContext **pb = &mkv->tags.bc; > > mkv->wrote_tags = 1; > > ff_metadata_conv_ctx(s, ff_mkv_metadata_conv, NULL); > > - if (mkv_check_tag(s->metadata, 0)) { > - ret = mkv_write_tag(mkv, s->metadata, &mkv->tags.bc, NULL, 0, 0); > - if (ret < 0) > - return ret; > - } > + ret = mkv_write_tag(mkv, s->metadata, pb, tw, 0, 0); > + if (ret < 0) > + return ret; > > for (i = 0; i < s->nb_streams; i++) { > const AVStream *st = s->streams[i]; > @@ -1561,28 +1670,24 @@ static int mkv_write_tags(AVFormatContext *s) > if (st->codecpar->codec_type == AVMEDIA_TYPE_ATTACHMENT) > continue; > > - if (!tagp && !mkv_check_tag(st->metadata, > MATROSKA_ID_TAGTARGETS_TRACKUID)) > - continue; > - > - ret = mkv_write_tag(mkv, st->metadata, &mkv->tags.bc, tagp, > + ret = mkv_write_tag(mkv, st->metadata, pb, tw, > MATROSKA_ID_TAGTARGETS_TRACKUID, track->uid); > if (ret < 0) > return ret; > > - if (tagp) { > - AVIOContext *pb = mkv->tags.bc; > - ebml_master simpletag; > + if (IS_SEEKABLE(s->pb, mkv)) { > + static const AVDictionaryEntry duration_tag = { > + .key = (char *)"DURATION", > + .value = (char *)TAG_DURATION_PLACEHOLDER, > + }; > > - simpletag = start_ebml_master(pb, MATROSKA_ID_SIMPLETAG, > - 2 + 1 + 8 + 23); > - put_ebml_string(pb, MATROSKA_ID_TAGNAME, "DURATION"); > - track->duration_offset = avio_tell(pb); > + ret = mkv_write_simpletag(mkv, pb, tw, &duration_tag, > + MATROSKA_ID_TAGTARGETS_TRACKUID, > track->uid); > + if (ret < 0) > + return ret; > > - // Reserve space to write duration as a 20-byte string. > - // 2 (ebml id) + 1 (data size) + 20 (data) > - put_ebml_void(pb, 23); > - end_ebml_master(pb, simpletag); > - end_ebml_master(pb, tag); > + /* Save position at the start of tag value. */ > + track->duration_offset = avio_tell(*pb) - > (sizeof(TAG_DURATION_PLACEHOLDER) - 1); > } > } > > @@ -1594,24 +1699,24 @@ static int mkv_write_tags(AVFormatContext *s) > if (st->codecpar->codec_type != AVMEDIA_TYPE_ATTACHMENT) > continue; > > - if (!mkv_check_tag(st->metadata, > MATROSKA_ID_TAGTARGETS_ATTACHUID)) > - continue; > - > - ret = mkv_write_tag(mkv, st->metadata, &mkv->tags.bc, NULL, > + ret = mkv_write_tag(mkv, st->metadata, pb, tw, > MATROSKA_ID_TAGTARGETS_ATTACHUID, > track->uid); > if (ret < 0) > return ret; > } > } > > - if (mkv->tags.bc) { > + ret = mkv_write_simpletag(mkv, pb, tw, NULL, 0, 0); > + if (ret < 0) > + return ret; > + > + if (mkv->tags.bc) > return end_ebml_master_crc32_tentatively(s->pb, &mkv->tags, mkv, > MATROSKA_ID_TAGS); > - } > return 0; > } > > -static int mkv_write_chapters(AVFormatContext *s) > +static int mkv_write_chapters(AVFormatContext *s, TagWriterContext *tw) > { > MatroskaMuxContext *mkv = s->priv_data; > AVIOContext *dyn_cp = NULL, *dyn_tags = NULL, **tags, *pb = s->pb; > @@ -1669,23 +1774,29 @@ static int mkv_write_chapters(AVFormatContext *s) > } > end_ebml_master(dyn_cp, chapteratom); > > - if (tags && mkv_check_tag(c->metadata, > MATROSKA_ID_TAGTARGETS_CHAPTERUID)) { > - ret = mkv_write_tag(mkv, c->metadata, tags, NULL, > + if (tags) { > + ret = mkv_write_tag(mkv, c->metadata, tags, tw, > MATROSKA_ID_TAGTARGETS_CHAPTERUID, > (uint32_t)c->id + chapter_id_offset); > if (ret < 0) > goto fail; > } > } > + > end_ebml_master(dyn_cp, editionentry); > mkv->wrote_chapters = 1; > > ret = end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS, 0, > 0, 1); > if (ret < 0) > goto fail; > - if (dyn_tags) > + if (dyn_tags) { > + ret = mkv_write_simpletag(mkv, &dyn_tags, tw, NULL, 0, 0); > + if (ret < 0) > + return ret; > + > return end_ebml_master_crc32(pb, &dyn_tags, mkv, > MATROSKA_ID_TAGS, 0, 0, 1); > + } > return 0; > > fail: > @@ -1791,6 +1902,7 @@ static int mkv_write_header(AVFormatContext *s) > const AVDictionaryEntry *tag; > int ret, i, version = 2; > int64_t creation_time; > + TagWriterContext tw = { 0 }; > > if (mkv->mode != MODE_WEBM || > av_dict_get(s->metadata, "stereo_mode", NULL, 0) || > @@ -1881,7 +1993,7 @@ static int mkv_write_header(AVFormatContext *s) > if (ret < 0) > return ret; > > - ret = mkv_write_chapters(s); > + ret = mkv_write_chapters(s, &tw); > if (ret < 0) > return ret; > > @@ -1893,7 +2005,7 @@ static int mkv_write_header(AVFormatContext *s) > > /* Must come after mkv_write_chapters() to write chapter tags > * into the same Tags element as the other tags. */ > - ret = mkv_write_tags(s); > + ret = mkv_write_tags(s, &tw); > if (ret < 0) > return ret; > > @@ -2458,6 +2570,7 @@ static int mkv_write_trailer(AVFormatContext *s) > AVIOContext *pb = s->pb; > int64_t endpos, ret64; > int ret, ret2 = 0; > + TagWriterContext tw = { 0 }; > > // check if we have an audio packet cached > if (mkv->cur_audio_pkt.size > 0) { > @@ -2476,7 +2589,7 @@ static int mkv_write_trailer(AVFormatContext *s) > return ret; > } > > - ret = mkv_write_chapters(s); > + ret = mkv_write_chapters(s, &tw); > if (ret < 0) > return ret; > > @@ -2579,20 +2692,19 @@ after_cues: > const AVStream *st = s->streams[i]; > const mkv_track *track = &mkv->tracks[i]; > > - if (track->duration_offset > 0) { > + if (track->duration_offset) { > double duration_sec = track->duration * > av_q2d(st->time_base); > - char duration_string[20] = ""; > + char duration_string[sizeof TAG_DURATION_PLACEHOLDER]; > > av_log(s, AV_LOG_DEBUG, "stream %d end duration = %" PRIu64 > "\n", i, > track->duration); > > - avio_seek(mkv->tags.bc, track->duration_offset, SEEK_SET); > - > - snprintf(duration_string, 20, "%02d:%02d:%012.9f", > - (int) duration_sec / 3600, ((int) duration_sec / > 60) % 60, > + snprintf(duration_string, sizeof duration_string, > "%02d:%02d:%012.9f", > + (int)duration_sec / 3600, ((int)duration_sec / 60) > % 60, > fmod(duration_sec, 60)); > > - put_ebml_binary(mkv->tags.bc, MATROSKA_ID_TAGSTRING, > duration_string, 20); > + avio_seek(mkv->tags.bc, track->duration_offset, SEEK_SET); > + avio_write(mkv->tags.bc, duration_string, sizeof > TAG_DURATION_PLACEHOLDER - 1); > } > } > >
Your patch requires changes to FATE, our regression-testsuite. This is not bad in itself and to be expected from a patch like this, but the changes need to be part of the very same patch. - Andreas _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".