Merge remote-tracking branch 'origin/master' into 0.11
authorTim-Philipp Müller <tim.muller@collabora.co.uk>
Fri, 2 Dec 2011 01:58:30 +0000 (01:58 +0000)
committerTim-Philipp Müller <tim.muller@collabora.co.uk>
Fri, 2 Dec 2011 01:58:30 +0000 (01:58 +0000)
Conflicts:
docs/plugins/inspect/plugin-esdsink.xml
docs/plugins/inspect/plugin-gconfelements.xml
ext/pulse/pulseaudiosink.c
gst/matroska/matroska-demux.c
gst/matroska/matroska-mux.c
gst/multifile/gstmultifilesink.c

1  2 
configure.ac
docs/plugins/Makefile.am
docs/plugins/gst-plugins-good-plugins-docs.sgml
docs/plugins/gst-plugins-good-plugins-sections.txt
gst/effectv/gstripple.c
gst/matroska/matroska-demux.c
gst/matroska/matroska-ids.h
gst/matroska/matroska-mux.c
gst/multifile/gstmultifilesink.c
gst/multifile/gstsplitfilesrc.c
tests/check/Makefile.am

diff --cc configure.ac
Simple merge
Simple merge
Simple merge
index 414c02fec328d17495c40237452c63289f5f1e7c,3d5a5694c965279c2f862c6645b61888cd76ecd6..be9456807d1574950a9bc4a86c20019e3172ca6e
@@@ -2933,31 -2931,37 +2933,36 @@@ gst_matroska_demux_check_subtitle_buffe
    if (utf8 == NULL)
      utf8 = g_strdup ("invalid subtitle");
  
 -  newbuf = gst_buffer_new ();
 -  GST_BUFFER_MALLOCDATA (newbuf) = (guint8 *) utf8;
 -  GST_BUFFER_DATA (newbuf) = (guint8 *) utf8;
 -  GST_BUFFER_SIZE (newbuf) = strlen (utf8);
 -  gst_buffer_copy_metadata (newbuf, *buf, GST_BUFFER_COPY_ALL);
 +  newbuf = gst_buffer_new_wrapped (utf8, strlen (utf8));
 +  gst_buffer_copy_into (newbuf, *buf,
-       GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_FLAGS, 0, -1);
++      GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_META,
++      0, -1);
 +  gst_buffer_unmap (*buf, data, size);
++
    gst_buffer_unref (*buf);
  
    *buf = newbuf;
 -  data = (const gchar *) GST_BUFFER_DATA (*buf);
 -  size = GST_BUFFER_SIZE (*buf);
 +  data = gst_buffer_map (*buf, &size, NULL, GST_MAP_READ);
  
  next:
