[Xfce4-commits] <tumbler:master> Add GStreamer-based thumbnailer for video thumbnails

Jannis Pohlmann noreply at xfce.org
Fri May 20 01:20:01 CEST 2011


Updating branch refs/heads/master
         to 88a5e9692fcc2f2b64c152f25a98721d631b2ad7 (commit)
       from 57a3b9f3042d7d1e8f781dca5eb333d50cb1bb53 (commit)

commit 88a5e9692fcc2f2b64c152f25a98721d631b2ad7
Author: Ross Burton <ross at linux.intel.com>
Date:   Mon Jan 31 15:48:34 2011 +0000

    Add GStreamer-based thumbnailer for video thumbnails
    
    Signed-off-by: Jannis Pohlmann <jannis at xfce.org>

 acinclude.m4                                       |   24 ++
 configure.ac                                       |    7 +
 plugins/Makefile.am                                |    1 +
 .../Makefile.am                                    |   30 +-
 plugins/gst-thumbnailer/gst-helper.c               |  192 ++++++++++
 plugins/gst-thumbnailer/gst-helper.h               |   35 ++
 .../gst-thumbnailer-plugin.c}                      |   20 +-
 plugins/gst-thumbnailer/gst-thumbnailer-provider.c |  151 ++++++++
 plugins/gst-thumbnailer/gst-thumbnailer-provider.h |   45 +++
 plugins/gst-thumbnailer/gst-thumbnailer.c          |  389 ++++++++++++++++++++
 plugins/gst-thumbnailer/gst-thumbnailer.h          |   45 +++
 11 files changed, 917 insertions(+), 22 deletions(-)

diff --git a/acinclude.m4 b/acinclude.m4
index e2d374b..2271773 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -145,6 +145,30 @@ AC_MSG_RESULT([$ac_tumbler_ffmpeg_thumbnailer])
 
 
 
