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