-   /* caps claim markup text, so we need to escape text,
-    * except if text is already markup and then needs no further escaping */
-   sub_stream->seen_markup_tag = sub_stream->seen_markup_tag ||
-       gst_matroska_demux_subtitle_chunk_has_tag (element, data);
-   if (!sub_stream->seen_markup_tag) {
-     utf8 = g_markup_escape_text (data, size);
-     newbuf = gst_buffer_new_wrapped (utf8, strlen (utf8));
-     gst_buffer_copy_into (newbuf, *buf,
-         GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_FLAGS, 0, -1);
-     gst_buffer_unmap (*buf, data, size);
-     gst_buffer_unref (*buf);
--
-     *buf = newbuf;
+   if (sub_stream->check_markup) {
+     /* caps claim markup text, so we need to escape text,
+      * except if text is already markup and then needs no further escaping */
+     sub_stream->seen_markup_tag = sub_stream->seen_markup_tag ||
+         gst_matroska_demux_subtitle_chunk_has_tag (element, data);
+     if (!sub_stream->seen_markup_tag) {
+       utf8 = g_markup_escape_text (data, size);
 -      newbuf = gst_buffer_new ();
 -      GST_BUFFER_MALLOCDATA (newbuf) = (guint8 *) utf8;
 -      GST_BUFFER_DATA (newbuf) = (guint8 *) utf8;
 -      GST_BUFFER_SIZE (newbuf) = strlen (utf8);
 -      gst_buffer_copy_metadata (newbuf, *buf, GST_BUFFER_COPY_ALL);
++      newbuf = gst_buffer_new_wrapped (utf8, strlen (utf8));
++      gst_buffer_copy_into (newbuf, *buf,
++          GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_FLAGS |
++          GST_BUFFER_COPY_META, 0, -1);
++      gst_buffer_unmap (*buf, data, size);
+       gst_buffer_unref (*buf);
+       *buf = newbuf;
+     }
    }
  
    return GST_FLOW_OK;
@@@ -5446,24 -5395,28 +5451,28 @@@ gst_matroska_demux_subtitle_caps (GstMa
     * Check if we have to do something with codec_private */
    if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_UTF8)) {
      /* well, plain text simply does not have a lot of markup ... */
 -    caps = gst_caps_new_simple ("text/x-pango-markup", NULL);
 +    caps = gst_caps_new_empty_simple ("text/x-pango-markup");
      context->postprocess_frame = gst_matroska_demux_check_subtitle_buffer;
+     subtitlecontext->check_markup = TRUE;
    } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_SSA)) {
 -    caps = gst_caps_new_simple ("application/x-ssa", NULL);
 +    caps = gst_caps_new_empty_simple ("application/x-ssa");
      context->postprocess_frame = gst_matroska_demux_check_subtitle_buffer;
+     subtitlecontext->check_markup = FALSE;
    } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_ASS)) {
 -    caps = gst_caps_new_simple ("application/x-ass", NULL);
 +    caps = gst_caps_new_empty_simple ("application/x-ass");
      context->postprocess_frame = gst_matroska_demux_check_subtitle_buffer;
+     subtitlecontext->check_markup = FALSE;
    } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_USF)) {
 -    caps = gst_caps_new_simple ("application/x-usf", NULL);
 +    caps = gst_caps_new_empty_simple ("application/x-usf");
      context->postprocess_frame = gst_matroska_demux_check_subtitle_buffer;
+     subtitlecontext->check_markup = FALSE;
    } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_VOBSUB)) {
 -    caps = gst_caps_new_simple ("video/x-dvd-subpicture", NULL);
 +    caps = gst_caps_new_empty_simple ("video/x-dvd-subpicture");
      ((GstMatroskaTrackContext *) subtitlecontext)->send_dvd_event = TRUE;
    } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_HDMVPGS)) {
 -    caps = gst_caps_new_simple ("subpicture/x-pgs", NULL);
 +    caps = gst_caps_new_empty_simple ("subpicture/x-pgs");
    } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_KATE)) {
 -    caps = gst_caps_new_simple ("subtitle/x-kate", NULL);
 +    caps = gst_caps_new_empty_simple ("subtitle/x-kate");
      context->send_xiph_headers = TRUE;
    } else {
      GST_DEBUG ("Unknown subtitle stream: codec_id='%s'", codec_id);
Simple merge
index bc87b80a12d2dbe2963bf8d345b871b34dd18345,99bcc42aaa5f6b6ef52d721eb40a79412620656d..55fd051aaa544f587373aacf8187eeaca6972304
@@@ -484,9 -617,9 +581,9 @@@ gst_matroska_pad_reset (GstMatroskaPad 
   * Release resources of a matroska collect pad.
   */
  static void
--gst_matroska_pad_free (GstMatroskaPad * collect_pad)
++gst_matroska_pad_free (GstPad * collect_pad)
  {
--  gst_matroska_pad_reset (collect_pad, TRUE);
++  gst_matroska_pad_reset ((GstMatroskaPad *) collect_pad, TRUE);
  }
  
  
@@@ -1816,10 -1948,10 +1921,10 @@@ gst_matroska_mux_request_new_pad (GstEl
    GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
    GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
    GstMatroskaPad *collect_pad;
-   GstPad *newpad = NULL;
+   GstMatroskamuxPad *newpad;
    gchar *name = NULL;
    const gchar *pad_name = NULL;
 -  GstPadSetCapsFunction setcapsfunc = NULL;
 +  GstMatroskaCapsFunc capsfunc = NULL;
    GstMatroskaTrackContext *context = NULL;
    gint pad_id;
  
      return NULL;
    }
  
-   newpad = gst_pad_new_from_template (templ, pad_name);
+   newpad = g_object_new (GST_TYPE_MATROSKAMUX_PAD,
+       "name", pad_name, "direction", templ->direction, "template", templ, NULL);
    g_free (name);
+   gst_matroskamux_pad_init (newpad);
    collect_pad = (GstMatroskaPad *)
-       gst_collect_pads_add_pad (mux->collect, newpad,
 -      gst_collect_pads_add_pad_full (mux->collect, GST_PAD (newpad),
 -      sizeof (GstMatroskamuxPad),
++      gst_collect_pads_add_pad (mux->collect, GST_PAD (newpad),
 +      sizeof (GstMatroskaPad),
        (GstCollectDataDestroyNotify) gst_matroska_pad_free);
  
    collect_pad->track = context;
     * This would allow (clean) transcoding of info from demuxer/streams
     * to another muxer */
    mux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
-   gst_pad_set_event_function (newpad,
+   gst_pad_set_event_function (GST_PAD (newpad),
        GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event));
  
 -  gst_pad_set_setcaps_function (GST_PAD (newpad), setcapsfunc);
 +  collect_pad->capsfunc = capsfunc;
-   gst_pad_set_active (newpad, TRUE);
-   if (!gst_element_add_pad (element, newpad))
+   gst_pad_set_active (GST_PAD (newpad), TRUE);
+   if (!gst_element_add_pad (element, GST_PAD (newpad)))
      goto pad_add_failed;
  
    mux->num_streams++;
index d2d5603816df262c98008169b7c1556096bcd0ce,984e223961de4a23789cbaaba1553f13aeccc83e..1b6b7a06c7bd89bea7e08b6a3ec9d8ce1e36c8e3
@@@ -248,18 -288,11 +274,21 @@@ gst_multi_file_sink_class_init (GstMult
    gstbasesink_class->get_times = NULL;
    gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_multi_file_sink_stop);
    gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_multi_file_sink_render);
+   gstbasesink_class->render_list =
+       GST_DEBUG_FUNCPTR (gst_multi_file_sink_render_list);
    gstbasesink_class->set_caps =
        GST_DEBUG_FUNCPTR (gst_multi_file_sink_set_caps);
+   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_multi_file_sink_event);
 +
 +  GST_DEBUG_CATEGORY_INIT (gst_multi_file_sink_debug, "multifilesink", 0,
 +      "multifilesink element");
 +
 +  gst_element_class_add_pad_template (gstelement_class,
 +      gst_static_pad_template_get (&sinktemplate));
 +  gst_element_class_set_details_simple (gstelement_class, "Multi-File Sink",
 +      "Sink/File",
 +      "Write buffers to a sequentially named set of files",
 +      "David Schleef <ds@schleef.org>");
  }
  
  static void
@@@ -385,38 -455,56 +450,59 @@@ static voi
  gst_multi_file_sink_post_message (GstMultiFileSink * multifilesink,
      GstBuffer * buffer, const char *filename)
  {
-   if (multifilesink->post_messages) {
-     GstClockTime duration, timestamp;
-     GstClockTime running_time, stream_time;
-     guint64 offset, offset_end;
-     GstStructure *s;
-     GstSegment *segment;
-     GstFormat format;
-     segment = &GST_BASE_SINK (multifilesink)->segment;
-     format = segment->format;
-     timestamp = GST_BUFFER_TIMESTAMP (buffer);
-     duration = GST_BUFFER_DURATION (buffer);
-     offset = GST_BUFFER_OFFSET (buffer);
-     offset_end = GST_BUFFER_OFFSET_END (buffer);
-     running_time = gst_segment_to_running_time (segment, format, timestamp);
-     stream_time = gst_segment_to_stream_time (segment, format, timestamp);
-     s = gst_structure_new ("GstMultiFileSink",
-         "filename", G_TYPE_STRING, filename,
-         "index", G_TYPE_INT, multifilesink->index,
-         "timestamp", G_TYPE_UINT64, timestamp,
-         "stream-time", G_TYPE_UINT64, stream_time,
-         "running-time", G_TYPE_UINT64, running_time,
-         "duration", G_TYPE_UINT64, duration,
-         "offset", G_TYPE_UINT64, offset,
-         "offset-end", G_TYPE_UINT64, offset_end, NULL);
-     gst_element_post_message (GST_ELEMENT_CAST (multifilesink),
-         gst_message_new_element (GST_OBJECT_CAST (multifilesink), s));
+   GstClockTime duration, timestamp;
+   GstClockTime running_time, stream_time;
+   guint64 offset, offset_end;
+   GstSegment *segment;
+   GstFormat format;
+   if (!multifilesink->post_messages)
+     return;
+   segment = &GST_BASE_SINK (multifilesink)->segment;
+   format = segment->format;
+   timestamp = GST_BUFFER_TIMESTAMP (buffer);
+   duration = GST_BUFFER_DURATION (buffer);
+   offset = GST_BUFFER_OFFSET (buffer);
+   offset_end = GST_BUFFER_OFFSET_END (buffer);
+   running_time = gst_segment_to_running_time (segment, format, timestamp);
+   stream_time = gst_segment_to_stream_time (segment, format, timestamp);
+   gst_multi_file_sink_post_message_full (multifilesink, timestamp, duration,
+       offset, offset_end, running_time, stream_time, filename);
+ }
+ static gboolean
+ gst_multi_file_sink_write_stream_headers (GstMultiFileSink * sink)
+ {
+   int i;
+   if (sink->streamheaders == NULL)
+     return TRUE;
+   /* we want to write these at the beginning */
+   g_assert (sink->cur_file_size == 0);
+   for (i = 0; i < sink->n_streamheaders; i++) {
+     GstBuffer *hdr;
++    guint8 *sdata;
++    gsize ssize;
+     int ret;
+     hdr = sink->streamheaders[i];
 -
 -    ret = fwrite (GST_BUFFER_DATA (hdr), GST_BUFFER_SIZE (hdr), 1, sink->file);
++    sdata = gst_buffer_map (hdr, &ssize, NULL, GST_MAP_READ);
++    ret = fwrite (sdata, ssize, 1, sink->file);
++    gst_buffer_unmap (hdr, sdata, ssize);
+     if (ret != 1)
+       return FALSE;
 -    sink->cur_file_size += GST_BUFFER_SIZE (hdr);
++    sink->cur_file_size += ssize;
    }
+   return TRUE;
  }
  
  static GstFlowReturn
@@@ -484,31 -574,61 +570,62 @@@ gst_multi_file_sink_render (GstBaseSin
        }
  
        if (multifilesink->file == NULL) {
-         int i;
+         if (!gst_multi_file_sink_open_next_file (multifilesink))
+           goto stdio_write_error;
  
 -      ret = fwrite (GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer), 1,
 -          multifilesink->file);
+         gst_multi_file_sink_write_stream_headers (multifilesink);
+       }
++      ret = fwrite (data, size, 1, multifilesink->file);
+       if (ret != 1)
+         goto stdio_write_error;
+       break;
+     case GST_MULTI_FILE_SINK_NEXT_KEY_UNIT_EVENT:
+       if (multifilesink->file == NULL) {
          if (!gst_multi_file_sink_open_next_file (multifilesink))
            goto stdio_write_error;
+       }
  
-         if (multifilesink->streamheaders) {
-           for (i = 0; i < multifilesink->n_streamheaders; i++) {
-             guint8 *sdata;
-             gsize ssize;
-             sdata = gst_buffer_map (multifilesink->streamheaders[i], &ssize,
-                 NULL, GST_MAP_READ);
-             ret = fwrite (data, ssize, 1, multifilesink->file);
-             gst_buffer_unmap (multifilesink->streamheaders[i], sdata, ssize);
-             if (ret != 1)
-               goto stdio_write_error;
-           }
-         }
 -      ret = fwrite (GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer), 1,
 -          multifilesink->file);
