Add generic table of contents (TOC) support
authorAlexander Saprykin <xelfium@gmail.com>
Wed, 14 Mar 2012 16:01:51 +0000 (20:01 +0400)
committerStefan Sauer <ensonic@users.sf.net>
Mon, 2 Apr 2012 08:49:38 +0000 (10:49 +0200)
gst/Makefile.am
gst/gst.c
gst/gst.h
gst/gst_private.h
gst/gsttoc.c [new file with mode: 0644]
gst/gsttoc.h [new file with mode: 0644]

index 60d4b11a0214efbc66342910f80271e21aa26621..a9852de903b89a4fb68e0b3bc8e1fff58f403f9c 100644 (file)
@@ -98,6 +98,7 @@ libgstreamer_@GST_MAJORMINOR@_la_SOURCES = \
        gsttagsetter.c          \
        gsttask.c               \
        gsttaskpool.c           \
+       gsttoc.c                \
        $(GST_TRACE_SRC)        \
        gsttypefind.c           \
        gsttypefindfactory.c    \
@@ -188,6 +189,7 @@ gst_headers =                       \
        gsttagsetter.h          \
        gsttask.h               \
        gsttaskpool.h           \
+       gsttoc.h                \
        gsttrace.h              \
        gsttypefind.h           \
        gsttypefindfactory.h    \
