From: Alexander Saprykin Date: Thu, 29 Mar 2012 19:05:14 +0000 (+0400) Subject: matroska: add chapter support in GstMatroskaReadCommon X-Git-Tag: RELEASE-0.11.90~22^2~2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=bd7761635ab00975889ca6f34ac2ce32ce7294ea;p=platform%2Fupstream%2Fgst-plugins-good.git matroska: add chapter support in GstMatroskaReadCommon --- diff --git a/gst/matroska/matroska-read-common.c b/gst/matroska/matroska-read-common.c index d2c2bea..a08e880 100644 --- a/gst/matroska/matroska-read-common.c +++ b/gst/matroska/matroska-read-common.c @@ -56,6 +56,10 @@ GST_DEBUG_CATEGORY (matroskareadcommon_debug); GST_DEBUG_OBJECT (common, "Parsing " element " element " \ " finished with '%s'", gst_flow_get_name (ret)) +#define GST_MATROSKA_TOC_UID_CHAPTER "chapter" +#define GST_MATROSKA_TOC_UID_EDITION "edition" +#define GST_MATROSKA_TOC_UID_EMPTY "empty" + static gboolean gst_matroska_decompress_data (GstMatroskaTrackEncoding * enc, guint8 ** data_out, guint * size_out, @@ -694,16 +698,375 @@ gst_matroska_read_common_parse_attachments (GstMatroskaReadCommon * common, return ret; } +static void +gst_matroska_read_common_parse_toc_tag (GstTocEntry * entry, + GArray * edition_targets, GArray * chapter_targtes, GstTagList * tags) +{ + gchar *uid; + guint i; + guint64 tgt; + GArray *targets; + GList *cur; + + targets = + (entry->type == + GST_TOC_ENTRY_TYPE_EDITION) ? edition_targets : chapter_targtes; + + for (i = 0; i < targets->len; ++i) { + tgt = g_array_index (targets, guint64, i); + + if (tgt == 0) + gst_tag_list_insert (entry->tags, tags, GST_TAG_MERGE_APPEND); + else { + uid = g_strdup_printf ("%" G_GUINT64_FORMAT, tgt); + if (g_strcmp0 (entry->uid, uid) == 0) + gst_tag_list_insert (entry->tags, tags, GST_TAG_MERGE_APPEND); + g_free (uid); + } + } + + cur = entry->subentries; + while (cur != NULL) { + gst_matroska_read_common_parse_toc_tag (cur->data, edition_targets, + chapter_targtes, tags); + cur = cur->next; + } +} + +static GstFlowReturn +gst_matroska_read_common_parse_metadata_targets (GstMatroskaReadCommon * common, + GstEbmlRead * ebml, GArray * edition_targets, GArray * chapter_targets) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + guint64 uid; + + DEBUG_ELEMENT_START (common, ebml, "TagTargets"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (common, ebml, "TagTargets", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_TARGETCHAPTERUID: + if ((ret = gst_ebml_read_uint (ebml, &id, &uid)) == GST_FLOW_OK) + g_array_append_val (chapter_targets, uid); + break; + + case GST_MATROSKA_ID_TARGETEDITIONUID: + if ((ret = gst_ebml_read_uint (ebml, &id, &uid)) == GST_FLOW_OK) + g_array_append_val (edition_targets, uid); + break; + + default: + ret = + gst_matroska_read_common_parse_skip (common, ebml, "TagTargets", + id); + break; + } + } + + DEBUG_ELEMENT_STOP (common, ebml, "TagTargets", ret); + + return ret; +} + +static void +gst_matroska_read_common_postprocess_toc_entries (GList * toc_entries, + guint64 max, const gchar * parent_uid) +{ + GstTocEntry *cur_info, *prev_info, *next_info; + GList *cur_list, *prev_list, *next_list; + gchar *iter_digit; + gint i = 0; + gint64 cur_start, prev_start, stop; + + cur_list = toc_entries; + while (cur_list != NULL) { + ++i; + cur_info = cur_list->data; + + iter_digit = g_strdup_printf ("%d", i); + + switch (cur_info->type) { + case GST_TOC_ENTRY_TYPE_EDITION: + /* in Matroska terms edition has duration of full track */ + gst_toc_entry_set_start_stop (cur_info, 0, max); + + if (cur_info->uid == NULL) + cur_info->uid = + g_strconcat (parent_uid, "/", GST_MATROSKA_TOC_UID_EDITION, + iter_digit, NULL); + + gst_matroska_read_common_postprocess_toc_entries (cur_info->subentries, + max, cur_info->uid); + break; + + case GST_TOC_ENTRY_TYPE_CHAPTER: + prev_list = cur_list->prev; + next_list = cur_list->next; + + if (prev_list != NULL) + prev_info = prev_list->data; + else + prev_info = NULL; + + if (next_list != NULL) + next_info = next_list->data; + else + next_info = NULL; + + if (cur_info->uid == NULL) + cur_info->uid = + g_strconcat (parent_uid, "/", GST_MATROSKA_TOC_UID_CHAPTER, + iter_digit, NULL); + + /* updated stop time in previous chapter and it's subchapters */ + if (prev_info != NULL) { + gst_toc_entry_get_start_stop (prev_info, &prev_start, &stop); + gst_toc_entry_get_start_stop (cur_info, &cur_start, &stop); + + stop = cur_start; + gst_toc_entry_set_start_stop (prev_info, prev_start, stop); + + gst_matroska_read_common_postprocess_toc_entries + (prev_info->subentries, cur_start, prev_info->uid); + } + + /* updated stop time in current chapter and it's subchapters */ + if (next_info == NULL) { + gst_toc_entry_get_start_stop (cur_info, &cur_start, &stop); + + if (stop == -1) { + stop = max; + gst_toc_entry_set_start_stop (cur_info, cur_start, stop); + } + + gst_matroska_read_common_postprocess_toc_entries + (cur_info->subentries, stop, cur_info->uid); + } + break; + } + cur_list = cur_list->next; + g_free (iter_digit); + } +} + +static GstFlowReturn +gst_matroska_read_common_parse_chapter_titles (GstMatroskaReadCommon * common, + GstEbmlRead * ebml, GstTagList * titles) +{ + guint32 id; + gchar *title = NULL; + GstFlowReturn ret = GST_FLOW_OK; + + DEBUG_ELEMENT_START (common, ebml, "ChaptersTitles"); + + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (common, ebml, "ChaptersTitles", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_CHAPSTRING: + ret = gst_ebml_read_utf8 (ebml, &id, &title); + break; + + default: + ret = + gst_matroska_read_common_parse_skip (common, ebml, "ChaptersTitles", + id); + break; + } + } + + DEBUG_ELEMENT_STOP (common, ebml, "ChaptersTitles", ret); + + if (title != NULL && ret == GST_FLOW_OK) + gst_tag_list_add (titles, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, title, NULL); + + g_free (title); + return ret; +} + +static GstFlowReturn +gst_matroska_read_common_parse_chapter_element (GstMatroskaReadCommon * common, + GstEbmlRead * ebml, GstTocEntry * toc_entry) +{ + guint32 id; + guint64 start_time = -1, stop_time = -1; + guint64 is_hidden = 0, is_enabled = 1, uid = 0; + GstFlowReturn ret = GST_FLOW_OK; + GstTocEntry *chapter_info; + GstTagList *titles; + + DEBUG_ELEMENT_START (common, ebml, "ChaptersElement"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (common, ebml, "ChaptersElement", ret); + return ret; + } + + titles = gst_tag_list_new (); + chapter_info = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, + GST_MATROSKA_TOC_UID_EMPTY); + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_CHAPTERUID: + ret = gst_ebml_read_uint (ebml, &id, &uid); + break; + + case GST_MATROSKA_ID_CHAPTERTIMESTART: + ret = gst_ebml_read_uint (ebml, &id, &start_time); + break; + + case GST_MATROSKA_ID_CHAPTERTIMESTOP: + ret = gst_ebml_read_uint (ebml, &id, &stop_time); + break; + + case GST_MATROSKA_ID_CHAPTERATOM: + ret = + gst_matroska_read_common_parse_chapter_element (common, ebml, + chapter_info); + break; + + case GST_MATROSKA_ID_CHAPTERDISPLAY: + ret = + gst_matroska_read_common_parse_chapter_titles (common, ebml, + titles); + break; + + case GST_MATROSKA_ID_CHAPTERFLAGHIDDEN: + ret = gst_ebml_read_uint (ebml, &id, &is_hidden); + break; + + case GST_MATROSKA_ID_CHAPTERFLAGENABLED: + ret = gst_ebml_read_uint (ebml, &id, &is_enabled); + break; + + default: + ret = + gst_matroska_read_common_parse_skip (common, ebml, + "ChaptersElement", id); + break; + } + } + + gst_toc_entry_set_start_stop (chapter_info, start_time, stop_time); + + DEBUG_ELEMENT_STOP (common, ebml, "ChaptersElement", ret); + + g_free (chapter_info->uid); + + if (uid != 0) + chapter_info->uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uid); + else + chapter_info->uid = NULL; + + /* start time is mandatory and has no default value, + * so we should skip chapters without it */ + if (is_hidden == 0 && is_enabled > 0 && + start_time != -1 && ret == GST_FLOW_OK) { + if (!gst_tag_list_is_empty (titles)) + gst_tag_list_insert (chapter_info->tags, titles, GST_TAG_MERGE_APPEND); + + toc_entry->subentries = g_list_append (toc_entry->subentries, chapter_info); + } else + gst_toc_entry_free (chapter_info); + + gst_tag_list_free (titles); + return ret; +} + +static GstFlowReturn +gst_matroska_read_common_parse_chapter_edition (GstMatroskaReadCommon * common, + GstEbmlRead * ebml, GstToc * toc) +{ + guint32 id; + guint64 is_hidden = 0, uid = 0; + GstFlowReturn ret = GST_FLOW_OK; + GstTocEntry *edition_info; + + DEBUG_ELEMENT_START (common, ebml, "ChaptersEdition"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (common, ebml, "ChaptersEdition", ret); + return ret; + } + + edition_info = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, + GST_MATROSKA_TOC_UID_EMPTY); + + gst_toc_entry_set_start_stop (edition_info, -1, -1); + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_EDITIONUID: + ret = gst_ebml_read_uint (ebml, &id, &uid); + break; + + case GST_MATROSKA_ID_CHAPTERATOM: + ret = + gst_matroska_read_common_parse_chapter_element (common, ebml, + edition_info); + break; + + case GST_MATROSKA_ID_EDITIONFLAGHIDDEN: + ret = gst_ebml_read_uint (ebml, &id, &is_hidden); + break; + + default: + ret = + gst_matroska_read_common_parse_skip (common, ebml, + "ChaptersEdition", id); + break; + } + } + + DEBUG_ELEMENT_STOP (common, ebml, "ChaptersEdition", ret); + + g_free (edition_info->uid); + + if (uid != 0) + edition_info->uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uid); + else + edition_info->uid = NULL; + + if (is_hidden == 0 && edition_info->subentries != NULL && ret == GST_FLOW_OK) + toc->entries = g_list_prepend (toc->entries, edition_info); + else { + GST_DEBUG_OBJECT (common, + "Skipping empty or hidden edition in the chapters TOC"); + gst_toc_entry_free (edition_info); + } + + return ret; +} + GstFlowReturn gst_matroska_read_common_parse_chapters (GstMatroskaReadCommon * common, GstEbmlRead * ebml) { guint32 id; GstFlowReturn ret = GST_FLOW_OK; - - GST_WARNING_OBJECT (common, "Parsing of chapters not implemented yet"); - - /* TODO: implement parsing of chapters */ + GstToc *toc; DEBUG_ELEMENT_START (common, ebml, "Chapters"); @@ -712,17 +1075,36 @@ gst_matroska_read_common_parse_chapters (GstMatroskaReadCommon * common, return ret; } + toc = gst_toc_new (); + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; switch (id) { + case GST_MATROSKA_ID_EDITIONENTRY: + ret = + gst_matroska_read_common_parse_chapter_edition (common, ebml, toc); + break; + default: - ret = gst_ebml_read_skip (ebml); + ret = + gst_matroska_read_common_parse_skip (common, ebml, "Chapters", id); break; } } + if (toc->entries != NULL) { + toc->entries = g_list_reverse (toc->entries); + gst_matroska_read_common_postprocess_toc_entries (toc->entries, + common->segment.duration, ""); + + common->toc = toc; + } else + gst_toc_free (toc); + + common->chapters_parsed = TRUE; + DEBUG_ELEMENT_STOP (common, ebml, "Chapters", ret); return ret; } @@ -1470,6 +1852,9 @@ gst_matroska_read_common_parse_metadata_id_tag (GstMatroskaReadCommon * common, { guint32 id; GstFlowReturn ret; + GArray *chapter_targets, *edition_targets; + GstTagList *taglist; + GList *cur; DEBUG_ELEMENT_START (common, ebml, "Tag"); @@ -1478,6 +1863,10 @@ gst_matroska_read_common_parse_metadata_id_tag (GstMatroskaReadCommon * common, return ret; } + edition_targets = g_array_new (FALSE, FALSE, sizeof (guint64)); + chapter_targets = g_array_new (FALSE, FALSE, sizeof (guint64)); + taglist = gst_tag_list_new (); + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { /* read all sub-entries */ @@ -1487,7 +1876,13 @@ gst_matroska_read_common_parse_metadata_id_tag (GstMatroskaReadCommon * common, switch (id) { case GST_MATROSKA_ID_SIMPLETAG: ret = gst_matroska_read_common_parse_metadata_id_simple_tag (common, - ebml, p_taglist); + ebml, &taglist); + break; + + case GST_MATROSKA_ID_TARGETS: + ret = + gst_matroska_read_common_parse_metadata_targets (common, ebml, + edition_targets, chapter_targets); break; default: @@ -1498,6 +1893,27 @@ gst_matroska_read_common_parse_metadata_id_tag (GstMatroskaReadCommon * common, DEBUG_ELEMENT_STOP (common, ebml, "Tag", ret); + /* if tag is chapter/edition specific - try to find that entry */ + if (G_UNLIKELY (chapter_targets->len > 0 || edition_targets->len > 0)) { + if (common->toc == NULL) + GST_WARNING_OBJECT (common, + "Found chapter/edition specific tag, but TOC doesn't present"); + else { + cur = common->toc->entries; + while (cur != NULL) { + gst_matroska_read_common_parse_toc_tag (cur->data, edition_targets, + chapter_targets, taglist); + cur = cur->next; + } + common->toc_updated = TRUE; + } + } else + gst_tag_list_insert (*p_taglist, taglist, GST_TAG_MERGE_APPEND); + + gst_tag_list_free (taglist); + g_array_unref (chapter_targets); + g_array_unref (edition_targets); + return ret; } @@ -1537,6 +1953,7 @@ gst_matroska_read_common_parse_metadata (GstMatroskaReadCommon * common, } taglist = gst_tag_list_new (); + common->toc_updated = FALSE; while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) @@ -1552,15 +1969,15 @@ gst_matroska_read_common_parse_metadata (GstMatroskaReadCommon * common, ret = gst_matroska_read_common_parse_skip (common, ebml, "Tags", id); break; /* FIXME: Use to limit the tags to specific pads */ - case GST_MATROSKA_ID_TARGETS: - ret = gst_ebml_read_skip (ebml); - break; } } DEBUG_ELEMENT_STOP (common, ebml, "Tags", ret); - gst_matroska_read_common_found_global_tag (common, el, taglist); + if (G_LIKELY (!gst_tag_list_is_empty (taglist))) + gst_matroska_read_common_found_global_tag (common, el, taglist); + else + gst_tag_list_free (taglist); return ret; } diff --git a/gst/matroska/matroska-read-common.h b/gst/matroska/matroska-read-common.h index cf617e6..91e6646 100644 --- a/gst/matroska/matroska-read-common.h +++ b/gst/matroska/matroska-read-common.h @@ -59,12 +59,18 @@ typedef struct _GstMatroskaReadCommon { /* state */ GstMatroskaReadState state; + /* did we parse cues/tracks/segmentinfo already? */ gboolean index_parsed; gboolean segmentinfo_parsed; gboolean attachments_parsed; + gboolean chapters_parsed; GList *tags_parsed; + /* chapters stuff */ + GstToc *toc; + gboolean toc_updated; + /* start-of-segment */ guint64 ebml_segment_start; @@ -80,7 +86,7 @@ typedef struct _GstMatroskaReadCommon { GstTagList *global_tags; /* pull mode caching */ - GstBuffer *cached_buffer; + GstBuffer *cached_buffer; /* push and pull mode */ guint64 offset;