Added gst-plugins-base-subtitles0.10-0.10.34 for Meego Harmattan 1.2
[mafwsubrenderer] / gst-plugins-base-subtitles0.10 / debian / patches / 0003-tagreading-add-tagreadbin-element-for-fast-and-easy-.patch
1 From 6c810495ff6c876e17c7eaf260959683c77450a5 Mon Sep 17 00:00:00 2001
2 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= <tim.muller@collabora.co.uk>
3 Date: Sat, 23 May 2009 00:42:01 +0100
4 Subject: [PATCH] tagreading: add tagreadbin element for fast and easy
5  metadata extraction
6
7 Similar to playbin, just for metadata (in future maybe also thumbnailing).
8 See #564749.
9 ---
10  configure.ac                                       |    2 +
11  docs/plugins/Makefile.am                           |    1 +
12  docs/plugins/gst-plugins-base-plugins-docs.sgml    |    2 +
13  docs/plugins/gst-plugins-base-plugins-sections.txt |   17 +
14  docs/plugins/inspect/plugin-tagreading.xml         |   23 +
15  gst/tagreading/Makefile.am                         |   28 +
16  gst/tagreading/gsttagreadbin.c                     |  893 ++++++++++++++++++++
17  gst/tagreading/gsttagreadbin.h                     |  104 +++
18  gst/tagreading/gsttagreadutils.c                   |  309 +++++++
19  gst/tagreading/gsttagreadutils.h                   |   62 ++
20  gst/tagreading/gsttagsink.c                        |  785 +++++++++++++++++
21  gst/tagreading/gsttagsink.h                        |  103 +++
22  12 files changed, 2329 insertions(+), 0 deletions(-)
23  create mode 100644 docs/plugins/inspect/plugin-tagreading.xml
24  create mode 100644 gst/tagreading/Makefile.am
25  create mode 100644 gst/tagreading/gsttagreadbin.c
26  create mode 100644 gst/tagreading/gsttagreadbin.h
27  create mode 100644 gst/tagreading/gsttagreadutils.c
28  create mode 100644 gst/tagreading/gsttagreadutils.h
29  create mode 100644 gst/tagreading/gsttagsink.c
30  create mode 100644 gst/tagreading/gsttagsink.h
31
32 diff --git a/configure.ac b/configure.ac
33 index 8131f8a..7a6ae5a 100644
34 --- a/configure.ac
35 +++ b/configure.ac
36 @@ -430,6 +430,7 @@ AG_GST_CHECK_PLUGIN(gdp)
37  AG_GST_CHECK_PLUGIN(playback)
38  AG_GST_CHECK_PLUGIN(audioresample)
39  AG_GST_CHECK_PLUGIN(subparse)
40 +AG_GST_CHECK_PLUGIN(tagreading)
41  AG_GST_CHECK_PLUGIN(tcp)
42  AG_GST_CHECK_PLUGIN(typefind)
43  AG_GST_CHECK_PLUGIN(videotestsrc)
44 @@ -931,6 +932,7 @@ gst/gdp/Makefile
45  gst/playback/Makefile
46  gst/audioresample/Makefile
47  gst/subparse/Makefile
48 +gst/tagreading/Makefile
49  gst/tcp/Makefile
50  gst/typefind/Makefile
51  gst/videotestsrc/Makefile
52 diff --git a/docs/plugins/Makefile.am b/docs/plugins/Makefile.am
53 index c987b85..8226d38 100644
54 --- a/docs/plugins/Makefile.am
55 +++ b/docs/plugins/Makefile.am
56 @@ -111,6 +111,7 @@ EXTRA_HFILES = \
57         $(top_srcdir)/gst/playback/gstsubtitleoverlay.h \
58         $(top_srcdir)/gst/audiorate/gstaudiorate.h \
59         $(top_srcdir)/gst/audioresample/gstaudioresample.h \
60 +       $(top_srcdir)/gst/tagreading/gsttagreadbin.h \
61         $(top_srcdir)/gst/tcp/gstmultifdsink.h \
62         $(top_srcdir)/gst/tcp/gsttcpclientsrc.h \
63         $(top_srcdir)/gst/tcp/gsttcpclientsink.h \
64 diff --git a/docs/plugins/gst-plugins-base-plugins-docs.sgml b/docs/plugins/gst-plugins-base-plugins-docs.sgml
65 index ea603d6..deebd97 100644
66 --- a/docs/plugins/gst-plugins-base-plugins-docs.sgml
67 +++ b/docs/plugins/gst-plugins-base-plugins-docs.sgml
68 @@ -46,6 +46,7 @@
69      <xi:include href="xml/element-oggmux.xml" />
70      <xi:include href="xml/element-playbin.xml" />
71      <xi:include href="xml/element-playbin2.xml" />
72 +    <xi:include href="xml/element-tagreadbin.xml" />
73      <xi:include href="xml/element-subtitleoverlay.xml" />
74      <xi:include href="xml/element-tcpclientsrc.xml" />
75      <xi:include href="xml/element-tcpclientsink.xml" />
76 @@ -93,6 +94,7 @@
77      <xi:include href="xml/plugin-subparse.xml" />
78      <xi:include href="xml/plugin-tcp.xml" />
79      <xi:include href="xml/plugin-theora.xml" />
80 +    <xi:include href="xml/plugin-tagreading.xml" />
81      <xi:include href="xml/plugin-typefindfunctions.xml" />
82      <xi:include href="xml/plugin-uridecodebin.xml" />
83      <xi:include href="xml/plugin-video4linux.xml" />
84 diff --git a/docs/plugins/gst-plugins-base-plugins-sections.txt b/docs/plugins/gst-plugins-base-plugins-sections.txt
85 index e94c450..97dc8ca 100644
86 --- a/docs/plugins/gst-plugins-base-plugins-sections.txt
87 +++ b/docs/plugins/gst-plugins-base-plugins-sections.txt
88 @@ -504,6 +504,23 @@ gst_subtitle_overlay_get_type
89  </SECTION>
90  
91  <SECTION>
92 +<FILE>element-tagreadbin</FILE>
93 +<TITLE>tagreadbin</TITLE>
94 +GstTagReadBin
95 +<SUBSECTION Standard>
96 +GST_TAG_READ_BIN
97 +GST_TAG_READ_BIN_CLASS
98 +GST_TYPE_TAG_READ_BIN
99 +GST_IS_TAG_READ_BIN
100 +GST_IS_TAG_READ_BIN_CLASS
101 +GST_TAG_READ_BIN_DYN_LOCK
102 +GST_TAG_READ_BIN_DYN_UNLOCK
103 +GST_TAG_READ_BIN_SHUTDOWN_LOCK
104 +GST_TAG_READ_BIN_SHUTDOWN_UNLOCK
105 +GstTagReadBinClass
106 +</SECTION>
107 +
108 +<SECTION>
109  <FILE>element-tcpclientsrc</FILE>
110  <TITLE>tcpclientsrc</TITLE>
111  GstTCPClientSrc
112 diff --git a/docs/plugins/inspect/plugin-tagreading.xml b/docs/plugins/inspect/plugin-tagreading.xml
113 new file mode 100644
114 index 0000000..6d00a48
115 --- /dev/null
116 +++ b/docs/plugins/inspect/plugin-tagreading.xml
117 @@ -0,0 +1,23 @@
118 +<plugin>
119 +  <name>tagreading</name>
120 +  <description>Tag reading support</description>
121 +  <filename>../../gst/tagreading/.libs/libgsttagreading.so</filename>
122 +  <basename>libgsttagreading.so</basename>
123 +  <version>0.10.22.3</version>
124 +  <license>LGPL</license>
125 +  <source>gst-plugins-base</source>
126 +  <package>GStreamer Base Plug-ins CVS/prerelease</package>
127 +  <origin>Unknown package origin</origin>
128 +  <elements>
129 +    <element>
130 +      <name>tagreadbin</name>
131 +      <longname>Tag Read Bin</longname>
132 +      <class>Generic/Bin/Metadata</class>
133 +      <description>Extract metadata from an uri</description>
134 +      <author>Tim-Philipp Müller &lt;tim centricular net&gt;</author>
135 +      <pads>
136 +
137 +      </pads>
138 +    </element>
139 +  </elements>
140 +</plugin>
141 \ No newline at end of file
142 diff --git a/gst/tagreading/Makefile.am b/gst/tagreading/Makefile.am
143 new file mode 100644
144 index 0000000..0a0d32d
145 --- /dev/null
146 +++ b/gst/tagreading/Makefile.am
147 @@ -0,0 +1,28 @@
148 +plugindir = $(libdir)/gstreamer-@GST_MAJORMINOR@
149 +
150 +plugin_LTLIBRARIES = libgsttagreading.la
151 +
152 +libgsttagreading_la_SOURCES = \
153 +       gsttagreadbin.c \
154 +       gsttagreadbin.h \
155 +       gsttagreadutils.c \
156 +       gsttagreadutils.h \
157 +       gsttagsink.c \
158 +       gsttagsink.h
159 +libgsttagreading_la_CFLAGS = \
160 +       $(GST_PLUGINS_BASE_CFLAGS) \
161 +       $(GST_BASE_CFLAGS) \
162 +       $(GST_CFLAGS)
163 +libgsttagreading_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
164 +libgsttagreading_la_LIBADD = \
165 +       $(top_builddir)/gst-libs/gst/tag/libgsttag-@GST_MAJORMINOR@.la \
166 +       $(top_builddir)/gst-libs/gst/pbutils/libgstpbutils-@GST_MAJORMINOR@.la \
167 +       $(GST_BASE_LIBS) \
168 +       $(GST_LIBS)
169 +libgsttagreading_la_LIBTOOLFLAGS = --tag=disable-static
170 +
171 +noinst_HEADERS = \
172 +       gsttagreadbin.h \
173 +       gsttagreadutils.h \
174 +       gsttagsink.h
175 +
176 diff --git a/gst/tagreading/gsttagreadbin.c b/gst/tagreading/gsttagreadbin.c
177 new file mode 100644
178 index 0000000..d62d8eb
179 --- /dev/null
180 +++ b/gst/tagreading/gsttagreadbin.c
181 @@ -0,0 +1,893 @@
182 +/* GStreamer tag read bin
183 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
184 + *
185 + * This library is free software; you can redistribute it and/or
186 + * modify it under the terms of the GNU Library General Public
187 + * License as published by the Free Software Foundation; either
188 + * version 2 of the License, or (at your option) any later version.
189 + *
190 + * This library is distributed in the hope that it will be useful,
191 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
192 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
193 + * Library General Public License for more details.
194 + *
195 + * You should have received a copy of the GNU Library General Public
196 + * License along with this library; if not, write to the
197 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
198 + * Boston, MA 02111-1307, USA.
199 + */
200 +
201 +/**
202 + * SECTION:element-tagreadbin
203 + * @short_description: Extract metadata (tags and stream info)
204 + *
205 + * <refsect2>
206 + * <para>
207 + * Tagreadbin is a stand-alone element that extracts metadata (tags and
208 + * other stream info such as video resolution, audio sample rate etc.) from
209 + * files given an URI.
210 + * </para>
211 + * <para>
212 + * <note>
213 + * This element is still considered EXPERIMENTAL. Even though we don't expect
214 + * any changes, the API provided in properties, signals and bus messages may
215 + * yet change in the near future.
216 + * </note>
217 + * </para>
218 + * <para>
219 + * Tagreadbin will open the file requested and automatically plug the elements
220 + * required to extract the metadata. Only demuxers, parsers or specialised tag
221 + * reading elements (implementing the #GstTagReader interface) will be used. No
222 + * decoders will be plugged, unless video thumbnailing has been requested
223 + * (which is not implemented yet).
224 + * </para>
225 + * </refsect2>
226 + * <refsect2>
227 + * <title>Basic usage</title>
228 + * <para>
229 + * Tagreadbin is a #GstPipeline, so it can be used like any other GStreamer
230 + * pipeline. In particular, you can retrieve the #GstBus to watch for tag
231 + * and error messages using gst_pipeline_get_bus(). Once the
232 + * <literal>uri</literal> property has been set, the element should be
233 + * set to PLAYING state using gst_element_set_state(). TAG and ERROR messages
234 + * will then be posted on the pipeline's #GstBus soon after. If there was no
235 + * error, one or more TAG messages will be posted on the bus, followed by an
236 + * EOS message. Once an ERROR or EOS message has been received, the element
237 + * should be shut down by setting it to NULL state with gst_element_set_state().
238 + * It can then be re-used to extract metadata from another file.
239 + * </para>
240 + * </refsect2>
241 + * <refsect2>
242 + * <title>Tag messages</title>
243 + * <para>
244 + * Tagreadbin will collect tags from various elements and try to group them.
245 + * The first tag message posted on the bus should contain the 'global tags',
246 + * ie. tags that are valid for all sub-streams contained in the file. At the
247 + * very least, this taglist should contain information about the container
248 + * format in a #GST_TAG_CONTAINER_FORMAT tag (where applicable, otherwise it
249 + * will contain a translated string saying 'Unknown'). If there is a
250 + * #GST_TAG_DURATION tag it will contain the maximum value of all the stream
251 + * durations (as queried).
252 + * </para>
253 + * <para>
254 + * After the tag message with the global tags there will be one tag message
255 + * for each audio/video/subtitle stream, containing the information for that
256 + * particular stream. At the very least, there should be a #GST_TAG_AUDIO_CODEC,
257 + * #GST_TAG_VIDEO_CODEC, #GST_TAG_SUBITLE_CODEC, or #GST_TAG_CODEC tag. There
258 + * may also be a #GST_TAG_DURATION tag with the stream's duration.
259 + * </para>
260 + * <para>
261 + * For audio streams, there will be "channels" and "rate" tags (of type int)
262 + * in the taglist, provided that information could be extracted.
263 + * </para>
264 + * <para>
265 + * For video streams, there will be "width", "height", "pixel-aspect-ratio"
266 + * and "framerate" fields (of type int, int, fraction and fraction respectively)
267 + * if that information was available. A framerate of 0/N means that the
268 + * framerate is variable and no average framerate is known.
269 + * </para>
270 + * <para>
271 + * Just like playbin, this element does not expose any pads.
272 + * </para>
273 + * <para>
274 + * Since 0.10.24
275 + * </para>
276 + * </refsect2>
277 + */
278 +
279 +/* TODO:
280 + *  - thumbnailing
281 + *
282 + *  - wait with preroll until duration stabilises (e.g. for mp3/aac/mpeg)
283 + *
284 + *  - missing plugin messages
285 + *
286 + *  - Fix parser/decoder elements so that global tags / stream tags
287 + *    differentiation actually works right, ie. make sure they send tags
288 + *    received from upstream before pushing their own tags (we'll only consider
289 + *    taglists received before the first taglist with a *_CODEC tag suitable
290 + *    for global tags)
291 + */
292 +#ifdef HAVE_CONFIG_H
293 +#include "config.h"
294 +#endif
295 +
296 +#include "gsttagreadbin.h"
297 +#include "gsttagreadutils.h"
298 +#include "gsttagsink.h"
299 +
300 +#include <gst/pbutils/pbutils.h>
301 +#include <gst/tag/gsttagreader.h>
302 +
303 +#include <string.h>
304 +
305 +GST_DEBUG_CATEGORY (tagreadbin_debug);
306 +#define GST_CAT_DEFAULT tagreadbin_debug
307 +
308 +/* from gst/playback/gstplay-enum.h */
309 +typedef enum
310 +{
311 +  GST_AUTOPLUG_SELECT_TRY,
312 +  GST_AUTOPLUG_SELECT_EXPOSE,
313 +  GST_AUTOPLUG_SELECT_SKIP
314 +} GstAutoplugSelectResult;
315 +
316 +/* props */
317 +#define DEFAULT_URI               NULL
318 +
319 +enum
320 +{
321 +  PROP_0,
322 +  PROP_URI
323 +};
324 +
325 +static void gst_tag_read_bin_dispose (GObject * object);
326 +static void gst_tag_read_bin_finalize (GObject * object);
327 +static void gst_tag_read_bin_set_property (GObject * object, guint prop_id,
328 +    const GValue * value, GParamSpec * spec);
329 +static void gst_tag_read_bin_get_property (GObject * object, guint prop_id,
330 +    GValue * value, GParamSpec * spec);
331 +
332 +static GstStateChangeReturn gst_tag_read_bin_change_state (GstElement * element,
333 +    GstStateChange transition);
334 +static void gst_tag_read_bin_handle_message (GstBin * bin, GstMessage * msg);
335 +
336 +GST_BOILERPLATE (GstTagReadBin, gst_tag_read_bin, GstPipeline,
337 +    GST_TYPE_PIPELINE);
338 +
339 +static void
340 +gst_tag_read_bin_base_init (gpointer g_klass)
341 +{
342 +  /* nothing to do */
343 +}
344 +
345 +static void
346 +gst_tag_read_bin_class_init (GstTagReadBinClass * klass)
347 +{
348 +  GstElementClass *gstelement_klass = (GstElementClass *) klass;
349 +  GObjectClass *gobject_klass = (GObjectClass *) klass;
350 +  GstBinClass *gstbin_klass = (GstBinClass *) klass;
351 +
352 +  gst_element_class_set_details_simple (gstelement_klass,
353 +      "Tag Read Bin", "Generic/Bin/Metadata", "Extract metadata from an uri",
354 +      "Tim-Philipp Müller <tim centricular net>");
355 +
356 +  gobject_klass->set_property = gst_tag_read_bin_set_property;
357 +  gobject_klass->get_property = gst_tag_read_bin_get_property;
358 +
359 +  gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_tag_read_bin_dispose);
360 +  gobject_klass->finalize = GST_DEBUG_FUNCPTR (gst_tag_read_bin_finalize);
361 +
362 +  /**
363 +   * GstTagReadBin:uri
364 +   *
365 +   * URI to extract metadata from. Only file:// URIs are supported at the
366 +   * moment. To create an URI from a filename, you can use g_filename_to_uri().
367 +   */
368 +  g_object_class_install_property (gobject_klass, PROP_URI,
369 +      g_param_spec_string ("uri", "URI", "URI to extract tags from",
370 +          NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
371 +
372 +  gstelement_klass->change_state =
373 +      GST_DEBUG_FUNCPTR (gst_tag_read_bin_change_state);
374 +
375 +  gstbin_klass->handle_message =
376 +      GST_DEBUG_FUNCPTR (gst_tag_read_bin_handle_message);
377 +}
378 +
379 +static gboolean
380 +gst_tag_read_bin_factory_is_demuxer (GstElementFactory * factory)
381 +{
382 +  const gchar *klass = gst_element_factory_get_klass (factory);
383 +
384 +  return (klass != NULL && strstr (klass, "Demux") != NULL);
385 +}
386 +
387 +static gboolean
388 +gst_tag_read_bin_factory_is_parser (GstElementFactory * factory)
389 +{
390 +  const gchar *klass = gst_element_factory_get_klass (factory);
391 +
392 +  return (klass != NULL && strstr (klass, "Parse") != NULL);
393 +}
394 +
395 +static gboolean
396 +gst_tag_read_bin_factory_is_metadata_extractor (GstElementFactory * factory)
397 +{
398 +  const gchar *klass = gst_element_factory_get_klass (factory);
399 +
400 +  return (klass && strstr (klass, "Metadata") && strstr (klass, "Extracter"));
401 +}
402 +
403 +static gboolean
404 +gst_tag_read_bin_factory_is_tag_reader (GstElementFactory * factory)
405 +{
406 +  guint rank;
407 +  const gchar *klass;
408 +
409 +  klass = gst_element_factory_get_klass (factory);
410 +
411 +  /* only demuxers, parsers or metadata extractor elements can play
412 +   * (note: in parcticular, we don't want to plug decodebin2 even though
413 +   * it does implement the tag reader interface as well) */
414 +  if (!gst_tag_read_bin_factory_is_demuxer (factory) &&
415 +      !gst_tag_read_bin_factory_is_parser (factory) &&
416 +      !gst_tag_read_bin_factory_is_metadata_extractor (factory)) {
417 +    return FALSE;
418 +  }
419 +
420 +  /* tag readers are fine whatever their rank */
421 +  if (gst_element_factory_has_interface (factory, "GstTagReader")) {
422 +    GST_LOG ("selecting %s, it implements the tag reader interface",
423 +        GST_PLUGIN_FEATURE_NAME (factory));
424 +    return TRUE;
425 +  }
426 +
427 +  /* only select elements with autoplugging rank */
428 +  rank = gst_plugin_feature_get_rank (GST_PLUGIN_FEATURE (factory));
429 +  if (rank < GST_RANK_MARGINAL)
430 +    return FALSE;
431 +
432 +  GST_LOG ("selecting %s, class/rank ok", GST_PLUGIN_FEATURE_NAME (factory));
433 +  return TRUE;
434 +}
435 +
436 +/* function used to sort element features. We first sort on the tag reader
437 + * interface, then rank, then on the element name (to get a consistent,
438 + * predictable list) */
439 +static gint
440 +gst_tag_read_bin_compare_ranks (GValue * v1, GValue * v2)
441 +{
442 +  const gchar *rname1, *rname2;
443 +  GstPluginFeature *f1, *f2;
444 +  gboolean is_tagreader1, is_tagreader2;
445 +  gint diff;
446 +
447 +  f1 = g_value_get_object (v1);
448 +  f2 = g_value_get_object (v2);
449 +
450 +  is_tagreader1 = gst_element_factory_has_interface (GST_ELEMENT_FACTORY (f1),
451 +      "GstTagReader");
452 +  is_tagreader2 = gst_element_factory_has_interface (GST_ELEMENT_FACTORY (f2),
453 +      "GstTagReader");
454 +
455 +  if (is_tagreader1 != is_tagreader2)
456 +    return (is_tagreader2) ? 1 : -1;
457 +
458 +  diff = gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1);
459 +  if (diff != 0)
460 +    return diff;
461 +
462 +  rname1 = gst_plugin_feature_get_name (f1);
463 +  rname2 = gst_plugin_feature_get_name (f2);
464 +
465 +  diff = strcmp (rname2, rname1);
466 +
467 +  return diff;
468 +}
469 +
470 +static GValueArray *
471 +gst_tag_read_bin_get_tag_readers (void)
472 +{
473 +  GValueArray *result;
474 +  GList *l, *list;
475 +
476 +  result = g_value_array_new (0);
477 +
478 +  /* get the feature list using the filter */
479 +  list = gst_registry_get_feature_list (gst_registry_get_default (),
480 +      GST_TYPE_ELEMENT_FACTORY);
481 +
482 +  /* filter and convert to an array */
483 +  for (l = list; l != NULL; l = l->next) {
484 +    GstElementFactory *factory = GST_ELEMENT_FACTORY (l->data);
485 +
486 +    if (gst_tag_read_bin_factory_is_tag_reader (factory)) {
487 +      GValue val = { 0, };
488 +
489 +      g_value_init (&val, GST_TYPE_ELEMENT_FACTORY);
490 +      g_value_set_object (&val, factory);
491 +      g_value_array_append (result, &val);
492 +      g_value_unset (&val);
493 +    }
494 +  }
495 +
496 +  gst_plugin_feature_list_free (list);
497 +
498 +  /* sort on tag reader interface rank and name */
499 +  g_value_array_sort (result, (GCompareFunc) gst_tag_read_bin_compare_ranks);
500 +
501 +  return result;
502 +}
503 +
504 +static gboolean
505 +gst_tag_read_bin_factory_list_has_demuxer (GValueArray * factories)
506 +{
507 +  guint i;
508 +
509 +  for (i = 0; i < factories->n_values; ++i) {
510 +    GstElementFactory *factory;
511 +    GValue *val;
512 +
513 +    val = g_value_array_get_nth (factories, i);
514 +    factory = GST_ELEMENT_FACTORY (g_value_get_object (val));
515 +
516 +    if (gst_tag_read_bin_factory_is_demuxer (factory))
517 +      return TRUE;
518 +  }
519 +
520 +  return FALSE;
521 +}
522 +
523 +static void
524 +gst_tag_read_bin_init (GstTagReadBin * tbin, GstTagReadBinClass * klass)
525 +{
526 +  tbin->dyn_lock = g_mutex_new ();
527 +
528 +  /* cache interesting element factories */
529 +  tbin->elements = gst_tag_read_bin_get_tag_readers ();
530 +}
531 +
532 +static void
533 +gst_tag_read_bin_dispose (GObject * object)
534 +{
535 +  GstTagReadBin *tbin = GST_TAG_READ_BIN (object);
536 +
537 +  g_value_array_free (tbin->elements);
538 +
539 +  G_OBJECT_CLASS (parent_class)->dispose (object);
540 +}
541 +
542 +static void
543 +gst_tag_read_bin_finalize (GObject * object)
544 +{
545 +  GstTagReadBin *tbin = GST_TAG_READ_BIN (object);
546 +
547 +  g_mutex_free (tbin->dyn_lock);
548 +  tbin->dyn_lock = NULL;
549 +  g_free (tbin->uri);
550 +  tbin->uri = NULL;
551 +
552 +  G_OBJECT_CLASS (parent_class)->finalize (object);
553 +}
554 +
555 +static void
556 +gst_tag_read_bin_post_tags (GstTagReadBin * tbin)
557 +{
558 +  GstTagList *tags;
559 +  guint i;
560 +
561 +  GST_DEBUG_OBJECT (tbin, "posting tags");
562 +
563 +  tags = gst_tag_sink_get_global_tags (GST_TAG_SINK (tbin->tagsink));
564 +  GST_INFO_OBJECT (tbin, "global tags: %" GST_PTR_FORMAT, tags);
565 +  if (tags != NULL) {
566 +    /* if there's no container format in the tags, add one based on caps */
567 +    if (!gst_tag_read_utils_list_has_container_tag (tags) &&
568 +        tbin->container_caps != NULL) {
569 +      gst_tag_read_utils_add_container_tag_from_caps (tags,
570 +          tbin->container_caps);
571 +    }
572 +
573 +    GST_BIN_CLASS (parent_class)->handle_message (GST_BIN (tbin),
574 +        gst_message_new_tag (GST_OBJECT_CAST (tbin), tags));
575 +  }
576 +
577 +  for (i = 0;; ++i) {
578 +    tags = gst_tag_sink_get_stream_tags (GST_TAG_SINK (tbin->tagsink), i);
579 +
580 +    /* there should always at least be a *_CODEC tag based on the caps */
581 +    if (tags == NULL)
582 +      break;
583 +
584 +    GST_BIN_CLASS (parent_class)->handle_message (GST_BIN (tbin),
585 +        gst_message_new_tag (GST_OBJECT_CAST (tbin), tags));
586 +  }
587 +}
588 +
589 +static void
590 +gst_tag_read_bin_handle_message (GstBin * bin, GstMessage * msg)
591 +{
592 +  GstTagReadBin *tbin = GST_TAG_READ_BIN (bin);
593 +
594 +  /* Suppress all tag messages from other elements. We'll collect tags from
595 +   * the pads and then post messages of our own when we're done and we have
596 +   * all the information */
597 +  switch (GST_MESSAGE_TYPE (msg)) {
598 +    case GST_MESSAGE_TAG:
599 +    {
600 +      GST_LOG_OBJECT (bin, "suppressing tags from element %s: %" GST_PTR_FORMAT,
601 +          GST_OBJECT_NAME (msg->src), msg->structure);
602 +      GST_OBJECT_LOCK (tbin);
603 +      tbin->tag_messages = g_list_append (tbin->tag_messages, msg);
604 +      msg = NULL;
605 +      GST_OBJECT_UNLOCK (tbin);
606 +      break;
607 +    }
608 +    case GST_MESSAGE_ASYNC_DONE:
609 +    {
610 +      if (msg->src == GST_OBJECT_CAST (tbin->tagsink)) {
611 +        GST_DEBUG_OBJECT (bin, "done collecting tags");
612 +        gst_tag_read_bin_post_tags (tbin);
613 +      }
614 +      break;
615 +    }
616 +    default:
617 +      break;
618 +  }
619 +
620 +  if (msg != NULL)
621 +    GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
622 +}
623 +
624 +
625 +static void
626 +gst_tag_read_bin_set_uri (GstTagReadBin * tbin, const gchar * uri)
627 +{
628 +  if (uri == NULL || !gst_uri_is_valid (uri)) {
629 +    g_warning ("Can't set invalid URI '%s' on tagreadbin element %s",
630 +        GST_STR_NULL (uri), GST_ELEMENT_NAME (tbin));
631 +    return;
632 +  }
633 +
634 +  GST_FIXME_OBJECT (tbin, "Add support for non-file:// URIs");
635 +
636 +  if (!gst_uri_has_protocol (uri, "file")) {
637 +    g_warning ("Tagreadbin only supports file:// URIs for the time being");
638 +    return;
639 +  }
640 +
641 +  GST_OBJECT_LOCK (tbin);
642 +  if (tbin->uri != uri) {
643 +    g_free (tbin->uri);
644 +    tbin->uri = g_strdup (uri);
645 +  }
646 +  GST_OBJECT_UNLOCK (tbin);
647 +
648 +  GST_INFO_OBJECT (tbin, "Set URI to '%s'", uri);
649 +}
650 +
651 +static void
652 +gst_tag_read_bin_set_property (GObject * object, guint prop_id,
653 +    const GValue * value, GParamSpec * pspec)
654 +{
655 +  GstTagReadBin *tbin = GST_TAG_READ_BIN (object);
656 +
657 +  switch (prop_id) {
658 +    case PROP_URI:
659 +      gst_tag_read_bin_set_uri (tbin, g_value_get_string (value));
660 +      break;
661 +    default:
662 +      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
663 +      break;
664 +  }
665 +}
666 +
667 +static void
668 +gst_tag_read_bin_get_property (GObject * object, guint prop_id, GValue * value,
669 +    GParamSpec * pspec)
670 +{
671 +  GstTagReadBin *tbin = GST_TAG_READ_BIN (object);
672 +
673 +  switch (prop_id) {
674 +    case PROP_URI:
675 +      GST_OBJECT_LOCK (tbin);
676 +      g_value_set_string (value, tbin->uri);
677 +      GST_OBJECT_UNLOCK (tbin);
678 +      break;
679 +    default:
680 +      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
681 +      break;
682 +  }
683 +}
684 +
685 +/* This function is called when a new pad is added to uridecodebin. */
686 +static void
687 +tag_read_bin_decodebin_pad_added (GstElement * decodebin, GstPad * pad,
688 +    GstTagReadBin * tbin)
689 +{
690 +  GstPadLinkReturn res;
691 +  GstPad *request_pad;
692 +
693 +  GST_TAG_READ_BIN_SHUTDOWN_LOCK (tbin, shutdown);
694 +
695 +  request_pad = gst_element_get_request_pad (tbin->tagsink, "sink%d");
696 +  GST_LOG_OBJECT (request_pad, "got new request pad");
697 +  tbin->request_pads = g_list_prepend (tbin->request_pads, request_pad);
698 +
699 +  res = gst_pad_link (pad, request_pad);
700 +
701 +  if (GST_PAD_LINK_SUCCESSFUL (res)) {
702 +    GST_DEBUG_OBJECT (tbin, "linked new pad %s:%s to %s:%s",
703 +        GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (request_pad));
704 +  } else {
705 +    GST_WARNING_OBJECT (tbin, "failed to link new pad %s:%s to %s:%s: %d",
706 +        GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (request_pad), res);
707 +  }
708 +
709 +  GST_TAG_READ_BIN_SHUTDOWN_UNLOCK (tbin);
710 +  return;
711 +
712 +shutdown:
713 +  {
714 +    GST_DEBUG_OBJECT (tbin, "shutdown");
715 +    return;
716 +  }
717 +}
718 +
719 +/* we get called when all pads are available and we must connect the sinks to
720 + * them.
721 + * The main purpose of the code is to see if we have video/audio and subtitles
722 + * and pick the right pipelines to display them.
723 + *
724 + * The selectors installed on the group tell us about the presence of
725 + * audio/video and subtitle streams. This allows us to see if we need
726 + * visualisation, video or/and audio.
727 + */
728 +static void
729 +tag_read_bin_decodebin_no_more_pads (GstElement * decodebin,
730 +    GstTagReadBin * tbin)
731 +{
732 +  GST_DEBUG_OBJECT (decodebin, "no more pads");
733 +}
734 +
735 +static GValueArray *
736 +gst_factory_list_filter_by_sinkcaps (GValueArray * array, const GstCaps * caps)
737 +{
738 +  GValueArray *result;
739 +  gint i;
740 +
741 +  result = g_value_array_new (0);
742 +
743 +  GST_DEBUG ("finding factories for %" GST_PTR_FORMAT, caps);
744 +
745 +  /* loop over all the factories */
746 +  for (i = 0; i < array->n_values; i++) {
747 +    GValue *value;
748 +    GstElementFactory *factory;
749 +    const GList *templates;
750 +    GList *walk;
751 +
752 +    value = g_value_array_get_nth (array, i);
753 +    factory = g_value_get_object (value);
754 +
755 +    /* get the templates from the element factory */
756 +    templates = gst_element_factory_get_static_pad_templates (factory);
757 +    for (walk = (GList *) templates; walk; walk = g_list_next (walk)) {
758 +      GstStaticPadTemplate *templ = walk->data;
759 +
760 +      /* we only care about the sink templates */
761 +      if (templ->direction == GST_PAD_SINK) {
762 +        GstCaps *intersect;
763 +        GstCaps *tmpl_caps;
764 +
765 +        /* try to intersect the caps with the caps of the template */
766 +        tmpl_caps = gst_static_caps_get (&templ->static_caps);
767 +
768 +        /* FIXME, intersect is not the right method, we ideally want to check
769 +         * for a subset here */
770 +        intersect = gst_caps_intersect (caps, tmpl_caps);
771 +        gst_caps_unref (tmpl_caps);
772 +
773 +        /* check if the intersection is empty */
774 +        if (!gst_caps_is_empty (intersect)) {
775 +          /* non empty intersection, we can use this element */
776 +          GValue resval = { 0, };
777 +          g_value_init (&resval, G_TYPE_OBJECT);
778 +          g_value_set_object (&resval, factory);
779 +          g_value_array_append (result, &resval);
780 +          g_value_unset (&resval);
781 +          gst_caps_unref (intersect);
782 +          break;
783 +        }
784 +        gst_caps_unref (intersect);
785 +      }
786 +    }
787 +  }
788 +  GST_DEBUG ("found %u factories for %" GST_PTR_FORMAT, result->n_values, caps);
789 +  return result;
790 +}
791 +
792 +/* Called when we must provide a list of factories to plug pad with caps */
793 +static GValueArray *
794 +tag_read_bin_decodebin_autoplug_factories (GstElement * decodebin,
795 +    GstPad * pad, GstCaps * caps, GstTagReadBin * tbin)
796 +{
797 +  GValueArray *result = NULL;
798 +
799 +  GST_TAG_READ_BIN_SHUTDOWN_LOCK (tbin, shutdown);
800 +
801 +  GST_DEBUG_OBJECT (tbin, "getting factories for caps %" GST_PTR_FORMAT, caps);
802 +
803 +  /* filter out the elements based on the caps */
804 +  result = gst_factory_list_filter_by_sinkcaps (tbin->elements, caps);
805 +
806 +  if (tbin->container_caps == NULL &&
807 +      gst_tag_read_bin_factory_list_has_demuxer (result)) {
808 +    GST_DEBUG_OBJECT (tbin, "possible container caps: %" GST_PTR_FORMAT, caps);
809 +    gst_caps_replace (&tbin->container_caps, caps);
810 +  }
811 +
812 +  GST_DEBUG_OBJECT (tbin, "found %u factories", result->n_values);
813 +
814 +  /* nothing else found => expose pad as it is now and hope for the best */
815 +  if (result->n_values == 0) {
816 +    if (!gst_tag_read_utils_caps_complete (caps)) {
817 +      GST_FIXME ("caps without all the info we want and no more tagreaders/"
818 +          "parsers to plug for %" GST_PTR_FORMAT, caps);
819 +    }
820 +    g_value_array_free (result);
821 +    result = NULL;
822 +  }
823 +
824 +  GST_TAG_READ_BIN_SHUTDOWN_UNLOCK (tbin);
825 +
826 +  return result;
827 +
828 +shutdown:
829 +  {
830 +    GST_DEBUG_OBJECT (tbin, "shutdown");
831 +    return NULL;
832 +  }
833 +}
834 +
835 +static gboolean
836 +tag_read_bin_decodebin_autoplug_continue (GstElement * decodebin, GstPad * pad,
837 +    GstCaps * caps, GstTagReadBin * tbin)
838 +{
839 +  GstFormat fmt = GST_FORMAT_TIME;
840 +  gboolean do_continue = FALSE;
841 +  gboolean query_ok, capsinfo_ok;
842 +  gint64 dur = 0;
843 +
844 +  GST_TAG_READ_BIN_SHUTDOWN_LOCK (tbin, shutdown);
845 +
846 +  GST_DEBUG_OBJECT (tbin, "continue with caps %" GST_PTR_FORMAT "?", caps);
847 +
848 +  query_ok = gst_pad_query_duration (pad, &fmt, &dur) && (dur > 0);
849 +  GST_LOG_OBJECT (tbin, "duration query: res=%d, duration=%" G_GINT64_FORMAT,
850 +      query_ok, dur);
851 +
852 +  capsinfo_ok = gst_tag_read_utils_caps_complete (caps);
853 +  GST_LOG_OBJECT (tbin, "caps info complete: %d", capsinfo_ok);
854 +
855 +  /* stop if we have both a duration and the caps are complete enough */
856 +  do_continue = !query_ok || !capsinfo_ok;
857 +  GST_LOG_OBJECT (tbin, "continue: %d", do_continue);
858 +
859 +  GST_TAG_READ_BIN_SHUTDOWN_UNLOCK (tbin);
860 +  return do_continue;
861 +
862 +shutdown:
863 +  {
864 +    GST_DEBUG_OBJECT (tbin, "shutting down");
865 +    return FALSE;
866 +  }
867 +}
868 +
869 +/* We are asked to select an element. We can return TRY or EXPOSE */
870 +static GstAutoplugSelectResult
871 +tag_read_bin_decodebin_autoplug_select (GstElement * decodebin, GstPad * pad,
872 +    GstCaps * caps, GstElementFactory * factory, GstTagReadBin * tbin)
873 +{
874 +  GST_LOG_OBJECT (tbin, "selecting %s for caps %" GST_PTR_FORMAT,
875 +      GST_PLUGIN_FEATURE_NAME (factory), caps);
876 +
877 +  return GST_AUTOPLUG_SELECT_TRY;
878 +}
879 +
880 +#define REMOVE_SIGNAL(obj,id)            \
881 +if (id) {                                \
882 +  g_signal_handler_disconnect (obj, id); \
883 +  id = 0;                                \
884 +}
885 +
886 +static void
887 +gst_tag_read_bin_cleanup (GstTagReadBin * tbin)
888 +{
889 +  while (tbin->request_pads) {
890 +    GstPad *pad = tbin->request_pads->data;
891 +
892 +    gst_element_release_request_pad (tbin->tagsink, pad);
893 +    gst_object_unref (pad);
894 +    tbin->request_pads =
895 +        g_list_delete_link (tbin->request_pads, tbin->request_pads);
896 +  }
897 +  if (tbin->uridecodebin) {
898 +    GST_LOG_OBJECT (tbin, "removing existing decodebin");
899 +    REMOVE_SIGNAL (tbin->uridecodebin, tbin->pad_added_id);
900 +    REMOVE_SIGNAL (tbin->uridecodebin, tbin->no_more_pads_id);
901 +    REMOVE_SIGNAL (tbin->uridecodebin, tbin->autoplug_factories_id);
902 +    REMOVE_SIGNAL (tbin->uridecodebin, tbin->autoplug_select_id);
903 +    gst_element_set_state (tbin->uridecodebin, GST_STATE_NULL);
904 +    gst_bin_remove (GST_BIN (tbin), tbin->uridecodebin);
905 +    tbin->uridecodebin = NULL;
906 +    GST_DEBUG_OBJECT (tbin, "removed decodebin");
907 +  }
908 +  if (tbin->tagsink) {
909 +    GST_LOG_OBJECT (tbin, "removing tagsink");
910 +    gst_element_set_state (tbin->tagsink, GST_STATE_NULL);
911 +    gst_bin_remove (GST_BIN (tbin), tbin->tagsink);
912 +    tbin->tagsink = NULL;
913 +    GST_DEBUG_OBJECT (tbin, "removed tagsink");
914 +  }
915 +  while (tbin->tag_messages) {
916 +    gst_message_unref (tbin->tag_messages->data);
917 +    tbin->tag_messages = g_list_delete_link (tbin->tag_messages,
918 +        tbin->tag_messages);
919 +  }
920 +  gst_caps_replace (&tbin->container_caps, NULL);
921 +}
922 +
923 +static gboolean
924 +gst_tag_read_bin_setup (GstTagReadBin * tbin)
925 +{
926 +  GstStateChangeReturn state_ret;
927 +  GstState pending;
928 +
929 +  tbin->container_caps = NULL;
930 +
931 +  GST_DEBUG_OBJECT (tbin, "creating new decodebin");
932 +  tbin->uridecodebin = gst_element_factory_make ("uridecodebin", NULL);
933 +  if (tbin->uridecodebin == NULL)
934 +    goto no_decodebin;
935 +
936 +  gst_tag_reader_set_tag_reading_mode (GST_TAG_READER (tbin->uridecodebin),
937 +      GST_TAG_READING_MODE_TAGS_ONLY);
938 +
939 +  gst_bin_add (GST_BIN_CAST (tbin), tbin->uridecodebin);
940 +
941 +  /* pad-added: we connect the newly exposed pad to the tagsink  */
942 +  tbin->pad_added_id = g_signal_connect (tbin->uridecodebin, "pad-added",
943 +      G_CALLBACK (tag_read_bin_decodebin_pad_added), tbin);
944 +
945 +  /* no-more-pads: we just connect this for logging purposes */
946 +  tbin->no_more_pads_id = g_signal_connect (tbin->uridecodebin, "no-more-pads",
947 +      G_CALLBACK (tag_read_bin_decodebin_no_more_pads), tbin);
948 +
949 +  /* autoplug-factories: will be called when a new media type is found. We
950 +   * return a list of tagreaders or parser for decodebin to try */
951 +  tbin->autoplug_factories_id =
952 +      g_signal_connect (tbin->uridecodebin, "autoplug-factories",
953 +      G_CALLBACK (tag_read_bin_decodebin_autoplug_factories), tbin);
954 +
955 +  /* autoplug-continue: determine whether we've got the kind of caps we're
956 +   * looking for etc. or if we want to plug further tagreaders/parsers */
957 +  tbin->autoplug_continue_id = g_signal_connect (tbin->uridecodebin,
958 +      "autoplug-continue",
959 +      G_CALLBACK (tag_read_bin_decodebin_autoplug_continue), tbin);
960 +
961 +  /* autoplug-select: report what's being plugged next */
962 +  tbin->autoplug_select_id = g_signal_connect (tbin->uridecodebin,
963 +      "autoplug-select", G_CALLBACK (tag_read_bin_decodebin_autoplug_select),
964 +      tbin);
965 +
966 +  g_object_set (tbin->uridecodebin, "uri", tbin->uri, NULL);
967 +
968 +  GST_STATE_LOCK (tbin);
969 +  pending = GST_STATE_PENDING (tbin);
970 +  GST_STATE_UNLOCK (tbin);
971 +
972 +  tbin->tagsink = g_object_new (GST_TYPE_TAG_SINK, NULL);
973 +  gst_bin_add (GST_BIN (tbin), tbin->tagsink);
974 +  gst_element_set_state (tbin->tagsink, pending);
975 +
976 +  state_ret = gst_element_set_state (tbin->uridecodebin, pending);
977 +  if (state_ret == GST_STATE_CHANGE_FAILURE)
978 +    goto decodebin_failure;
979 +
980 +  return TRUE;
981 +
982 +  /* ERRORS */
983 +no_decodebin:
984 +  {
985 +    gst_element_post_message (GST_ELEMENT_CAST (tbin),
986 +        gst_missing_element_message_new (GST_ELEMENT (tbin), "uridecodebin"));
987 +    GST_ELEMENT_ERROR (tbin, CORE, MISSING_PLUGIN, (NULL), ("no uridecodebin"));
988 +    return FALSE;
989 +  }
990 +decodebin_failure:
991 +  {
992 +    GST_DEBUG_OBJECT (tbin, "decodebin state change failed");
993 +    gst_tag_read_bin_cleanup (tbin);
994 +    /* an error message should have been posted already, but better be safe */
995 +    GST_ELEMENT_ERROR (tbin, CORE, STATE_CHANGE, (NULL), (NULL));
996 +    return FALSE;
997 +  }
998 +}
999 +
1000 +static GstStateChangeReturn
1001 +gst_tag_read_bin_change_state (GstElement * element, GstStateChange transition)
1002 +{
1003 +  GstStateChangeReturn ret;
1004 +  GstTagReadBin *tbin;
1005 +
1006 +  tbin = GST_TAG_READ_BIN (element);
1007 +
1008 +  GST_LOG_OBJECT (tbin, "transition %s -> %s",
1009 +      gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
1010 +      gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
1011 +
1012 +  switch (transition) {
1013 +    case GST_STATE_CHANGE_READY_TO_PAUSED:
1014 +      GST_LOG_OBJECT (tbin, "clearing shutdown flag");
1015 +      g_atomic_int_set (&tbin->shutdown, 0);
1016 +      if (!gst_tag_read_bin_setup (tbin))
1017 +        return GST_STATE_CHANGE_FAILURE;
1018 +      break;
1019 +    case GST_STATE_CHANGE_PAUSED_TO_READY:
1020 +      GST_LOG_OBJECT (tbin, "setting shutdown flag");
1021 +      g_atomic_int_set (&tbin->shutdown, 1);
1022 +
1023 +      /* wait for all callbacks to end by taking the lock.
1024 +       * No dynamic (critical) new callbacks will
1025 +       * be able to happen as we set the shutdown flag. */
1026 +      GST_TAG_READ_BIN_DYN_LOCK (tbin);
1027 +      GST_LOG_OBJECT (tbin, "dynamic lock taken, we can continue shutdown");
1028 +      GST_TAG_READ_BIN_DYN_UNLOCK (tbin);
1029 +      break;
1030 +    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1031 +      gst_element_post_message (element,
1032 +          gst_message_new_eos (GST_OBJECT_CAST (element)));
1033 +      break;
1034 +    case GST_STATE_CHANGE_READY_TO_NULL:
1035 +      break;
1036 +    default:
1037 +      break;
1038 +  }
1039 +
1040 +  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1041 +  if (ret == GST_STATE_CHANGE_FAILURE)
1042 +    return ret;
1043 +
1044 +  switch (transition) {
1045 +    case GST_STATE_CHANGE_PAUSED_TO_READY:
1046 +    case GST_STATE_CHANGE_READY_TO_NULL:
1047 +      gst_tag_read_bin_cleanup (tbin);
1048 +      break;
1049 +    default:
1050 +      break;
1051 +  }
1052 +
1053 +  GST_LOG_OBJECT (tbin, "transition %s -> %s, returning %s",
1054 +      gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
1055 +      gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)),
1056 +      gst_element_state_change_return_get_name (ret));
1057 +
1058 +  return ret;
1059 +}
1060 +
1061 +static gboolean
1062 +plugin_init (GstPlugin * plugin)
1063 +{
1064 +  GST_DEBUG_CATEGORY_INIT (tagreadbin_debug, "tagreadbin", 0, "Tag read bin");
1065 +
1066 +  gst_tag_read_utils_register_caps_tags ();
1067 +
1068 +  return gst_element_register (plugin, "tagreadbin", GST_RANK_NONE,
1069 +      GST_TYPE_TAG_READ_BIN);
1070 +}
1071 +
1072 +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
1073 +    "tagreading", "Tag reading support", plugin_init, VERSION, GST_LICENSE,
1074 +    GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
1075 diff --git a/gst/tagreading/gsttagreadbin.h b/gst/tagreading/gsttagreadbin.h
1076 new file mode 100644
1077 index 0000000..d380bce
1078 --- /dev/null
1079 +++ b/gst/tagreading/gsttagreadbin.h
1080 @@ -0,0 +1,104 @@
1081 +/* GStreamer tag read bin
1082 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
1083 + *
1084 + * This library is free software; you can redistribute it and/or
1085 + * modify it under the terms of the GNU Library General Public
1086 + * License as published by the Free Software Foundation; either
1087 + * version 2 of the License, or (at your option) any later version.
1088 + *
1089 + * This library is distributed in the hope that it will be useful,
1090 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1091 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
1092 + * Library General Public License for more details.
1093 + *
1094 + * You should have received a copy of the GNU Library General Public
1095 + * License along with this library; if not, write to the
1096 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
1097 + * Boston, MA 02111-1307, USA.
1098 + */
1099 +
1100 +#ifndef __GST_TAG_READ_BIN_H__
1101 +#define __GST_TAG_READ_BIN_H__
1102 +
1103 +#include <gst/gst.h>
1104 +
1105 +G_BEGIN_DECLS
1106 +
1107 +#define GST_TYPE_TAG_READ_BIN               (gst_tag_read_bin_get_type())
1108 +#define GST_TAG_READ_BIN(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAG_READ_BIN,GstTagReadBin))
1109 +#define GST_TAG_READ_BIN_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAG_READ_BIN,GstTagReadBinClass))
1110 +#define GST_IS_TAG_READ_BIN(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TAG_READ_BIN))
1111 +#define GST_IS_TAG_READ_BIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAG_READ_BIN))
1112 +
1113 +#define GST_TAG_READ_BIN_DYN_LOCK(tbin)    g_mutex_lock ((tbin)->dyn_lock)
1114 +#define GST_TAG_READ_BIN_DYN_UNLOCK(tbin)  g_mutex_unlock ((tbin)->dyn_lock)
1115 +
1116 +/* lock for shutdown */
1117 +#define GST_TAG_READ_BIN_SHUTDOWN_LOCK(tbin,label)       \
1118 +G_STMT_START {                                           \
1119 +  if (G_UNLIKELY (g_atomic_int_get (&tbin->shutdown)))   \
1120 +    goto label;                                          \
1121 +  GST_TAG_READ_BIN_DYN_LOCK (tbin);                      \
1122 +  if (G_UNLIKELY (g_atomic_int_get (&tbin->shutdown))) { \
1123 +    GST_TAG_READ_BIN_DYN_UNLOCK (tbin);                  \
1124 +    goto label;                                          \
1125 +  }                                                      \
1126 +} G_STMT_END
1127 +
1128 +/* unlock for shutdown */
1129 +#define GST_TAG_READ_BIN_SHUTDOWN_UNLOCK(bin)         \
1130 +  GST_TAG_READ_BIN_DYN_UNLOCK (bin);                  \
1131 +
1132 +typedef struct _GstTagReadBin GstTagReadBin;
1133 +typedef struct _GstTagReadBinClass GstTagReadBinClass;
1134 +
1135 +/**
1136 + * GstTagReadBin:
1137 + *
1138 + * tagreadbin element structure (opaque)
1139 + *
1140 + */
1141 +struct _GstTagReadBin
1142 +{
1143 +  GstPipeline parent;
1144 +
1145 +  /*< private >*/
1146 +  GstElement   * uridecodebin;
1147 +  gchar        * uri;
1148 +
1149 +  GstElement   * tagsink;
1150 +
1151 +  /* factories we can use for selecting elements */
1152 +  GValueArray  * elements;
1153 +
1154 +  /* lock protecting dynamic adding/removing */
1155 +  GMutex       * dyn_lock;
1156 +
1157 +  /* if we are shutting down or not */
1158 +  gint           shutdown;                             /* ATOMIC */
1159 +
1160 +  /* tag messages we dropped (to compare to tag events collected from pads) */
1161 +  GList         *tag_messages;                         /* OBJECT_LOCK */
1162 +
1163 +  GstCaps       *container_caps;                       /* DYN_LOCK */
1164 +
1165 +  GList         *request_pads;                         /* DYN_LOCK */
1166 +
1167 +  /* decodebin signals */
1168 +  gulong         pad_added_id;
1169 +  gulong         no_more_pads_id;
1170 +  gulong         autoplug_factories_id;
1171 +  gulong         autoplug_continue_id;
1172 +  gulong         autoplug_select_id;
1173 +};
1174 +
1175 +struct _GstTagReadBinClass
1176 +{
1177 +  GstPipelineClass parent_class;
1178 +};
1179 +
1180 +GType  gst_tag_read_bin_get_type (void);
1181 +
1182 +G_END_DECLS
1183 +
1184 +#endif /* __GST_TAG_READ_BIN_H__ */
1185 diff --git a/gst/tagreading/gsttagreadutils.c b/gst/tagreading/gsttagreadutils.c
1186 new file mode 100644
1187 index 0000000..acaebb6
1188 --- /dev/null
1189 +++ b/gst/tagreading/gsttagreadutils.c
1190 @@ -0,0 +1,309 @@
1191 +/* GStreamer tag read bin utilities
1192 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
1193 + *
1194 + * This library is free software; you can redistribute it and/or
1195 + * modify it under the terms of the GNU Library General Public
1196 + * License as published by the Free Software Foundation; either
1197 + * version 2 of the License, or (at your option) any later version.
1198 + *
1199 + * This library is distributed in the hope that it will be useful,
1200 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1201 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
1202 + * Library General Public License for more details.
1203 + *
1204 + * You should have received a copy of the GNU Library General Public
1205 + * License along with this library; if not, write to the
1206 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
1207 + * Boston, MA 02111-1307, USA.
1208 + */
1209 +
1210 +#include "gsttagreadutils.h"
1211 +#include <gst/pbutils/pbutils.h>
1212 +
1213 +GST_DEBUG_CATEGORY_EXTERN (tagreadbin_debug);
1214 +#define GST_CAT_DEFAULT tagreadbin_debug
1215 +
1216 +GstTagReadStreamType
1217 +gst_tag_read_utils_get_caps_type (const GstCaps * caps)
1218 +{
1219 +  GstStructure *s;
1220 +  const gchar *name;
1221 +
1222 +  s = gst_caps_get_structure (caps, 0);
1223 +  name = gst_structure_get_name (s);
1224 +
1225 +  if (g_str_equal (name, "video/x-dvd-subpicture") ||
1226 +      g_str_has_prefix (name, "text/")) {
1227 +    return GST_TAG_READ_STREAM_TYPE_SUBTITLE;
1228 +  }
1229 +
1230 +  if (g_str_has_prefix (name, "application/x-subtitle"))
1231 +    return GST_TAG_READ_STREAM_TYPE_SUBTITLE;
1232 +
1233 +  if (g_str_has_prefix (name, "audio/"))
1234 +    return GST_TAG_READ_STREAM_TYPE_AUDIO;
1235 +
1236 +  if (g_str_has_prefix (name, "video/") || g_str_has_prefix (name, "image/")) {
1237 +    return GST_TAG_READ_STREAM_TYPE_VIDEO;
1238 +  }
1239 +
1240 +  return GST_TAG_READ_STREAM_TYPE_UNKNOWN;
1241 +}
1242 +
1243 +gboolean
1244 +gst_tag_read_utils_caps_complete (const GstCaps * caps)
1245 +{
1246 +  GstStructure *s;
1247 +  const gchar *name;
1248 +  gboolean complete;
1249 +
1250 +  s = gst_caps_get_structure (caps, 0);
1251 +  name = gst_structure_get_name (s);
1252 +
1253 +  if (g_str_equal (name, "video/x-dvd-subpicture") ||
1254 +      g_str_has_prefix (name, "text/")) {
1255 +    complete = TRUE;
1256 +  } else if (g_str_has_prefix (name, "application/x-subtitle")) {
1257 +    /* subtitle parser might give us more info */
1258 +    complete = FALSE;
1259 +  } else if (g_str_has_prefix (name, "audio/")) {
1260 +    complete = gst_structure_has_field (s, "channels") &&
1261 +        gst_structure_has_field (s, "rate");
1262 +  } else if (g_str_has_prefix (name, "video/") ||
1263 +      g_str_has_prefix (name, "image/")) {
1264 +    complete = gst_structure_has_field (s, "width") &&
1265 +        gst_structure_has_field (s, "height");
1266 +  } else {
1267 +    complete = FALSE;
1268 +  }
1269 +
1270 +  return complete;
1271 +}
1272 +
1273 +void
1274 +gst_tag_read_utils_add_codec_tag_from_caps (GstTagList * tags,
1275 +    const GstCaps * caps)
1276 +{
1277 +  GstTagReadStreamType type;
1278 +  const gchar *tag_name, *tag_name_unknown;
1279 +
1280 +  gst_pb_utils_init ();
1281 +
1282 +  type = gst_tag_read_utils_get_caps_type (caps);
1283 +  switch (type) {
1284 +    case GST_TAG_READ_STREAM_TYPE_AUDIO:
1285 +      tag_name = GST_TAG_AUDIO_CODEC;
1286 +      tag_name_unknown = GST_TAG_AUDIO_CODEC "-unknown";
1287 +      break;
1288 +    case GST_TAG_READ_STREAM_TYPE_VIDEO:
1289 +      tag_name = GST_TAG_VIDEO_CODEC;
1290 +      tag_name_unknown = GST_TAG_VIDEO_CODEC "-unknown";
1291 +      break;
1292 +    case GST_TAG_READ_STREAM_TYPE_SUBTITLE:
1293 +      tag_name = GST_TAG_SUBTITLE_CODEC;
1294 +      tag_name_unknown = GST_TAG_SUBTITLE_CODEC "-unknown";
1295 +      break;
1296 +    default:
1297 +      tag_name = GST_TAG_CODEC;
1298 +      tag_name_unknown = GST_TAG_CODEC "-unknown";
1299 +      break;
1300 +  }
1301 +
1302 +  GST_LOG ("Adding %s field to tags from %" GST_PTR_FORMAT, tag_name, caps);
1303 +  gst_pb_utils_add_codec_description_to_tag_list (tags, tag_name, caps);
1304 +
1305 +  if (!gst_tag_read_utils_list_has_codec_tag (tags)) {
1306 +    GST_FIXME ("Couldn't get codec name for caps %" GST_PTR_FORMAT, caps);
1307 +    if (!gst_tag_exists (tag_name_unknown)) {
1308 +      gst_tag_register (tag_name_unknown, GST_TAG_FLAG_ENCODED,
1309 +          G_TYPE_BOOLEAN, "codec unknown",
1310 +          "TRUE if the codec  is not known and the *-codec "
1311 +          "tag contains the string 'unknown' or somesuch", NULL);
1312 +
1313 +      /* FIXME: translate */
1314 +      gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, tag_name,
1315 +          "Unknown", tag_name_unknown, TRUE, NULL);
1316 +    }
1317 +  }
1318 +}
1319 +
1320 +/* FIXME: remove once we depend on 0.10.24 */
1321 +#ifndef GST_TAG_CONTAINER_FORMAT
1322 +#define GST_TAG_CONTAINER_FORMAT "container-format"
1323 +#endif
1324 +
1325 +void
1326 +gst_tag_read_utils_add_container_tag_from_caps (GstTagList * tags,
1327 +    const GstCaps * caps)
1328 +{
1329 +  gst_pb_utils_init ();
1330 +
1331 +  /* FIXME: remove once we depend on 0.10.24 */
1332 +  if (!gst_tag_exists (GST_TAG_CONTAINER_FORMAT)) {
1333 +    gst_tag_register (GST_TAG_CONTAINER_FORMAT, GST_TAG_FLAG_ENCODED,
1334 +        G_TYPE_STRING, "container format", "container format", NULL);
1335 +  }
1336 +
1337 +  GST_LOG ("Adding container-format field to tags from %" GST_PTR_FORMAT, caps);
1338 +  gst_pb_utils_add_codec_description_to_tag_list (tags,
1339 +      GST_TAG_CONTAINER_FORMAT, caps);
1340 +
1341 +  /* Adding bogus container-format tag */
1342 +  if (!gst_tag_read_utils_list_has_container_tag (tags)) {
1343 +    GST_FIXME ("Couldn't get container format for caps %" GST_PTR_FORMAT, caps);
1344 +
1345 +    if (!gst_tag_exists ("container-format-unknown")) {
1346 +      gst_tag_register ("container-format-unknown", GST_TAG_FLAG_ENCODED,
1347 +          G_TYPE_BOOLEAN, "container format unknown",
1348 +          "TRUE if the container format is not known and the contaier-format "
1349 +          "tag contains the string 'unknown' or somesuch", NULL);
1350 +    }
1351 +
1352 +    /* FIXME: translate */
1353 +    gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_CONTAINER_FORMAT,
1354 +        "Unknown", "container-format-unknown", TRUE, NULL);
1355 +  }
1356 +}
1357 +
1358 +gboolean
1359 +gst_tag_read_utils_list_has_container_tag (GstTagList * tags)
1360 +{
1361 +  const GValue *v;
1362 +
1363 +  v = gst_tag_list_get_value_index (tags, GST_TAG_CONTAINER_FORMAT, 0);
1364 +  return (v != NULL);
1365 +}
1366 +
1367 +gboolean
1368 +gst_tag_read_utils_list_has_codec_tag (GstTagList * tags)
1369 +{
1370 +  return (gst_tag_list_get_value_index (tags, GST_TAG_CODEC, 0) != NULL) ||
1371 +      (gst_tag_list_get_value_index (tags, GST_TAG_VIDEO_CODEC, 0) != NULL) ||
1372 +      (gst_tag_list_get_value_index (tags, GST_TAG_AUDIO_CODEC, 0) != NULL);
1373 +}
1374 +
1375 +static void
1376 +gst_tag_read_utils_list_merge_field (GstTagList * tags, GstStructure * s,
1377 +    const gchar * field, GType expected_type)
1378 +{
1379 +  const GValue *v;
1380 +
1381 +  if ((v = gst_structure_get_value (s, field))) {
1382 +    if (G_VALUE_TYPE (v) == expected_type) {
1383 +      if (gst_tag_list_get_value_index (tags, field, 0) != NULL) {
1384 +        GST_WARNING ("overwriting tag '%s' with value from caps", field);
1385 +      }
1386 +      gst_tag_list_add_values (tags, GST_TAG_MERGE_REPLACE, field, v, NULL);
1387 +      GST_LOG ("added field '%s' to taglist %" GST_PTR_FORMAT, field, tags);
1388 +    } else {
1389 +      GST_WARNING ("field '%s' in caps %" GST_PTR_FORMAT " does not have the "
1390 +          "expected type (%s)", field, s, g_type_name (expected_type));
1391 +    }
1392 +  } else {
1393 +    GST_LOG ("no field '%s' in caps %" GST_PTR_FORMAT, field, s);
1394 +  }
1395 +}
1396 +
1397 +void
1398 +gst_tag_read_utils_register_caps_tags (void)
1399 +{
1400 +  /* audio tags */
1401 +  if (!gst_tag_exists ("channels")) {
1402 +    gst_tag_register ("channels", GST_TAG_FLAG_ENCODED,
1403 +        G_TYPE_INT, "channels", "number of audio channels", NULL);
1404 +  }
1405 +  if (!gst_tag_exists ("rate")) {
1406 +    gst_tag_register ("rate", GST_TAG_FLAG_ENCODED,
1407 +        G_TYPE_INT, "rate", "sample rate of audio", NULL);
1408 +  }
1409 +
1410 +  /* video/image tags */
1411 +  if (!gst_tag_exists ("width")) {
1412 +    gst_tag_register ("width", GST_TAG_FLAG_ENCODED,
1413 +        G_TYPE_INT, "width", "width of video or image", NULL);
1414 +  }
1415 +  if (!gst_tag_exists ("height")) {
1416 +    gst_tag_register ("height", GST_TAG_FLAG_ENCODED,
1417 +        G_TYPE_INT, "height", "height of video or image", NULL);
1418 +  }
1419 +  if (!gst_tag_exists ("framerate")) {
1420 +    gst_tag_register ("framerate", GST_TAG_FLAG_ENCODED,
1421 +        GST_TYPE_FRACTION, "framerate", "framerate of video", NULL);
1422 +  }
1423 +  if (!gst_tag_exists ("pixel-aspect-ratio")) {
1424 +    gst_tag_register ("pixel-aspect-ratio", GST_TAG_FLAG_ENCODED,
1425 +        GST_TYPE_FRACTION, "pixel-aspect-ratio", "PAR of video", NULL);
1426 +  }
1427 +}
1428 +
1429 +void
1430 +gst_tag_read_utils_list_add_caps_info (GstTagList * tags, const GstCaps * caps)
1431 +{
1432 +  GstTagReadStreamType type;
1433 +  GstStructure *s;
1434 +
1435 +  if (caps == NULL)
1436 +    return;
1437 +
1438 +  gst_tag_read_utils_register_caps_tags ();
1439 +
1440 +  s = gst_caps_get_structure (caps, 0);
1441 +
1442 +  type = gst_tag_read_utils_get_caps_type (caps);
1443 +  switch (type) {
1444 +      /* keep fields in sync with gst_tag_read_utils_register_caps_tags() */
1445 +    case GST_TAG_READ_STREAM_TYPE_AUDIO:
1446 +      gst_tag_read_utils_list_merge_field (tags, s, "channels", G_TYPE_INT);
1447 +      gst_tag_read_utils_list_merge_field (tags, s, "rate", G_TYPE_INT);
1448 +      break;
1449 +      /* keep fields in sync with gst_tag_read_utils_register_caps_tags() */
1450 +    case GST_TAG_READ_STREAM_TYPE_VIDEO:
1451 +      gst_tag_read_utils_list_merge_field (tags, s, "width", G_TYPE_INT);
1452 +      gst_tag_read_utils_list_merge_field (tags, s, "height", G_TYPE_INT);
1453 +      gst_tag_read_utils_list_merge_field (tags, s, "framerate",
1454 +          GST_TYPE_FRACTION);
1455 +      gst_tag_read_utils_list_merge_field (tags, s, "pixel-aspect-ratio",
1456 +          GST_TYPE_FRACTION);
1457 +      break;
1458 +    case GST_TAG_READ_STREAM_TYPE_SUBTITLE:
1459 +      break;
1460 +    default:
1461 +      break;
1462 +  }
1463 +}
1464 +
1465 +void
1466 +gst_tag_read_utils_list_add_duration (GstTagList * tags, GstClockTime dur)
1467 +{
1468 +  if (GST_CLOCK_TIME_IS_VALID (dur) && dur > 0) {
1469 +    gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_DURATION, dur, NULL);
1470 +    GST_LOG ("added field 'duration' to taglist %" GST_PTR_FORMAT, tags);
1471 +  } else {
1472 +    GST_DEBUG ("not adding 'duration' field to taglist, duration not valid");
1473 +  }
1474 +}
1475 +
1476 +static void
1477 +taglist_remove_private_foreach_func (const GstTagList * list,
1478 +    const gchar * tag, gpointer user_data)
1479 +{
1480 +  GList **p_list = user_data;
1481 +
1482 +  /* tag is a quark string, we don't need to worry about it getting freed */
1483 +  if (g_str_has_prefix (tag, "private-"))
1484 +    *p_list = g_list_prepend (*p_list, (gpointer) tag);
1485 +}
1486 +
1487 +void
1488 +gst_tag_read_utils_list_remove_private_tags (GstTagList * tags)
1489 +{
1490 +  GList *list = NULL;
1491 +
1492 +  gst_tag_list_foreach (tags, taglist_remove_private_foreach_func, &list);
1493 +  while (list != NULL) {
1494 +    gst_tag_list_remove_tag (tags, list->data);
1495 +    GST_LOG ("Removed tag %s, taglist now %" GST_PTR_FORMAT,
1496 +        (const char *) list->data, tags);
1497 +    list = g_list_delete_link (list, list);
1498 +  }
1499 +}
1500 diff --git a/gst/tagreading/gsttagreadutils.h b/gst/tagreading/gsttagreadutils.h
1501 new file mode 100644
1502 index 0000000..06ce9ea
1503 --- /dev/null
1504 +++ b/gst/tagreading/gsttagreadutils.h
1505 @@ -0,0 +1,62 @@
1506 +/* GStreamer tag read bin utilities
1507 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
1508 + *
1509 + * This library is free software; you can redistribute it and/or
1510 + * modify it under the terms of the GNU Library General Public
1511 + * License as published by the Free Software Foundation; either
1512 + * version 2 of the License, or (at your option) any later version.
1513 + *
1514 + * This library is distributed in the hope that it will be useful,
1515 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1516 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
1517 + * Library General Public License for more details.
1518 + *
1519 + * You should have received a copy of the GNU Library General Public
1520 + * License along with this library; if not, write to the
1521 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
1522 + * Boston, MA 02111-1307, USA.
1523 + */
1524 +
1525 +#ifndef __GST_TAG_READ_UTILS_H__
1526 +#define __GST_TAG_READ_UTILS_H__
1527 +
1528 +#include <gst/gst.h>
1529 +
1530 +G_BEGIN_DECLS
1531 +
1532 +typedef enum
1533 +{
1534 +  GST_TAG_READ_STREAM_TYPE_UNKNOWN = 0,
1535 +  GST_TAG_READ_STREAM_TYPE_AUDIO,
1536 +  GST_TAG_READ_STREAM_TYPE_VIDEO,
1537 +  GST_TAG_READ_STREAM_TYPE_SUBTITLE
1538 +} GstTagReadStreamType;
1539 +
1540 +GstTagReadStreamType  gst_tag_read_utils_get_caps_type (const GstCaps * caps);
1541 +
1542 +gboolean              gst_tag_read_utils_caps_complete (const GstCaps * caps);
1543 +
1544 +void                  gst_tag_read_utils_add_codec_tag_from_caps (GstTagList    * tags,
1545 +                                                                  const GstCaps * caps);
1546 +
1547 +void                  gst_tag_read_utils_add_container_tag_from_caps (GstTagList    * tags,
1548 +                                                                      const GstCaps * caps);
1549 +
1550 +gboolean              gst_tag_read_utils_list_has_codec_tag (GstTagList * tags);
1551 +
1552 +gboolean              gst_tag_read_utils_list_has_container_tag (GstTagList * tags);
1553 +
1554 +
1555 +void                  gst_tag_read_utils_register_caps_tags (void);
1556 +
1557 +void                  gst_tag_read_utils_list_add_caps_info (GstTagList    * tags,
1558 +                                                             const GstCaps * caps);
1559 +
1560 +void                  gst_tag_read_utils_list_add_duration (GstTagList * tags,
1561 +                                                            GstClockTime dur);
1562 +
1563 +void                  gst_tag_read_utils_list_remove_private_tags (GstTagList * tags);
1564 +
1565 +G_END_DECLS
1566 +
1567 +#endif /* __GST_TAG_UTILS_H__ */
1568 diff --git a/gst/tagreading/gsttagsink.c b/gst/tagreading/gsttagsink.c
1569 new file mode 100644
1570 index 0000000..3741897
1571 --- /dev/null
1572 +++ b/gst/tagreading/gsttagsink.c
1573 @@ -0,0 +1,785 @@
1574 +/* GStreamer tag read bin sink
1575 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
1576 + *
1577 + * This library is free software; you can redistribute it and/or
1578 + * modify it under the terms of the GNU Library General Public
1579 + * License as published by the Free Software Foundation; either
1580 + * version 2 of the License, or (at your option) any later version.
1581 + *
1582 + * This library is distributed in the hope that it will be useful,
1583 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1584 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
1585 + * Library General Public License for more details.
1586 + *
1587 + * You should have received a copy of the GNU Library General Public
1588 + * License along with this library; if not, write to the
1589 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
1590 + * Boston, MA 02111-1307, USA.
1591 + */
1592 +
1593 +/* Sink with N request sink pads; collects caps and tags on each stream
1594 + * until we have seen a buffer or EOS event on all streams. Once we've
1595 + * collected all the data we want, the sink "prerolls".
1596 + *
1597 + * We derive from basesink to avoid having to implement async state change
1598 + * behaviour ourselves, which is quite tricky to get right.
1599 + *
1600 + * Of course we could have done all this very differently, eg. use one sink per
1601 + * stream or use buffer probes in the tagreadbin etc., but having a separate
1602 + * element that handles all the collection logic seems conceptually nicer.
1603 + * If http://bugzilla.gnome.org/show_bug.cgi?id=342155 gets implemented,
1604 + * we could just derive one tagsink per stream and then use that API.
1605 + * (we currently don't even need this, but we might in the future, e.g.
1606 + * should we want to preroll only once the duration returned is somewhat
1607 + * stable, or if we want to get a thumbnail of the N-th keyframe or so.)
1608 + */
1609 +
1610 +#ifdef HAVE_CONFIG_H
1611 +#include "config.h"
1612 +#endif
1613 +
1614 +#include <gst/gst-i18n-plugin.h>
1615 +
1616 +#include "gsttagsink.h"
1617 +#include "gsttagreadutils.h"
1618 +#include <string.h>
1619 +
1620 +/* after this many buffers we mark a stream as done even if haven't gotten
1621 + * all the information we're looking for yet (caps, duration etc.) */
1622 +#define GST_TAG_SINK_MAX_BUFFERS  90
1623 +
1624 +GST_DEBUG_CATEGORY_EXTERN (tagreadbin_debug);
1625 +#define GST_CAT_DEFAULT tagreadbin_debug
1626 +
1627 +static GstStaticPadTemplate sinkn_template =
1628 +GST_STATIC_PAD_TEMPLATE ("sink%d", GST_PAD_SINK,
1629 +    GST_PAD_REQUEST, GST_STATIC_CAPS_ANY);
1630 +static GstStaticPadTemplate sink_template =
1631 +GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK,
1632 +    GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
1633 +
1634 +static void gst_tag_sink_dispose (GObject * object);
1635 +static void gst_tag_sink_finalize (GObject * object);
1636 +static gboolean gst_tag_sink_start (GstBaseSink * basesink);
1637 +static gboolean gst_tag_sink_stop (GstBaseSink * basesink);
1638 +static gboolean gst_tag_sink_unlock (GstBaseSink * basesink);
1639 +static GstPad *gst_tag_sink_request_pad (GstElement * element,
1640 +    GstPadTemplate * templ, const gchar * unused);
1641 +static void gst_tag_sink_release_pad (GstElement * element, GstPad * pad);
1642 +static gboolean gst_tag_sink_sink_event (GstPad * pad, GstEvent * event);
1643 +static gboolean gst_tag_sink_setcaps (GstPad * pad, GstCaps * caps);
1644 +
1645 +GST_BOILERPLATE (GstTagSink, gst_tag_sink, GstBaseSink, GST_TYPE_BASE_SINK);
1646 +
1647 +static void
1648 +gst_tag_sink_base_init (gpointer g_klass)
1649 +{
1650 +  /* nothing to do */
1651 +}
1652 +
1653 +static void
1654 +gst_tag_sink_class_init (GstTagSinkClass * klass)
1655 +{
1656 +  GstBaseSinkClass *basesink_klass = (GstBaseSinkClass *) klass;
1657 +  GstElementClass *element_klass = (GstElementClass *) klass;
1658 +  GObjectClass *gobject_klass = (GObjectClass *) klass;
1659 +
1660 +  /* dummy template for the normal base sink "sink" pad, which is never
1661 +   * actually linked, we only use it to force basesink to preroll for us
1662 +   * when we're ready */
1663 +  gst_element_class_add_pad_template (element_klass,
1664 +      gst_static_pad_template_get (&sink_template));
1665 +
1666 +  /* template for the request pads, which are used */
1667 +  gst_element_class_add_pad_template (element_klass,
1668 +      gst_static_pad_template_get (&sinkn_template));
1669 +
1670 +  gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_tag_sink_dispose);
1671 +  gobject_klass->finalize = GST_DEBUG_FUNCPTR (gst_tag_sink_finalize);
1672 +
1673 +  element_klass->release_pad = GST_DEBUG_FUNCPTR (gst_tag_sink_release_pad);
1674 +  element_klass->request_new_pad = GST_DEBUG_FUNCPTR (gst_tag_sink_request_pad);
1675 +
1676 +  basesink_klass->start = GST_DEBUG_FUNCPTR (gst_tag_sink_start);
1677 +  basesink_klass->stop = GST_DEBUG_FUNCPTR (gst_tag_sink_stop);
1678 +  basesink_klass->unlock = GST_DEBUG_FUNCPTR (gst_tag_sink_unlock);
1679 +}
1680 +
1681 +static void
1682 +gst_tag_sink_init (GstTagSink * tagsink, GstTagSinkClass * klass)
1683 +{
1684 +  g_static_rec_mutex_init (&tagsink->streams_lock);
1685 +  tagsink->streams = g_array_new (FALSE, FALSE, sizeof (GstTagSinkStream));
1686 +
1687 +  gst_base_sink_set_async_enabled (GST_BASE_SINK (tagsink), TRUE);
1688 +  gst_base_sink_set_sync (GST_BASE_SINK (tagsink), FALSE);
1689 +}
1690 +
1691 +static void
1692 +gst_tag_sink_dispose (GObject * object)
1693 +{
1694 +  GstTagSink *tagsink = GST_TAG_SINK (object);
1695 +
1696 +  if (tagsink->streams != NULL) {
1697 +    if (tagsink->streams->len > 0) {
1698 +      g_warning ("GstTagSink: %u unreleased pads in ::dispose()",
1699 +          tagsink->streams->len);
1700 +    }
1701 +    /* just leak the contents of any unreleased streams (shouldn't happen) */
1702 +    g_array_free (tagsink->streams, TRUE);
1703 +    tagsink->streams = NULL;
1704 +  }
1705 +
1706 +  G_OBJECT_CLASS (parent_class)->dispose (object);
1707 +}
1708 +
1709 +static void
1710 +gst_tag_sink_finalize (GObject * object)
1711 +{
1712 +  GstTagSink *tagsink = GST_TAG_SINK (object);
1713 +
1714 +  g_static_rec_mutex_free (&tagsink->streams_lock);
1715 +
1716 +  if (tagsink->global_tags) {
1717 +    gst_tag_list_free (tagsink->global_tags);
1718 +    tagsink->global_tags = NULL;
1719 +  }
1720 +  G_OBJECT_CLASS (parent_class)->finalize (object);
1721 +}
1722 +
1723 +/* must be called with STREAMS_LOCK held */
1724 +static gboolean
1725 +gst_tag_sink_stream_query_duration (GstTagSinkStream * stream)
1726 +{
1727 +  GstFormat fmt = GST_FORMAT_TIME;
1728 +  gboolean res;
1729 +  gint64 dur = -1;
1730 +
1731 +  res = gst_pad_query_peer_duration (stream->pad, &fmt, &dur) && (dur > 0);
1732 +
1733 +  /* this likely indicates a bug in some upstream element */
1734 +  if (!res && GST_CLOCK_TIME_IS_VALID (stream->duration))
1735 +    GST_ERROR_OBJECT (stream->pad, "previous duration good, now we got none");
1736 +  else
1737 +    stream->duration = dur;
1738 +
1739 +  GST_LOG_OBJECT (stream->pad, "duration: %" GST_TIME_FORMAT,
1740 +      GST_TIME_ARGS (dur));
1741 +
1742 +  return res;
1743 +}
1744 +
1745 +static gboolean
1746 +gst_tag_sink_stream_is_done (GstTagSinkStream * stream)
1747 +{
1748 +  /* we're definitely done if we've seen an EOS */
1749 +  if (stream->got_eos) {
1750 +    /* warn if we didn't get anything before the EOS */
1751 +    if (stream->last_buffer == NULL)
1752 +      GST_WARNING_OBJECT (stream->pad, "EOS without any buffer");
1753 +    if (!gst_tag_sink_stream_query_duration (stream))
1754 +      GST_WARNING_OBJECT (stream->pad, "EOS and duration query failed");
1755 +    return TRUE;
1756 +  }
1757 +
1758 +  if (stream->last_buffer == NULL) {
1759 +    GST_LOG_OBJECT (stream->pad, "no buffer yet");
1760 +    return FALSE;
1761 +  }
1762 +
1763 +  /* bail out after this many buffers */
1764 +  if (stream->num_buffers >= GST_TAG_SINK_MAX_BUFFERS) {
1765 +    GST_WARNING_OBJECT (stream->pad, "Marking stream as done after %u buffers,"
1766 +        " even if we don't have caps and/or duration yet", stream->num_buffers);
1767 +    return TRUE;
1768 +  }
1769 +
1770 +  if (stream->caps == NULL) {
1771 +    GST_LOG_OBJECT (stream->pad, "got buffer, but no caps!");
1772 +    return FALSE;
1773 +  }
1774 +
1775 +  if (!gst_tag_sink_stream_query_duration (stream)) {
1776 +    GST_LOG_OBJECT (stream->pad, "no duration yet");
1777 +    return FALSE;
1778 +  }
1779 +
1780 +  return TRUE;
1781 +}
1782 +
1783 +/* must be called with STREAMS_LOCK held */
1784 +static gboolean
1785 +gst_tag_sink_check_preroll (GstTagSink * tagsink, GstTagSinkStream * stream)
1786 +{
1787 +  /* have we prerolled already? */
1788 +  if (!tagsink->async_pending)
1789 +    return TRUE;
1790 +
1791 +  /* is this stream done? */
1792 +  if (!gst_tag_sink_stream_is_done (stream)) {
1793 +    GST_LOG_OBJECT (stream->pad, "stream not done yet");
1794 +    return FALSE;
1795 +  }
1796 +
1797 +  if (!stream->done) {
1798 +    stream->done = TRUE;
1799 +    --tagsink->num_streams_pending;
1800 +    GST_INFO_OBJECT (stream->pad, "stream is done now, %u more pending",
1801 +        tagsink->num_streams_pending);
1802 +  }
1803 +
1804 +  if (tagsink->num_streams_pending > 0) {
1805 +    GST_LOG_OBJECT (tagsink, "still %u streams pending",
1806 +        tagsink->num_streams_pending);
1807 +    return FALSE;
1808 +  }
1809 +
1810 +  GST_INFO_OBJECT (tagsink, "info on all sink pads complete, preroll");
1811 +
1812 +  tagsink->async_pending = FALSE;
1813 +
1814 +  /* just send an EOS event on basesink's sink pad to make us preroll. We
1815 +   * can't use gst_base_sink_do_preroll() because it won't set the internal
1816 +   * EOS flag right, which will make it do a _wait_for_preroll() when going
1817 +   * back from PLAYING to PAUSED.
1818 +   * If you think this is ugly, try deriving from GstElement and implement
1819 +   * async state change behaviour in a correct and race-free way. */
1820 +  GST_LOG_OBJECT (tagsink, "pushing EOS event on sinkpad to make us preroll");
1821 +  gst_pad_send_event (GST_BASE_SINK_PAD (tagsink),
1822 +      gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, 0, -1, 0));
1823 +  gst_pad_send_event (GST_BASE_SINK_PAD (tagsink), gst_event_new_eos ());
1824 +  GST_LOG_OBJECT (tagsink, "pushed EOS");
1825 +  return TRUE;
1826 +}
1827 +
1828 +/* must be called with the STREAMS_LOCK held */
1829 +static GstTagSinkStream *
1830 +gst_tag_sink_get_stream_from_pad (GstTagSink * tagsink, GstPad * pad)
1831 +{
1832 +  gint i;
1833 +
1834 +  for (i = 0; i < tagsink->streams->len; ++i) {
1835 +    GstTagSinkStream *s;
1836 +
1837 +    s = &g_array_index (tagsink->streams, GstTagSinkStream, i);
1838 +    if (s->pad == pad)
1839 +      return s;
1840 +  }
1841 +
1842 +  return NULL;
1843 +}
1844 +
1845 +static gboolean
1846 +gst_tag_sink_setcaps (GstPad * pad, GstCaps * caps)
1847 +{
1848 +  GstTagSinkStream *s;
1849 +  GstTagSink *tagsink;
1850 +
1851 +  tagsink = GST_TAG_SINK (GST_PAD_PARENT (pad));
1852 +
1853 +  GST_INFO_OBJECT (pad, "caps: %" GST_PTR_FORMAT, caps);
1854 +
1855 +  GST_TAG_SINK_STREAMS_LOCK (tagsink);
1856 +  s = gst_tag_sink_get_stream_from_pad (tagsink, pad);
1857 +  gst_caps_replace (&s->caps, caps);
1858 +  GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
1859 +
1860 +  return TRUE;
1861 +}
1862 +
1863 +static gboolean
1864 +gst_tag_sink_sink_event (GstPad * pad, GstEvent * event)
1865 +{
1866 +  GstTagSink *tagsink;
1867 +
1868 +  tagsink = GST_TAG_SINK (GST_PAD_PARENT (pad));
1869 +
1870 +  GST_LOG_OBJECT (pad, "%s event: %" GST_PTR_FORMAT,
1871 +      GST_EVENT_TYPE_NAME (event), event->structure);
1872 +
1873 +  GST_TAG_SINK_SHUTDOWN_LOCK (tagsink, shutdown);
1874 +
1875 +  switch (GST_EVENT_TYPE (event)) {
1876 +    case GST_EVENT_EOS:
1877 +    {
1878 +      GstTagSinkStream *s;
1879 +
1880 +      s = gst_tag_sink_get_stream_from_pad (tagsink, pad);
1881 +      if (!s->got_eos) {
1882 +        s->got_eos = TRUE;
1883 +        gst_tag_sink_check_preroll (tagsink, s);
1884 +      }
1885 +      break;
1886 +    }
1887 +    case GST_EVENT_TAG:
1888 +    {
1889 +      GstTagSinkStream *s;
1890 +      GstTagList *taglist = NULL;
1891 +
1892 +      gst_event_parse_tag (event, &taglist);
1893 +
1894 +      s = gst_tag_sink_get_stream_from_pad (tagsink, pad);
1895 +      taglist = gst_tag_list_copy (taglist);
1896 +      gst_tag_read_utils_list_remove_private_tags (taglist);
1897 +      s->tags = g_list_append (s->tags, taglist);
1898 +      break;
1899 +    }
1900 +    default:
1901 +      break;
1902 +  }
1903 +
1904 +  GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
1905 +  return gst_pad_event_default (pad, event);
1906 +
1907 +shutdown:
1908 +  {
1909 +    GST_DEBUG_OBJECT (tagsink, "shutting down");
1910 +    return FALSE;
1911 +  }
1912 +}
1913 +
1914 +static GstFlowReturn
1915 +gst_tag_sink_chain (GstPad * pad, GstBuffer * buf)
1916 +{
1917 +  GstTagSinkStream *s;
1918 +  GstFlowReturn flow;
1919 +  GstTagSink *tagsink;
1920 +
1921 +  tagsink = GST_TAG_SINK (GST_PAD_PARENT (pad));
1922 +
1923 +  GST_TAG_SINK_SHUTDOWN_LOCK (tagsink, shutdown);
1924 +
1925 +  s = gst_tag_sink_get_stream_from_pad (tagsink, pad);
1926 +
1927 +  /* if we're done already, just skip straight to the preroll check */
1928 +  if (s->done)
1929 +    goto check_preroll;
1930 +
1931 +  if (s->caps == NULL)
1932 +    GST_ERROR_OBJECT (pad, "received buffer without caps");
1933 +
1934 +  ++s->num_buffers;
1935 +  gst_buffer_replace (&s->last_buffer, buf);
1936 +  GST_LOG_OBJECT (pad, "buffer %d", s->num_buffers);
1937 +
1938 +check_preroll:
1939 +
1940 +  if (gst_tag_sink_check_preroll (tagsink, s)) {
1941 +    gst_pad_push_event (pad, gst_event_new_flush_start ());
1942 +    flow = GST_FLOW_WRONG_STATE;
1943 +  } else {
1944 +    flow = GST_FLOW_OK;
1945 +  }
1946 +
1947 +  GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
1948 +
1949 +done:
1950 +
1951 +  gst_buffer_unref (buf);
1952 +  return flow;
1953 +
1954 +shutdown:
1955 +  {
1956 +    GST_DEBUG_OBJECT (tagsink, "shutting down");
1957 +    flow = GST_FLOW_WRONG_STATE;
1958 +    goto done;
1959 +  }
1960 +}
1961 +
1962 +static GstPad *
1963 +gst_tag_sink_request_pad (GstElement * element, GstPadTemplate * templ,
1964 +    const gchar * unused)
1965 +{
1966 +  GstTagSinkStream stream = { NULL, };
1967 +  GstTagSink *tagsink;
1968 +  GstPad *pad;
1969 +  gchar *name;
1970 +  gint padcount;
1971 +
1972 +  if (templ->direction != GST_PAD_SINK)
1973 +    goto not_sink;
1974 +
1975 +  tagsink = GST_TAG_SINK (element);
1976 +
1977 +  /* increment pad counter */
1978 +  padcount = g_atomic_int_exchange_and_add (&tagsink->padcount, 1);
1979 +
1980 +  name = g_strdup_printf ("sink%d", padcount);
1981 +  pad = gst_pad_new_from_template (templ, name);
1982 +  GST_DEBUG_OBJECT (tagsink, "request new pad %s, got pad %p", name, pad);
1983 +  g_free (name);
1984 +
1985 +  gst_pad_set_setcaps_function (pad, GST_DEBUG_FUNCPTR (gst_tag_sink_setcaps));
1986 +  gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_tag_sink_sink_event));
1987 +  gst_pad_set_chain_function (pad, GST_DEBUG_FUNCPTR (gst_tag_sink_chain));
1988 +
1989 +  gst_pad_set_active (pad, TRUE);
1990 +
1991 +  stream.pad = gst_object_ref (pad);
1992 +  stream.duration = GST_CLOCK_TIME_NONE;
1993 +  stream.done = FALSE;
1994 +
1995 +  /* takes ownership of the pad */
1996 +  if (!gst_element_add_pad (element, pad))
1997 +    goto could_not_add;
1998 +
1999 +  GST_TAG_SINK_STREAMS_LOCK (tagsink);
2000 +  ++tagsink->num_streams_pending;
2001 +  g_array_append_vals (tagsink->streams, &stream, 1);
2002 +  GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
2003 +
2004 +  GST_INFO_OBJECT (stream.pad, "added pad");
2005 +  return stream.pad;
2006 +
2007 +  /* ERRORS */
2008 +not_sink:
2009 +  {
2010 +    g_warning ("GstTagSink: requested new pad that is not a SINK pad!");
2011 +    return NULL;
2012 +  }
2013 +could_not_add:
2014 +  {
2015 +    GST_DEBUG_OBJECT (element, "could not add pad %s", GST_PAD_NAME (pad));
2016 +    gst_object_unref (pad);
2017 +    return NULL;
2018 +  }
2019 +}
2020 +
2021 +static void
2022 +gst_tag_sink_release_pad (GstElement * element, GstPad * pad)
2023 +{
2024 +  GstTagSinkStream stream = { NULL, };
2025 +  GstTagSink *tagsink;
2026 +  gint i;
2027 +
2028 +  tagsink = GST_TAG_SINK (element);
2029 +  GST_DEBUG_OBJECT (tagsink, "release pad %s", GST_PAD_NAME (pad));
2030 +
2031 +  GST_TAG_SINK_STREAMS_LOCK (tagsink);
2032 +  for (i = 0; i < tagsink->streams->len; ++i) {
2033 +    GstTagSinkStream *s;
2034 +
2035 +    s = &g_array_index (tagsink->streams, GstTagSinkStream, i);
2036 +    if (s->pad == pad) {
2037 +      stream = *s;
2038 +      g_array_remove_index (tagsink->streams, i);
2039 +      break;
2040 +    }
2041 +  }
2042 +
2043 +  if (stream.pad != NULL && !stream.done)
2044 +    --tagsink->num_streams_pending;
2045 +
2046 +  GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
2047 +
2048 +  if (stream.pad == NULL) {
2049 +    GST_ERROR_OBJECT (tagsink, "pad %s:%s to release not found in pad list!",
2050 +        GST_DEBUG_PAD_NAME (pad));
2051 +  } else {
2052 +    GST_INFO_OBJECT (stream.pad, "releasing pad");
2053 +    gst_caps_replace (&stream.caps, NULL);
2054 +    g_list_foreach (stream.tags, (GFunc) gst_tag_list_free, NULL);
2055 +    g_list_free (stream.tags);
2056 +    stream.tags = NULL;
2057 +    if (stream.stream_tags) {
2058 +      gst_tag_list_free (stream.stream_tags);
2059 +      stream.stream_tags = NULL;
2060 +    }
2061 +    gst_pad_set_active (stream.pad, FALSE);
2062 +    gst_element_remove_pad (element, stream.pad);
2063 +    gst_object_unref (stream.pad);
2064 +    stream.pad = NULL;
2065 +    gst_buffer_replace (&stream.last_buffer, NULL);
2066 +    GST_INFO_OBJECT (tagsink, "released pad");
2067 +  }
2068 +}
2069 +
2070 +static gboolean
2071 +gst_tag_sink_start (GstBaseSink * basesink)
2072 +{
2073 +  GstTagSink *tagsink = GST_TAG_SINK (basesink);
2074 +
2075 +  GST_LOG ("here we go");
2076 +  tagsink->async_pending = TRUE;
2077 +  tagsink->num_streams_pending = 0;
2078 +  return TRUE;
2079 +}
2080 +
2081 +static gboolean
2082 +gst_tag_sink_unlock (GstBaseSink * basesink)
2083 +{
2084 +  GstTagSink *tagsink = GST_TAG_SINK (basesink);
2085 +
2086 +  GST_LOG_OBJECT (tagsink, "setting shutdown flag");
2087 +  g_atomic_int_set (&tagsink->shutdown, 1);
2088 +  return TRUE;
2089 +}
2090 +
2091 +static gboolean
2092 +gst_tag_sink_stop (GstBaseSink * basesink)
2093 +{
2094 +  GstTagSink *tagsink = GST_TAG_SINK (basesink);
2095 +
2096 +  g_atomic_int_set (&tagsink->shutdown, 1);
2097 +  /* wait for all pad callbacks to end by taking the lock.
2098 +   * No dynamic (critical) new callbacks will
2099 +   * be able to happen as we set the shutdown flag. */
2100 +  GST_LOG_OBJECT (tagsink, "trying to acquire streams lock");
2101 +  GST_TAG_SINK_STREAMS_LOCK (tagsink);
2102 +  GST_LOG_OBJECT (tagsink, "streams lock taken, we can continue shutdown");
2103 +  GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
2104 +  return TRUE;
2105 +}
2106 +
2107 +/* methods used by tagreadbin */
2108 +
2109 +/* FIXME: make API in core to test structures/taglists for equality public */
2110 +static gboolean
2111 +tag_lists_are_equal (GstTagList * tags1, GstTagList * tags2)
2112 +{
2113 +  GstCaps *caps1, *caps2;
2114 +  gboolean res;
2115 +
2116 +  /* not very efficient, but simple .. */
2117 +  caps1 = gst_caps_new_full (gst_structure_copy (tags1), NULL);
2118 +  caps2 = gst_caps_new_full (gst_structure_copy (tags2), NULL);
2119 +  res = gst_caps_is_equal (caps1, caps2);
2120 +  gst_caps_unref (caps1);
2121 +  gst_caps_unref (caps2);
2122 +  return res;
2123 +}
2124 +
2125 +/* must be called with STREAMS_LOCK held */
2126 +static GList *
2127 +gst_tag_sink_stream_find_tags (GstTagSinkStream * stream, GstTagList * tags)
2128 +{
2129 +  GList *l;
2130 +
2131 +  for (l = stream->tags; l != NULL; l = l->next) {
2132 +    if (tag_lists_are_equal (l->data, tags))
2133 +      return l;
2134 +  }
2135 +
2136 +  return NULL;
2137 +}
2138 +
2139 +/* must be called with STREAMS_LOCK held */
2140 +static void
2141 +gst_tag_sink_remove_tags_from_all_streams (GstTagSink * tsink,
2142 +    GstTagList * global_tags)
2143 +{
2144 +  GstTagList *copy;
2145 +  guint i;
2146 +
2147 +  copy = gst_tag_list_copy (global_tags);
2148 +  for (i = 0; i < tsink->streams->len; ++i) {
2149 +    GstTagSinkStream *stream;
2150 +    GList *node;
2151 +
2152 +    stream = &g_array_index (tsink->streams, GstTagSinkStream, i);
2153 +    if ((node = gst_tag_sink_stream_find_tags (stream, copy))) {
2154 +      gst_tag_list_free (node->data);
2155 +      stream->tags = g_list_delete_link (stream->tags, node);
2156 +    }
2157 +  }
2158 +  gst_tag_list_free (copy);
2159 +}
2160 +
2161 +/* must be called with STREAMS_LOCK held */
2162 +static gboolean
2163 +gst_tag_sink_stream_has_tags (GstTagSinkStream * stream, GstTagList * tags)
2164 +{
2165 +  return (gst_tag_sink_stream_find_tags (stream, tags) != NULL);
2166 +}
2167 +
2168 +/* must be called with STREAMS_LOCK held */
2169 +static gboolean
2170 +gst_tag_sink_tags_are_global (GstTagSink * tsink, GstTagList * tags)
2171 +{
2172 +  gboolean res = TRUE;
2173 +  guint i;
2174 +
2175 +  /* Are the given tags common to all streams? */
2176 +  for (i = 0; i < tsink->streams->len; ++i) {
2177 +    GstTagSinkStream *stream;
2178 +
2179 +    stream = &g_array_index (tsink->streams, GstTagSinkStream, i);
2180 +    if (!gst_tag_sink_stream_has_tags (stream, tags)) {
2181 +      res = FALSE;
2182 +      break;
2183 +    }
2184 +  }
2185 +
2186 +  GST_LOG_OBJECT (tsink, "tags %" GST_PTR_FORMAT " are %s", tags,
2187 +      (res) ? "global" : "not global");
2188 +
2189 +  return res;
2190 +}
2191 +
2192 +/* Try to determine 'global tags' common to all streams. Must be called with
2193 + * STREAMS_LOCK held. */
2194 +static void
2195 +gst_tag_sink_find_global_tags (GstTagSink * tsink)
2196 +{
2197 +  GList *l, *global_lists = NULL;
2198 +  GstClockTime max_dur = 0;
2199 +  guint i;
2200 +
2201 +  if (tsink->global_tags != NULL)
2202 +    return;
2203 +
2204 +start:
2205 +
2206 +  /* Find all tags that are common to all streams */
2207 +  for (i = 0; i < tsink->streams->len; ++i) {
2208 +    GstTagSinkStream *stream;
2209 +
2210 +    stream = &g_array_index (tsink->streams, GstTagSinkStream, i);
2211 +    for (l = stream->tags; l != NULL; l = l->next) {
2212 +      GstTagList *tags = l->data;
2213 +
2214 +      /* only tags received before a taglist with a *_CODEC tag are global */
2215 +      if (gst_tag_read_utils_list_has_codec_tag (tags))
2216 +        break;
2217 +
2218 +      if (gst_tag_sink_tags_are_global (tsink, tags)) {
2219 +        GST_DEBUG_OBJECT (tsink, "global tags: %" GST_PTR_FORMAT, tags);
2220 +        global_lists = g_list_prepend (global_lists, gst_tag_list_copy (tags));
2221 +        gst_tag_sink_remove_tags_from_all_streams (tsink, tags);
2222 +        goto start;
2223 +      }
2224 +    }
2225 +  }
2226 +
2227 +  global_lists = g_list_reverse (global_lists);
2228 +
2229 +  if (global_lists == NULL)
2230 +    goto no_global_tags;
2231 +
2232 +  /* merge all global tags into one tag list */
2233 +  tsink->global_tags = gst_tag_list_new ();
2234 +  while ((l = global_lists)) {
2235 +    gst_tag_list_insert (tsink->global_tags, l->data, GST_TAG_MERGE_APPEND);
2236 +    gst_tag_list_free (l->data);
2237 +    global_lists = g_list_delete_link (global_lists, l);
2238 +  }
2239 +
2240 +done:
2241 +
2242 +  /* Find biggest duration of all streams */
2243 +  max_dur = 0;
2244 +  for (i = 0; i < tsink->streams->len; ++i) {
2245 +    GstTagSinkStream *stream;
2246 +
2247 +    stream = &g_array_index (tsink->streams, GstTagSinkStream, i);
2248 +    if (GST_CLOCK_TIME_IS_VALID (stream->duration))
2249 +      max_dur = MAX (stream->duration, max_dur);
2250 +  }
2251 +
2252 +  gst_tag_read_utils_list_add_duration (tsink->global_tags, max_dur);
2253 +
2254 +  GST_INFO_OBJECT (tsink, "global tags: %" GST_PTR_FORMAT, tsink->global_tags);
2255 +  return;
2256 +
2257 +no_global_tags:
2258 +  {
2259 +    GST_LOG_OBJECT (tsink, "couldn't find any global tags");
2260 +    tsink->global_tags = gst_tag_list_new ();
2261 +    goto done;
2262 +  }
2263 +
2264 +}
2265 +
2266 +GstTagList *
2267 +gst_tag_sink_get_global_tags (GstTagSink * tsink)
2268 +{
2269 +  GstTagList *ret = NULL;
2270 +
2271 +  GST_TAG_SINK_STREAMS_LOCK (tsink);
2272 +
2273 +  gst_tag_sink_find_global_tags (tsink);
2274 +
2275 +  if (tsink->global_tags != NULL) {
2276 +    ret = gst_tag_list_copy (tsink->global_tags);
2277 +  } else {
2278 +    ret = gst_tag_list_new ();
2279 +  }
2280 +
2281 +  GST_TAG_SINK_STREAMS_UNLOCK (tsink);
2282 +
2283 +  return ret;
2284 +}
2285 +
2286 +/* Postprocess stream tags. Must be called with the STREAMS_LOCK held */
2287 +static void
2288 +gst_tag_sink_stream_analyse_tags (GstTagSink * tsink, GstTagSinkStream * stream)
2289 +{
2290 +  GList *l;
2291 +
2292 +  /* have we done all of this before? */
2293 +  if (stream->stream_tags != NULL)
2294 +    return;
2295 +
2296 +  /* remove global tags first if we haven't done that yet */
2297 +  gst_tag_sink_find_global_tags (tsink);
2298 +
2299 +  /* merge all remaining tags into one tag list */
2300 +  stream->stream_tags = gst_tag_list_new ();
2301 +  while ((l = stream->tags)) {
2302 +    gst_tag_list_insert (stream->stream_tags, l->data, GST_TAG_MERGE_APPEND);
2303 +    gst_tag_list_free (l->data);
2304 +    stream->tags = g_list_delete_link (stream->tags, l);
2305 +  }
2306 +
2307 +  /* if we don't have a codec tag for this stream, add one based on the caps */
2308 +  if (!gst_tag_read_utils_list_has_codec_tag (stream->stream_tags)) {
2309 +    gst_tag_read_utils_add_codec_tag_from_caps (stream->stream_tags,
2310 +        stream->caps);
2311 +  }
2312 +
2313 +  /* merge interesting stuff from tags into the tags */
2314 +  gst_tag_read_utils_list_add_caps_info (stream->stream_tags, stream->caps);
2315 +
2316 +  /* merge duration into the tags */
2317 +  gst_tag_read_utils_list_add_duration (stream->stream_tags, stream->duration);
2318 +
2319 +  GST_INFO_OBJECT (tsink, "stream tags: %" GST_PTR_FORMAT, stream->stream_tags);
2320 +}
2321 +
2322 +/* will return an empty tag list for 'no tags' and NULL if no such stream
2323 + * (although there should always be at least a codec tag based on the caps) */
2324 +GstTagList *
2325 +gst_tag_sink_get_stream_tags (GstTagSink * tsink, guint num)
2326 +{
2327 +  GstTagSinkStream *stream;
2328 +  GstTagList *ret;
2329 +
2330 +  GST_TAG_SINK_STREAMS_LOCK (tsink);
2331 +
2332 +  if (num >= tsink->streams->len) {
2333 +    ret = NULL;
2334 +    goto out;
2335 +  }
2336 +
2337 +  stream = &g_array_index (tsink->streams, GstTagSinkStream, num);
2338 +
2339 +  if (stream->stream_tags != NULL) {
2340 +    ret = gst_tag_list_copy (stream->stream_tags);
2341 +    goto out;
2342 +  }
2343 +
2344 +  gst_tag_sink_stream_analyse_tags (tsink, stream);
2345 +
2346 +  if (stream->stream_tags != NULL) {
2347 +    ret = gst_tag_list_copy (stream->stream_tags);
2348 +  } else {
2349 +    ret = gst_tag_list_new ();
2350 +    GST_LOG_OBJECT (tsink, "no stream tags for stream %u", num);
2351 +  }
2352 +
2353 +out:
2354 +
2355 +  GST_TAG_SINK_STREAMS_UNLOCK (tsink);
2356 +
2357 +  return ret;
2358 +}
2359 diff --git a/gst/tagreading/gsttagsink.h b/gst/tagreading/gsttagsink.h
2360 new file mode 100644
2361 index 0000000..adf5ff3
2362 --- /dev/null
2363 +++ b/gst/tagreading/gsttagsink.h
2364 @@ -0,0 +1,103 @@
2365 +/* GStreamer tag read bin sink
2366 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
2367 + *
2368 + * This library is free software; you can redistribute it and/or
2369 + * modify it under the terms of the GNU Library General Public
2370 + * License as published by the Free Software Foundation; either
2371 + * version 2 of the License, or (at your option) any later version.
2372 + *
2373 + * This library is distributed in the hope that it will be useful,
2374 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2375 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
2376 + * Library General Public License for more details.
2377 + *
2378 + * You should have received a copy of the GNU Library General Public
2379 + * License along with this library; if not, write to the
2380 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
2381 + * Boston, MA 02111-1307, USA.
2382 + */
2383 +
2384 +#ifndef __GST_TAG_SINK_H__
2385 +#define __GST_TAG_SINK_H__
2386 +
2387 +#include <gst/gst.h>
2388 +#include <gst/base/gstbasesink.h>
2389 +
2390 +G_BEGIN_DECLS
2391 +
2392 +#define GST_TYPE_TAG_SINK               (gst_tag_sink_get_type())
2393 +#define GST_TAG_SINK(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAG_SINK,GstTagSink))
2394 +#define GST_TAG_SINK_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAG_SINK,GstTagSinkClass))
2395 +#define GST_IS_TAG_SINK(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TAG_SINK))
2396 +#define GST_IS_TAG_SINK_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAG_SINK))
2397 +
2398 +#define GST_TAG_SINK_STREAMS_LOCK(tsink)    g_static_rec_mutex_lock (&tsink->streams_lock)
2399 +#define GST_TAG_SINK_STREAMS_UNLOCK(tsink)  g_static_rec_mutex_unlock (&tsink->streams_lock)
2400 +
2401 +/* lock for shutdown */
2402 +#define GST_TAG_SINK_SHUTDOWN_LOCK(tsink,label)           \
2403 +G_STMT_START {                                            \
2404 +  if (G_UNLIKELY (g_atomic_int_get (&tsink->shutdown)))   \
2405 +    goto label;                                           \
2406 +  GST_TAG_SINK_STREAMS_LOCK (tsink);                      \
2407 +  if (G_UNLIKELY (g_atomic_int_get (&tsink->shutdown))) { \
2408 +    GST_TAG_SINK_STREAMS_UNLOCK (tsink);                  \
2409 +    goto label;                                           \
2410 +  }                                                       \
2411 +} G_STMT_END
2412 +
2413 +/* unlock for shutdown */
2414 +#define GST_TAG_SINK_SHUTDOWN_UNLOCK(tsink)              \
2415 +  GST_TAG_SINK_STREAMS_UNLOCK (tsink);                   \
2416 +
2417 +typedef struct _GstTagSink GstTagSink;
2418 +typedef struct _GstTagSinkClass GstTagSinkClass;
2419 +
2420 +typedef struct {
2421 +  GstPad        *pad;
2422 +  GstCaps       *caps;
2423 +  GList         *tags;          /* tags received through events, sorted     */
2424 +  GstBuffer     *last_buffer;
2425 +  guint          num_buffers;
2426 +  gboolean       got_eos;
2427 +  GstTagList    *stream_tags;   /* merged (non-global) tags                 */
2428 +  GstClockTime   duration;      /* duration, or GST_CLOCK_TIME_NONE         */
2429 +  gboolean       done;          /* got all the info we need                 */
2430 +} GstTagSinkStream;
2431 +
2432 +struct _GstTagSink
2433 +{
2434 +  GstBaseSink       basesink;
2435 +
2436 +  /*< private >*/
2437 +  gint              padcount;                                 /* ATOMIC */
2438 +
2439 +  /* if we are shutting down or not */
2440 +  gint              shutdown;                                 /* ATOMIC */
2441 +
2442 +  gboolean          async_pending;                            /* STREAMS_LOCK */
2443 +
2444 +  /* number of streams for which we don't have all the info we want yet */
2445 +  guint             num_streams_pending;                      /* STREAMS_LOCK */
2446 +
2447 +  GStaticRecMutex   streams_lock;
2448 +  GArray           *streams;                                  /* STREAMS_LOCK */
2449 +
2450 +  GstTagList       *global_tags;                              /* STREAMS_LOCK */
2451 +};
2452 +
2453 +struct _GstTagSinkClass
2454 +{
2455 +  GstBaseSinkClass basesink_class;
2456 +};
2457 +
2458 +GType         gst_tag_sink_get_type (void);
2459 +
2460 +GstTagList  * gst_tag_sink_get_global_tags (GstTagSink * sink);
2461 +
2462 +GstTagList  * gst_tag_sink_get_stream_tags (GstTagSink * sink, guint num);
2463 +
2464 +
2465 +G_END_DECLS
2466 +
2467 +#endif /* __GST_TAG_SINK_H__ */