++      if (!gst_multi_file_sink_write_stream_headers (multifilesink))
++        goto stdio_write_error;
++
++      ret = fwrite (data, size, 1, multifilesink->file);
++
+       if (ret != 1)
+         goto stdio_write_error;
+       break;
+     case GST_MULTI_FILE_SINK_NEXT_MAX_SIZE:{
+       guint64 new_size;
 -      new_size = multifilesink->cur_file_size + GST_BUFFER_SIZE (buffer);
++      new_size = multifilesink->cur_file_size + size;
+       if (new_size > multifilesink->max_file_size) {
+         GST_INFO_OBJECT (multifilesink, "current size: %" G_GUINT64_FORMAT
+             ", new_size: %" G_GUINT64_FORMAT ", max. size %" G_GUINT64_FORMAT,
+             multifilesink->cur_file_size, new_size,
+             multifilesink->max_file_size);
+         if (multifilesink->file != NULL)
+           gst_multi_file_sink_close_file (multifilesink, NULL);
+       }
+       if (multifilesink->file == NULL) {
+         if (!gst_multi_file_sink_open_next_file (multifilesink))
+           goto stdio_write_error;
+         gst_multi_file_sink_write_stream_headers (multifilesink);
        }
  
 -      ret = fwrite (GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer), 1,
 -          multifilesink->file);
 +      ret = fwrite (data, size, 1, multifilesink->file);
        if (ret != 1)
          goto stdio_write_error;
  
 -      multifilesink->cur_file_size += GST_BUFFER_SIZE (buffer);
