toc: avoid unnecessary GValue acrobatics
[platform/upstream/gstreamer.git] / gst / gsttoc.c
1 /* GStreamer
2  * (c) 2010, 2012 Alexander Saprykin <xelfium@gmail.com>
3  *
4  * gsttoc.c: GstToc initialization and parsing/creation
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 /**
23  * SECTION:gsttoc
24  * @short_description: Generic table of contents support
25  * @see_also: #GstStructure, #GstEvent, #GstMessage, #GstQuery, #GstPad
26  *
27  * #GstToc functions are used to create/free #GstToc and #GstTocEntry structures.
28  * Also they are used to convert #GstToc into #GstStructure and vice versa.
29  *
30  * #GstToc lets you to inform other elements in pipeline or application that playing
31  * source has some kind of table of contents (TOC). These may be chapters, editions,
32  * angles or other types. For example: DVD chapters, Matroska chapters or cue sheet
33  * TOC. Such TOC will be useful for applications to display instead of just a
34  * playlist.
35  *
36  * Using TOC is very easy. Firstly, create #GstToc structure which represents root
37  * contents of the source. You can also attach TOC-specific tags to it. Then fill
38  * it with #GstTocEntry entries by appending them to #GstToc.entries #GstTocEntry.subentries
39  * lists. You should use GST_TOC_ENTRY_TYPE_CHAPTER for generic TOC entry and
40  * GST_TOC_ENTRY_TYPE_EDITION for the entries which are considered to be alternatives
41  * (like DVD angles, Matroska editions and so on).
42  *
43  * Note that root level of the TOC can contain only either editions or chapters. You
44  * should not mix them together at the same level. Otherwise you will get serialization
45  * /deserialization errors. Make sure that no one of the entries has negative start and
46  *  stop values.
47  *
48  * Please, use #GstToc.info and #GstTocEntry.info fields in that way: create a #GstStructure,
49  * put all info related to your element there and put this structure into the info field under
50  * the name of your element. Some fields in the info structure can be used for internal purposes,
51  * so you should use it in the way described above to not to overwrite already existent fields.
52  *
53  * Use gst_event_new_toc() to create a new TOC #GstEvent, and gst_event_parse_toc() to
54  * parse received TOC event. Use gst_event_new_toc_select() to create a new TOC select #GstEvent,
55  * and gst_event_parse_toc_select() to parse received TOC select event. The same rule for
56  * the #GstMessage: gst_message_new_toc() to create new TOC #GstMessage, and
57  * gst_message_parse_toc() to parse received TOC message. Also you can create a new TOC query
58  * with gst_query_new_toc(), set it with gst_query_set_toc() and parse it with
59  * gst_query_parse_toc().
60  */
61
62 #ifdef HAVE_CONFIG_H
63 #  include "config.h"
64 #endif
65
66 #include "gst_private.h"
67 #include "gstenumtypes.h"
68 #include "gsttaglist.h"
69 #include "gststructure.h"
70 #include "gstvalue.h"
71 #include "gsttoc.h"
72 #include "gstpad.h"
73 #include "gstquark.h"
74
75 /**
76  * gst_toc_new:
77  *
78  * Create new #GstToc structure.
79  *
80  * Returns: newly allocated #GstToc structure, free it with gst_toc_free().
81  *
82  * Since: 0.10.37
83  */
84 GstToc *
85 gst_toc_new (void)
86 {
87   GstToc *toc;
88
89   toc = g_slice_new0 (GstToc);
90   toc->tags = gst_tag_list_new_empty ();
91   toc->info = gst_structure_new_id_empty (GST_QUARK (INFO_STRUCTURE));
92
93   return toc;
94 }
95
96 /**
97  * gst_toc_entry_new:
98  * @type: entry type.
99  * @uid: unique ID (UID) in the whole TOC.
100  *
101  * Create new #GstTocEntry structure.
102  *
103  * Returns: newly allocated #GstTocEntry structure, free it with gst_toc_entry_free().
104  *
105  * Since: 0.10.37
106  */
107 GstTocEntry *
108 gst_toc_entry_new (GstTocEntryType type, const gchar * uid)
109 {
110   GstTocEntry *entry;
111
112   g_return_val_if_fail (uid != NULL, NULL);
113
114   entry = g_slice_new0 (GstTocEntry);
115   entry->uid = g_strdup (uid);
116   entry->type = type;
117   entry->tags = gst_tag_list_new_empty ();
118   entry->info = gst_structure_new_id_empty (GST_QUARK (INFO_STRUCTURE));
119
120   return entry;
121 }
122
123 /**
124  * gst_toc_entry_new_with_pad:
125  * @type: entry type.
126  * @uid: unique ID (UID) in the whole TOC.
127  * @pad: #GstPad related to this entry.
128  *
129  * Create new #GstTocEntry structure with #GstPad related.
130  *
131  * Returns: newly allocated #GstTocEntry structure, free it with gst_toc_entry_free()
132  * when done.
133  *
134  * Since: 0.10.37
135  */
136 GstTocEntry *
137 gst_toc_entry_new_with_pad (GstTocEntryType type, const gchar * uid,
138     gpointer pad)
139 {
140   GstTocEntry *entry;
141
142   g_return_val_if_fail (uid != NULL, NULL);
143
144   entry = g_slice_new0 (GstTocEntry);
145   entry->uid = g_strdup (uid);
146   entry->type = type;
147   entry->tags = gst_tag_list_new_empty ();
148   entry->info = gst_structure_new_id_empty (GST_QUARK (INFO_STRUCTURE));
149
150   if (pad != NULL && GST_IS_PAD (pad))
151     entry->pads = g_list_append (entry->pads, gst_object_ref (pad));
152
153   return entry;
154 }
155
156 /**
157  * gst_toc_free:
158  * @toc: #GstToc structure to free.
159  *
160  * Free unused #GstToc structure.
161  *
162  * Since: 0.10.37
163  */
164 void
165 gst_toc_free (GstToc * toc)
166 {
167   g_return_if_fail (toc != NULL);
168
169   g_list_foreach (toc->entries, (GFunc) gst_toc_entry_free, NULL);
170   g_list_free (toc->entries);
171
172   if (toc->tags != NULL)
173     gst_tag_list_free (toc->tags);
174
175   if (toc->info != NULL)
176     gst_structure_free (toc->info);
177
178   g_slice_free (GstToc, toc);
179 }
180
181 /**
182  * gst_toc_entry_free:
183  * @entry: #GstTocEntry structure to free.
184  *
185  * Free unused #GstTocEntry structure. Note that #GstTocEntry.uid will
186  * be freed with g_free() and all #GstPad objects in the #GstTocEntry.pads
187  * list will be unrefed with gst_object_unref().
188  *
189  * Since: 0.10.37
190  */
191 void
192 gst_toc_entry_free (GstTocEntry * entry)
193 {
194   GList *cur;
195
196   g_return_if_fail (entry != NULL);
197
198   g_list_foreach (entry->subentries, (GFunc) gst_toc_entry_free, NULL);
199   g_list_free (entry->subentries);
200
201   g_free (entry->uid);
202
203   if (entry->tags != NULL)
204     gst_tag_list_free (entry->tags);
205
206   if (entry->info != NULL)
207     gst_structure_free (entry->info);
208
209   cur = entry->pads;
210   while (cur != NULL) {
211     if (GST_IS_PAD (cur->data))
212       gst_object_unref (cur->data);
213     cur = cur->next;
214   }
215
216   g_list_free (entry->pads);
217
218   g_slice_free (GstTocEntry, entry);
219 }
220
221 static GstStructure *
222 gst_toc_structure_new (GstTagList * tags, GstStructure * info)
223 {
224   GstStructure *ret;
225
226   ret = gst_structure_new_id_empty (GST_QUARK (TOC));
227
228   if (tags != NULL) {
229     // FIXME: don't use GST_TYPE_STRUCTURE for taglist
230     gst_structure_id_set (ret, GST_QUARK (TAGS), GST_TYPE_STRUCTURE, tags,
231         NULL);
232   }
233
234   if (info != NULL) {
235     gst_structure_id_set (ret, GST_QUARK (INFO), GST_TYPE_STRUCTURE, info,
236         NULL);
237   }
238
239   return ret;
240 }
241
242 static GstStructure *
243 gst_toc_entry_structure_new (GstTocEntryType type, const gchar * uid,
244     GstTagList * tags, GstStructure * info)
245 {
246   GstStructure *ret;
247
248   ret = gst_structure_new_id_empty (GST_QUARK (TOC_ENTRY));
249
250   gst_structure_id_set (ret, GST_QUARK (TYPE), GST_TYPE_TOC_ENTRY_TYPE, type,
251       NULL);
252   gst_structure_id_set (ret, GST_QUARK (UID), G_TYPE_STRING, uid, NULL);
253
254   if (tags != NULL) {
255     // FIXME: taglist type
256     gst_structure_id_set (ret, GST_QUARK (TAGS), GST_TYPE_STRUCTURE, tags,
257         NULL);
258   }
259
260   if (info != NULL) {
261     gst_structure_id_set (ret, GST_QUARK (INFO), GST_TYPE_STRUCTURE, info,
262         NULL);
263   }
264
265   return ret;
266 }
267
268 static guint
269 gst_toc_entry_structure_n_subentries (const GstStructure * entry)
270 {
271   if (G_UNLIKELY (!gst_structure_id_has_field_typed (entry,
272               GST_QUARK (SUB_ENTRIES), GST_TYPE_ARRAY)))
273     return 0;
274   else
275     return gst_value_array_get_size ((gst_structure_id_get_value (entry,
276                 GST_QUARK (SUB_ENTRIES))));
277 }
278
279 static const GstStructure *
280 gst_toc_entry_structure_nth_subentry (const GstStructure * entry, guint nth)
281 {
282   guint count;
283   const GValue *array;
284
285   count = gst_toc_entry_structure_n_subentries (entry);
286
287   if (count < nth)
288     return NULL;
289
290   if (G_UNLIKELY (!gst_structure_id_has_field_typed (entry,
291               GST_QUARK (SUB_ENTRIES), GST_TYPE_ARRAY)))
292     return NULL;
293   else {
294     array =
295         gst_value_array_get_value (gst_structure_id_get_value (entry,
296             GST_QUARK (SUB_ENTRIES)), nth);
297     return gst_value_get_structure (array);
298   }
299 }
300
301 static GstTocEntry *
302 gst_toc_entry_from_structure (const GstStructure * entry, guint level)
303 {
304   GstTocEntry *ret, *subentry;
305   const GValue *val;
306   const GstStructure *subentry_struct;
307   GstTagList *list;
308   GstStructure *st;
309   gint count, i;
310   const gchar *uid;
311   guint chapters_count = 0, editions_count = 0;
312
313   g_return_val_if_fail (entry != NULL, NULL);
314   g_return_val_if_fail (gst_structure_id_has_field_typed (entry,
315           GST_QUARK (UID), G_TYPE_STRING), NULL);
316   g_return_val_if_fail (gst_structure_id_has_field_typed (entry,
317           GST_QUARK (TYPE), GST_TYPE_TOC_ENTRY_TYPE), NULL);
318
319   val = gst_structure_id_get_value (entry, GST_QUARK (UID));
320   uid = g_value_get_string (val);
321
322   ret = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, uid);
323
324   gst_structure_get_enum (entry, g_quark_to_string (GST_QUARK (TYPE)),
325       GST_TYPE_TOC_ENTRY_TYPE, (gint *) & (ret->type));
326
327   if (gst_structure_id_has_field_typed (entry,
328           GST_QUARK (SUB_ENTRIES), GST_TYPE_ARRAY)) {
329     count = gst_toc_entry_structure_n_subentries (entry);
330
331     for (i = 0; i < count; ++i) {
332       subentry_struct = gst_toc_entry_structure_nth_subentry (entry, i);
333       subentry = gst_toc_entry_from_structure (subentry_struct, level + 1);
334
335       /* skip empty editions */
336       if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
337               && subentry->subentries == NULL)) {
338         g_warning
339             ("Empty edition found while deserializing TOC from GstStructure, skipping");
340         continue;
341       }
342
343       if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
344         ++editions_count;
345       else
346         ++chapters_count;
347
348       /* check for mixed content */
349       if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
350         g_critical
351             ("Mixed editions and chapters in the TOC contents, the TOC is broken");
352         gst_toc_entry_free (subentry);
353         gst_toc_entry_free (ret);
354         return NULL;
355       }
356
357       if (G_UNLIKELY (subentry == NULL)) {
358         gst_toc_entry_free (ret);
359         return NULL;
360       }
361
362       ret->subentries = g_list_prepend (ret->subentries, subentry);
363     }
364
365     ret->subentries = g_list_reverse (ret->subentries);
366   }
367
368   if (gst_structure_id_has_field_typed (entry,
369           GST_QUARK (TAGS), GST_TYPE_STRUCTURE)) {
370     val = gst_structure_id_get_value (entry, GST_QUARK (TAGS));
371
372     if (G_LIKELY (GST_IS_TAG_LIST (gst_value_get_structure (val)))) {
373       list = gst_tag_list_copy (GST_TAG_LIST (gst_value_get_structure (val)));
374       gst_tag_list_free (ret->tags);
375       ret->tags = list;
376     }
377   }
378
379   if (gst_structure_id_has_field_typed (entry,
380           GST_QUARK (INFO), GST_TYPE_STRUCTURE)) {
381     val = gst_structure_id_get_value (entry, GST_QUARK (INFO));
382
383     if (G_LIKELY (GST_IS_STRUCTURE (gst_value_get_structure (val)))) {
384       st = gst_structure_copy (gst_value_get_structure (val));
385       gst_structure_free (ret->info);
386       ret->info = st;
387     }
388   }
389
390   return ret;
391 }
392
393 GstToc *
394 __gst_toc_from_structure (const GstStructure * toc)
395 {
396   GstToc *ret;
397   GstTocEntry *subentry;
398   const GstStructure *subentry_struct;
399   const GValue *val;
400   GstTagList *list;
401   GstStructure *st;
402   guint count, i;
403   guint editions_count = 0, chapters_count = 0;
404
405   g_return_val_if_fail (toc != NULL, NULL);
406
407   ret = gst_toc_new ();
408
409   if (gst_structure_id_has_field_typed (toc,
410           GST_QUARK (SUB_ENTRIES), GST_TYPE_ARRAY)) {
411     count = gst_toc_entry_structure_n_subentries (toc);
412
413     for (i = 0; i < count; ++i) {
414       subentry_struct = gst_toc_entry_structure_nth_subentry (toc, i);
415       subentry = gst_toc_entry_from_structure (subentry_struct, 0);
416
417       /* skip empty editions */
418       if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
419               && subentry->subentries == NULL)) {
420         g_warning
421             ("Empty edition found while deserializing TOC from GstStructure, skipping");
422         continue;
423       }
424
425       /* check for success */
426       if (G_UNLIKELY (subentry == NULL)) {
427         g_critical ("Couldn't serialize deserializing TOC from GstStructure");
428         gst_toc_free (ret);
429         return NULL;
430       }
431
432       if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
433         ++editions_count;
434       else
435         ++chapters_count;
436
437       /* check for mixed content */
438       if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
439         g_critical
440             ("Mixed editions and chapters in the TOC contents, the TOC is broken");
441         gst_toc_entry_free (subentry);
442         gst_toc_free (ret);
443         return NULL;
444       }
445
446       ret->entries = g_list_prepend (ret->entries, subentry);
447     }
448
449     ret->entries = g_list_reverse (ret->entries);
450   }
451
452   if (gst_structure_id_has_field_typed (toc,
453           GST_QUARK (TAGS), GST_TYPE_STRUCTURE)) {
454     val = gst_structure_id_get_value (toc, GST_QUARK (TAGS));
455
456     if (G_LIKELY (GST_IS_TAG_LIST (gst_value_get_structure (val)))) {
457       list = gst_tag_list_copy (GST_TAG_LIST (gst_value_get_structure (val)));
458       gst_tag_list_free (ret->tags);
459       ret->tags = list;
460     }
461   }
462
463   if (gst_structure_id_has_field_typed (toc,
464           GST_QUARK (INFO), GST_TYPE_STRUCTURE)) {
465     val = gst_structure_id_get_value (toc, GST_QUARK (INFO));
466
467     if (G_LIKELY (GST_IS_STRUCTURE (gst_value_get_structure (val)))) {
468       st = gst_structure_copy (gst_value_get_structure (val));
469       gst_structure_free (ret->info);
470       ret->info = st;
471     }
472   }
473
474   if (G_UNLIKELY (ret->entries == NULL)) {
475     gst_toc_free (ret);
476     return NULL;
477   }
478
479   return ret;
480 }
481
482 static GstStructure *
483 gst_toc_entry_to_structure (const GstTocEntry * entry, guint level)
484 {
485   GstStructure *ret, *subentry_struct;
486   GstTocEntry *subentry;
487   GList *cur;
488   GValue subentries_val = { 0 };
489   GValue entry_val = { 0 };
490   guint chapters_count = 0, editions_count = 0;
491
492   g_return_val_if_fail (entry != NULL, NULL);
493
494   ret =
495       gst_toc_entry_structure_new (entry->type, entry->uid, entry->tags,
496       entry->info);
497
498   g_value_init (&subentries_val, GST_TYPE_ARRAY);
499   g_value_init (&entry_val, GST_TYPE_STRUCTURE);
500
501   cur = entry->subentries;
502   while (cur != NULL) {
503     subentry = cur->data;
504
505     if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
506       ++editions_count;
507     else
508       ++chapters_count;
509
510     /* check for mixed content */
511     if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
512       g_critical
513           ("Mixed editions and chapters in the TOC contents, the TOC is broken");
514       gst_structure_free (ret);
515       g_value_unset (&entry_val);
516       g_value_unset (&subentries_val);
517       return NULL;
518     }
519
520     /* skip empty editions */
521     if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
522             && subentry->subentries == NULL)) {
523       g_warning
524           ("Empty edition found while serializing TOC to GstStructure, skipping");
525       cur = cur->next;
526       continue;
527     }
528
529     subentry_struct = gst_toc_entry_to_structure (subentry, level + 1);
530
531     /* check for success */
532     if (G_UNLIKELY (subentry_struct == NULL)) {
533       gst_structure_free (ret);
534       g_value_unset (&subentries_val);
535       g_value_unset (&entry_val);
536       return NULL;
537     }
538
539     /* skip empty editions */
540     if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
541             && subentry->subentries == NULL)) {
542       g_warning
543           ("Empty edition found while serializing TOC to GstStructure, skipping");
544       cur = cur->next;
545       continue;
546     }
547
548     gst_value_set_structure (&entry_val, subentry_struct);
549     gst_value_array_append_value (&subentries_val, &entry_val);
550     gst_structure_free (subentry_struct);
551
552     cur = cur->next;
553   }
554
555   gst_structure_id_set_value (ret, GST_QUARK (SUB_ENTRIES), &subentries_val);
556
557   g_value_unset (&subentries_val);
558   g_value_unset (&entry_val);
559   return ret;
560 }
561
562 GstStructure *
563 __gst_toc_to_structure (const GstToc * toc)
564 {
565   GValue val = { 0 };
566   GValue subentries_val = { 0 };
567   GstStructure *ret, *subentry_struct;
568   GstTocEntry *subentry;
569   GList *cur;
570   guint editions_count = 0, chapters_count = 0;
571
572   g_return_val_if_fail (toc != NULL, NULL);
573   g_return_val_if_fail (toc->entries != NULL, NULL);
574
575   ret = gst_toc_structure_new (toc->tags, toc->info);
576
577   g_value_init (&val, GST_TYPE_STRUCTURE);
578   g_value_init (&subentries_val, GST_TYPE_ARRAY);
579   cur = toc->entries;
580
581   while (cur != NULL) {
582     subentry = cur->data;
583
584     if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
585       ++editions_count;
586     else
587       ++chapters_count;
588
589     /* check for mixed content */
590     if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
591       g_critical
592           ("Mixed editions and chapters in the TOC contents, the TOC is broken");
593       gst_structure_free (ret);
594       g_value_unset (&val);
595       g_value_unset (&subentries_val);
596       return NULL;
597     }
598
599     /* skip empty editions */
600     if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
601             && subentry->subentries == NULL)) {
602       g_warning
603           ("Empty edition found while serializing TOC to GstStructure, skipping");
604       cur = cur->next;
605       continue;
606     }
607
608     subentry_struct = gst_toc_entry_to_structure (subentry, 0);
609
610     /* check for success */
611     if (G_UNLIKELY (subentry_struct == NULL)) {
612       g_critical ("Couldn't serialize TOC to GstStructure");
613       gst_structure_free (ret);
614       g_value_unset (&val);
615       g_value_unset (&subentries_val);
616       return NULL;
617     }
618
619     gst_value_set_structure (&val, subentry_struct);
620     gst_value_array_append_value (&subentries_val, &val);
621     gst_structure_free (subentry_struct);
622
623     cur = cur->next;
624   }
625
626   gst_structure_id_set_value (ret, GST_QUARK (SUB_ENTRIES), &subentries_val);
627
628   g_value_unset (&val);
629   g_value_unset (&subentries_val);
630   return ret;
631 }
632
633 static gboolean
634 gst_toc_check_entry_for_uid (const GstTocEntry * entry, const gchar * uid)
635 {
636   GList *cur;
637
638   g_return_val_if_fail (entry != NULL, FALSE);
639   g_return_val_if_fail (uid != NULL, FALSE);
640
641   if (g_strcmp0 (entry->uid, uid) == 0)
642     return TRUE;
643
644   cur = entry->subentries;
645   while (cur != NULL) {
646     if (gst_toc_check_entry_for_uid (cur->data, uid))
647       return TRUE;
648     cur = cur->next;
649   }
650
651   return FALSE;
652 }
653
654 /**
655  * gst_toc_find_entry:
656  * @toc: #GstToc to search in.
657  * @uid: UID to find #GstTocEntry with.
658  *
659  * Find #GstTocEntry with given @uid in the @toc.
660  *
661  * Returns: #GstTocEntry with specified @uid from the @toc, or NULL if not found.
662  *
663  * Since: 0.10.37
664  */
665 GstTocEntry *
666 gst_toc_find_entry (const GstToc * toc, const gchar * uid)
667 {
668   GList *cur;
669
670   g_return_val_if_fail (toc != NULL, NULL);
671   g_return_val_if_fail (uid != NULL, NULL);
672
673   cur = toc->entries;
674   while (cur != NULL) {
675     if (gst_toc_check_entry_for_uid (cur->data, uid))
676       return cur->data;
677     cur = cur->next;
678   }
679
680   return NULL;
681 }
682
683 /**
684  * gst_toc_entry_copy:
685  * @entry: #GstTocEntry to copy.
686  *
687  * Copy #GstTocEntry with all subentries (deep copy).
688  *
689  * Returns: newly allocated #GstTocEntry in case of success, NULL otherwise;
690  * free it when done with gst_toc_entry_free().
691  *
692  * Since: 0.10.37
693  */
694 GstTocEntry *
695 gst_toc_entry_copy (const GstTocEntry * entry)
696 {
697   GstTocEntry *ret, *sub;
698   GList *cur;
699   GstTagList *list;
700   GstStructure *st;
701
702   g_return_val_if_fail (entry != NULL, NULL);
703
704   ret = gst_toc_entry_new (entry->type, entry->uid);
705
706   if (GST_IS_STRUCTURE (entry->info)) {
707     st = gst_structure_copy (entry->info);
708     gst_structure_free (ret->info);
709     ret->info = st;
710   }
711
712   if (GST_IS_TAG_LIST (entry->tags)) {
713     list = gst_tag_list_copy (entry->tags);
714     gst_tag_list_free (ret->tags);
715     ret->tags = list;
716   }
717
718   cur = entry->pads;
719   while (cur != NULL) {
720     if (GST_IS_PAD (cur->data))
721       ret->pads = g_list_prepend (ret->pads, gst_object_ref (cur->data));
722     cur = cur->next;
723   }
724   ret->pads = g_list_reverse (ret->pads);
725
726   cur = entry->subentries;
727   while (cur != NULL) {
728     sub = gst_toc_entry_copy (cur->data);
729
730     if (sub != NULL)
731       ret->subentries = g_list_prepend (ret->subentries, sub);
732
733     cur = cur->next;
734   }
735   ret->subentries = g_list_reverse (ret->subentries);
736
737   return ret;
738 }
739
740 /**
741  * gst_toc_copy:
742  * @toc: #GstToc to copy.
743  *
744  * Copy #GstToc with all subentries (deep copy).
745  *
746  * Returns: newly allocated #GstToc in case of success, NULL otherwise;
747  * free it when done with gst_toc_free().
748  *
749  * Since: 0.10.37
750  */
751 GstToc *
752 gst_toc_copy (const GstToc * toc)
753 {
754   GstToc *ret;
755   GstTocEntry *entry;
756   GList *cur;
757   GstTagList *list;
758   GstStructure *st;
759
760   g_return_val_if_fail (toc != NULL, NULL);
761
762   ret = gst_toc_new ();
763
764   if (GST_IS_STRUCTURE (toc->info)) {
765     st = gst_structure_copy (toc->info);
766     gst_structure_free (ret->info);
767     ret->info = st;
768   }
769
770   if (GST_IS_TAG_LIST (toc->tags)) {
771     list = gst_tag_list_copy (toc->tags);
772     gst_tag_list_free (ret->tags);
773     ret->tags = list;
774   }
775
776   cur = toc->entries;
777   while (cur != NULL) {
778     entry = gst_toc_entry_copy (cur->data);
779
780     if (entry != NULL)
781       ret->entries = g_list_prepend (ret->entries, entry);
782
783     cur = cur->next;
784   }
785   ret->entries = g_list_reverse (ret->entries);
786
787   return ret;
788 }
789
790 /**
791  * gst_toc_entry_set_start_stop:
792  * @entry: #GstTocEntry to set values.
793  * @start: start value to set.
794  * @stop: stop value to set.
795  *
796  * Set @start and @stop values for the @entry.
797  *
798  * Since: 0.10.37
799  */
800 void
801 gst_toc_entry_set_start_stop (GstTocEntry * entry, gint64 start, gint64 stop)
802 {
803   const GValue *val;
804   GstStructure *structure = NULL;
805
806   g_return_if_fail (entry != NULL);
807   g_return_if_fail (GST_IS_STRUCTURE (entry->info));
808
809   if (gst_structure_id_has_field_typed (entry->info, GST_QUARK (TIME),
810           GST_TYPE_STRUCTURE)) {
811     val = gst_structure_id_get_value (entry->info, GST_QUARK (TIME));
812     structure = gst_structure_copy (gst_value_get_structure (val));
813   }
814
815   if (structure == NULL)
816     structure = gst_structure_new_id_empty (GST_QUARK (TIME_STRUCTURE));
817
818   gst_structure_id_set (structure, GST_QUARK (START),
819       G_TYPE_INT64, start, GST_QUARK (STOP), G_TYPE_INT64, stop, NULL);
820
821   gst_structure_id_set (entry->info, GST_QUARK (TIME),
822       GST_TYPE_STRUCTURE, structure, NULL);
823
824   gst_structure_free (structure);
825 }
826
827 /**
828  * gst_toc_entry_get_start_stop:
829  * @entry: #GstTocEntry to get values from.
830  * @start: (out): the storage for the start value, leave #NULL if not need.
831  * @stop: (out): the storage for the stop value, leave #NULL if not need.
832  *
833  * Get start and stop values from the @entry and write them into appropriate storages.
834  *
835  * Returns: TRUE if all non-NULL storage pointers were filled with appropriate values,
836  * FALSE otherwise.
837  *
838  * Since: 0.10.37
839  */
840 gboolean
841 gst_toc_entry_get_start_stop (const GstTocEntry * entry, gint64 * start,
842     gint64 * stop)
843 {
844   gboolean ret = TRUE;
845   const GValue *val;
846   const GstStructure *structure;
847
848   g_return_val_if_fail (entry != NULL, FALSE);
849   g_return_val_if_fail (GST_IS_STRUCTURE (entry->info), FALSE);
850
851   if (!gst_structure_id_has_field_typed (entry->info,
852           GST_QUARK (TIME), GST_TYPE_STRUCTURE))
853     return FALSE;
854
855   val = gst_structure_id_get_value (entry->info, GST_QUARK (TIME));
856   structure = gst_value_get_structure (val);
857
858   if (start != NULL) {
859     if (gst_structure_id_has_field_typed (structure,
860             GST_QUARK (START), G_TYPE_INT64))
861       *start =
862           g_value_get_int64 (gst_structure_id_get_value (structure,
863               GST_QUARK (START)));
864     else
865       ret = FALSE;
866   }
867
868   if (stop != NULL) {
869     if (gst_structure_id_has_field_typed (structure,
870             GST_QUARK (STOP), G_TYPE_INT64))
871       *stop =
872           g_value_get_int64 (gst_structure_id_get_value (structure,
873               GST_QUARK (STOP)));
874     else
875       ret = FALSE;
876   }
877
878   return ret;
879 }
880
881 /**
882  * gst_toc_entry_type_get_nick:
883  * @type: a #GstTocEntryType.
884  *
885  * Converts @type to a string representation.
886  *
887  * Returns: Returns the human-readable @type. Can be NULL if an error occurred.
888  * Since: 0.11.92
889  */
890 const gchar *
891 gst_toc_entry_type_get_nick (GstTocEntryType type)
892 {
893   const gchar *entry_types[] = { "chapter", "edition" };
894
895   g_return_val_if_fail ((gint) type >= 0
896       && (gint) type < G_N_ELEMENTS (entry_types), NULL);
897   return entry_types[type];
898 }
899
900 gboolean
901 __gst_toc_structure_get_updated (const GstStructure * toc)
902 {
903   const GValue *val;
904
905   g_return_val_if_fail (GST_IS_STRUCTURE (toc), FALSE);
906
907   if (G_LIKELY (gst_structure_id_has_field_typed (toc,
908               GST_QUARK (UPDATED), G_TYPE_BOOLEAN))) {
909     val = gst_structure_id_get_value (toc, GST_QUARK (UPDATED));
910     return g_value_get_boolean (val);
911   }
912
913   return FALSE;
914 }
915
916 void
917 __gst_toc_structure_set_updated (GstStructure * toc, gboolean updated)
918 {
919   g_return_if_fail (toc != NULL);
920
921   gst_structure_id_set (toc, GST_QUARK (UPDATED), G_TYPE_BOOLEAN, updated,
922       NULL);
923 }
924
925 gchar *
926 __gst_toc_structure_get_extend_uid (const GstStructure * toc)
927 {
928   const GValue *val;
929
930   g_return_val_if_fail (GST_IS_STRUCTURE (toc), NULL);
931
932   if (G_LIKELY (gst_structure_id_has_field_typed (toc,
933               GST_QUARK (EXTEND_UID), G_TYPE_STRING))) {
934     val = gst_structure_id_get_value (toc, GST_QUARK (EXTEND_UID));
935     return g_strdup (g_value_get_string (val));
936   }
937
938   return NULL;
939 }
940
941 void
942 __gst_toc_structure_set_extend_uid (GstStructure * toc,
943     const gchar * extend_uid)
944 {
945   g_return_if_fail (toc != NULL);
946   g_return_if_fail (extend_uid != NULL);
947
948   gst_structure_id_set (toc, GST_QUARK (EXTEND_UID), G_TYPE_STRING, extend_uid,
949       NULL);
950 }