toc: remove padding now that the structs are private
[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
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 struct _GstTocEntry
76 {
77   GstMiniObject mini_object;
78
79   gchar *uid;
80   GstTocEntryType type;
81   GstClockTime start, stop;
82   GList *subentries;
83   GstTagList *tags;
84 };
85
86 struct _GstToc
87 {
88   GstMiniObject mini_object;
89
90   GList *entries;
91   GstTagList *tags;
92 };
93
94 #undef gst_toc_copy
95 static GstToc *gst_toc_copy (const GstToc * toc);
96 static void gst_toc_free (GstToc * toc);
97 #undef gst_toc_entry_copy
98 static GstTocEntry *gst_toc_entry_copy (const GstTocEntry * toc);
99 static void gst_toc_entry_free (GstTocEntry * toc);
100
101 GST_DEFINE_MINI_OBJECT_TYPE (GstToc, gst_toc);
102 GST_DEFINE_MINI_OBJECT_TYPE (GstTocEntry, gst_toc_entry);
103
104 /**
105  * gst_toc_new:
106  *
107  * Create a new #GstToc structure.
108  *
109  * Returns: (transfer full): newly allocated #GstToc structure, free it
110  *     with gst_toc_unref().
111  *
112  * Since: 0.10.37
113  */
114 GstToc *
115 gst_toc_new (void)
116 {
117   GstToc *toc;
118
119   toc = g_slice_new0 (GstToc);
120
121   gst_mini_object_init (GST_MINI_OBJECT_CAST (toc), 0, GST_TYPE_TOC,
122       (GstMiniObjectCopyFunction) gst_toc_copy, NULL,
123       (GstMiniObjectFreeFunction) gst_toc_free);
124
125   toc->tags = gst_tag_list_new_empty ();
126
127   return toc;
128 }
129
130 /**
131  * gst_toc_set_tags:
132  * @toc: A #GstToc instance
133  * @tags: (allow-none) (transfer full): A #GstTagList or %NULL
134  *
135  * Set a #GstTagList with tags for the complete @toc.
136  *
137  * Since: 0.10.37
138  */
139 void
140 gst_toc_set_tags (GstToc * toc, GstTagList * tags)
141 {
142   g_return_if_fail (toc != NULL);
143   g_return_if_fail (gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (toc)));
144
145   if (toc->tags)
146     gst_tag_list_unref (toc->tags);
147   toc->tags = tags;
148 }
149
150 /**
151  * gst_toc_merge_tags:
152  * @toc: A #GstToc instance
153  * @tags: (allow-none): A #GstTagList or %NULL
154  * @mode: A #GstTagMergeMode
155  *
156  * Merge @tags into the existing tags of @toc using @mode.
157  *
158  * Since: 0.10.37
159  */
160 void
161 gst_toc_merge_tags (GstToc * toc, GstTagList * tags, GstTagMergeMode mode)
162 {
163   g_return_if_fail (toc != NULL);
164   g_return_if_fail (gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (toc)));
165
166   if (!toc->tags) {
167     toc->tags = gst_tag_list_ref (tags);
168   } else {
169     GstTagList *tmp = gst_tag_list_merge (toc->tags, tags, mode);
170     gst_tag_list_unref (toc->tags);
171     toc->tags = tmp;
172   }
173 }
174
175 /**
176  * gst_toc_get_tags:
177  * @toc: A #GstToc instance
178  *
179  * Gets the tags for @toc.
180  *
181  * Returns: (transfer none): A #GstTagList for @entry
182  *
183  * Since: 0.10.37
184  */
185 GstTagList *
186 gst_toc_get_tags (const GstToc * toc)
187 {
188   g_return_val_if_fail (toc != NULL, NULL);
189
190   return toc->tags;
191 }
192
193 /**
194  * gst_toc_append_entry:
195  * @toc: A #GstToc instance
196  * @entry: (transfer full): A #GstTocEntry
197  *
198  * Appends the #GstTocEntry @entry to @toc.
199  *
200  * Since: 0.10.37
201  */
202 void
203 gst_toc_append_entry (GstToc * toc, GstTocEntry * entry)
204 {
205   g_return_if_fail (toc != NULL);
206   g_return_if_fail (gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (toc)));
207
208   toc->entries = g_list_append (toc->entries, entry);
209
210   GST_LOG ("appended %s entry with uid %s to toc %p",
211       gst_toc_entry_type_get_nick (entry->type), entry->uid, toc);
212
213   gst_toc_dump (toc);
214 }
215
216 /**
217  * gst_toc_get_entries:
218  * @toc: A #GstToc instance
219  *
220  * Gets the list of #GstTocEntry of @toc.
221  *
222  * Returns: (transfer none) (element-type Gst.TocEntry): A #GList of #GstTocEntry for @entry
223  *
224  * Since: 0.10.37
225  */
226 GList *
227 gst_toc_get_entries (const GstToc * toc)
228 {
229   g_return_val_if_fail (toc != NULL, NULL);
230
231   return toc->entries;
232 }
233
234 static GstTocEntry *
235 gst_toc_entry_new_internal (GstTocEntryType type, const gchar * uid)
236 {
237   GstTocEntry *entry;
238
239   entry = g_slice_new0 (GstTocEntry);
240
241   gst_mini_object_init (GST_MINI_OBJECT_CAST (entry), 0, GST_TYPE_TOC_ENTRY,
242       (GstMiniObjectCopyFunction) gst_toc_entry_copy, NULL,
243       (GstMiniObjectFreeFunction) gst_toc_entry_free);
244
245   entry->uid = g_strdup (uid);
246   entry->type = type;
247   entry->tags = NULL;
248   entry->start = entry->stop = GST_CLOCK_TIME_NONE;
249
250   return entry;
251 }
252
253 /**
254  * gst_toc_entry_new:
255  * @type: entry type.
256  * @uid: unique ID (UID) in the whole TOC.
257  *
258  * Create new #GstTocEntry structure.
259  *
260  * Returns: newly allocated #GstTocEntry structure, free it with gst_toc_entry_unref().
261  *
262  * Since: 0.10.37
263  */
264 GstTocEntry *
265 gst_toc_entry_new (GstTocEntryType type, const gchar * uid)
266 {
267   g_return_val_if_fail (uid != NULL, NULL);
268
269   return gst_toc_entry_new_internal (type, uid);
270 }
271
272 static void
273 gst_toc_free (GstToc * toc)
274 {
275   g_list_foreach (toc->entries, (GFunc) gst_mini_object_unref, NULL);
276   g_list_free (toc->entries);
277
278   if (toc->tags != NULL)
279     gst_tag_list_unref (toc->tags);
280
281   g_slice_free (GstToc, toc);
282 }
283
284 static void
285 gst_toc_entry_free (GstTocEntry * entry)
286 {
287   g_return_if_fail (entry != NULL);
288
289   g_list_foreach (entry->subentries, (GFunc) gst_mini_object_unref, NULL);
290   g_list_free (entry->subentries);
291
292   g_free (entry->uid);
293
294   if (entry->tags != NULL)
295     gst_tag_list_unref (entry->tags);
296
297   g_slice_free (GstTocEntry, entry);
298 }
299
300 static gboolean
301 gst_toc_check_entry_for_uid (const GstTocEntry * entry, const gchar * uid)
302 {
303   GList *cur;
304
305   g_return_val_if_fail (entry != NULL, FALSE);
306   g_return_val_if_fail (uid != NULL, FALSE);
307
308   if (g_strcmp0 (entry->uid, uid) == 0)
309     return TRUE;
310
311   cur = entry->subentries;
312   while (cur != NULL) {
313     if (gst_toc_check_entry_for_uid (cur->data, uid))
314       return TRUE;
315     cur = cur->next;
316   }
317
318   return FALSE;
319 }
320
321 /**
322  * gst_toc_find_entry:
323  * @toc: #GstToc to search in.
324  * @uid: UID to find #GstTocEntry with.
325  *
326  * Find #GstTocEntry with given @uid in the @toc.
327  *
328  * Returns: #GstTocEntry with specified @uid from the @toc, or NULL if not found.
329  *
330  * Since: 0.10.37
331  */
332 GstTocEntry *
333 gst_toc_find_entry (const GstToc * toc, const gchar * uid)
334 {
335   GList *cur;
336
337   g_return_val_if_fail (toc != NULL, NULL);
338   g_return_val_if_fail (uid != NULL, NULL);
339
340   cur = toc->entries;
341   while (cur != NULL) {
342     if (gst_toc_check_entry_for_uid (cur->data, uid))
343       return cur->data;
344     cur = cur->next;
345   }
346
347   return NULL;
348 }
349
350 /**
351  * gst_toc_entry_copy:
352  * @entry: #GstTocEntry to copy.
353  *
354  * Copy #GstTocEntry with all subentries (deep copy).
355  *
356  * Returns: newly allocated #GstTocEntry in case of success, NULL otherwise;
357  * free it when done with gst_toc_entry_unref().
358  *
359  * Since: 0.10.37
360  */
361 static GstTocEntry *
362 gst_toc_entry_copy (const GstTocEntry * entry)
363 {
364   GstTocEntry *ret, *sub;
365   GstTagList *list;
366   GList *cur;
367
368   g_return_val_if_fail (entry != NULL, NULL);
369
370   ret = gst_toc_entry_new (entry->type, entry->uid);
371
372   ret->start = entry->start;
373   ret->stop = entry->stop;
374
375   if (GST_IS_TAG_LIST (entry->tags)) {
376     list = gst_tag_list_copy (entry->tags);
377     if (ret->tags)
378       gst_tag_list_unref (ret->tags);
379     ret->tags = list;
380   }
381
382   cur = entry->subentries;
383   while (cur != NULL) {
384     sub = gst_toc_entry_copy (cur->data);
385
386     if (sub != NULL)
387       ret->subentries = g_list_prepend (ret->subentries, sub);
388
389     cur = cur->next;
390   }
391   ret->subentries = g_list_reverse (ret->subentries);
392
393   return ret;
394 }
395
396 /**
397  * gst_toc_copy:
398  * @toc: #GstToc to copy.
399  *
400  * Copy #GstToc with all subentries (deep copy).
401  *
402  * Returns: newly allocated #GstToc in case of success, NULL otherwise;
403  * free it when done with gst_toc_free().
404  *
405  * Since: 0.10.37
406  */
407 static GstToc *
408 gst_toc_copy (const GstToc * toc)
409 {
410   GstToc *ret;
411   GstTocEntry *entry;
412   GList *cur;
413   GstTagList *list;
414
415   g_return_val_if_fail (toc != NULL, NULL);
416
417   ret = gst_toc_new ();
418
419   if (GST_IS_TAG_LIST (toc->tags)) {
420     list = gst_tag_list_copy (toc->tags);
421     gst_tag_list_unref (ret->tags);
422     ret->tags = list;
423   }
424
425   cur = toc->entries;
426   while (cur != NULL) {
427     entry = gst_toc_entry_copy (cur->data);
428
429     if (entry != NULL)
430       ret->entries = g_list_prepend (ret->entries, entry);
431
432     cur = cur->next;
433   }
434   ret->entries = g_list_reverse (ret->entries);
435
436   return ret;
437 }
438
439 /**
440  * gst_toc_entry_set_start_stop_times:
441  * @entry: #GstTocEntry to set values.
442  * @start: start value to set.
443  * @stop: stop value to set.
444  *
445  * Set @start and @stop values for the @entry.
446  *
447  * Since: 0.10.37
448  */
449 void
450 gst_toc_entry_set_start_stop_times (GstTocEntry * entry, gint64 start,
451     gint64 stop)
452 {
453   g_return_if_fail (entry != NULL);
454
455   entry->start = start;
456   entry->stop = stop;
457 }
458
459 /**
460  * gst_toc_entry_get_start_stop_times:
461  * @entry: #GstTocEntry to get values from.
462  * @start: (out): the storage for the start value, leave #NULL if not need.
463  * @stop: (out): the storage for the stop value, leave #NULL if not need.
464  *
465  * Get start and stop values from the @entry and write them into appropriate storages.
466  *
467  * Returns: TRUE if all non-NULL storage pointers were filled with appropriate values,
468  * FALSE otherwise.
469  *
470  * Since: 0.10.37
471  */
472 gboolean
473 gst_toc_entry_get_start_stop_times (const GstTocEntry * entry, gint64 * start,
474     gint64 * stop)
475 {
476   gboolean ret = TRUE;
477
478   g_return_val_if_fail (entry != NULL, FALSE);
479
480   if (start != NULL)
481     *start = entry->start;
482   if (stop != NULL)
483     *stop = entry->stop;
484
485   return ret;
486 }
487
488 /**
489  * gst_toc_entry_type_get_nick:
490  * @type: a #GstTocEntryType.
491  *
492  * Converts @type to a string representation.
493  *
494  * Returns: Returns a human-readable string for @type. This string is
495  *    only for debugging purpose and should not be displayed in a user
496  *    interface.
497  */
498 const gchar *
499 gst_toc_entry_type_get_nick (GstTocEntryType type)
500 {
501   switch (type) {
502     case GST_TOC_ENTRY_TYPE_ANGLE:
503       return "angle";
504     case GST_TOC_ENTRY_TYPE_VERSION:
505       return "version";
506     case GST_TOC_ENTRY_TYPE_EDITION:
507       return "edition";
508     case GST_TOC_ENTRY_TYPE_TITLE:
509       return "title";
510     case GST_TOC_ENTRY_TYPE_TRACK:
511       return "track";
512     case GST_TOC_ENTRY_TYPE_CHAPTER:
513       return "chapter";
514     default:
515       break;
516   }
517   return "invalid";
518 }
519
520 /**
521  * gst_toc_entry_get_entry_type:
522  * @entry: a #GstTocEntry
523  *
524  * Returns: @entry's entry type
525  */
526 GstTocEntryType
527 gst_toc_entry_get_entry_type (const GstTocEntry * entry)
528 {
529   g_return_val_if_fail (entry != NULL, GST_TOC_ENTRY_TYPE_INVALID);
530
531   return entry->type;
532 }
533
534 /**
535  * gst_toc_entry_is_alternative:
536  * @entry: a #GstTocEntry
537  *
538  * Returns: %TRUE if @entry's type is an alternative type, otherwise %FALSE
539  */
540 gboolean
541 gst_toc_entry_is_alternative (const GstTocEntry * entry)
542 {
543   g_return_val_if_fail (entry != NULL, FALSE);
544
545   return GST_TOC_ENTRY_TYPE_IS_ALTERNATIVE (entry->type);
546 }
547
548 /**
549  * gst_toc_entry_is_sequence:
550  * @entry: a #GstTocEntry
551  *
552  * Returns: %TRUE if @entry's type is a sequence type, otherwise %FALSE
553  */
554 gboolean
555 gst_toc_entry_is_sequence (const GstTocEntry * entry)
556 {
557   g_return_val_if_fail (entry != NULL, FALSE);
558
559   return GST_TOC_ENTRY_TYPE_IS_SEQUENCE (entry->type);
560 }
561
562 /**
563  * gst_toc_entry_get_uid:
564  * @entry: A #GstTocEntry instance
565  *
566  * Gets the UID of @entry.
567  *
568  * Returns: (transfer none): The UID of @entry
569  *
570  * Since: 0.10.37
571  */
572 const gchar *
573 gst_toc_entry_get_uid (const GstTocEntry * entry)
574 {
575   g_return_val_if_fail (entry != NULL, NULL);
576
577   return entry->uid;
578 }
579
580 /**
581  * gst_toc_entry_append_sub_entry:
582  * @entry: A #GstTocEntry instance
583  * @subentry: (transfer full): A #GstTocEntry
584  *
585  * Appends the #GstTocEntry @subentry to @entry.
586  *
587  * Since: 0.10.37
588  */
589 void
590 gst_toc_entry_append_sub_entry (GstTocEntry * entry, GstTocEntry * subentry)
591 {
592   g_return_if_fail (entry != NULL);
593   g_return_if_fail (subentry != NULL);
594   g_return_if_fail (gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (entry)));
595
596   entry->subentries = g_list_append (entry->subentries, subentry);
597
598   GST_LOG ("appended %s subentry with uid %s to entry %s",
599       gst_toc_entry_type_get_nick (subentry->type), subentry->uid, entry->uid);
600 }
601
602 /**
603  * gst_toc_entry_get_uid:
604  * @entry: A #GstTocEntry instance
605  *
606  * Gets the sub-entries of @entry.
607  *
608  * Returns: (transfer none) (element-type Gst.TocEntry): A #GList of #GstTocEntry of @entry
609  *
610  * Since: 0.10.37
611  */
612 GList *
613 gst_toc_entry_get_sub_entries (const GstTocEntry * entry)
614 {
615   g_return_val_if_fail (entry != NULL, NULL);
616
617   return entry->subentries;
618 }
619
620 /**
621  * gst_toc_entry_set_tags:
622  * @entry: A #GstTocEntry instance
623  * @tags: (allow-none) (transfer full): A #GstTagList or %NULL
624  *
625  * Set a #GstTagList with tags for the complete @entry.
626  *
627  * Since: 0.10.37
628  */
629 void
630 gst_toc_entry_set_tags (GstTocEntry * entry, GstTagList * tags)
631 {
632   g_return_if_fail (entry != NULL);
633   g_return_if_fail (gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (entry)));
634
635   if (entry->tags)
636     gst_tag_list_unref (entry->tags);
637   entry->tags = tags;
638 }
639
640 /**
641  * gst_toc_entry_merge_tags:
642  * @entry: A #GstTocEntry instance
643  * @tags: (allow-none): A #GstTagList or %NULL
644  * @mode: A #GstTagMergeMode
645  *
646  * Merge @tags into the existing tags of @entry using @mode.
647  *
648  * Since: 0.10.37
649  */
650 void
651 gst_toc_entry_merge_tags (GstTocEntry * entry, GstTagList * tags,
652     GstTagMergeMode mode)
653 {
654   g_return_if_fail (entry != NULL);
655   g_return_if_fail (gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (entry)));
656
657   if (!entry->tags) {
658     entry->tags = gst_tag_list_ref (tags);
659   } else {
660     GstTagList *tmp = gst_tag_list_merge (entry->tags, tags, mode);
661     gst_tag_list_unref (entry->tags);
662     entry->tags = tmp;
663   }
664 }
665
666 /**
667  * gst_toc_entry_get_tags:
668  * @entry: A #GstTocEntry instance
669  *
670  * Gets the tags for @entry.
671  *
672  * Returns: (transfer none): A #GstTagList for @entry
673  *
674  * Since: 0.10.37
675  */
676 GstTagList *
677 gst_toc_entry_get_tags (const GstTocEntry * entry)
678 {
679   g_return_val_if_fail (entry != NULL, NULL);
680
681   return entry->tags;
682 }
683
684 #ifndef GST_DISABLE_GST_DEBUG
685 static void
686 gst_toc_dump_entries (GList * entries, guint depth)
687 {
688   GList *e;
689   gchar *indent;
690
691   indent = g_malloc0 (depth + 1);
692   memset (indent, ' ', depth);
693   for (e = entries; e != NULL; e = e->next) {
694     GstTocEntry *entry = e->data;
695
696     GST_TRACE ("%s+ %s (%s), %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT ", "
697         "tags: %" GST_PTR_FORMAT, indent, entry->uid,
698         gst_toc_entry_type_get_nick (entry->type),
699         GST_TIME_ARGS (entry->start), GST_TIME_ARGS (entry->stop), entry->tags);
700
701     if (entry->subentries != NULL)
702       gst_toc_dump_entries (entry->subentries, depth + 2);
703   }
704   g_free (indent);
705 }
706 #endif
707
708 void
709 gst_toc_dump (GstToc * toc)
710 {
711 #ifndef GST_DISABLE_GST_DEBUG
712   GST_TRACE ("        Toc %p, tags: %" GST_PTR_FORMAT, toc, toc->tags);
713   gst_toc_dump_entries (toc->entries, 2);
714 #endif
715 }