++      multifilesink->cur_file_size += size;
        break;
+     }
      default:
        g_assert_not_reached ();
    }
@@@ -538,14 -656,64 +655,71 @@@ write_error
      return GST_FLOW_ERROR;
    }
  stdio_write_error:
 -  GST_ELEMENT_ERROR (multifilesink, RESOURCE, WRITE,
 -      ("Error while writing to file."), (NULL));
 -  return GST_FLOW_ERROR;
 +  {
 +    GST_ELEMENT_ERROR (multifilesink, RESOURCE, WRITE,
 +        ("Error while writing to file."), (NULL));
 +    gst_buffer_unmap (buffer, data, size);
 +    return GST_FLOW_ERROR;
 +  }
  }
  
 -static GstBufferListItem
 -buffer_list_calc_size (GstBuffer ** buf, guint group, guint idx, gpointer data)
++static gboolean
++buffer_list_calc_size (GstBuffer ** buf, guint idx, gpointer data)
+ {
+   guint *p_size = data;
+   guint buf_size;
 -  buf_size = GST_BUFFER_SIZE (*buf);
 -  GST_TRACE ("buffer %u in group %u has size %u", idx, group, buf_size);
++  buf_size = gst_buffer_get_size (*buf);
++  GST_TRACE ("buffer %u has size %" G_GSIZE_FORMAT, idx, buf_size);
+   *p_size += buf_size;
 -  return GST_BUFFER_LIST_CONTINUE;
++  return TRUE;
+ }
 -static GstBufferListItem
 -buffer_list_copy_data (GstBuffer ** buf, guint group, guint idx, gpointer data)
