Merge remote-tracking branch 'origin/0.10'
[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
74 #define GST_TOC_TOC_NAME            "toc"
75 #define GST_TOC_ENTRY_NAME          "entry"
76
77 #define GST_TOC_TOC_UPDATED_FIELD   "updated"
78 #define GST_TOC_TOC_EXTENDUID_FIELD "extenduid"
79 #define GST_TOC_INFO_FIELD          "info"
80
81 #define GST_TOC_ENTRY_UID_FIELD     "uid"
82 #define GST_TOC_ENTRY_TYPE_FIELD    "type"
83 #define GST_TOC_ENTRY_TAGS_FIELD    "tags"
84
85 #define GST_TOC_TOC_ENTRIES_FIELD   "subentries"
86
87 #define GST_TOC_INFO_NAME           "info-structure"
88 #define GST_TOC_INFO_TIME_FIELD     "time"
89
90 #define GST_TOC_TIME_NAME           "time-structure"
91 #define GST_TOC_TIME_START_FIELD    "start"
92 #define GST_TOC_TIME_STOP_FIELD     "stop"
93
94
95 enum
96 {
97   GST_TOC_TOC = 0,
98   GST_TOC_ENTRY = 1,
99   GST_TOC_UPDATED = 2,
100   GST_TOC_EXTENDUID = 3,
101   GST_TOC_UID = 4,
102   GST_TOC_TYPE = 5,
103   GST_TOC_TAGS = 6,
104   GST_TOC_SUBENTRIES = 7,
105   GST_TOC_INFO = 8,
106   GST_TOC_INFONAME = 9,
107   GST_TOC_TIME = 10,
108   GST_TOC_TIMENAME = 11,
109   GST_TOC_TIME_START = 12,
110   GST_TOC_TIME_STOP = 13,
111   GST_TOC_LAST = 14
112 };
113
114 static GQuark gst_toc_fields[GST_TOC_LAST] = { 0 };
115
116 void
117 _priv_gst_toc_initialize (void)
118 {
119   static gboolean inited = FALSE;
120
121   if (G_LIKELY (!inited)) {
122     gst_toc_fields[GST_TOC_TOC] = g_quark_from_static_string (GST_TOC_TOC_NAME);
123     gst_toc_fields[GST_TOC_ENTRY] =
124         g_quark_from_static_string (GST_TOC_ENTRY_NAME);
125
126     gst_toc_fields[GST_TOC_UPDATED] =
127         g_quark_from_static_string (GST_TOC_TOC_UPDATED_FIELD);
128     gst_toc_fields[GST_TOC_EXTENDUID] =
129         g_quark_from_static_string (GST_TOC_TOC_EXTENDUID_FIELD);
130     gst_toc_fields[GST_TOC_INFO] =
131         g_quark_from_static_string (GST_TOC_INFO_FIELD);
132
133     gst_toc_fields[GST_TOC_UID] =
134         g_quark_from_static_string (GST_TOC_ENTRY_UID_FIELD);
135     gst_toc_fields[GST_TOC_TYPE] =
136         g_quark_from_static_string (GST_TOC_ENTRY_TYPE_FIELD);
137     gst_toc_fields[GST_TOC_TAGS] =
138         g_quark_from_static_string (GST_TOC_ENTRY_TAGS_FIELD);
139
140     gst_toc_fields[GST_TOC_SUBENTRIES] =
141         g_quark_from_static_string (GST_TOC_TOC_ENTRIES_FIELD);
142
143     gst_toc_fields[GST_TOC_INFONAME] =
144         g_quark_from_static_string (GST_TOC_INFO_NAME);
145     gst_toc_fields[GST_TOC_TIME] =
146         g_quark_from_static_string (GST_TOC_INFO_TIME_FIELD);
147     gst_toc_fields[GST_TOC_TIMENAME] =
148         g_quark_from_static_string (GST_TOC_TIME_NAME);
149     gst_toc_fields[GST_TOC_TIME_START] =
150         g_quark_from_static_string (GST_TOC_TIME_START_FIELD);
151     gst_toc_fields[GST_TOC_TIME_STOP] =
152         g_quark_from_static_string (GST_TOC_TIME_STOP_FIELD);
153
154     inited = TRUE;
155   }
156 }
157
158 /**
159  * gst_toc_new:
160  *
161  * Create new #GstToc structure.
162  *
163  * Returns: newly allocated #GstToc structure, free it with gst_toc_free().
164  *
165  * Since: 0.10.37
166  */
167 GstToc *
168 gst_toc_new (void)
169 {
170   GstToc *toc;
171
172   toc = g_slice_new0 (GstToc);
173   toc->tags = gst_tag_list_new_empty ();
174   toc->info = gst_structure_new_id_empty (gst_toc_fields[GST_TOC_INFONAME]);
175
176   return toc;
177 }
178
179 /**
180  * gst_toc_entry_new:
181  * @type: entry type.
182  * @uid: unique ID (UID) in the whole TOC.
183  *
184  * Create new #GstTocEntry structure.
185  *
186  * Returns: newly allocated #GstTocEntry structure, free it with gst_toc_entry_free().
187  *
188  * Since: 0.10.37
189  */
190 GstTocEntry *
191 gst_toc_entry_new (GstTocEntryType type, const gchar * uid)
192 {
193   GstTocEntry *entry;
194
195   g_return_val_if_fail (uid != NULL, NULL);
196
197   entry = g_slice_new0 (GstTocEntry);
198   entry->uid = g_strdup (uid);
199   entry->type = type;
200   entry->tags = gst_tag_list_new_empty ();
201   entry->info = gst_structure_new_id_empty (gst_toc_fields[GST_TOC_INFONAME]);
202
203   return entry;
204 }
205
206 /**
207  * gst_toc_entry_new_with_pad:
208  * @type: entry type.
209  * @uid: unique ID (UID) in the whole TOC.
210  * @pad: #GstPad related to this entry.
211  *
212  * Create new #GstTocEntry structure with #GstPad related.
213  *
214  * Returns: newly allocated #GstTocEntry structure, free it with gst_toc_entry_free()
215  * when done.
216  *
217  * Since: 0.10.37
218  */
219 GstTocEntry *
220 gst_toc_entry_new_with_pad (GstTocEntryType type, const gchar * uid,
221     gpointer pad)
222 {
223   GstTocEntry *entry;
224
225   g_return_val_if_fail (uid != NULL, NULL);
226
227   entry = g_slice_new0 (GstTocEntry);
228   entry->uid = g_strdup (uid);
229   entry->type = type;
230   entry->tags = gst_tag_list_new_empty ();
231
232   if (pad != NULL && GST_IS_PAD (pad))
233     entry->pads = g_list_append (entry->pads, gst_object_ref (pad));
234
235   return entry;
236 }
237
238 /**
239  * gst_toc_free:
240  * @toc: #GstToc structure to free.
241  *
242  * Free unused #GstToc structure.
243  *
244  * Since: 0.10.37
245  */
246 void
247 gst_toc_free (GstToc * toc)
248 {
249   g_return_if_fail (toc != NULL);
250
251   g_list_foreach (toc->entries, (GFunc) gst_toc_entry_free, NULL);
252   g_list_free (toc->entries);
253
254   if (toc->tags != NULL)
255     gst_tag_list_free (toc->tags);
256
257   if (toc->info != NULL)
258     gst_structure_free (toc->info);
259
260   g_slice_free (GstToc, toc);
261 }
262
263 /**
264  * gst_toc_entry_free:
265  * @entry: #GstTocEntry structure to free.
266  *
267  * Free unused #GstTocEntry structure. Note that #GstTocEntry.uid will
268  * be freed with g_free() and all #GstPad objects in the #GstTocEntry.pads
269  * list will be unrefed with gst_object_unref().
270  *
271  * Since: 0.10.37
272  */
273 void
274 gst_toc_entry_free (GstTocEntry * entry)
275 {
276   GList *cur;
277
278   g_return_if_fail (entry != NULL);
279
280   g_list_foreach (entry->subentries, (GFunc) gst_toc_entry_free, NULL);
281   g_list_free (entry->subentries);
282
283   g_free (entry->uid);
284
285   if (entry->tags != NULL)
286     gst_tag_list_free (entry->tags);
287
288   if (entry->info != NULL)
289     gst_structure_free (entry->info);
290
291   cur = entry->pads;
292   while (cur != NULL) {
293     if (GST_IS_PAD (cur->data))
294       gst_object_unref (cur->data);
295     cur = cur->next;
296   }
297
298   g_list_free (entry->pads);
299
300   g_slice_free (GstTocEntry, entry);
301 }
302
303 static GstStructure *
304 gst_toc_structure_new (GstTagList * tags, GstStructure * info)
305 {
306   GstStructure *ret;
307   GValue val = { 0 };
308
309   ret = gst_structure_new_id_empty (gst_toc_fields[GST_TOC_TOC]);
310
311   if (tags != NULL) {
312     g_value_init (&val, GST_TYPE_STRUCTURE);
313     gst_value_set_structure (&val, GST_STRUCTURE (tags));
314     gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_TAGS], &val);
315     g_value_unset (&val);
316   }
317
318   if (info != NULL) {
319     g_value_init (&val, GST_TYPE_STRUCTURE);
320     gst_value_set_structure (&val, info);
321     gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_INFO], &val);
322     g_value_unset (&val);
323   }
324
325   return ret;
326 }
327
328 static GstStructure *
329 gst_toc_entry_structure_new (GstTocEntryType type, const gchar * uid,
330     GstTagList * tags, GstStructure * info)
331 {
332   GValue val = { 0 };
333   GstStructure *ret;
334
335   ret = gst_structure_new_id_empty (gst_toc_fields[GST_TOC_ENTRY]);
336
337   gst_structure_id_set (ret, gst_toc_fields[GST_TOC_TYPE],
338       GST_TYPE_TOC_ENTRY_TYPE, type, NULL);
339
340   g_value_init (&val, G_TYPE_STRING);
341   g_value_set_string (&val, uid);
342   gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_UID], &val);
343   g_value_unset (&val);
344
345   if (tags != NULL) {
346     g_value_init (&val, GST_TYPE_STRUCTURE);
347     gst_value_set_structure (&val, GST_STRUCTURE (tags));
348     gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_TAGS], &val);
349     g_value_unset (&val);
350   }
351
352   if (info != NULL) {
353     g_value_init (&val, GST_TYPE_STRUCTURE);
354     gst_value_set_structure (&val, info);
355     gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_INFO], &val);
356     g_value_unset (&val);
357   }
358
359   return ret;
360 }
361
362 static guint
363 gst_toc_entry_structure_n_subentries (const GstStructure * entry)
364 {
365   if (G_UNLIKELY (!gst_structure_id_has_field_typed (entry,
366               gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY)))
367     return 0;
368   else
369     return gst_value_array_get_size ((gst_structure_id_get_value (entry,
370                 gst_toc_fields[GST_TOC_SUBENTRIES])));
371 }
372
373 static const GstStructure *
374 gst_toc_entry_structure_nth_subentry (const GstStructure * entry, guint nth)
375 {
376   guint count;
377   const GValue *array;
378
379   count = gst_toc_entry_structure_n_subentries (entry);
380
381   if (count < nth)
382     return NULL;
383
384   if (G_UNLIKELY (!gst_structure_id_has_field_typed (entry,
385               gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY)))
386     return NULL;
387   else {
388     array =
389         gst_value_array_get_value (gst_structure_id_get_value (entry,
390             gst_toc_fields[GST_TOC_SUBENTRIES]), nth);
391     return gst_value_get_structure (array);
392   }
393 }
394
395 static GstTocEntry *
396 gst_toc_entry_from_structure (const GstStructure * entry, guint level)
397 {
398   GstTocEntry *ret, *subentry;
399   const GValue *val;
400   const GstTagList *entry_tags;
401   const GstStructure *subentry_struct;
402   gint count, i;
403   const gchar *uid;
404   guint chapters_count = 0, editions_count = 0;
405
406   g_return_val_if_fail (entry != NULL, NULL);
407   g_return_val_if_fail (gst_structure_id_has_field_typed (entry,
408           gst_toc_fields[GST_TOC_UID], G_TYPE_STRING), NULL);
409   g_return_val_if_fail (gst_structure_id_has_field_typed (entry,
410           gst_toc_fields[GST_TOC_TYPE], GST_TYPE_TOC_ENTRY_TYPE), NULL);
411
412   val = gst_structure_id_get_value (entry, gst_toc_fields[GST_TOC_UID]);
413   uid = g_value_get_string (val);
414
415   ret = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, uid);
416
417   gst_structure_get_enum (entry, GST_TOC_ENTRY_TYPE_FIELD,
418       GST_TYPE_TOC_ENTRY_TYPE, (gint *) & (ret->type));
419
420   if (gst_structure_id_has_field_typed (entry,
421           gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY)) {
422     count = gst_toc_entry_structure_n_subentries (entry);
423
424     for (i = 0; i < count; ++i) {
425       subentry_struct = gst_toc_entry_structure_nth_subentry (entry, i);
426       subentry = gst_toc_entry_from_structure (subentry_struct, level + 1);
427
428       /* skip empty editions */
429       if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
430               && subentry->subentries == NULL)) {
431         g_warning
432             ("Empty edition found while deserializing TOC from GstStructure, skipping");
433         continue;
434       }
435
436       if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
437         ++editions_count;
438       else
439         ++chapters_count;
440
441       /* check for mixed content */
442       if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
443         g_critical
444             ("Mixed editions and chapters in the TOC contents, the TOC is broken");
445         gst_toc_entry_free (subentry);
446         gst_toc_entry_free (ret);
447         return NULL;
448       }
449
450       if (G_UNLIKELY (subentry == NULL)) {
451         gst_toc_entry_free (ret);
452         return NULL;
453       }
454
455       ret->subentries = g_list_prepend (ret->subentries, subentry);
456     }
457
458     ret->subentries = g_list_reverse (ret->subentries);
459   }
460
461   if (gst_structure_id_has_field_typed (entry,
462           gst_toc_fields[GST_TOC_TAGS], GST_TYPE_STRUCTURE)) {
463     val = gst_structure_id_get_value (entry, gst_toc_fields[GST_TOC_TAGS]);
464
465     if (G_LIKELY (GST_IS_TAG_LIST (gst_value_get_structure (val)))) {
466       entry_tags = GST_TAG_LIST (gst_value_get_structure (val));
467       ret->tags = gst_tag_list_copy (entry_tags);
468     }
469   }
470
471   if (gst_structure_id_has_field_typed (entry,
472           gst_toc_fields[GST_TOC_INFO], GST_TYPE_STRUCTURE)) {
473     val = gst_structure_id_get_value (entry, gst_toc_fields[GST_TOC_INFO]);
474
475     if (G_LIKELY (GST_IS_STRUCTURE (gst_value_get_structure (val))))
476       ret->info = gst_structure_copy (gst_value_get_structure (val));
477   }
478
479   return ret;
480 }
481
482 GstToc *
483 __gst_toc_from_structure (const GstStructure * toc)
484 {
485   GstToc *ret;
486   GstTocEntry *subentry;
487   const GstStructure *subentry_struct;
488   const GValue *val;
489   const GstTagList *entry_tags;
490   guint count, i;
491   guint editions_count = 0, chapters_count = 0;
492
493   g_return_val_if_fail (toc != NULL, NULL);
494
495   ret = gst_toc_new ();
496
497   if (gst_structure_id_has_field_typed (toc,
498           gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY)) {
499     count = gst_toc_entry_structure_n_subentries (toc);
500
501     for (i = 0; i < count; ++i) {
502       subentry_struct = gst_toc_entry_structure_nth_subentry (toc, i);
503       subentry = gst_toc_entry_from_structure (subentry_struct, 0);
504
505       /* skip empty editions */
506       if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
507               && subentry->subentries == NULL)) {
508         g_warning
509             ("Empty edition found while deserializing TOC from GstStructure, skipping");
510         continue;
511       }
512
513       /* check for success */
514       if (G_UNLIKELY (subentry == NULL)) {
515         g_critical ("Couldn't serialize deserializing TOC from GstStructure");
516         gst_toc_free (ret);
517         return NULL;
518       }
519
520       if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
521         ++editions_count;
522       else
523         ++chapters_count;
524
525       /* check for mixed content */
526       if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
527         g_critical
528             ("Mixed editions and chapters in the TOC contents, the TOC is broken");
529         gst_toc_entry_free (subentry);
530         gst_toc_free (ret);
531         return NULL;
532       }
533
534       ret->entries = g_list_prepend (ret->entries, subentry);
535     }
536
537     ret->entries = g_list_reverse (ret->entries);
538   }
539
540   if (gst_structure_id_has_field_typed (toc,
541           gst_toc_fields[GST_TOC_TAGS], GST_TYPE_STRUCTURE)) {
542     val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_TAGS]);
543
544     if (G_LIKELY (GST_IS_TAG_LIST (gst_value_get_structure (val)))) {
545       entry_tags = GST_TAG_LIST (gst_value_get_structure (val));
546       ret->tags = gst_tag_list_copy (entry_tags);
547     }
548   }
549
550   if (gst_structure_id_has_field_typed (toc,
551           gst_toc_fields[GST_TOC_INFO], GST_TYPE_STRUCTURE)) {
552     val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_INFO]);
553
554     if (G_LIKELY (GST_IS_STRUCTURE (gst_value_get_structure (val))))
555       ret->info = gst_structure_copy (gst_value_get_structure (val));
556   }
557
558   if (G_UNLIKELY (ret->entries == NULL)) {
559     gst_toc_free (ret);
560     return NULL;
561   }
562
563   return ret;
564 }
565
566 static GstStructure *
567 gst_toc_entry_to_structure (const GstTocEntry * entry, guint level)
568 {
569   GstStructure *ret, *subentry_struct;
570   GstTocEntry *subentry;
571   GList *cur;
572   GValue subentries_val = { 0 };
573   GValue entry_val = { 0 };
574   guint chapters_count = 0, editions_count = 0;
575
576   g_return_val_if_fail (entry != NULL, NULL);
577
578   ret =
579       gst_toc_entry_structure_new (entry->type, entry->uid, entry->tags,
580       entry->info);
581
582   g_value_init (&subentries_val, GST_TYPE_ARRAY);
583   g_value_init (&entry_val, GST_TYPE_STRUCTURE);
584
585   cur = entry->subentries;
586   while (cur != NULL) {
587     subentry = cur->data;
588
589     if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
590       ++editions_count;
591     else
592       ++chapters_count;
593
594     /* check for mixed content */
595     if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
596       g_critical
597           ("Mixed editions and chapters in the TOC contents, the TOC is broken");
598       gst_structure_free (ret);
599       g_value_unset (&entry_val);
600       g_value_unset (&subentries_val);
601       return NULL;
602     }
603
604     /* skip empty editions */
605     if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
606             && subentry->subentries == NULL)) {
607       g_warning
608           ("Empty edition found while serializing TOC to GstStructure, skipping");
609       cur = cur->next;
610       continue;
611     }
612
613     subentry_struct = gst_toc_entry_to_structure (subentry, level + 1);
614
615     /* check for success */
616     if (G_UNLIKELY (subentry_struct == NULL)) {
617       gst_structure_free (ret);
618       g_value_unset (&subentries_val);
619       g_value_unset (&entry_val);
620       return NULL;
621     }
622
623     /* skip empty editions */
624     if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
625             && subentry->subentries == NULL)) {
626       g_warning
627           ("Empty edition found while serializing TOC to GstStructure, skipping");
628       cur = cur->next;
629       continue;
630     }
631
632     gst_value_set_structure (&entry_val, subentry_struct);
633     gst_value_array_append_value (&subentries_val, &entry_val);
634     gst_structure_free (subentry_struct);
635
636     cur = cur->next;
637   }
638
639   gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_SUBENTRIES],
640       &subentries_val);
641
642   g_value_unset (&subentries_val);
643   g_value_unset (&entry_val);
644   return ret;
645 }
646
647 GstStructure *
648 __gst_toc_to_structure (const GstToc * toc)
649 {
650   GValue val = { 0 };
651   GValue subentries_val = { 0 };
652   GstStructure *ret, *subentry_struct;
653   GstTocEntry *subentry;
654   GList *cur;
655   guint editions_count = 0, chapters_count = 0;
656
657   g_return_val_if_fail (toc != NULL, NULL);
658   g_return_val_if_fail (toc->entries != NULL, NULL);
659
660   ret = gst_toc_structure_new (toc->tags, toc->info);
661
662   g_value_init (&val, GST_TYPE_STRUCTURE);
663   g_value_init (&subentries_val, GST_TYPE_ARRAY);
664   cur = toc->entries;
665
666   while (cur != NULL) {
667     subentry = cur->data;
668
669     if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION)
670       ++editions_count;
671     else
672       ++chapters_count;
673
674     /* check for mixed content */
675     if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) {
676       g_critical
677           ("Mixed editions and chapters in the TOC contents, the TOC is broken");
678       gst_structure_free (ret);
679       g_value_unset (&val);
680       g_value_unset (&subentries_val);
681       return NULL;
682     }
683
684     /* skip empty editions */
685     if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION
686             && subentry->subentries == NULL)) {
687       g_warning
688           ("Empty edition found while serializing TOC to GstStructure, skipping");
689       cur = cur->next;
690       continue;
691     }
692
693     subentry_struct = gst_toc_entry_to_structure (subentry, 0);
694
695     /* check for success */
696     if (G_UNLIKELY (subentry_struct == NULL)) {
697       g_critical ("Couldn't serialize TOC to GstStructure");
698       gst_structure_free (ret);
699       g_value_unset (&val);
700       g_value_unset (&subentries_val);
701       return NULL;
702     }
703
704     gst_value_set_structure (&val, subentry_struct);
705     gst_value_array_append_value (&subentries_val, &val);
706     gst_structure_free (subentry_struct);
707
708     cur = cur->next;
709   }
710
711   gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_SUBENTRIES],
712       &subentries_val);
713
714   g_value_unset (&val);
715   g_value_unset (&subentries_val);
716   return ret;
717 }
718
719 static gboolean
720 gst_toc_check_entry_for_uid (const GstTocEntry * entry, const gchar * uid)
721 {
722   GList *cur;
723
724   g_return_val_if_fail (entry != NULL, FALSE);
725   g_return_val_if_fail (uid != NULL, FALSE);
726
727   if (g_strcmp0 (entry->uid, uid) == 0)
728     return TRUE;
729
730   cur = entry->subentries;
731   while (cur != NULL) {
732     if (gst_toc_check_entry_for_uid (cur->data, uid))
733       return TRUE;
734     cur = cur->next;
735   }
736
737   return FALSE;
738 }
739
740 /**
741  * gst_toc_find_entry:
742  * @toc: #GstToc to search in.
743  * @uid: UID to find #GstTocEntry with.
744  *
745  * Find #GstTocEntry with given @uid in the @toc.
746  *
747  * Returns: #GstTocEntry with specified @uid from the @toc, or NULL if not found.
748  *
749  * Since: 0.10.37
750  */
751 GstTocEntry *
752 gst_toc_find_entry (const GstToc * toc, const gchar * uid)
753 {
754   GList *cur;
755
756   g_return_val_if_fail (toc != NULL, NULL);
757   g_return_val_if_fail (uid != NULL, NULL);
758
759   cur = toc->entries;
760   while (cur != NULL) {
761     if (gst_toc_check_entry_for_uid (cur->data, uid))
762       return cur->data;
763     cur = cur->next;
764   }
765
766   return NULL;
767 }
768
769 /**
770  * gst_toc_entry_copy:
771  * @entry: #GstTocEntry to copy.
772  *
773  * Copy #GstTocEntry with all subentries (deep copy).
774  *
775  * Returns: newly allocated #GstTocEntry in case of success, NULL otherwise;
776  * free it when done with gst_toc_entry_free().
777  *
778  * Since: 0.10.37
779  */
780 GstTocEntry *
781 gst_toc_entry_copy (const GstTocEntry * entry)
782 {
783   GstTocEntry *ret, *sub;
784   GList *cur;
785
786   g_return_val_if_fail (entry != NULL, NULL);
787
788   ret = gst_toc_entry_new (entry->type, entry->uid);
789
790   if (GST_IS_STRUCTURE (entry->info))
791     ret->info = gst_structure_copy (entry->info);
792
793   if (GST_IS_TAG_LIST (entry->tags))
794     ret->tags = gst_tag_list_copy (entry->tags);
795
796   cur = entry->pads;
797   while (cur != NULL) {
798     if (GST_IS_PAD (cur->data))
799       ret->pads = g_list_prepend (ret->pads, gst_object_ref (cur->data));
800     cur = cur->next;
801   }
802   ret->pads = g_list_reverse (ret->pads);
803
804   cur = entry->subentries;
805   while (cur != NULL) {
806     sub = gst_toc_entry_copy (cur->data);
807
808     if (sub != NULL)
809       ret->subentries = g_list_prepend (ret->subentries, sub);
810
811     cur = cur->next;
812   }
813   ret->subentries = g_list_reverse (ret->subentries);
814
815   return ret;
816 }
817
818 /**
819  * gst_toc_copy:
820  * @toc: #GstToc to copy.
821  *
822  * Copy #GstToc with all subentries (deep copy).
823  *
824  * Returns: newly allocated #GstToc in case of success, NULL otherwise;
825  * free it when done with gst_toc_free().
826  *
827  * Since: 0.10.37
828  */
829 GstToc *
830 gst_toc_copy (const GstToc * toc)
831 {
832   GstToc *ret;
833   GstTocEntry *entry;
834   GList *cur;
835
836   g_return_val_if_fail (toc != NULL, NULL);
837
838   ret = gst_toc_new ();
839
840   if (GST_IS_STRUCTURE (toc->info))
841     ret->info = gst_structure_copy (toc->info);
842
843   if (GST_IS_TAG_LIST (toc->tags))
844     ret->tags = gst_tag_list_copy (toc->tags);
845
846   cur = toc->entries;
847   while (cur != NULL) {
848     entry = gst_toc_entry_copy (cur->data);
849
850     if (entry != NULL)
851       ret->entries = g_list_prepend (ret->entries, entry);
852
853     cur = cur->next;
854   }
855   ret->entries = g_list_reverse (ret->entries);
856
857   return ret;
858 }
859
860 /**
861  * gst_toc_entry_set_start_stop:
862  * @entry: #GstTocEntry to set values.
863  * @start: start value to set.
864  * @stop: stop value to set.
865  *
866  * Set @start and @stop values for the @entry.
867  *
868  * Since: 0.10.37
869  */
870 void
871 gst_toc_entry_set_start_stop (GstTocEntry * entry, gint64 start, gint64 stop)
872 {
873   const GValue *val;
874   GstStructure *structure = NULL;
875
876   g_return_if_fail (entry != NULL);
877   g_return_if_fail (GST_IS_STRUCTURE (entry->info));
878
879   if (gst_structure_id_has_field_typed (entry->info,
880           gst_toc_fields[GST_TOC_TIME], GST_TYPE_STRUCTURE)) {
881     val =
882         gst_structure_id_get_value (entry->info, gst_toc_fields[GST_TOC_TIME]);
883     structure = gst_structure_copy (gst_value_get_structure (val));
884   }
885
886   if (structure == NULL)
887     structure = gst_structure_new_id_empty (gst_toc_fields[GST_TOC_TIMENAME]);
888
889   gst_structure_id_set (structure, gst_toc_fields[GST_TOC_TIME_START],
890       G_TYPE_INT64, start, gst_toc_fields[GST_TOC_TIME_STOP], G_TYPE_INT64,
891       stop, NULL);
892
893   gst_structure_id_set (entry->info, gst_toc_fields[GST_TOC_TIME],
894       GST_TYPE_STRUCTURE, structure, NULL);
895
896   gst_structure_free (structure);
897 }
898
899 /**
900  * gst_toc_entry_get_start_stop:
901  * @entry: #GstTocEntry to get values from.
902  * @start: (out): the storage for the start value, leave #NULL if not need.
903  * @stop: (out): the storage for the stop value, leave #NULL if not need.
904  *
905  * Get start and stop values from the @entry and write them into appropriate storages.
906  *
907  * Returns: TRUE if all non-NULL storage pointers were filled with appropriate values,
908  * FALSE otherwise.
909  *
910  * Since: 0.10.37
911  */
912 gboolean
913 gst_toc_entry_get_start_stop (const GstTocEntry * entry, gint64 * start,
914     gint64 * stop)
915 {
916   gboolean ret = TRUE;
917   const GValue *val;
918   const GstStructure *structure;
919
920   g_return_val_if_fail (entry != NULL, FALSE);
921   g_return_val_if_fail (GST_IS_STRUCTURE (entry->info), FALSE);
922
923   if (!gst_structure_id_has_field_typed (entry->info,
924           gst_toc_fields[GST_TOC_TIME], GST_TYPE_STRUCTURE))
925     return FALSE;
926
927   val = gst_structure_id_get_value (entry->info, gst_toc_fields[GST_TOC_TIME]);
928   structure = gst_value_get_structure (val);
929
930   if (start != NULL) {
931     if (gst_structure_id_has_field_typed (structure,
932             gst_toc_fields[GST_TOC_TIME_START], G_TYPE_INT64))
933       *start =
934           g_value_get_int64 (gst_structure_id_get_value (structure,
935               gst_toc_fields[GST_TOC_TIME_START]));
936     else
937       ret = FALSE;
938   }
939
940   if (stop != NULL) {
941     if (gst_structure_id_has_field_typed (structure,
942             gst_toc_fields[GST_TOC_TIME_STOP], G_TYPE_INT64))
943       *stop =
944           g_value_get_int64 (gst_structure_id_get_value (structure,
945               gst_toc_fields[GST_TOC_TIME_STOP]));
946     else
947       ret = FALSE;
948   }
949
950   return ret;
951 }
952
953 gboolean
954 __gst_toc_structure_get_updated (const GstStructure * toc)
955 {
956   const GValue *val;
957
958   g_return_val_if_fail (GST_IS_STRUCTURE (toc), FALSE);
959
960   if (G_LIKELY (gst_structure_id_has_field_typed (toc,
961               gst_toc_fields[GST_TOC_UPDATED], G_TYPE_BOOLEAN))) {
962     val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_UPDATED]);
963     return g_value_get_boolean (val);
964   }
965
966   return FALSE;
967 }
968
969 void
970 __gst_toc_structure_set_updated (GstStructure * toc, gboolean updated)
971 {
972   GValue val = { 0 };
973
974   g_return_if_fail (toc != NULL);
975
976   g_value_init (&val, G_TYPE_BOOLEAN);
977   g_value_set_boolean (&val, updated);
978   gst_structure_id_set_value (toc, gst_toc_fields[GST_TOC_UPDATED], &val);
979   g_value_unset (&val);
980 }
981
982 gchar *
983 __gst_toc_structure_get_extend_uid (const GstStructure * toc)
984 {
985   const GValue *val;
986
987   g_return_val_if_fail (GST_IS_STRUCTURE (toc), NULL);
988
989   if (G_LIKELY (gst_structure_id_has_field_typed (toc,
990               gst_toc_fields[GST_TOC_EXTENDUID], G_TYPE_STRING))) {
991     val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_EXTENDUID]);
992     return g_strdup (g_value_get_string (val));
993   }
994
995   return NULL;
996 }
997
998 void
999 __gst_toc_structure_set_extend_uid (GstStructure * toc,
1000     const gchar * extend_uid)
1001 {
1002   GValue val = { 0 };
1003
1004   g_return_if_fail (toc != NULL);
1005   g_return_if_fail (extend_uid != NULL);
1006
1007   g_value_init (&val, G_TYPE_STRING);
1008   g_value_set_string (&val, extend_uid);
1009   gst_structure_id_set_value (toc, gst_toc_fields[GST_TOC_EXTENDUID], &val);
1010   g_value_unset (&val);
1011 }