+dnl TUMBLER_GSTREAMER_THUMBNAILER()
+dnl
+dnl Check whether to build and install the gstreamer video thumbnailer plugin.
+dnl
+AC_DEFUN([TUMBLER_GSTREAMER_THUMBNAILER],
+[
+AC_ARG_ENABLE([gstreamer-thumbnailer], [AC_HELP_STRING([--disable-gstreamer-thumbnailer], [Don't build the GStreamer video thumbnailer plugin])],
+  [ac_tumbler_gstreamer_thumbnailer=$enableval], [ac_tumbler_gstreamer_thumbnailer=yes])
+if test x"$ac_tumbler_gstreamer_thumbnailer" = x"yes"; then
+  dnl Check for gdk-pixbuf
+  PKG_CHECK_MODULES([GDK_PIXBUF], [gdk-pixbuf-2.0 >= 2.14],
+  [
+    dnl Check for libgstreamerthumbnailer
+    PKG_CHECK_MODULES([GSTREAMER], [gstreamer-0.10], [], [ac_tumbler_gstreamer_thumbnailer=no])
+  ], [ac_tumbler_gstreamer_thumbnailer=no])
+fi
+
+AC_MSG_CHECKING([whether to build the gstreamer video thumbnailer plugin])
+AM_CONDITIONAL([TUMBLER_GSTREAMER_THUMBNAILER], [test x"$ac_tumbler_gstreamer_thumbnailer" = x"yes"])
+AC_MSG_RESULT([$ac_tumbler_gstreamer_thumbnailer])
+])
+
+
+
 dnl TUMBLER_POPPLER_THUMBNAILER()
 dnl
 dnl Check whether to build and install the poppler PDF/PS thumbnailer plugin.
diff --git a/configure.ac b/configure.ac
index 0c9fac7..6e7b8a2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -154,6 +154,7 @@ TUMBLER_FONT_THUMBNAILER()
 TUMBLER_JPEG_THUMBNAILER()
 TUMBLER_PIXBUF_THUMBNAILER()
 TUMBLER_FFMPEG_THUMBNAILER()
+TUMBLER_GSTREAMER_THUMBNAILER()
 TUMBLER_POPPLER_THUMBNAILER()
 TUMBLER_XDG_CACHE()
 
@@ -181,6 +182,7 @@ docs/reference/tumbler/Makefile
 docs/reference/tumbler/version.xml
 plugins/Makefile
 plugins/font-thumbnailer/Makefile
+plugins/gst-thumbnailer/Makefile
 plugins/jpeg-thumbnailer/Makefile
 plugins/pixbuf-thumbnailer/Makefile
 plugins/ffmpeg-thumbnailer/Makefile
@@ -223,6 +225,11 @@ echo "  * Video thumbnailer plugin using ffmpegthumbnailer: yes"
 else
 echo "  * Video thumbnailer plugin using ffmpegthumbnailer: no"
 fi
+if test x"$ac_tumbler_gstreamer_thumbnailer" = x"yes"; then
+echo "  * Video thumbnailer plugin using GStreamer:         yes"
+else
+echo "  * Video thumbnailer plugin using GStreamer        : no"
+fi
 if test x"$ac_tumbler_poppler_thumbnailer" = x"yes"; then
 echo "  * PDF/PS thumbnailer plugin using poppler:          yes"
 else
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 7894a05..7e810d4 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -19,6 +19,7 @@
 
 SUBDIRS =								\
 	font-thumbnailer						\
+	gst-thumbnailer							\
 	jpeg-thumbnailer						\
 	pixbuf-thumbnailer						\
 	ffmpeg-thumbnailer						\
diff --git a/plugins/pixbuf-thumbnailer/Makefile.am b/plugins/gst-thumbnailer/Makefile.am
similarity index 72%
copy from plugins/pixbuf-thumbnailer/Makefile.am
copy to plugins/gst-thumbnailer/Makefile.am
index 691f3d1..24ed555 100644
--- a/plugins/pixbuf-thumbnailer/Makefile.am
+++ b/plugins/gst-thumbnailer/Makefile.am
@@ -17,44 +17,48 @@
 # Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-if TUMBLER_PIXBUF_THUMBNAILER
+if TUMBLER_GSTREAMER_THUMBNAILER
 
 tumbler_plugindir = $(libdir)/tumbler-$(TUMBLER_VERSION_API)/plugins
 tumbler_plugin_LTLIBRARIES =						\
-	tumbler-pixbuf-thumbnailer.la
+	tumbler-gst-thumbnailer.la
 
-tumbler_pixbuf_thumbnailer_la_SOURCES =					\
-	pixbuf-thumbnailer-plugin.c					\
-	pixbuf-thumbnailer-provider.c					\
-	pixbuf-thumbnailer-provider.h					\
-	pixbuf-thumbnailer.c						\
-	pixbuf-thumbnailer.h
+tumbler_gst_thumbnailer_la_SOURCES =					\
+	gst-thumbnailer-plugin.c					\
+	gst-thumbnailer-provider.c					\
+	gst-thumbnailer-provider.h					\
+	gst-thumbnailer.c						\
+	gst-thumbnailer.h						\
+	gst-helper.c							\
+	gst-helper.h
 
-tumbler_pixbuf_thumbnailer_la_CFLAGS =					\
+tumbler_gst_thumbnailer_la_CFLAGS =					\
 	-I$(top_builddir)						\
 	-I$(top_builddir)/plugins					\
 	-I$(top_srcdir)							\
 	-I$(top_srcdir)/plugins						\
-	-DG_LOG_DOMAIN=\"tumbler-pixbuf-thumbnailer\"			\
+	-DG_LOG_DOMAIN=\"tumbler-gst-thumbnailer\"			\
 	-DPACKAGE_LOCALE_DIR=\"$(localedir)\"				\
 	$(GDK_PIXBUF_CFLAGS)						\
+	$(GSTREAMER_CFLAGS)						\
 	$(GIO_CFLAGS)							\
 	$(GLIB_CFLAGS)							\
 	$(PLATFORM_CPPFLAGS)
 
-tumbler_pixbuf_thumbnailer_la_LDFLAGS =					\
+tumbler_gst_thumbnailer_la_LDFLAGS =					\
 	-avoid-version							\
 	-export-dynamic							\
 	-module								\
 	$(PLATFORM_LDFLAGS)
 
-tumbler_pixbuf_thumbnailer_la_LIBADD =					\
+tumbler_gst_thumbnailer_la_LIBADD =					\
 	$(top_builddir)/tumbler/libtumbler-$(TUMBLER_VERSION_API).la	\
 	$(GDK_PIXBUF_LIBS)						\
+	$(GSTREAMER_LIBS)						\
 	$(GIO_LIBS)							\
 	$(GLIB_LIBS)
 
-tumbler_pixbuf_thumbnailer_la_DEPENDENCIES =				\
+tumbler_gst_thumbnailer_la_DEPENDENCIES =				\
 	$(top_builddir)/tumbler/libtumbler-$(TUMBLER_VERSION_API).la
 
 endif
diff --git a/plugins/gst-thumbnailer/gst-helper.c b/plugins/gst-thumbnailer/gst-helper.c
new file mode 100644
index 0000000..c5a571c
--- /dev/null
+++ b/plugins/gst-thumbnailer/gst-helper.c
@@ -0,0 +1,192 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*
+ * Originally from Bickley - a meta data management framework.
+ * Copyright © 2008, 2011 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gst/gst.h>
+#include <tumbler/tumbler.h>
+#include "gst-helper.h"
+
+static void
+push_buffer (GstElement *element,
+             GstBuffer  *out_buffer,
+             GstPad     *pad,
+             GstBuffer  *in_buffer)
+{
+  gst_buffer_set_caps (out_buffer, GST_BUFFER_CAPS (in_buffer));
+  GST_BUFFER_SIZE (out_buffer) = GST_BUFFER_SIZE (in_buffer);
+  memcpy (GST_BUFFER_DATA (out_buffer), GST_BUFFER_DATA (in_buffer),
+          GST_BUFFER_SIZE (in_buffer));
+}
+
+static void
+pull_buffer (GstElement *element,
+             GstBuffer  *in_buffer,
+             GstPad     *pad,
+             GstBuffer **out_buffer)
+{
+  *out_buffer = gst_buffer_ref (in_buffer);
+}
+
+GdkPixbuf *
+gst_helper_convert_buffer_to_pixbuf (GstBuffer    *buffer,
+                                     GCancellable *cancellable,
+                                     TumblerThumbnailFlavor *flavour)
+{
+  GstCaps *pb_caps;
+  GstElement *pipeline;
+  GstBuffer *out_buffer = NULL;
+  GstElement *src, *sink, *colorspace, *scale, *filter;
+  GstBus *bus;
+  GstMessage *msg;
+  GstStateChangeReturn state;
+  gboolean ret;
+  int thumb_size = 0, width, height, dw, dh, i;
+  GstStructure *s;
+
+  /* TODO: get the width and height, and handle them being different when
+     scaling */
+  tumbler_thumbnail_flavor_get_size (flavour, &thumb_size, NULL);
+
+  s = gst_caps_get_structure (GST_BUFFER_CAPS (buffer), 0);
+  gst_structure_get_int (s, "width", &width);
+  gst_structure_get_int (s, "height", &height);
+
+  if (width > height)
+    {
+      double ratio;
+
+      ratio = (double) thumb_size / (double) width;
+      dw = thumb_size;
+      dh = height * ratio;
+    }
+  else
+    {
+      double ratio;
+
+      ratio = (double) thumb_size / (double) height;
+      dh = thumb_size;
+      dw = width * ratio;
+    }
+
+  pb_caps = gst_caps_new_simple ("video/x-raw-rgb",
+                                 "bpp", G_TYPE_INT, 24,
+                                 "depth", G_TYPE_INT, 24,
+                                 "width", G_TYPE_INT, dw,
+                                 "height", G_TYPE_INT, dh,
+                                 "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
+                                 NULL);
+
+  pipeline = gst_pipeline_new ("pipeline");
+
+  src = gst_element_factory_make ("fakesrc", "src");
+  colorspace = gst_element_factory_make ("ffmpegcolorspace", "colorspace");
+  scale = gst_element_factory_make ("videoscale", "scale");
+  filter = gst_element_factory_make ("capsfilter", "filter");
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  gst_bin_add_many (GST_BIN (pipeline), src, colorspace, scale,
+                    filter, sink, NULL);
+
+  g_object_set (filter,
+                "caps", pb_caps,
+                NULL);
+  g_object_set (src,
+                "num-buffers", 1,
+                "sizetype", 2,
+                "sizemax", GST_BUFFER_SIZE (buffer),
+                "signal-handoffs", TRUE,
+                NULL);
+  g_signal_connect (src, "handoff",
+                    G_CALLBACK (push_buffer), buffer);
+
+  g_object_set (sink,
+                "signal-handoffs", TRUE,
+                "preroll-queue-len", 1,
+                NULL);
+  g_signal_connect (sink, "handoff",
+                    G_CALLBACK (pull_buffer), &out_buffer);
+
+  ret = gst_element_link (src, colorspace);
+  if (ret == FALSE)
+    {
+      g_warning ("Failed to link src->colorspace");
+      return NULL;
+    }
+
+  ret = gst_element_link (colorspace, scale);
+  if (ret == FALSE)
+    {
+      g_warning ("Failed to link colorspace->scale");
+      return NULL;
+    }
+
+  ret = gst_element_link (scale, filter);
+  if (ret == FALSE)
+    {
+      g_warning ("Failed to link scale->filter");
+      return NULL;
+    }
+
+  ret = gst_element_link (filter, sink);
+  if (ret == FALSE)
+    {
+      g_warning ("Failed to link filter->sink");
+      return NULL;
+    }
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+  state = gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
+
+  i = 0;
+  msg = NULL;
+  while (msg == NULL && i < 5)
+    {
+      msg = gst_bus_timed_pop_filtered (bus, GST_SECOND,
+                                        GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
+      i++;
+    }
+
+  /* FIXME: Notify about error? */
+  gst_message_unref (msg);
+
+  gst_caps_unref (pb_caps);
+
+  if (out_buffer)
+    {
+      GdkPixbuf *pixbuf;
+      char *data;
+
+      data = g_memdup (GST_BUFFER_DATA (out_buffer),
+                       GST_BUFFER_SIZE (out_buffer));
+      pixbuf = gdk_pixbuf_new_from_data ((guchar *) data,
+                                         GDK_COLORSPACE_RGB, FALSE,
+                                         8, dw, dh, GST_ROUND_UP_4 (dw * 3),
+                                         (GdkPixbufDestroyNotify) g_free,
+                                         NULL);
+
+      gst_buffer_unref (buffer);
+      return pixbuf;
+    }
+
+  /* FIXME: Check what buffers need freed */
+  return NULL;
+}
diff --git a/plugins/gst-thumbnailer/gst-helper.h b/plugins/gst-thumbnailer/gst-helper.h
new file mode 100644
index 0000000..594e13c
--- /dev/null
+++ b/plugins/gst-thumbnailer/gst-helper.h
@@ -0,0 +1,35 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*
+ * Originally from Bickley - a meta data management framework.
+ * Copyright © 2008, 2011 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GST_HELPER_H__
+#define __GST_HELPER_H__
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gst/gst.h>
+#include <tumbler/tumbler.h>
+
+G_BEGIN_DECLS
+
+GdkPixbuf *gst_helper_convert_buffer_to_pixbuf (GstBuffer              *buffer,
+                                                GCancellable           *cancellable,
+                                                TumblerThumbnailFlavor *flavour);
+
+G_END_DECLS
+
+#endif
diff --git a/plugins/poppler-thumbnailer/poppler-thumbnailer-plugin.c b/plugins/gst-thumbnailer/gst-thumbnailer-plugin.c
similarity index 81%
copy from plugins/poppler-thumbnailer/poppler-thumbnailer-plugin.c
copy to plugins/gst-thumbnailer/gst-thumbnailer-plugin.c
index 6567de1..5b302bb 100644
--- a/plugins/poppler-thumbnailer/poppler-thumbnailer-plugin.c
+++ b/plugins/gst-thumbnailer/gst-thumbnailer-plugin.c
@@ -1,6 +1,8 @@
 /* vi:set et ai sw=2 sts=2 ts=2: */
-/*-
- * Copyright (c) 2010 Jannis Pohlmann <jannis at xfce.org>
+/*
+ * Copyright (c) 2011 Intel Corporation
+ *
+ * Author: Ross Burton <ross at linux.intel.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -28,8 +30,8 @@
 
 #include <tumbler/tumbler.h>
 
-#include <poppler-thumbnailer/poppler-thumbnailer-provider.h>
-#include <poppler-thumbnailer/poppler-thumbnailer.h>
+#include <gst-thumbnailer/gst-thumbnailer-provider.h>
+#include <gst-thumbnailer/gst-thumbnailer.h>
 
 
 
@@ -59,15 +61,15 @@ tumbler_plugin_initialize (TumblerProviderPlugin *plugin)
     }
 
 #ifdef DEBUG
-  g_message (_("Initializing the Tumbler Poppler PDF/PS Thumbnailer plugin"));
+  g_message (_("Initializing the Tumbler GStreamer Thumbnailer plugin"));
 #endif
 
   /* register the types provided by this plugin */
-  poppler_thumbnailer_register (plugin);
-  poppler_thumbnailer_provider_register (plugin);
+  gst_thumbnailer_register (plugin);
+  gst_thumbnailer_provider_register (plugin);
 
   /* set up the plugin provider type list */
-  type_list[0] = TYPE_POPPLER_THUMBNAILER_PROVIDER;
+  type_list[0] = TYPE_GST_THUMBNAILER_PROVIDER;
 }
 
 
@@ -76,7 +78,7 @@ void
 tumbler_plugin_shutdown (void)
 {
 #ifdef DEBUG
-  g_message (_("Shutting down the Tumbler Pixbuf Thumbnailer plugin"));
+  g_message (_("Shutting down the Tumbler GStreamer Thumbnailer plugin"));
 #endif
 }
 
diff --git a/plugins/gst-thumbnailer/gst-thumbnailer-provider.c b/plugins/gst-thumbnailer/gst-thumbnailer-provider.c
new file mode 100644
index 0000000..44285f8
--- /dev/null
+++ b/plugins/gst-thumbnailer/gst-thumbnailer-provider.c
@@ -0,0 +1,151 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*
+ * Copyright (c) 2011 Intel Corporation
+ *
+ * Author: Ross Burton <ross at linux.intel.com>
+ *
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gst/gst.h>
+
+#include <tumbler/tumbler.h>
+
+#include <gst-thumbnailer/gst-thumbnailer-provider.h>
+#include <gst-thumbnailer/gst-thumbnailer.h>
+
+
+static void   gst_thumbnailer_provider_thumbnailer_provider_init (TumblerThumbnailerProviderIface *iface);
+static GList *gst_thumbnailer_provider_get_thumbnailers          (TumblerThumbnailerProvider      *provider);
+
+
+struct _GstThumbnailerProviderClass
+{
+  GObjectClass __parent__;
+};
+
+struct _GstThumbnailerProvider
+{
+  GObject __parent__;
+};
+
+
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GstThumbnailerProvider,
+                                gst_thumbnailer_provider,
+                                G_TYPE_OBJECT,
+                                0,
+                                TUMBLER_ADD_INTERFACE (TUMBLER_TYPE_THUMBNAILER_PROVIDER,
+                                                       gst_thumbnailer_provider_thumbnailer_provider_init));
+
+
+
+void
+gst_thumbnailer_provider_register (TumblerProviderPlugin *plugin)
+{
+  gst_thumbnailer_provider_register_type (G_TYPE_MODULE (plugin));
+}
+
+
+
+static void
+gst_thumbnailer_provider_class_init (GstThumbnailerProviderClass *klass)
+{
+}
+
+
+
+static void
+gst_thumbnailer_provider_class_finalize (GstThumbnailerProviderClass *klass)
+{
+}
+
+
+
+static void
+gst_thumbnailer_provider_thumbnailer_provider_init (TumblerThumbnailerProviderIface *iface)
+{
+  iface->get_thumbnailers = gst_thumbnailer_provider_get_thumbnailers;
+}
+
+
+
+static void
+gst_thumbnailer_provider_init (GstThumbnailerProvider *provider)
+{
+}
+
+static GList *
+gst_thumbnailer_provider_get_thumbnailers (TumblerThumbnailerProvider *provider)
+{
+  static const char *mime_types[] = {
+    /* This list is mainly from Totem. Generating a list from GStreamer isn't
+       realistic, so we have to hardcode it. */
+    "application/asx",
+    "application/ogg",
+    "application/x-flash-video",
+    "application/x-ms-wmp",
+    "application/x-ms-wms",
+    "application/x-ogg",
+    "video/3gpp",
+    "video/divx",
+    "video/flv",
+    "video/jpeg",
+    "video/mp4",
+    "video/mpeg",
+    "video/ogg",
+    "video/quicktime",
+    "video/x-flv",
+    "video/x-m4v",
+    "video/x-matroska",
+    "video/x-ms-asf",
+    "video/x-ms-wm",
+    "video/x-ms-wmp",
+    "video/x-ms-wmv",
+    "video/x-ms-wvx",
+    "video/x-msvideo",
+    "video/x-ogg",
+    "video/x-wmv",
+    NULL
+  };
+
+  GstThumbnailer *thumbnailer;
+  GStrv           uri_schemes;
+  GError         *error = NULL;
+
+  if (!gst_init_check (0, NULL, &error))
+    {
+      g_warning ("Cannot initialize GStreamer, thumbnailer not loaded: %s", error->message);
+      return NULL;
+    }
+
+  uri_schemes = tumbler_util_get_supported_uri_schemes ();
+
+  thumbnailer = g_object_new (TYPE_GST_THUMBNAILER,
+                              "uri-schemes", uri_schemes,
+                              "mime-types", mime_types,
+                              NULL);
+
+  g_strfreev (uri_schemes);
+
+  return g_list_append (NULL, thumbnailer);
+}
diff --git a/plugins/gst-thumbnailer/gst-thumbnailer-provider.h b/plugins/gst-thumbnailer/gst-thumbnailer-provider.h
new file mode 100644
index 0000000..44ce46e
--- /dev/null
+++ b/plugins/gst-thumbnailer/gst-thumbnailer-provider.h
@@ -0,0 +1,45 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*
+ * Copyright (c) 2011 Intel Corporation
+ *
+ * Author: Ross Burton <ross at linux.intel.com>
+ *
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_THUMBNAILER_PROVIDER_H__
+#define __GST_THUMBNAILER_PROVIDER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS;
+
+#define TYPE_GST_THUMBNAILER_PROVIDER            (gst_thumbnailer_provider_get_type ())
+#define GST_THUMBNAILER_PROVIDER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_GST_THUMBNAILER_PROVIDER, GstThumbnailerProvider))
+#define GST_THUMBNAILER_PROVIDER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_GST_THUMBNAILER_PROVIDER, GstThumbnailerProviderClass))
+#define IS_GST_THUMBNAILER_PROVIDER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_GST_THUMBNAILER_PROVIDER))
+#define IS_GST_THUMBNAILER_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_GST_THUMBNAILER_PROVIDER)
+#define GST_THUMBNAILER_PROVIDER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_GST_THUMBNAILER_PROVIDER, GstThumbnailerProviderClass))
+
+typedef struct _GstThumbnailerProviderClass GstThumbnailerProviderClass;
+typedef struct _GstThumbnailerProvider      GstThumbnailerProvider;
+
+GType gst_thumbnailer_provider_get_type (void) G_GNUC_CONST;
+void  gst_thumbnailer_provider_register (TumblerProviderPlugin *plugin);
+
+G_END_DECLS;
+
+#endif /* !__GST_THUMBNAILER_PROVIDER_H__ */
diff --git a/plugins/gst-thumbnailer/gst-thumbnailer.c b/plugins/gst-thumbnailer/gst-thumbnailer.c
new file mode 100644
index 0000000..4f95694
--- /dev/null
+++ b/plugins/gst-thumbnailer/gst-thumbnailer.c
@@ -0,0 +1,389 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*
+ * Copyright (c) 2011 Intel Corporation
+ *
+ * Author: Ross Burton <ross at linux.intel.com>
+ *
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <math.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <tumbler/tumbler.h>
+#include <gst/gst.h>
+
+#include "gst-thumbnailer.h"
+#include "gst-helper.h"
+
+#ifdef DEBUG
+#define LOG(...) g_message (__VA_ARGS__)
+#else
+#define LOG(...)
+#endif
+
+static void gst_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer,
+                                    GCancellable               *cancellable,
+                                    TumblerFileInfo            *info);
+
+struct _GstThumbnailerClass
+{
+  TumblerAbstractThumbnailerClass __parent__;
+};
+
+struct _GstThumbnailer
+{
+  TumblerAbstractThumbnailer __parent__;
+};
+
+
+G_DEFINE_DYNAMIC_TYPE (GstThumbnailer,
+                       gst_thumbnailer,
+                       TUMBLER_TYPE_ABSTRACT_THUMBNAILER);
+
+
+void
+gst_thumbnailer_register (TumblerProviderPlugin *plugin)
+{
+  gst_thumbnailer_register_type (G_TYPE_MODULE (plugin));
+}
+
+static void
+gst_thumbnailer_class_init (GstThumbnailerClass *klass)
+{
+  TumblerAbstractThumbnailerClass *abstractthumbnailer_class;
+
+  abstractthumbnailer_class = TUMBLER_ABSTRACT_THUMBNAILER_CLASS (klass);
+  abstractthumbnailer_class->create = gst_thumbnailer_create;
+}
+
+static void
+gst_thumbnailer_class_finalize (GstThumbnailerClass *klass)
+{
+}
+
+static void
+gst_thumbnailer_init (GstThumbnailer *thumbnailer)
+{
+}
+
+
+/*
+ * Determine if the image is "interesting" or not.  This implementation reduces
+ * the RGB from 24 to 12 bits and examines the distribution of colours.
+ *
+ * This function is taken from Bickley, Copyright (c) Intel Corporation 2008.
+ */
+static gboolean
+is_interesting (GdkPixbuf *pixbuf)
+{
+  int width, height, y, rowstride;
+  gboolean has_alpha;
+  guint32 histogram[4][4][4] = {{{0,},},};
+  guchar *pixels;
+  guint pxl_count = 0, count, i;
+
+  g_assert (GDK_IS_PIXBUF (pixbuf));
+
+  width = gdk_pixbuf_get_width (pixbuf);
+  height = gdk_pixbuf_get_height (pixbuf);
+  rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+  has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
+
+  pixels = gdk_pixbuf_get_pixels (pixbuf);
+  for (y = 0; y < height; y++)
+    {
+      guchar *row = pixels + (y * rowstride);
+      int c;
+
+      for (c = 0; c < width; c++)
+        {
+          guchar r, g, b;
+
+          r = row[0];
+          g = row[1];
+          b = row[2];
+
+          histogram[r / 64][g / 64][b / 64]++;
+
+          if (has_alpha)
+            {
+              row += 4;
+            }
+          else
+            {
+              row += 3;
+            }
+
+          pxl_count++;
+        }
+    }
+
+  count = 0;
+  for (i = 0; i < 4; i++)
+    {
+      int j;
+      for (j = 0; j < 4; j++)
+        {
+          int k;
+
+          for (k = 0; k < 4; k++)
+            {
+              /* Count how many bins have more than
+                 1% of the pixels in the histogram */
+              if (histogram[i][j][k] > pxl_count / 100)
+                count++;
+            }
+        }
+    }
+
+  /* Image is boring if there is only 1 bin with > 1% of pixels */
+  return count > 1;
+}
+
+/*
+ * Construct a pipline for a given @info, cancelling during initialisation on
+ * @cancellable.  This function will either return a #GstElement that has been
+ * prerolled and is in the paused state, or %NULL if the initialisation is
+ * cancelled or an error occurs.
+ */
+static GstElement *
+make_pipeline (TumblerFileInfo *info, GCancellable *cancellable)
+{
+  GstElement *playbin, *audio_sink, *video_sink;
+  int count = 0, n_video = 0;
+  GstStateChangeReturn state;
+
+  g_assert (info);
+
+  playbin = gst_element_factory_make ("playbin2", "playbin");
+  g_assert (playbin);
+  audio_sink = gst_element_factory_make ("fakesink", "audiosink");
+  g_assert (audio_sink);
+  video_sink = gst_element_factory_make ("fakesink", "videosink");
+  g_assert (video_sink);
+
+  g_object_set (playbin,
+                "uri", tumbler_file_info_get_uri (info),
+                "audio-sink", audio_sink,
+                "video-sink", video_sink,
+                NULL);
+
+  g_object_set (video_sink,
+                "sync", TRUE,
+                NULL);
+
+  /* Change to paused state so we're ready to seek */
+  state = gst_element_set_state (playbin, GST_STATE_PAUSED);
+  while (state == GST_STATE_CHANGE_ASYNC
+         && count < 5
+         && !g_cancellable_is_cancelled (cancellable))
+    {
+      state = gst_element_get_state (playbin, NULL, 0, 1 * GST_SECOND);
+      count++;
+
+      /* Spin mainloop so we can pick up the cancels */
+      while (g_main_context_pending (NULL))
+        g_main_context_iteration (NULL, FALSE);
+    }
+
+  if (state == GST_STATE_CHANGE_FAILURE || state == GST_STATE_CHANGE_ASYNC)
+    {
+      LOG ("failed to or still changing state, aborting (state change %d)", state);
+      g_object_unref (playbin);
+      return NULL;
+    }
+
+  g_object_get (playbin, "n-video", &n_video, NULL);
+  if (n_video == 0)
+    {
+      LOG ("no video stream, aborting");
+      g_object_unref (playbin);
+      return NULL;
+    }
+
+  return playbin;
+}
+
+/*
+ * Get the total duration in nanoseconds of the stream.
+ */
+static gint64
+get_duration (GstElement *playbin)
+{
+  GstFormat format = GST_FORMAT_TIME;
+  gint64 duration = 0;
+
+  g_assert (playbin);
+
+  gst_element_query_duration (playbin, &format, &duration);
+
+  return duration;
+}
+
+/*
+ * Generate a thumbnail for @info.
+ */
+static void
+gst_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer,
+                        GCancellable               *cancellable,
+                        TumblerFileInfo            *info)
+{
+  /* These positions are taken from Totem */
+  const double positions[] = {
+    1.0 / 3.0,
+    2.0 / 3.0,
+    0.1,
+    0.9,
+    0.5
+  };
+  GstElement             *playbin;
+  gint64                  duration;
+  unsigned int            i;
+  GstBuffer              *frame;
+  GdkPixbuf              *shot;
+  TumblerThumbnail       *thumbnail;
+  TumblerThumbnailFlavor *flavour;
+  TumblerImageData        data;
+  GError                 *error = NULL;
+
+  g_return_if_fail (IS_GST_THUMBNAILER (thumbnailer));
+  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (TUMBLER_IS_FILE_INFO (info));
+
+  /* Check for early cancellation */
+  if (g_cancellable_is_cancelled (cancellable))
+    return;
+
+  playbin = make_pipeline (info, cancellable);
+  if (playbin == NULL)
+    {
+      /* TODO: emit an error, but the specification won't let me. */
+      return;
+    }
+
+  duration = get_duration (playbin);
+
+  /* Now we have a pipeline that we know has video and is paused, ready for
+     seeking.  Try to find an interesting frame at each of the positions in
+     order. */
+  for (i = 0; i < G_N_ELEMENTS (positions); i++)
+    {
+      /* Check if we've been cancelled */
+      if (g_cancellable_is_cancelled (cancellable))
+        {
+          gst_element_set_state (playbin, GST_STATE_NULL);
+          g_object_unref (playbin);
+          return;
+        }
+
+      LOG ("trying position %f", positions[i]);
+
+      gst_element_seek_simple (playbin,
+                               GST_FORMAT_TIME,
+                               GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
+                               (gint64)(positions[i] * duration));
+
+      if (gst_element_get_state (playbin, NULL, NULL, 1 * GST_SECOND)
+          == GST_STATE_CHANGE_FAILURE)
+        {
+          LOG ("Could not seek");
+          gst_element_set_state (playbin, GST_STATE_NULL);
+          g_object_unref (playbin);
+          return;
+        }
+
+      g_object_get (playbin, "frame", &frame, NULL);
+
+      if (frame == NULL)
+        {
+          LOG ("No frame found!");
+          gst_element_set_state (playbin, GST_STATE_NULL);
+          g_object_unref (playbin);
+          continue;
+        }
+
+      thumbnail = tumbler_file_info_get_thumbnail (info);
+      flavour = tumbler_thumbnail_get_flavor (thumbnail);
+      /* This frees the buffer for us */
+      shot = gst_helper_convert_buffer_to_pixbuf (frame, cancellable, flavour);
+      g_object_unref (flavour);
+
+      /* If it's not interesting, throw it away and try again*/
+      if (is_interesting (shot))
+        {
+          /* Got an interesting image, break out */
+          LOG ("Found an interesting image");
+          break;
+        }
+
+      /*
+       * If we've still got positions to try, free the current uninteresting
+       * shot. Otherwise we'll make do with what we have.
+       */
+      if (i + 1 < G_N_ELEMENTS (positions) && shot)
+        {
+          g_object_unref (shot);
+          shot = NULL;
+        }
+
+      /* Spin mainloop so we can pick up the cancels */
+      while (g_main_context_pending (NULL))
+        {
+          g_main_context_iteration (NULL, FALSE);
+        }
+    }
+
+  gst_element_set_state (playbin, GST_STATE_NULL);
+  g_object_unref (playbin);
+
+  if (shot)
+    {
+      data.data = gdk_pixbuf_get_pixels (shot);
+      data.has_alpha = gdk_pixbuf_get_has_alpha (shot);
+      data.bits_per_sample = gdk_pixbuf_get_bits_per_sample (shot);
+      data.width = gdk_pixbuf_get_width (shot);
+      data.height = gdk_pixbuf_get_height (shot);
+      data.rowstride = gdk_pixbuf_get_rowstride (shot);
+      data.colorspace = (TumblerColorspace) gdk_pixbuf_get_colorspace (shot);
+
+      tumbler_thumbnail_save_image_data (thumbnail, &data,
+                                         tumbler_file_info_get_mtime (info),
+                                         NULL, &error);
+
+      g_object_unref (shot);
+
+      if (error != NULL)
+        {
+          g_signal_emit_by_name (thumbnailer, "error",
+                                 tumbler_file_info_get_uri (info),
+                                 error->code, error->message);
+          g_error_free (error);
+        }
+      else
+        {
+          g_signal_emit_by_name (thumbnailer, "ready",
+                                 tumbler_file_info_get_uri (info));
+        }
+    }
+}
diff --git a/plugins/gst-thumbnailer/gst-thumbnailer.h b/plugins/gst-thumbnailer/gst-thumbnailer.h
new file mode 100644
index 0000000..c587f63
--- /dev/null
+++ b/plugins/gst-thumbnailer/gst-thumbnailer.h
@@ -0,0 +1,45 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*
+ * Copyright (c) 2011 Intel Corporation
+ *
+ * Author: Ross Burton <ross at linux.intel.com>
+ *
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_THUMBNAILER_H__
+#define __GST_THUMBNAILER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS;
+
+#define TYPE_GST_THUMBNAILER            (gst_thumbnailer_get_type ())
+#define GST_THUMBNAILER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_GST_THUMBNAILER, GstThumbnailer))
+#define GST_THUMBNAILER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_GST_THUMBNAILER, GstThumbnailerClass))
+#define IS_GST_THUMBNAILER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_GST_THUMBNAILER))
+#define IS_GST_THUMBNAILER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_GST_THUMBNAILER)
+#define GST_THUMBNAILER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_GST_THUMBNAILER, GstThumbnailerClass))
+
+typedef struct _GstThumbnailerClass   GstThumbnailerClass;
+typedef struct _GstThumbnailer        GstThumbnailer;
+
+GType gst_thumbnailer_get_type (void) G_GNUC_CONST;
+void  gst_thumbnailer_register (TumblerProviderPlugin *plugin);
+
+G_END_DECLS;
+
+#endif /* !__GST_THUMBNAILER_H__ */



More information about the Xfce4-commits mailing list