++static gboolean
++buffer_list_copy_data (GstBuffer ** buf, guint idx, gpointer data)
+ {
+   GstBuffer *dest = data;
++  guint num, i;
++
++  if (idx == 0)
++    gst_buffer_copy_into (dest, *buf, GST_BUFFER_COPY_METADATA, 0, -1);
 -  if (group == 0 && idx == 0)
 -    gst_buffer_copy_metadata (dest, *buf, GST_BUFFER_COPY_ALL);
++  num = gst_buffer_n_memory (*buf);
++  for (i = 0; i < num; ++i) {
++    GstMemory *mem;
 -  memcpy (GST_BUFFER_DATA (dest) + GST_BUFFER_SIZE (dest),
 -      GST_BUFFER_DATA (*buf), GST_BUFFER_SIZE (*buf));
 -  GST_BUFFER_SIZE (dest) += GST_BUFFER_SIZE (*buf);
++    mem = gst_buffer_peek_memory (*buf, i, GST_MAP_READ);
++    gst_buffer_take_memory (dest, -1, gst_memory_ref (mem));
++  }
 -  return GST_BUFFER_LIST_CONTINUE;
++  return TRUE;
+ }
+ /* Our assumption for now is that the buffers in a buffer list should always
+  * end up in the same file. If someone wants different behaviour, they'll just
+  * have to add a property for that. */
+ static GstFlowReturn
+ gst_multi_file_sink_render_list (GstBaseSink * sink, GstBufferList * list)
+ {
+   GstBuffer *buf;
+   guint size;
+   gst_buffer_list_foreach (list, buffer_list_calc_size, &size);
+   GST_LOG_OBJECT (sink, "total size of buffer list %p: %u", list, size);
+   /* copy all buffers in the list into one single buffer, so we can use
+    * the normal render function (FIXME: optimise to avoid the memcpy) */
 -  buf = gst_buffer_new_and_alloc (size);
 -  GST_BUFFER_SIZE (buf) = 0;
++  buf = gst_buffer_new ();
+   gst_buffer_list_foreach (list, buffer_list_copy_data, buf);
 -  g_assert (GST_BUFFER_SIZE (buf) == size);
++  g_assert (gst_buffer_get_size (buf) == size);
+   gst_multi_file_sink_render (sink, buf);
+   gst_buffer_unref (buf);
+   return GST_FLOW_OK;
+ }
  static gboolean
  gst_multi_file_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
  {
index 0000000000000000000000000000000000000000,31259976ea41b328a831fa01a8990d7b1f1b92e4..b06a1155edddd3e6279dfe900534c26e55ce61e3
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,616 +1,603 @@@
 -static gboolean gst_split_file_src_check_get_range (GstBaseSrc * basesrc);
+ /* GStreamer Split File Source
+  * Copyright (C) 2011 Collabora Ltd. <tim.muller@collabora.co.uk>
+  *
+  * This library is free software; you can redistribute it and/or
+  * modify it under the terms of the GNU Library General Public
+  * License as published by the Free Software Foundation; either
+  * version 2 of the License, or (at your option) any later version.
+  *
+  * This library is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * Library General Public License for more details.
+  *
+  * You should have received a copy of the GNU Library General Public
+  * License along with this library; if not, write to the
+  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+  * Boston, MA 02111-1307, USA.
+  */
+ /**
+  * SECTION:element-splitfilesrc
+  * @see_also: #GstFileSrc, #GstMultiFileSrc
+  *
+  * Reads data from multiple files, presenting those files as one continuous
+  * file to downstream elements. This is useful for reading a large file that
+  * had to be split into multiple parts due to filesystem file size limitations,
+  * for example.
+  *
+  * The files to select are chosen via the location property, which supports
+  * (and expects) shell-style wildcards (but only for the filename, not for
+  * directories). The results will be sorted.
+  *
+  * <refsect2>
+  * <title>Example launch line</title>
+  * |[
+  * gst-launch splitfilesrc location="/path/to/part-*.mpg" ! decodebin ! ... \
+  * ]| Plays the different parts as if they were one single MPEG file.
+  * </refsect2>
+  *
+  * Since: 0.10.31
+  */
+ /* TODO:
+  *  - implement splitfile:// URI handler?
+  */
+ #ifdef HAVE_CONFIG_H
+ #  include "config.h"
+ #endif
+ #include "gstsplitfilesrc.h"
+ #include "patternspec.h"
+ #include <string.h>
+ #ifdef G_OS_WIN32
+ #define DEFAULT_PATTERN_MATCH_MODE MATCH_MODE_UTF8
+ #else
+ #define DEFAULT_PATTERN_MATCH_MODE MATCH_MODE_AUTO
+ #endif
+ enum
+ {
+   PROP_LOCATION = 1
+ };
+ #define DEFAULT_LOCATION NULL
+ static void gst_split_file_src_set_property (GObject * object, guint prop_id,
+     const GValue * value, GParamSpec * pspec);
+ static void gst_split_file_src_get_property (GObject * object, guint prop_id,
+     GValue * value, GParamSpec * pspec);
+ static void gst_split_file_src_finalize (GObject * obj);
+ static gboolean gst_split_file_src_start (GstBaseSrc * basesrc);
+ static gboolean gst_split_file_src_stop (GstBaseSrc * basesrc);
+ static gboolean gst_split_file_src_can_seek (GstBaseSrc * basesrc);
 -GST_BOILERPLATE (GstSplitFileSrc, gst_split_file_src, GstBaseSrc,
 -    GST_TYPE_BASE_SRC);
 -
 -static void
 -gst_split_file_src_base_init (gpointer g_class)
 -{
 -  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
 -
 -  GST_DEBUG_CATEGORY_INIT (splitfilesrc_debug, "splitfilesrc", 0,
 -      "splitfilesrc element");
 -
 -  gst_element_class_add_pad_template (gstelement_class,
 -      gst_static_pad_template_get (&gst_split_file_src_pad_template));
 -
 -  gst_element_class_set_details_simple (gstelement_class, "Split-File Source",
 -      "Source/File",
 -      "Read a sequentially named set of files as if it was one large file",
 -      "Tim-Philipp Müller <tim.muller@collabora.co.uk>");
 -}
+ static gboolean gst_split_file_src_get_size (GstBaseSrc * basesrc, guint64 * s);
+ static gboolean gst_split_file_src_unlock (GstBaseSrc * basesrc);
+ static GstFlowReturn gst_split_file_src_create (GstBaseSrc * basesrc,
+     guint64 offset, guint size, GstBuffer ** buffer);
+ static GstStaticPadTemplate gst_split_file_src_pad_template =
+ GST_STATIC_PAD_TEMPLATE ("src",
+     GST_PAD_SRC,
+     GST_PAD_ALWAYS,
+     GST_STATIC_CAPS_ANY);
+ GST_DEBUG_CATEGORY_STATIC (splitfilesrc_debug);
+ #define GST_CAT_DEFAULT splitfilesrc_debug
 -  gstbasesrc_class->check_get_range =
 -      GST_DEBUG_FUNCPTR (gst_split_file_src_check_get_range);
++G_DEFINE_TYPE (GstSplitFileSrc, gst_split_file_src, GST_TYPE_BASE_SRC);
+ #ifdef G_OS_WIN32
+ #define WIN32_BLURB " Location string must be in UTF-8 encoding (on Windows)."
+ #else
+ #define WIN32_BLURB             /* nothing */
+ #endif
+ static void
+ gst_split_file_src_class_init (GstSplitFileSrcClass * klass)
+ {
+   GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
++  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+   gobject_class->set_property = gst_split_file_src_set_property;
+   gobject_class->get_property = gst_split_file_src_get_property;
+   gobject_class->finalize = gst_split_file_src_finalize;
+   g_object_class_install_property (gobject_class, PROP_LOCATION,
+       g_param_spec_string ("location", "File Location",
+           "Wildcard pattern to match file names of the input files. If "
+           "the location is an absolute path or contains directory components, "
+           "only the base file name part will be considered for pattern "
+           "matching. The results will be sorted." WIN32_BLURB,
+           DEFAULT_LOCATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+   gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_split_file_src_start);
+   gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_split_file_src_stop);
+   gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_split_file_src_create);
+   gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_split_file_src_get_size);
+   gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_split_file_src_unlock);
+   gstbasesrc_class->is_seekable =
+       GST_DEBUG_FUNCPTR (gst_split_file_src_can_seek);
 -gst_split_file_src_init (GstSplitFileSrc * splitfilesrc,
 -    GstSplitFileSrcClass * g_class)
