From 13233fc0898442e5ed44db0702dce3f095f100e3 Mon Sep 17 00:00:00 2001 From: Ross Burton Date: Mon, 31 Jan 2011 15:48:34 +0000 Subject: [PATCH] Add GStreamer-based thumbnailer for video thumbnails --- acinclude.m4 | 24 ++ configure.ac | 7 + plugins/Makefile.am | 1 + plugins/gst-thumbnailer/Makefile.am | 64 ++++ plugins/gst-thumbnailer/gst-helper.c | 192 ++++++++++ plugins/gst-thumbnailer/gst-helper.h | 35 ++ plugins/gst-thumbnailer/gst-thumbnailer-plugin.c | 93 +++++ 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, 1046 insertions(+), 0 deletions(-) create mode 100644 plugins/gst-thumbnailer/Makefile.am create mode 100644 plugins/gst-thumbnailer/gst-helper.c create mode 100644 plugins/gst-thumbnailer/gst-helper.h create mode 100644 plugins/gst-thumbnailer/gst-thumbnailer-plugin.c create mode 100644 plugins/gst-thumbnailer/gst-thumbnailer-provider.c create mode 100644 plugins/gst-thumbnailer/gst-thumbnailer-provider.h create mode 100644 plugins/gst-thumbnailer/gst-thumbnailer.c create mode 100644 plugins/gst-thumbnailer/gst-thumbnailer.h 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/gst-thumbnailer/Makefile.am b/plugins/gst-thumbnailer/Makefile.am new file mode 100644 index 0000000..24ed555 --- /dev/null +++ b/plugins/gst-thumbnailer/Makefile.am @@ -0,0 +1,64 @@ +# vi:set ts=8 sw=8 noet ai nocindent: +# - +# Copyright (c) 2009 Jannis Pohlmann +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free +# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. + +if TUMBLER_GSTREAMER_THUMBNAILER + +tumbler_plugindir = $(libdir)/tumbler-$(TUMBLER_VERSION_API)/plugins +tumbler_plugin_LTLIBRARIES = \ + tumbler-gst-thumbnailer.la + +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_gst_thumbnailer_la_CFLAGS = \ + -I$(top_builddir) \ + -I$(top_builddir)/plugins \ + -I$(top_srcdir) \ + -I$(top_srcdir)/plugins \ + -DG_LOG_DOMAIN=\"tumbler-gst-thumbnailer\" \ + -DPACKAGE_LOCALE_DIR=\"$(localedir)\" \ + $(GDK_PIXBUF_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(GIO_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(PLATFORM_CPPFLAGS) + +tumbler_gst_thumbnailer_la_LDFLAGS = \ + -avoid-version \ + -export-dynamic \ + -module \ + $(PLATFORM_LDFLAGS) + +tumbler_gst_thumbnailer_la_LIBADD = \ + $(top_builddir)/tumbler/libtumbler-$(TUMBLER_VERSION_API).la \ + $(GDK_PIXBUF_LIBS) \ + $(GSTREAMER_LIBS) \ + $(GIO_LIBS) \ + $(GLIB_LIBS) + +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 + +#include +#include +#include +#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 +#include +#include + +G_BEGIN_DECLS + +GdkPixbuf *gst_helper_convert_buffer_to_pixbuf (GstBuffer *buffer, + GCancellable *cancellable, + TumblerThumbnailFlavor *flavour); + +G_END_DECLS + +#endif diff --git a/plugins/gst-thumbnailer/gst-thumbnailer-plugin.c b/plugins/gst-thumbnailer/gst-thumbnailer-plugin.c new file mode 100644 index 0000000..5b302bb --- /dev/null +++ b/plugins/gst-thumbnailer/gst-thumbnailer-plugin.c @@ -0,0 +1,93 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* + * Copyright (c) 2011 Intel Corporation + * + * Author: Ross Burton + * + * 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 +#endif + +#include +#include +#include + +#include + +#include +#include + + + +G_MODULE_EXPORT void tumbler_plugin_initialize (TumblerProviderPlugin *plugin); +G_MODULE_EXPORT void tumbler_plugin_shutdown (void); +G_MODULE_EXPORT void tumbler_plugin_get_types (const GType **types, + gint *n_types); + + + +static GType type_list[1]; + + + +void +tumbler_plugin_initialize (TumblerProviderPlugin *plugin) +{ + const gchar *mismatch; + + /* verify that the tumbler versions are compatible */ + mismatch = tumbler_check_version (TUMBLER_MAJOR_VERSION, TUMBLER_MINOR_VERSION, + TUMBLER_MICRO_VERSION); + if (G_UNLIKELY (mismatch != NULL)) + { + g_warning (_("Version mismatch: %s"), mismatch); + return; + } + +#ifdef DEBUG + g_message (_("Initializing the Tumbler GStreamer Thumbnailer plugin")); +#endif + + /* register the types provided by this plugin */ + gst_thumbnailer_register (plugin); + gst_thumbnailer_provider_register (plugin); + + /* set up the plugin provider type list */ + type_list[0] = TYPE_GST_THUMBNAILER_PROVIDER; +} + + + +void +tumbler_plugin_shutdown (void) +{ +#ifdef DEBUG + g_message (_("Shutting down the Tumbler GStreamer Thumbnailer plugin")); +#endif +} + + + +void +tumbler_plugin_get_types (const GType **types, + gint *n_types) +{ + *types = type_list; + *n_types = G_N_ELEMENTS (type_list); +} 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 + * + * 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 +#endif + +#include +#include +#include + +#include + +#include +#include + + +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 + * + * 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 + +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 + * + * 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 +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#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 + * + * 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 + +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__ */ -- 1.7.2.3