index 8e6c54280c44117e472da2b0e07b1c0733a5f656..2323d3f64eef0283749e841d09c25aa3486618ef 100644 (file)
--- a/gst/gst.c
+++ b/gst/gst.c
@@ -767,6 +767,7 @@ init_post (GOptionContext * context, GOptionGroup * group, gpointer data,
   gst_buffer_list_iterator_get_type ();
   _gst_message_initialize ();
   _gst_tag_initialize ();
+  _gst_toc_initialize ();
   gst_parse_context_get_type ();
 
   _gst_plugin_initialize ();
index 35ef29035d9e6834a191a601d8f59e8dc617ad92..afe276fe8e2003040aabd2c510f1098dd184f21e 100644 (file)
--- a/gst/gst.h
+++ b/gst/gst.h
@@ -68,6 +68,7 @@
 #include <gst/gsttagsetter.h>
 #include <gst/gsttask.h>
 #include <gst/gsttaskpool.h>
+#include <gst/gsttoc.h>
 #include <gst/gsttrace.h>
 #include <gst/gsttypefind.h>
 #include <gst/gsttypefindfactory.h>
index c36a72d99d293b0b513771505068057b2962546f..78937e613e124293cfbeec4dbf615a58b475135d 100644 (file)
@@ -54,6 +54,9 @@ extern const char             g_log_domain_gstreamer[];
 /* for GstElement */
 #include "gstelement.h"
 
+/* for GstToc */
+#include "gsttoc.h"
+
 G_BEGIN_DECLS
 
 /* used by gstparse.c and grammar.y */
@@ -109,6 +112,17 @@ void  _gst_query_initialize (void);
 void  _gst_tag_initialize (void);
 void  _gst_value_initialize (void);
 
+void  _gst_toc_initialize (void);
+
+/* TOC functions */
+/* These functions are used to parse TOC messages, events and queries */
+GstToc*        _gst_toc_from_structure (const GstStructure *toc);
+GstStructure*  _gst_toc_to_structure (const GstToc *toc);
+gboolean       _gst_toc_structure_get_updated (const GstStructure * toc);
+void           _gst_toc_structure_set_updated (GstStructure * toc, gboolean updated);
+gchar*         _gst_toc_structure_get_extend_uid (const GstStructure * toc);
+void           _gst_toc_structure_set_extend_uid (GstStructure * toc, const gchar * extend_uid);
+
 /* Private registry functions */
 gboolean _priv_gst_registry_remove_cache_plugins (GstRegistry *registry);
 void _priv_gst_registry_cleanup (void);
diff --git a/gst/gsttoc.c b/gst/gsttoc.c
new file mode 100644 (file)
index 0000000..2553d24
--- /dev/null
@@ -0,0 +1,1010 @@
+/* GStreamer
+ * (c) 2010, 2012 Alexander Saprykin <xelfium@gmail.com>
+ *
+ * gsttoc.c: GstToc initialization and parsing/creation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:gsttoc
+ * @short_description: Generic table of contents support
+ * @see_also: #GstStructure, #GstEvent, #GstMessage, #GstQuery, #GstPad
+ *
+ * #GstToc functions are used to create/free #GstToc and #GstTocEntry structures.
+ * Also they are used to convert #GstToc into #GstStructure and vice versa.
+ *
+ * #GstToc lets you to inform other elements in pipeline or application that playing
+ * source has some kind of table of contents (TOC). These may be chapters, editions,
+ * angles or other types. For example: DVD chapters, Matroska chapters or cue sheet
+ * TOC. Such TOC will be useful for applications to display instead of just a
+ * playlist.
+ *
+ * Using TOC is very easy. Firstly, create #GstToc structure which represents root
+ * contents of the source. You can also attach TOC-specific tags to it. Then fill
+ * it with #GstTocEntry entries by appending them to #GstToc.entries #GstTocEntry.subentries
+ * lists. You should use GST_TOC_ENTRY_TYPE_CHAPTER for generic TOC entry and
+ * GST_TOC_ENTRY_TYPE_EDITION for the entries which are considered to be alternatives
+ * (like DVD angles, Matroska editions and so on).
+ *
+ * Note that root level of the TOC can contain only either editions or chapters. You
+ * should not mix them together at the same level. Otherwise you will get serialization
+ * /deserialization errors. Make sure that no one of the entries has negative start and
+ *  stop values.
+ *
+ * Please, use #GstToc.info and #GstTocEntry.info fields in that way: create a #GstStructure,
+ * put all info related to your element there and put this structure into the info field under
+ * the name of your element. Some fields in the info structure can be used for internal purposes,
+ * so you should use it in the way described above to not to overwrite already existent fields.
+ *
+ * Use gst_event_new_toc() to create a new TOC #GstEvent, and gst_event_parse_toc() to
+ * parse received TOC event. Use gst_event_new_toc_select() to create a new TOC select #GstEvent,
+ * and gst_event_parse_toc_select() to parse received TOC select event. The same rule for
+ * the #GstMessage: gst_message_new_toc() to create new TOC #GstMessage, and
+ * gst_message_parse_toc() to parse received TOC message. Also you can create a new TOC query
+ * with gst_query_new_toc(), set it with gst_query_set_toc() and parse it with
+ * gst_query_parse_toc().
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include "gst_private.h"
+#include "gstenumtypes.h"
+#include "gsttaglist.h"
+#include "gststructure.h"
+#include "gstvalue.h"
+#include "gsttoc.h"
+#include "gstpad.h"
+
+#define GST_TOC_TOC_NAME            "toc"
+#define GST_TOC_ENTRY_NAME          "entry"
+
+#define GST_TOC_TOC_UPDATED_FIELD   "updated"
+#define GST_TOC_TOC_EXTENDUID_FIELD "extenduid"
+#define GST_TOC_INFO_FIELD          "info"
+
+#define GST_TOC_ENTRY_UID_FIELD     "uid"
+#define GST_TOC_ENTRY_TYPE_FIELD    "type"
+#define GST_TOC_ENTRY_TAGS_FIELD    "tags"
+
+#define GST_TOC_TOC_ENTRIES_FIELD   "subentries"
+
+#define GST_TOC_INFO_NAME           "info-structure"
+#define GST_TOC_INFO_TIME_FIELD     "time"
+
+#define GST_TOC_TIME_NAME           "time-structure"
+#define GST_TOC_TIME_START_FIELD    "start"
+#define GST_TOC_TIME_STOP_FIELD     "stop"
+
+
+enum
+{
+  GST_TOC_TOC = 0,
+  GST_TOC_ENTRY = 1,
+  GST_TOC_UPDATED = 2,
+  GST_TOC_EXTENDUID = 3,
+  GST_TOC_UID = 4,
+  GST_TOC_TYPE = 5,
+  GST_TOC_TAGS = 6,
+  GST_TOC_SUBENTRIES = 7,
+  GST_TOC_INFO = 8,
+  GST_TOC_INFONAME = 9,
+  GST_TOC_TIME = 10,
+  GST_TOC_TIMENAME = 11,
+  GST_TOC_TIME_START = 12,
+  GST_TOC_TIME_STOP = 13,
+  GST_TOC_LAST = 14
+};
+
+static GQuark gst_toc_fields[GST_TOC_LAST] = { 0 };
+
+void
+_gst_toc_initialize (void)
+{
+  static gboolean inited = FALSE;
+
+  if (G_LIKELY (!inited)) {
+    gst_toc_fields[GST_TOC_TOC] = g_quark_from_static_string (GST_TOC_TOC_NAME);
+    gst_toc_fields[GST_TOC_ENTRY] =
+        g_quark_from_static_string (GST_TOC_ENTRY_NAME);
+
+    gst_toc_fields[GST_TOC_UPDATED] =
+        g_quark_from_static_string (GST_TOC_TOC_UPDATED_FIELD);
+    gst_toc_fields[GST_TOC_EXTENDUID] =
+        g_quark_from_static_string (GST_TOC_TOC_EXTENDUID_FIELD);
+    gst_toc_fields[GST_TOC_INFO] =
+        g_quark_from_static_string (GST_TOC_INFO_FIELD);
+
+    gst_toc_fields[GST_TOC_UID] =
+        g_quark_from_static_string (GST_TOC_ENTRY_UID_FIELD);
+    gst_toc_fields[GST_TOC_TYPE] =
+        g_quark_from_static_string (GST_TOC_ENTRY_TYPE_FIELD);
+    gst_toc_fields[GST_TOC_TAGS] =
+        g_quark_from_static_string (GST_TOC_ENTRY_TAGS_FIELD);
+
+    gst_toc_fields[GST_TOC_SUBENTRIES] =
+        g_quark_from_static_string (GST_TOC_TOC_ENTRIES_FIELD);
+
+    gst_toc_fields[GST_TOC_INFONAME] =
+        g_quark_from_static_string (GST_TOC_INFO_NAME);
+    gst_toc_fields[GST_TOC_TIME] =
+        g_quark_from_static_string (GST_TOC_INFO_TIME_FIELD);
+    gst_toc_fields[GST_TOC_TIMENAME] =
+        g_quark_from_static_string (GST_TOC_TIME_NAME);
+    gst_toc_fields[GST_TOC_TIME_START] =
+        g_quark_from_static_string (GST_TOC_TIME_START_FIELD);
+    gst_toc_fields[GST_TOC_TIME_STOP] =
+        g_quark_from_static_string (GST_TOC_TIME_STOP_FIELD);
+
+    inited = TRUE;
+  }
+}
+
+/**
+ * gst_toc_new:
+ *
+ * Create new #GstToc structure.
+ *
+ * Returns: newly allocated #GstToc structure, free it with gst_toc_free().
+ *
+ * Since: 0.10.37
+ */
+GstToc *
+gst_toc_new (void)
+{
+  GstToc *toc;
+
+  toc = g_slice_new0 (GstToc);
+  toc->tags = gst_tag_list_new ();
+  toc->info = gst_structure_id_empty_new (gst_toc_fields[GST_TOC_INFONAME]);
+
+  return toc;
+}
+
+/**
+ * gst_toc_entry_new:
+ * @type: entry type.
+ * @uid: unique ID (UID) in the whole TOC.
+ *
+ * Create new #GstTocEntry structure.
+ *
+ * Returns: newly allocated #GstTocEntry structure, free it with gst_toc_entry_free().
+ *
+ * Since: 0.10.37
+ */
+GstTocEntry *
+gst_toc_entry_new (GstTocEntryType type, const gchar * uid)
+{
+  GstTocEntry *entry;
+
+  g_return_val_if_fail (uid != NULL, NULL);
+
+  entry = g_slice_new0 (GstTocEntry);
+  entry->uid = g_strdup (uid);
+  entry->type = type;
+  entry->tags = gst_tag_list_new ();
+  entry->info = gst_structure_id_empty_new (gst_toc_fields[GST_TOC_INFONAME]);
+
+  return entry;
+}
+
+/**
+ * gst_toc_entry_new_with_pad:
+ * @type: entry type.
+ * @uid: unique ID (UID) in the whole TOC.
+ * @pad: #GstPad related to this entry.
+ *
+ * Create new #GstTocEntry structure with #GstPad related.
+ *
+ * Returns: newly allocated #GstTocEntry structure, free it with gst_toc_entry_free()
+ * when done.
+ *
+ * Since: 0.10.37
+ */
+GstTocEntry *
+gst_toc_entry_new_with_pad (GstTocEntryType type, const gchar * uid,
+    gpointer pad)
+{
+  GstTocEntry *entry;
+
+  g_return_val_if_fail (uid != NULL, NULL);
+
+  entry = g_slice_new0 (GstTocEntry);
+  entry->uid = g_strdup (uid);
+  entry->type = type;
+  entry->tags = gst_tag_list_new ();
+
+  if (pad != NULL && GST_IS_PAD (pad))
+    entry->pads = g_list_append (entry->pads, gst_object_ref (pad));
+
+  return entry;
+}
+
+/**
+ * gst_toc_free:
+ * @toc: #GstToc structure to free.
+ *
+ * Free unused #GstToc structure.
+ *
+ * Since: 0.10.37
+ */
+void
+gst_toc_free (GstToc * toc)
+{
+  g_return_if_fail (toc != NULL);
+
+  g_list_foreach (toc->entries, (GFunc) gst_toc_entry_free, NULL);
+  g_list_free (toc->entries);
+
+  if (toc->tags != NULL)
+    gst_tag_list_free (toc->tags);
+
+  if (toc->info != NULL)
+    gst_structure_free (toc->info);
+
+  g_slice_free (GstToc, toc);
+}
+
+/**
+ * gst_toc_entry_free:
+ * @entry: #GstTocEntry structure to free.
+ *
+ * Free unused #GstTocEntry structure. Note that #GstTocEntry.uid will
+ * be freed with g_free() and all #GstPad objects in the #GstTocEntry.pads
+ * list will be unrefed with gst_object_unref().
+ *
+ * Since: 0.10.37
+ */
+void
+gst_toc_entry_free (GstTocEntry * entry)
+{
+  GList *cur;
+
+  g_return_if_fail (entry != NULL);
+
+  g_list_foreach (entry->subentries, (GFunc) gst_toc_entry_free, NULL);
+  g_list_free (entry->subentries);
+
+  g_free (entry->uid);
+
+  if (entry->tags != NULL)
+    gst_tag_list_free (entry->tags);
+
+  if (entry->info != NULL)
+    gst_structure_free (entry->info);
+
+  cur = entry->pads;
+  while (cur != NULL) {
+    if (GST_IS_PAD (cur->data))
+      gst_object_unref (cur->data);
+    cur = cur->next;
+  }
+
+  g_list_free (entry->pads);
+
+  g_slice_free (GstTocEntry, entry);
+}
+
+static GstStructure *
+gst_toc_structure_new (GstTagList * tags, GstStructure * info)
+{
+  GstStructure *ret;
+  GValue val = { 0 };
+
+  ret = gst_structure_id_empty_new (gst_toc_fields[GST_TOC_TOC]);
+
+  if (tags != NULL) {
+    g_value_init (&val, GST_TYPE_STRUCTURE);
+    gst_value_set_structure (&val, GST_STRUCTURE (tags));
+    gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_TAGS], &val);
+    g_value_unset (&val);
+  }
+
+  if (info != NULL) {
+    g_value_init (&val, GST_TYPE_STRUCTURE);
+    gst_value_set_structure (&val, info);
+    gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_INFO], &val);
+    g_value_unset (&val);
+  }
+
+  return ret;
+}
+
+static GstStructure *
+gst_toc_entry_structure_new (GstTocEntryType type, const gchar * uid,
+    GstTagList * tags, GstStructure * info)
+{
+  GValue val = { 0 };
+  GstStructure *ret;
+
+  ret = gst_structure_id_empty_new (gst_toc_fields[GST_TOC_ENTRY]);
+
+  gst_structure_id_set (ret, gst_toc_fields[GST_TOC_TYPE],
+      GST_TYPE_TOC_ENTRY_TYPE, type, NULL);
+
+  g_value_init (&val, G_TYPE_STRING);
+  g_value_set_string (&val, uid);
+  gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_UID], &val);
+  g_value_unset (&val);
+
+  if (tags != NULL) {
+    g_value_init (&val, GST_TYPE_STRUCTURE);
+    gst_value_set_structure (&val, GST_STRUCTURE (tags));
+    gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_TAGS], &val);
+    g_value_unset (&val);
+  }
+
+  if (info != NULL) {
+    g_value_init (&val, GST_TYPE_STRUCTURE);
+    gst_value_set_structure (&val, info);
+    gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_INFO], &val);
+    g_value_unset (&val);
+  }
+
+  return ret;
+}
+
+static guint
+gst_toc_entry_structure_n_subentries (const GstStructure * entry)
+{
+  if (G_UNLIKELY (!gst_structure_id_has_field_typed (entry,
+              gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY)))
+    return 0;
+  else
+    return gst_value_array_get_size ((gst_structure_id_get_value (entry,
+                gst_toc_fields[GST_TOC_SUBENTRIES])));
+}
+
+static const GstStructure *
+gst_toc_entry_structure_nth_subentry (const GstStructure * entry, guint nth)
+{
+  guint count;
+  const GValue *array;
+
+  count = gst_toc_entry_structure_n_subentries (entry);
+
+  if (count < nth)
+    return NULL;
+
+  if (G_UNLIKELY (!gst_structure_id_has_field_typed (entry,
+              gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY)))
+    return NULL;
+  else {
+    array =
+        gst_value_array_get_value (gst_structure_id_get_value (entry,
+            gst_toc_fields[GST_TOC_SUBENTRIES]), nth);
+    return gst_value_get_structure (array);
+  }
+}
+
+static GstTocEntry *
+gst_toc_entry_from_structure (const GstStructure * entry, guint level)
+{
+  GstTocEntry *ret, *subentry;
+  const GValue *val;
+  const GstTagList *entry_tags;
+  const GstStructure *subentry_struct;
+  gint count, i;
+  const gchar *uid;
+  guint chapters_count = 0, editions_count = 0;
+
+  g_return_val_if_fail (entry != NULL, NULL);
+  g_return_val_if_fail (gst_structure_id_has_field_typed (entry,
+          gst_toc_fields[GST_TOC_UID], G_TYPE_STRING), NULL);
+  g_return_val_if_fail (gst_structure_id_has_field_typed (entry,
+          gst_toc_fields[GST_TOC_TYPE], GST_TYPE_TOC_ENTRY_TYPE), NULL);
+
+  val = gst_structure_id_get_value (entry, gst_toc_fields[GST_TOC_UID]);
+  uid = g_value_get_string (val);
+
+  ret = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, uid);
+
+  gst_structure_get_enum (entry, GST_TOC_ENTRY_TYPE_FIELD,
+      GST_TYPE_TOC_ENTRY_TYPE, (gint *) & (ret->type));
+
+  if (gst_structure_id_has_field_typed (entry,
+          gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY)) {
+    count = gst_toc_entry_structure_n_subentries (entry);
+
+    for (i = 0; i < count; ++i) {
+      subentry_struct = gst_toc_entry_structure_nth_subentry (entry, i);
+      subentry = gst_toc_entry_from_structure (subentry_struct, level + 1);
+
+      /* skip empty editions */
+      if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
+              && subentry->subentries == NULL)) {
+        g_warning
+            ("Empty edition found while deserializing TOC from GstStructure, skipping");
+        continue;
+      }
+
+      if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
+        ++editions_count;
+      else
+        ++chapters_count;
+
+      /* check for mixed content */
+      if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
+        g_critical
+            ("Mixed editions and chapters in the TOC contents, the TOC is broken");
+        gst_toc_entry_free (subentry);
+        gst_toc_entry_free (ret);
+        return NULL;
+      }
+
+      if (G_UNLIKELY (subentry == NULL)) {
+        gst_toc_entry_free (ret);
+        return NULL;
+      }
+
+      ret->subentries = g_list_prepend (ret->subentries, subentry);
+    }
+
+    ret->subentries = g_list_reverse (ret->subentries);
+  }
+
+  if (gst_structure_id_has_field_typed (entry,
+          gst_toc_fields[GST_TOC_TAGS], GST_TYPE_STRUCTURE)) {
+    val = gst_structure_id_get_value (entry, gst_toc_fields[GST_TOC_TAGS]);
+
+    if (G_LIKELY (GST_IS_TAG_LIST (gst_value_get_structure (val)))) {
+      entry_tags = GST_TAG_LIST (gst_value_get_structure (val));
+      ret->tags = gst_tag_list_copy (entry_tags);
+    }
+  }
+
+  if (gst_structure_id_has_field_typed (entry,
+          gst_toc_fields[GST_TOC_INFO], GST_TYPE_STRUCTURE)) {
+    val = gst_structure_id_get_value (entry, gst_toc_fields[GST_TOC_INFO]);
+
+    if (G_LIKELY (GST_IS_STRUCTURE (gst_value_get_structure (val))))
+      ret->info = gst_structure_copy (gst_value_get_structure (val));
+  }
+
+  return ret;
+}
+
+GstToc *
+_gst_toc_from_structure (const GstStructure * toc)
+{
+  GstToc *ret;
+  GstTocEntry *subentry;
+  const GstStructure *subentry_struct;
+  const GValue *val;
+  const GstTagList *entry_tags;
+  guint count, i;
+  guint editions_count = 0, chapters_count = 0;
+
+  g_return_val_if_fail (toc != NULL, NULL);
+
+  ret = gst_toc_new ();
+
+  if (gst_structure_id_has_field_typed (toc,
+          gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY)) {
+    count = gst_toc_entry_structure_n_subentries (toc);
+
+    for (i = 0; i < count; ++i) {
+      subentry_struct = gst_toc_entry_structure_nth_subentry (toc, i);
+      subentry = gst_toc_entry_from_structure (subentry_struct, 0);
+
+      /* skip empty editions */
+      if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
+              && subentry->subentries == NULL)) {
+        g_warning
+            ("Empty edition found while deserializing TOC from GstStructure, skipping");
+        continue;
+      }
+
+      /* check for success */
+      if (G_UNLIKELY (subentry == NULL)) {
+        g_critical ("Couldn't serialize deserializing TOC from GstStructure");
+        gst_toc_free (ret);
+        return NULL;
+      }
+
+      if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
+        ++editions_count;
+      else
+        ++chapters_count;
+
+      /* check for mixed content */
+      if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
+        g_critical
+            ("Mixed editions and chapters in the TOC contents, the TOC is broken");
+        gst_toc_entry_free (subentry);
+        gst_toc_free (ret);
+        return NULL;
+      }
+
+      ret->entries = g_list_prepend (ret->entries, subentry);
+    }
+
+    ret->entries = g_list_reverse (ret->entries);
+  }
+
+  if (gst_structure_id_has_field_typed (toc,
+          gst_toc_fields[GST_TOC_TAGS], GST_TYPE_STRUCTURE)) {
+    val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_TAGS]);
+
+    if (G_LIKELY (GST_IS_TAG_LIST (gst_value_get_structure (val)))) {
+      entry_tags = GST_TAG_LIST (gst_value_get_structure (val));
+      ret->tags = gst_tag_list_copy (entry_tags);
+    }
+  }
+
+  if (gst_structure_id_has_field_typed (toc,
+          gst_toc_fields[GST_TOC_INFO], GST_TYPE_STRUCTURE)) {
+    val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_INFO]);
+
+    if (G_LIKELY (GST_IS_STRUCTURE (gst_value_get_structure (val))))
+      ret->info = gst_structure_copy (gst_value_get_structure (val));
+  }
+
+  if (G_UNLIKELY (ret->entries == NULL)) {
+    gst_toc_free (ret);
+    return NULL;
+  }
+
+  return ret;
+}
+
+static GstStructure *
+gst_toc_entry_to_structure (const GstTocEntry * entry, guint level)
+{
+  GstStructure *ret, *subentry_struct;
+  GstTocEntry *subentry;
+  GList *cur;
+  GValue subentries_val = { 0 };
+  GValue entry_val = { 0 };
+  guint chapters_count = 0, editions_count = 0;
+
+  g_return_val_if_fail (entry != NULL, NULL);
+
+  ret =
+      gst_toc_entry_structure_new (entry->type, entry->uid, entry->tags,
+      entry->info);
+
+  g_value_init (&subentries_val, GST_TYPE_ARRAY);
+  g_value_init (&entry_val, GST_TYPE_STRUCTURE);
+
+  cur = entry->subentries;
+  while (cur != NULL) {
+    subentry = cur->data;
+
+    if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
+      ++editions_count;
+    else
+      ++chapters_count;
+
+    /* check for mixed content */
+    if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
+      g_critical
+          ("Mixed editions and chapters in the TOC contents, the TOC is broken");
+      gst_structure_free (ret);
+      g_value_unset (&entry_val);
+      g_value_unset (&subentries_val);
+      return NULL;
+    }
+
+    /* skip empty editions */
+    if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
+            && subentry->subentries == NULL)) {
+      g_warning
+          ("Empty edition found while serializing TOC to GstStructure, skipping");
+      cur = cur->next;
+      continue;
+    }
+
+    subentry_struct = gst_toc_entry_to_structure (subentry, level + 1);
+
+    /* check for success */
+    if (G_UNLIKELY (subentry_struct == NULL)) {
+      gst_structure_free (ret);
+      g_value_unset (&subentries_val);
+      g_value_unset (&entry_val);
+      return NULL;
+    }
+
+    /* skip empty editions */
+    if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
+            && subentry->subentries == NULL)) {
+      g_warning
+          ("Empty edition found while serializing TOC to GstStructure, skipping");
+      cur = cur->next;
+      continue;
+    }
+
+    gst_value_set_structure (&entry_val, subentry_struct);
+    gst_value_array_append_value (&subentries_val, &entry_val);
+    gst_structure_free (subentry_struct);
+
+    cur = cur->next;
+  }
+
+  gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_SUBENTRIES],
+      &subentries_val);
+
+  g_value_unset (&subentries_val);
+  g_value_unset (&entry_val);
+  return ret;
+}
+
+GstStructure *
+_gst_toc_to_structure (const GstToc * toc)
+{
+  GValue val = { 0 };
+  GValue subentries_val = { 0 };
+  GstStructure *ret, *subentry_struct;
+  GstTocEntry *subentry;
+  GList *cur;
+  guint editions_count = 0, chapters_count = 0;
+
+  g_return_val_if_fail (toc != NULL, NULL);
+  g_return_val_if_fail (toc->entries != NULL, NULL);
+
+  ret = gst_toc_structure_new (toc->tags, toc->info);
+
+  g_value_init (&val, GST_TYPE_STRUCTURE);
+  g_value_init (&subentries_val, GST_TYPE_ARRAY);
+  cur = toc->entries;
+
+  while (cur != NULL) {
+    subentry = cur->data;
+
+    if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
+      ++editions_count;
+    else
+      ++chapters_count;
+
+    /* check for mixed content */
+    if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
+      g_critical
+          ("Mixed editions and chapters in the TOC contents, the TOC is broken");
+      gst_structure_free (ret);
+      g_value_unset (&val);
+      g_value_unset (&subentries_val);
+      return NULL;
+    }
+
+    /* skip empty editions */
+    if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
+            && subentry->subentries == NULL)) {
+      g_warning
+          ("Empty edition found while serializing TOC to GstStructure, skipping");
+      cur = cur->next;
+      continue;
+    }
+
+    subentry_struct = gst_toc_entry_to_structure (subentry, 0);
+
+    /* check for success */
+    if (G_UNLIKELY (subentry_struct == NULL)) {
+      g_critical ("Couldn't serialize TOC to GstStructure");
+      gst_structure_free (ret);
+      g_value_unset (&val);
+      g_value_unset (&subentries_val);
+      return NULL;
+    }
+
+    gst_value_set_structure (&val, subentry_struct);
+    gst_value_array_append_value (&subentries_val, &val);
+    gst_structure_free (subentry_struct);
+
+    cur = cur->next;
+  }
+
+  gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_SUBENTRIES],
+      &subentries_val);
+
+  g_value_unset (&val);
+  g_value_unset (&subentries_val);
+  return ret;
+}
+
+static gboolean
+gst_toc_check_entry_for_uid (const GstTocEntry * entry, const gchar * uid)
+{
+  GList *cur;
+
+  g_return_val_if_fail (entry != NULL, FALSE);
+  g_return_val_if_fail (uid != NULL, FALSE);
+
+  if (g_strcmp0 (entry->uid, uid) == 0)
+    return TRUE;
+
+  cur = entry->subentries;
+  while (cur != NULL) {
+    if (gst_toc_check_entry_for_uid (cur->data, uid))
+      return TRUE;
+    cur = cur->next;
+  }
+
+  return FALSE;
+}
+
+/**
+ * gst_toc_find_entry:
+ * @toc: #GstToc to search in.
+ * @uid: UID to find #GstTocEntry with.
+ *
+ * Find #GstTocEntry with given @uid in the @toc.
+ *
+ * Returns: #GstTocEntry with specified @uid from the @toc, or NULL if not found.
+ *
+ * Since: 0.10.37
+ */
+GstTocEntry *
+gst_toc_find_entry (const GstToc * toc, const gchar * uid)
+{
+  GList *cur;
+
+  g_return_val_if_fail (toc != NULL, NULL);
+  g_return_val_if_fail (uid != NULL, NULL);
+
+  cur = toc->entries;
+  while (cur != NULL) {
+    if (gst_toc_check_entry_for_uid (cur->data, uid))
+      return cur->data;
+    cur = cur->next;
+  }
+
+  return NULL;
+}
+
+/**
+ * gst_toc_entry_copy:
+ * @entry: #GstTocEntry to copy.
+ *
+ * Copy #GstTocEntry with all subentries (deep copy).
+ *
+ * Returns: newly allocated #GstTocEntry in case of success, NULL otherwise;
+ * free it when done with gst_toc_entry_free().
+ *
+ * Since: 0.10.37
+ */
+GstTocEntry *
+gst_toc_entry_copy (const GstTocEntry * entry)
+{
+  GstTocEntry *ret, *sub;
+  GList *cur;
+
+  g_return_val_if_fail (entry != NULL, NULL);
+
+  ret = gst_toc_entry_new (entry->type, entry->uid);
+
+  if (GST_IS_STRUCTURE (entry->info))
+    ret->info = gst_structure_copy (entry->info);
+
+  if (GST_IS_TAG_LIST (entry->tags))
+    ret->tags = gst_tag_list_copy (entry->tags);
+
+  cur = entry->pads;
+  while (cur != NULL) {
+    if (GST_IS_PAD (cur->data))
+      ret->pads = g_list_prepend (ret->pads, gst_object_ref (cur->data));
+    cur = cur->next;
+  }
+  ret->pads = g_list_reverse (ret->pads);
+
+  cur = entry->subentries;
+  while (cur != NULL) {
+    sub = gst_toc_entry_copy (cur->data);
+
+    if (sub != NULL)
+      ret->subentries = g_list_prepend (ret->subentries, sub);
+
+    cur = cur->next;
+  }
+  ret->subentries = g_list_reverse (ret->subentries);
+
+  return ret;
+}
+
+/**
+ * gst_toc_copy:
+ * @toc: #GstToc to copy.
+ *
+ * Copy #GstToc with all subentries (deep copy).
+ *
+ * Returns: newly allocated #GstToc in case of success, NULL otherwise;
+ * free it when done with gst_toc_free().
+ *
+ * Since: 0.10.37
+ */
+GstToc *
+gst_toc_copy (const GstToc * toc)
+{
+  GstToc *ret;
+  GstTocEntry *entry;
+  GList *cur;
+
+  g_return_val_if_fail (toc != NULL, NULL);
+
+  ret = gst_toc_new ();
+
+  if (GST_IS_STRUCTURE (toc->info))
+    ret->info = gst_structure_copy (toc->info);
+
+  if (GST_IS_TAG_LIST (toc->tags))
+    ret->tags = gst_tag_list_copy (toc->tags);
+
+  cur = toc->entries;
+  while (cur != NULL) {
+    entry = gst_toc_entry_copy (cur->data);
+
+    if (entry != NULL)
+      ret->entries = g_list_prepend (ret->entries, entry);
+
+    cur = cur->next;
+  }
+  ret->entries = g_list_reverse (ret->entries);
+
+  return ret;
+}
+
+/**
+ * gst_toc_entry_set_start_stop:
+ * @entry: #GstTocEntry to set values.
+ * @start: start value to set.
+ * @stop: stop value to set.
+ *
+ * Set @start and @stop values for the @entry.
+ *
+ * Since: 0.10.37
+ */
+void
+gst_toc_entry_set_start_stop (GstTocEntry * entry, gint64 start, gint64 stop)
+{
+  const GValue *val;
+  GstStructure *structure = NULL;
+
+  g_return_if_fail (entry != NULL);
+  g_return_if_fail (GST_IS_STRUCTURE (entry->info));
+
+  if (gst_structure_id_has_field_typed (entry->info,
+          gst_toc_fields[GST_TOC_TIME], GST_TYPE_STRUCTURE)) {
+    val =
+        gst_structure_id_get_value (entry->info, gst_toc_fields[GST_TOC_TIME]);
+    structure = gst_structure_copy (gst_value_get_structure (val));
+  }
+
+  if (structure == NULL)
+    structure = gst_structure_id_empty_new (gst_toc_fields[GST_TOC_TIMENAME]);
+
+  gst_structure_id_set (structure, gst_toc_fields[GST_TOC_TIME_START],
+      G_TYPE_INT64, start, gst_toc_fields[GST_TOC_TIME_STOP], G_TYPE_INT64,
+      stop, NULL);
+
+  gst_structure_id_set (entry->info, gst_toc_fields[GST_TOC_TIME],
+      GST_TYPE_STRUCTURE, structure, NULL);
+
+  gst_structure_free (structure);
+}
+
+/**
+ * gst_toc_entry_get_start_stop:
+ * @entry: #GstTocEntry to get values from.
+ * @start: (out): the storage for the start value, leave #NULL if not need.
+ * @stop: (out): the storage for the stop value, leave #NULL if not need.
+ *
+ * Get start and stop values from the @entry and write them into appropriate storages.
+ *
+ * Returns: TRUE if all non-NULL storage pointers were filled with appropriate values,
+ * FALSE otherwise.
+ *
+ * Since: 0.10.37
+ */
+gboolean
+gst_toc_entry_get_start_stop (const GstTocEntry * entry, gint64 * start,
+    gint64 * stop)
+{
+  gboolean ret = TRUE;
+  const GValue *val;
+  const GstStructure *structure;
+
+  g_return_val_if_fail (entry != NULL, FALSE);
+  g_return_val_if_fail (GST_IS_STRUCTURE (entry->info), FALSE);
+
+  if (!gst_structure_id_has_field_typed (entry->info,
+          gst_toc_fields[GST_TOC_TIME], GST_TYPE_STRUCTURE))
+    return FALSE;
+
+  val = gst_structure_id_get_value (entry->info, gst_toc_fields[GST_TOC_TIME]);
+  structure = gst_value_get_structure (val);
+
+  if (start != NULL) {
+    if (gst_structure_id_has_field_typed (structure,
+            gst_toc_fields[GST_TOC_TIME_START], G_TYPE_INT64))
+      *start =
+          g_value_get_int64 (gst_structure_id_get_value (structure,
+              gst_toc_fields[GST_TOC_TIME_START]));
+    else
+      ret = FALSE;
+  }
+
+  if (stop != NULL) {
+    if (gst_structure_id_has_field_typed (structure,
+            gst_toc_fields[GST_TOC_TIME_STOP], G_TYPE_INT64))
+      *stop =
+          g_value_get_int64 (gst_structure_id_get_value (structure,
+              gst_toc_fields[GST_TOC_TIME_STOP]));
+    else
+      ret = FALSE;
+  }
+
+  return ret;
+}
+
+gboolean
+_gst_toc_structure_get_updated (const GstStructure * toc)
+{
+  const GValue *val;
+
+  g_return_val_if_fail (GST_IS_STRUCTURE (toc), FALSE);
+
+  if (G_LIKELY (gst_structure_id_has_field_typed (toc,
+              gst_toc_fields[GST_TOC_UPDATED], G_TYPE_BOOLEAN))) {
+    val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_UPDATED]);
+    return g_value_get_boolean (val);
+  }
+
+  return FALSE;
+}
+
+void
+_gst_toc_structure_set_updated (GstStructure * toc, gboolean updated)
+{
+  GValue val = { 0 };
+
+  g_return_if_fail (toc != NULL);
+
+  g_value_init (&val, G_TYPE_BOOLEAN);
+  g_value_set_boolean (&val, updated);
+  gst_structure_id_set_value (toc, gst_toc_fields[GST_TOC_UPDATED], &val);
+  g_value_unset (&val);
+}
+
+gchar *
+_gst_toc_structure_get_extend_uid (const GstStructure * toc)
+{
+  const GValue *val;
+
+  g_return_val_if_fail (GST_IS_STRUCTURE (toc), NULL);
+
+  if (G_LIKELY (gst_structure_id_has_field_typed (toc,
+              gst_toc_fields[GST_TOC_EXTENDUID], G_TYPE_STRING))) {
+    val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_EXTENDUID]);
+    return g_strdup (g_value_get_string (val));
+  }
+
+  return NULL;
+}
+
+void
+_gst_toc_structure_set_extend_uid (GstStructure * toc, const gchar * extend_uid)
+{
+  GValue val = { 0 };
+
+  g_return_if_fail (toc != NULL);
+  g_return_if_fail (extend_uid != NULL);
+
+  g_value_init (&val, G_TYPE_STRING);
+  g_value_set_string (&val, extend_uid);
+  gst_structure_id_set_value (toc, gst_toc_fields[GST_TOC_EXTENDUID], &val);
+  g_value_unset (&val);
+}
diff --git a/gst/gsttoc.h b/gst/gsttoc.h
new file mode 100644 (file)
index 0000000..a817efe
--- /dev/null
@@ -0,0 +1,111 @@
+/* GStreamer
+ * (c) 2010, 2012 Alexander Saprykin <xelfium@gmail.com>
+ *
+ * gsttoc.h: generic TOC API declaration
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_TOC_H__
+#define __GST_TOC_H__
+
+#include <gst/gstconfig.h>
+#include <gst/gsttaglist.h>
+#include <gst/gstformat.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GstTocEntry GstTocEntry;
+typedef struct _GstToc GstToc;
+
+/**
+ * GstTocEntryType:
+ * @GST_TOC_ENTRY_TYPE_CHAPTER: a chapter type entry.
+ * @GST_TOC_ENTRY_TYPE_EDITION: an edition entry (angle or alternative in other terms).
+ *
+ * The different types of TOC entry.
+ */
+typedef enum {
+  GST_TOC_ENTRY_TYPE_CHAPTER     = 0,
+  GST_TOC_ENTRY_TYPE_EDITION     = 1
+} GstTocEntryType;
+
+/**
+ * GstTocEntry:
+ * @uid: unique (for a whole TOC) id of the entry. This value should be persistent and
+ * should not be changed while updating TOC. @uid should be handled as "opaque" value
+ * without meaning (e.g. applications should not assume the /editionX/chapterY/chapter/Z structure,
+ * other demuxers could do something else), it should help to track updates of certain entries.
+ * @type: #GstTocEntryType of this entry.
+ * @subentries: list of #GstTocEntry children.
+ * @pads: list of #GstPad objects, related to this #GstTocEntry.
+ * @tags: tags related to this entry.
+ * @info: extra information related to this entry.
+ *
+ * Definition of TOC entry structure.
+ */
+struct _GstTocEntry {
+  gchar *uid;
+  GstTocEntryType type;
+  GList *subentries;
+  GList *pads;
+  GstTagList *tags;
+  GstStructure *info;
+
+  /*< private >*/
+  gpointer _gst_reserved[GST_PADDING];
+};
+
+/* FIXME: pad member should be GstPad type, but that's
+ * impossible due to recursive includes */
+
+/**
+ * GstToc:
+ * @entries: list of #GstTocEntry entries of the TOC.
+ * @tags: tags related to the whole TOC.
+ * @info: extra information related to the TOC.
+ *
+ * Definition of TOC structure.
+ */
+struct _GstToc {
+  GList *entries;
+  GstTagList *tags;
+  GstStructure *info;
+
+  /*< private >*/
+  gpointer _gst_reserved[GST_PADDING];
+};
+
+/* functions to create new structures */
+GstToc *        gst_toc_new                     (void);
+GstTocEntry *   gst_toc_entry_new               (GstTocEntryType type, const gchar *uid);
+GstTocEntry *   gst_toc_entry_new_with_pad      (GstTocEntryType type, const gchar *uid, gpointer pad);
+
+/* functions to free structures */
+void            gst_toc_entry_free              (GstTocEntry *entry);
+void            gst_toc_free                    (GstToc *toc);
+
+GstTocEntry *   gst_toc_find_entry              (const GstToc *toc, const gchar *uid);
+GstTocEntry *   gst_toc_entry_copy              (const GstTocEntry *entry);
+GstToc      *   gst_toc_copy                    (const GstToc *toc);
+
+void            gst_toc_entry_set_start_stop    (GstTocEntry *entry, gint64 start, gint64 stop);
+gboolean        gst_toc_entry_get_start_stop    (const GstTocEntry *entry, gint64 *start, gint64 *stop);
+
+G_END_DECLS
+
+#endif /* __GST_TOC_H__ */
+