++
++  GST_DEBUG_CATEGORY_INIT (splitfilesrc_debug, "splitfilesrc", 0,
++      "splitfilesrc element");
++
++  gst_element_class_add_pad_template (gstelement_class,
++      gst_static_pad_template_get (&gst_split_file_src_pad_template));
++
++  gst_element_class_set_details_simple (gstelement_class, "Split-File Source",
++      "Source/File",
++      "Read a sequentially named set of files as if it was one large file",
++      "Tim-Philipp Müller <tim.muller@collabora.co.uk>");
+ }
+ static void
 -  G_OBJECT_CLASS (parent_class)->finalize (obj);
++gst_split_file_src_init (GstSplitFileSrc * splitfilesrc)
+ {
+ }
+ static void
+ gst_split_file_src_finalize (GObject * obj)
+ {
+   GstSplitFileSrc *src = GST_SPLIT_FILE_SRC (obj);
+   g_free (src->location);
+   src->location = NULL;
 -static gboolean
 -gst_split_file_src_check_get_range (GstBaseSrc * basesrc)
 -{
 -  return TRUE;
 -}
 -
++  G_OBJECT_CLASS (gst_split_file_src_parent_class)->finalize (obj);
+ }
+ static gboolean
+ gst_split_file_src_can_seek (GstBaseSrc * basesrc)
+ {
+   return TRUE;
+ }
 -  buf = gst_buffer_new_and_alloc (size);
