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