summaryrefslogtreecommitdiff
path: root/gst
diff options
context:
space:
mode:
authorAlexander Saprykin <xelfium@gmail.com>2012-03-29 23:05:14 +0400
committerStefan Sauer <ensonic@users.sf.net>2012-04-02 22:11:51 +0200
commitbd7761635ab00975889ca6f34ac2ce32ce7294ea (patch)
tree4f090c4cde93ded25ecf8e5b680d96634fd8f124 /gst
parente7807712231f0674a3a0d31534e3d5699272e48d (diff)
downloadgstreamer-plugins-good-bd7761635ab00975889ca6f34ac2ce32ce7294ea.tar.gz
matroska: add chapter support in GstMatroskaReadCommon
Diffstat (limited to 'gst')
-rw-r--r--gst/matroska/matroska-read-common.c437
-rw-r--r--gst/matroska/matroska-read-common.h8
2 files changed, 434 insertions, 11 deletions
diff --git a/gst/matroska/matroska-read-common.c b/gst/matroska/matroska-read-common.c
index d2c2bea94..a08e880b2 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 cf617e6e8..91e66461d 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;