+ static gboolean
+ gst_split_file_src_unlock (GstBaseSrc * basesrc)
+ {
+   /* This is not actually that useful, since all normal file
+    * operations are fully blocking anyway */
+ #if 0
+   GstSplitFileSrc *src = GST_SPLIT_FILE_SRC (basesrc);
+   GST_DEBUG_OBJECT (src, "cancelling pending I/O operation if there is one");
+   /* g_cancellable_cancel (src->cancellable); */
+   GST_DEBUG_OBJECT (src, "done");
+ #endif
+   return TRUE;
+ }
+ static gboolean
+ gst_split_file_src_get_size (GstBaseSrc * basesrc, guint64 * size)
+ {
+   GstSplitFileSrc *src = GST_SPLIT_FILE_SRC (basesrc);
+   *size = src->parts[src->num_parts - 1].stop + 1;
+   return TRUE;
+ }
+ static void
+ gst_split_file_src_set_property (GObject * object, guint prop_id,
+     const GValue * value, GParamSpec * pspec)
+ {
+   GstSplitFileSrc *src = GST_SPLIT_FILE_SRC (object);
+   switch (prop_id) {
+     case PROP_LOCATION:
+       GST_OBJECT_LOCK (src);
+       g_free (src->location);
+       src->location = g_value_dup_string (value);
+ #ifdef G_OS_WIN32
+       if (!g_utf8_validate (src->location, -1, NULL)) {
+         g_warning ("splitfilesrc 'location' property must be in UTF-8 "
+             "encoding on Windows");
+       }
+ #endif
+       GST_OBJECT_UNLOCK (src);
+       break;
+     default:
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       break;
+   }
+ }
+ static void
+ gst_split_file_src_get_property (GObject * object, guint prop_id,
+     GValue * value, GParamSpec * pspec)
+ {
+   GstSplitFileSrc *src = GST_SPLIT_FILE_SRC (object);
+   switch (prop_id) {
+     case PROP_LOCATION:
+       GST_OBJECT_LOCK (src);
+       g_value_set_string (value, src->location);
+       GST_OBJECT_UNLOCK (src);
+       break;
+     default:
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       break;
+   }
+ }
+ static int
+ gst_split_file_src_array_sortfunc (gchar ** a, gchar ** b)
+ {
+   return strcmp (*a, *b);
+ }
+ static gchar **
+ gst_split_file_src_find_files (GstSplitFileSrc * src, const gchar * dirname,
+     const gchar * basename, GError ** err)
+ {
+   PatternSpec *pspec;
+   GPtrArray *files;
+   const gchar *name;
+   GDir *dir;
+   if (dirname == NULL || basename == NULL)
+     goto invalid_location;
+   GST_INFO_OBJECT (src, "checking in directory '%s' for pattern '%s'",
+       dirname, basename);
+   dir = g_dir_open (dirname, 0, err);
+   if (dir == NULL)
+     return NULL;
+   if (DEFAULT_PATTERN_MATCH_MODE == MATCH_MODE_UTF8 &&
+       !g_utf8_validate (basename, -1, NULL)) {
+     goto not_utf8;
+   }
+   /* mode will be AUTO on linux/unix and UTF8 on win32 */
+   pspec = pattern_spec_new (basename, DEFAULT_PATTERN_MATCH_MODE);
+   files = g_ptr_array_new ();
+   while ((name = g_dir_read_name (dir))) {
+     GST_TRACE_OBJECT (src, "check: %s", name);
+     if (pattern_match_string (pspec, name)) {
+       GST_DEBUG_OBJECT (src, "match: %s", name);
+       g_ptr_array_add (files, g_build_filename (dirname, name, NULL));
+     }
+   }
+   if (files->len == 0)
+     goto no_matches;
+   g_ptr_array_sort (files, (GCompareFunc) gst_split_file_src_array_sortfunc);
+   g_ptr_array_add (files, NULL);
+   pattern_spec_free (pspec);
+   g_dir_close (dir);
+   return (gchar **) g_ptr_array_free (files, FALSE);
+ /* ERRORS */
+ invalid_location:
+   {
+     g_set_error_literal (err, G_FILE_ERROR, G_FILE_ERROR_INVAL,
+         "No filename specified.");
+     return NULL;
+   }
+ not_utf8:
+   {
+     g_dir_close (dir);
+     g_set_error_literal (err, G_FILE_ERROR, G_FILE_ERROR_INVAL,
+         "Filename pattern must be UTF-8 on Windows.");
+     return NULL;
+   }
+ no_matches:
+   {
+     pattern_spec_free (pspec);
+     g_dir_close (dir);
+     g_set_error_literal (err, G_FILE_ERROR, G_FILE_ERROR_NOENT,
+         "Found no files matching the pattern.");
+     return NULL;
+   }
+ }
+ static gboolean
+ gst_split_file_src_start (GstBaseSrc * basesrc)
+ {
+   GstSplitFileSrc *src = GST_SPLIT_FILE_SRC (basesrc);
+   GCancellable *cancel;
+   gboolean ret = FALSE;
+   guint64 offset;
+   GError *err = NULL;
+   gchar *basename = NULL;
+   gchar *dirname = NULL;
+   gchar **files;
+   guint i;
+   GST_OBJECT_LOCK (src);
+   if (src->location != NULL && src->location[0] != '\0') {
+     basename = g_path_get_basename (src->location);
+     dirname = g_path_get_dirname (src->location);
+   }
+   GST_OBJECT_UNLOCK (src);
+   files = gst_split_file_src_find_files (src, dirname, basename, &err);
+   if (files == NULL || *files == NULL)
+     goto no_files;
+   src->num_parts = g_strv_length (files);
+   src->parts = g_new0 (GstFilePart, src->num_parts);
+   cancel = src->cancellable;
+   offset = 0;
+   for (i = 0; i < src->num_parts; ++i) {
+     GFileInputStream *stream;
+     GFileInfo *info;
+     goffset size;
+     GFile *file;
+     file = g_file_new_for_path (files[i]);
+     stream = g_file_read (file, cancel, &err);
+     g_object_unref (file);
+     if (err != NULL)
+       goto open_read_error;
+     info = g_file_input_stream_query_info (stream, "standard::*", NULL, &err);
+     if (err != NULL) {
+       g_object_unref (stream);
+       goto query_info_error;
+     }
+     size = g_file_info_get_size (info);
+     g_object_unref (info);
+     src->parts[i].stream = stream;
+     src->parts[i].path = g_strdup (files[i]);
+     src->parts[i].start = offset;
+     src->parts[i].stop = offset + size - 1;
+     GST_DEBUG ("[%010" G_GUINT64_FORMAT "-%010" G_GUINT64_FORMAT "] %s",
+         src->parts[i].start, src->parts[i].stop, src->parts[i].path);
+     offset += size;
+   }
+   GST_INFO ("Successfully opened %u file parts for reading", src->num_parts);
+   src->cur_part = 0;
+   src->cancellable = g_cancellable_new ();
+   ret = TRUE;
+ done:
+   if (err != NULL)
+     g_error_free (err);
+   g_strfreev (files);
+   g_free (basename);
+   g_free (dirname);
+   return ret;
+ /* ERRORS */
+ no_files:
+   {
+     if (err->code == G_IO_ERROR_CANCELLED)
+       goto cancelled;
+     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("%s", err->message),
+         ("Failed to find files in '%s' for pattern '%s'",
+             GST_STR_NULL (dirname), GST_STR_NULL (basename)));
+     goto done;
+   }
+ open_read_error:
+   {
+     if (err->code == G_IO_ERROR_CANCELLED)
+       goto cancelled;
+     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("%s", err->message),
+         ("Failed to open file '%s' for reading", files[i]));
+     goto done;
+   }
+ query_info_error:
+   {
+     if (err->code == G_IO_ERROR_CANCELLED)
+       goto cancelled;
+     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("%s", err->message),
+         ("Failed to query info for file '%s'", files[i]));
+     goto done;
+   }
+ cancelled:
+   {
+     GST_DEBUG_OBJECT (src, "I/O operation cancelled from another thread");
+     goto done;
+   }
+ }
+ static gboolean
+ gst_split_file_src_stop (GstBaseSrc * basesrc)
+ {
+   GstSplitFileSrc *src = GST_SPLIT_FILE_SRC (basesrc);
+   guint i;
+   for (i = 0; i < src->num_parts; ++i) {
+     if (src->parts[i].stream != NULL)
+       g_object_unref (src->parts[i].stream);
+     g_free (src->parts[i].path);
+   }
+   g_free (src->parts);
+   src->parts = NULL;
+   src->num_parts = 0;
+   g_object_unref (src->cancellable);
+   src->cancellable = NULL;
+   return TRUE;
+ }
+ static gboolean
+ gst_split_file_src_find_part_for_offset (GstSplitFileSrc * src, guint64 offset,
+     guint * part_number)
+ {
+   GstFilePart *part;
+   guint i;
+   /* TODO: could use gst_util_array_binary_search() here */
+   part = src->parts;
+   for (i = 0; i < src->num_parts; ++i) {
+     if (offset >= part->start && offset <= part->stop) {
+       *part_number = i;
+       return TRUE;
+     }
+     ++part;
+   }
+   return FALSE;
+ }
+ static GstFlowReturn
+ gst_split_file_src_create (GstBaseSrc * basesrc, guint64 offset, guint size,
+     GstBuffer ** buffer)
+ {
+   GstSplitFileSrc *src = GST_SPLIT_FILE_SRC (basesrc);
+   GstFilePart cur_part;
+   GInputStream *stream;
+   GCancellable *cancel;
+   GSeekable *seekable;
+   GstBuffer *buf;
+   GError *err = NULL;
+   guint64 read_offset;
+   guint8 *data;
+   guint to_read;
+   cur_part = src->parts[src->cur_part];
+   if (offset < cur_part.start || offset > cur_part.stop) {
+     if (!gst_split_file_src_find_part_for_offset (src, offset, &src->cur_part))
+       return GST_FLOW_UNEXPECTED;
+     cur_part = src->parts[src->cur_part];
+   }
+   GST_LOG_OBJECT (src, "current part: %u (%" G_GUINT64_FORMAT " - "
+       "%" G_GUINT64_FORMAT ", %s)", src->cur_part, cur_part.start,
+       cur_part.stop, cur_part.path);
 -  data = GST_BUFFER_DATA (buf);
++  buf = gst_buffer_new_allocate (NULL, size, 0);
+   GST_BUFFER_OFFSET (buf) = offset;
 -        GST_BUFFER_SIZE (buf) = offset - GST_BUFFER_OFFSET (buf);
++  data = gst_buffer_map (buf, NULL, NULL, GST_MAP_WRITE);
+   cancel = src->cancellable;
+   while (size > 0) {
+     guint64 bytes_to_end_of_part;
+     gsize read = 0;
+     /* we want the offset into the file part */
+     read_offset = offset - cur_part.start;
+     GST_LOG ("Reading part %03u from offset %" G_GUINT64_FORMAT " (%s)",
+         src->cur_part, read_offset, cur_part.path);
+     /* FIXME: only seek when needed (hopefully gio is smart) */
+     seekable = G_SEEKABLE (cur_part.stream);
+     if (!g_seekable_seek (seekable, read_offset, G_SEEK_SET, cancel, &err))
+       goto seek_failed;
+     GST_LOG_OBJECT (src, "now: %" G_GUINT64_FORMAT, g_seekable_tell (seekable));
+     bytes_to_end_of_part = (cur_part.stop - cur_part.start) + 1 - read_offset;
+     to_read = MIN (size, bytes_to_end_of_part);
+     GST_LOG_OBJECT (src, "reading %u bytes from part %u (bytes to end of "
+         "part: %u)", to_read, src->cur_part, (guint) bytes_to_end_of_part);
+     stream = G_INPUT_STREAM (cur_part.stream);
+     /* NB: we won't try to read beyond EOF */
+     if (!g_input_stream_read_all (stream, data, to_read, &read, cancel, &err))
+       goto read_failed;
+     GST_LOG_OBJECT (src, "read %u bytes", (guint) read);
+     data += read;
+     size -= read;
+     offset += read;
+     /* are we done? */
+     if (size == 0)
+       break;
+     GST_LOG_OBJECT (src, "%u bytes left to read for this chunk", size);
+     /* corner case, this should never really happen (assuming basesrc clips
+      * requests beyond the file size) */
+     if (read < to_read) {
+       if (src->cur_part == src->num_parts - 1) {
+         /* last file part, stop reading and truncate buffer */
 -  GST_LOG_OBJECT (src, "read %u bytes into buf %p", GST_BUFFER_SIZE (buf), buf);
++        gst_buffer_set_size (buf, offset - GST_BUFFER_OFFSET (buf));
+         break;
+       } else {
+         goto file_part_changed;
+       }
+     }
+     ++src->cur_part;
+     cur_part = src->parts[src->cur_part];
+   }
+   GST_BUFFER_OFFSET_END (buf) = offset;
++  gst_buffer_unmap (buf, data, size);
++
+   *buffer = buf;
++  GST_LOG_OBJECT (src, "read %" G_GSIZE_FORMAT " bytes into buf %p",
++      gst_buffer_get_size (buf), buf);
+   return GST_FLOW_OK;
+ /* ERRORS */
+ seek_failed:
+   {
+     if (err->code == G_IO_ERROR_CANCELLED)
+       goto cancelled;
+     GST_ELEMENT_ERROR (src, RESOURCE, SEEK, (NULL),
+         ("Seek to %" G_GUINT64_FORMAT " in %s failed", read_offset,
+             cur_part.path));
+     g_error_free (err);
+     gst_buffer_unref (buf);
+     return GST_FLOW_ERROR;
+   }
+ read_failed:
+   {
+     if (err->code == G_IO_ERROR_CANCELLED)
+       goto cancelled;
+     GST_ELEMENT_ERROR (src, RESOURCE, READ, ("%s", err->message),
+         ("Read from %" G_GUINT64_FORMAT " in %s failed", read_offset,
+             cur_part.path));
+     g_error_free (err);
+     gst_buffer_unref (buf);
+     return GST_FLOW_ERROR;
+   }
+ file_part_changed:
+   {
+     GST_ELEMENT_ERROR (src, RESOURCE, READ,
+         ("Read error while reading file part %s", cur_part.path),
+         ("Short read in file part, file may have been modified since start"));
+     gst_buffer_unref (buf);
+     return GST_FLOW_ERROR;
+   }
+ cancelled:
+   {
+     GST_DEBUG_OBJECT (src, "I/O operation cancelled from another thread");
+     g_error_free (err);
+     gst_buffer_unref (buf);
+     return GST_FLOW_WRONG_STATE;
+   }
+ }
Simple merge