[Xfce4-commits] <tumbler:master> Add cover-thumbnailer for movies.

Nick Schermer noreply at xfce.org
Thu Dec 27 11:30:03 CET 2012


Updating branch refs/heads/master
         to 72a8b6ad2af67a2455ac0da78a9304138a35c429 (commit)
       from f2dba5074031aa2faa447ec30ac107debdda7f26 (commit)

commit 72a8b6ad2af67a2455ac0da78a9304138a35c429
Author: Nick Schermer <nick at xfce.org>
Date:   Tue Dec 11 20:56:25 2012 +0100

    Add cover-thumbnailer for movies.
    
    Thumbnailer that tries to download the poster of
    the movie and use it as thumbnail.
    
    Plugin send local information over the internet, so you
    need to explicitly enable the plugin using an rc file.
    
    See http://docs.xfce.org/xfce/thunar/tumbler

 acinclude.m4                                       |   24 +
 configure.ac                                       |    9 +-
 plugins/Makefile.am                                |    1 +
 .../Makefile.am                                    |   30 +-
 .../cover-thumbnailer-plugin.c}                    |   14 +-
 .../cover-thumbnailer/cover-thumbnailer-provider.c |  200 ++++++
 .../cover-thumbnailer/cover-thumbnailer-provider.h |   43 ++
 plugins/cover-thumbnailer/cover-thumbnailer.c      |  708 ++++++++++++++++++++
 plugins/cover-thumbnailer/cover-thumbnailer.h      |   43 ++
 9 files changed, 1050 insertions(+), 22 deletions(-)

diff --git a/acinclude.m4 b/acinclude.m4
index 4105e37..9afa9c9 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -244,6 +244,30 @@ AC_MSG_RESULT([$ac_tumbler_raw_thumbnailer])
 
 
 
+dnl TUMBLER_COVER_THUMBNAILER()
+dnl
+dnl Check whether to build and install the Open Movie Database thumbnailer plugin.
+dnl
+AC_DEFUN([TUMBLER_COVER_THUMBNAILER],
+[
+AC_ARG_ENABLE([pixbuf-thumbnailer], [AC_HELP_STRING([--disable-cover-thumbnailer], [Don't build the Cover thumbnailer plugin])],
+  [ac_tumbler_cover_thumbnailer=$enableval], [ac_tumbler_cover_thumbnailer=yes])
+if test x"$ac_tumbler_cover_thumbnailer" = x"yes"; then
+  dnl Check for gdk-pixbuf
+  PKG_CHECK_MODULES([GDK_PIXBUF], [gdk-pixbuf-2.0 >= 2.14],
+  [
+    dnl CHeck for curl
+    PKG_CHECK_MODULES([CURL], [libcurl], [], [ac_tumbler_cover_thumbnailer=no])
+  ], [ac_tumbler_cover_thumbnailer=no])
+fi
+
+AC_MSG_CHECKING([whether to build the Cover thumbnailer plugin])
+AM_CONDITIONAL([TUMBLER_COVER_THUMBNAILER], [test x"$ac_tumbler_cover_thumbnailer" = x"yes"])
+AC_MSG_RESULT([$ac_tumbler_cover_thumbnailer])
+])
+
+
+
 dnl TUMBLER_XDG_CACHE()
 dnl
 dnl Check whether to build and install the freedesktop.org cache plugin.
diff --git a/configure.ac b/configure.ac
index f41c3a3..084a62e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -151,6 +151,7 @@ PKG_CHECK_MODULES([DBUS_GLIB], [dbus-glib-1 >= 0.72])
 dnl *************************
 dnl *** Check for plugins ***
 dnl *************************
+TUMBLER_COVER_THUMBNAILER()
 TUMBLER_FONT_THUMBNAILER()
 TUMBLER_JPEG_THUMBNAILER()
 TUMBLER_PIXBUF_THUMBNAILER()
@@ -184,6 +185,7 @@ docs/reference/Makefile
 docs/reference/tumbler/Makefile
 docs/reference/tumbler/version.xml
 plugins/Makefile
+plugins/cover-thumbnailer/Makefile
 plugins/font-thumbnailer/Makefile
 plugins/gst-thumbnailer/Makefile
 plugins/jpeg-thumbnailer/Makefile
@@ -206,7 +208,7 @@ dnl ***************************
 echo
 echo "Build Configuration:"
 echo
-echo "  * Debug:                                     $enable_debug"
+echo "  * Debug:                                            $enable_debug"
 echo
 echo "Plugins:"
 echo
@@ -215,6 +217,11 @@ echo "  * GdkPixbuf thumbnailer plugin:                     yes"
 else
 echo "  * GdkPixbuf thumbnailer plugin:                     no"
 fi
+if test x"$ac_tumbler_cover_thumbnailer" = x"yes"; then
+echo "  * Cover thumbnailer plugin:                         yes"
+else
+echo "  * Cover thumbnailer plugin:                         no"
+fi
 if test x"$ac_tumbler_font_thumbnailer" = x"yes"; then
 echo "  * FreeType font thumbnailer plugin:                 yes"
 else
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index aec18cc..f2c9f5f 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -18,6 +18,7 @@
 # Boston, MA 02110-1301, USA.
 
 SUBDIRS =								\
+	cover-thumbnailer						\
 	font-thumbnailer						\
 	gst-thumbnailer							\
 	jpeg-thumbnailer						\
diff --git a/plugins/pixbuf-thumbnailer/Makefile.am b/plugins/cover-thumbnailer/Makefile.am
similarity index 71%
copy from plugins/pixbuf-thumbnailer/Makefile.am
copy to plugins/cover-thumbnailer/Makefile.am
index 2852a46..7ffb891 100644
--- a/plugins/pixbuf-thumbnailer/Makefile.am
+++ b/plugins/cover-thumbnailer/Makefile.am
@@ -1,6 +1,6 @@
 # vi:set ts=8 sw=8 noet ai nocindent:
 # -
-# Copyright (c) 2009-2012 Jannis Pohlmann <jannis at xfce.org>
+# Copyright (c) 2012 Nick Schermer <nick at xfce.org>
 # 
 # This program is free software; you can redistribute it and/or 
 # modify it under the terms of the GNU General Public License as
@@ -17,46 +17,48 @@
 # Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-if TUMBLER_PIXBUF_THUMBNAILER
+if TUMBLER_COVER_THUMBNAILER
 
 tumbler_plugindir = $(libdir)/tumbler-$(TUMBLER_VERSION_API)/plugins
 tumbler_plugin_LTLIBRARIES =						\
-	tumbler-pixbuf-thumbnailer.la
+	tumbler-cover-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_cover_thumbnailer_la_SOURCES =					\
+	cover-thumbnailer-plugin.c					\
+	cover-thumbnailer-provider.c					\
+	cover-thumbnailer-provider.h					\
+	cover-thumbnailer.c						\
+	cover-thumbnailer.h
 
-tumbler_pixbuf_thumbnailer_la_CFLAGS =					\
+tumbler_cover_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-cover-thumbnailer\"			\
 	-DPACKAGE_LOCALE_DIR=\"$(localedir)\"				\
 	$(GDK_PIXBUF_CFLAGS)						\
 	$(GIO_CFLAGS)							\
 	$(GLIB_CFLAGS)							\
+	$(CURL_CFLAGS)							\
 	$(PLATFORM_CFLAGS)						\
 	$(PLATFORM_CPPFLAGS)
 
-tumbler_pixbuf_thumbnailer_la_LDFLAGS =					\
+tumbler_cover_thumbnailer_la_LDFLAGS =					\
 	-avoid-version							\
 	-export-dynamic							\
 	-module								\
 	$(PLATFORM_LDFLAGS)
 
-tumbler_pixbuf_thumbnailer_la_LIBADD =					\
+tumbler_cover_thumbnailer_la_LIBADD =					\
 	$(top_builddir)/tumbler/libtumbler-$(TUMBLER_VERSION_API).la	\
 	$(GDK_PIXBUF_LIBS)						\
 	$(GIO_LIBS)							\
 	$(GLIB_LIBS)							\
+	$(CURL_LIBS)							\
 	-lm
 
-tumbler_pixbuf_thumbnailer_la_DEPENDENCIES =				\
+tumbler_cover_thumbnailer_la_DEPENDENCIES =				\
 	$(top_builddir)/tumbler/libtumbler-$(TUMBLER_VERSION_API).la
 
 endif
diff --git a/plugins/odf-thumbnailer/odf-thumbnailer-plugin.c b/plugins/cover-thumbnailer/cover-thumbnailer-plugin.c
similarity index 84%
copy from plugins/odf-thumbnailer/odf-thumbnailer-plugin.c
copy to plugins/cover-thumbnailer/cover-thumbnailer-plugin.c
index 3463c5a..b612ea6 100644
--- a/plugins/odf-thumbnailer/odf-thumbnailer-plugin.c
+++ b/plugins/cover-thumbnailer/cover-thumbnailer-plugin.c
@@ -29,8 +29,8 @@
 
 #include <tumbler/tumbler.h>
 
-#include <odf-thumbnailer/odf-thumbnailer-provider.h>
-#include <odf-thumbnailer/odf-thumbnailer.h>
+#include <cover-thumbnailer/cover-thumbnailer-provider.h>
+#include <cover-thumbnailer/cover-thumbnailer.h>
 
 
 
@@ -61,15 +61,15 @@ tumbler_plugin_initialize (TumblerProviderPlugin *plugin)
     }
 
 #ifdef DEBUG
-  g_message (_("Initializing the Tumbler ODF Thumbnailer plugin"));
+  g_message (_("Initializing the Tumbler Cover Thumbnailer plugin"));
 #endif
 
   /* register the types provided by this plugin */
-  odf_thumbnailer_register (plugin);
-  odf_thumbnailer_provider_register (plugin);
+  cover_thumbnailer_register (plugin);
+  cover_thumbnailer_provider_register (plugin);
 
   /* set up the plugin provider type list */
-  type_list[0] = TYPE_ODF_THUMBNAILER_PROVIDER;
+  type_list[0] = TYPE_COVER_THUMBNAILER_PROVIDER;
 }
 
 
@@ -78,7 +78,7 @@ void
 tumbler_plugin_shutdown (void)
 {
 #ifdef DEBUG
-  g_message (_("Shutting down the Tumbler ODF Thumbnailer plugin"));
+  g_message (_("Shutting down the Tumbler Cover Thumbnailer plugin"));
 #endif
 }
 
diff --git a/plugins/cover-thumbnailer/cover-thumbnailer-provider.c b/plugins/cover-thumbnailer/cover-thumbnailer-provider.c
new file mode 100644
index 0000000..b29858c
--- /dev/null
+++ b/plugins/cover-thumbnailer/cover-thumbnailer-provider.c
@@ -0,0 +1,200 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*-
+ * Copyright (c) 2012 Nick Schermer <nick at xfce.org>
+ *
+ * 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 <glib/gi18n.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <tumbler/tumbler.h>
+#include <curl/curl.h>
+
+#include <cover-thumbnailer/cover-thumbnailer-provider.h>
+#include <cover-thumbnailer/cover-thumbnailer.h>
+
+
+
+static void   cover_thumbnailer_provider_thumbnailer_provider_init (TumblerThumbnailerProviderIface *iface);
+static GList *cover_thumbnailer_provider_get_thumbnailers          (TumblerThumbnailerProvider      *provider);
+static void   cover_thumbnailer_provider_finalize                  (GObject                         *object);
+
+
+
+struct _CoverThumbnailerProviderClass
+{
+  GObjectClass __parent__;
+};
+
+struct _CoverThumbnailerProvider
+{
+  GObject __parent__;
+
+  /* for themoviedb metadata */
+  gchar  *api_key;
+
+  /* allowed locations */
+  gchar **locations;
+};
+
+
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (CoverThumbnailerProvider,
+                                cover_thumbnailer_provider,
+                                G_TYPE_OBJECT,
+                                0,
+                                TUMBLER_ADD_INTERFACE (TUMBLER_TYPE_THUMBNAILER_PROVIDER,
+                                                       cover_thumbnailer_provider_thumbnailer_provider_init));
+
+
+
+void
+cover_thumbnailer_provider_register (TumblerProviderPlugin *plugin)
+{
+  cover_thumbnailer_provider_register_type (G_TYPE_MODULE (plugin));
+}
+
+
+
+static void
+cover_thumbnailer_provider_class_init (CoverThumbnailerProviderClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = cover_thumbnailer_provider_finalize;
+}
+
+
+
+static void
+cover_thumbnailer_provider_class_finalize (CoverThumbnailerProviderClass *klass)
+{
+}
+
+
+
+static void
+cover_thumbnailer_provider_thumbnailer_provider_init (TumblerThumbnailerProviderIface *iface)
+{
+  iface->get_thumbnailers = cover_thumbnailer_provider_get_thumbnailers;
+}
+
+
+
+static void
+cover_thumbnailer_provider_init (CoverThumbnailerProvider *provider)
+{
+  GKeyFile *keyfile;
+  gchar    *config_dir;
+
+  config_dir = g_build_filename (g_get_user_config_dir (), "tumbler", "cover.rc", NULL);
+  keyfile = g_key_file_new ();
+  if (g_key_file_load_from_file (keyfile, config_dir, G_KEY_FILE_NONE, NULL))
+    {
+      provider->api_key = g_key_file_get_string (keyfile, "TheMovieDB", "API-key", NULL);
+      provider->locations = g_key_file_get_string_list (keyfile, "General", "Locations", NULL, NULL);
+    }
+  g_key_file_free (keyfile);
+  g_free (config_dir);
+
+  /* curl */
+  if (provider->locations != NULL)
+    curl_global_init (CURL_GLOBAL_ALL);
+}
+
+
+
+static void
+cover_thumbnailer_provider_finalize (GObject *object)
+{
+  CoverThumbnailerProvider *provider = COVER_THUMBNAILER_PROVIDER (object);
+
+  g_free (provider->api_key);
+  g_strfreev (provider->locations);
+
+  /* curl */
+  if (provider->locations != NULL)
+    curl_global_cleanup ();
+
+  (*G_OBJECT_CLASS (cover_thumbnailer_provider_parent_class)->finalize) (object);
+}
+
+
+
+static GList *
+cover_thumbnailer_provider_get_thumbnailers (TumblerThumbnailerProvider *provider)
+{
+  CoverThumbnailerProvider *cover = COVER_THUMBNAILER_PROVIDER (provider);
+  CoverThumbnailer         *thumbnailer;
+  GList                    *thumbnailers = NULL;
+  GStrv                     uri_schemes;
+  static const gchar       *mime_types[] =
+  {
+    "video/divx",
+    "video/jpeg",
+    "video/mp4",
+    "video/mp2t",
+    "video/mpeg",
+    "video/quicktime",
+    "video/x-ms-asf",
+    "video/x-ms-wm",
+    "video/x-ms-wmv",
+    "video/x-msvideo",
+    "application/vnd.rn-realmedia",
+    "video/x-matroska",
+    "video/ogg",
+    NULL
+  };
+
+  if (cover->locations != NULL)
+    {
+      /* determine the URI schemes supported by GIO */
+      uri_schemes = tumbler_util_get_supported_uri_schemes ();
+
+      /* create the pixbuf thumbnailer */
+      thumbnailer = g_object_new (TYPE_COVER_THUMBNAILER,
+                                  "uri-schemes", uri_schemes,
+                                  "mime-types", mime_types,
+                                  "locations", cover->locations,
+                                  "api-key", cover->api_key,
+                                  NULL);
+
+      /* add the thumbnailer to the list */
+      thumbnailers = g_list_append (thumbnailers, thumbnailer);
+
+      /* free URI schemes */
+      g_strfreev (uri_schemes);
+    }
+  else
+    {
+      g_print ("\n");
+      g_print (_("Cover thumbnailer is disabled because there are no "
+                 "locations white-listed in the config file. See "
+                 "%s for more information."),
+               "http://docs.xfce.org/xfce/thunar/tumbler");
+      g_print ("\n\n");
+    }
+
+  return thumbnailers;
+}
diff --git a/plugins/cover-thumbnailer/cover-thumbnailer-provider.h b/plugins/cover-thumbnailer/cover-thumbnailer-provider.h
new file mode 100644
index 0000000..7f65337
--- /dev/null
+++ b/plugins/cover-thumbnailer/cover-thumbnailer-provider.h
@@ -0,0 +1,43 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*-
+ * Copyright (c) 2009 Jannis Pohlmann <jannis at xfce.org>
+ *
+ * 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 __COVER_THUMBNAILER_PROVIDER_H__
+#define __COVER_THUMBNAILER_PROVIDER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define TYPE_COVER_THUMBNAILER_PROVIDER            (cover_thumbnailer_provider_get_type ())
+#define COVER_THUMBNAILER_PROVIDER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_COVER_THUMBNAILER_PROVIDER, CoverThumbnailerProvider))
+#define COVER_THUMBNAILER_PROVIDER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_COVER_THUMBNAILER_PROVIDER, CoverThumbnailerProviderClass))
+#define IS_COVER_THUMBNAILER_PROVIDER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_COVER_THUMBNAILER_PROVIDER))
+#define IS_COVER_THUMBNAILER_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_COVER_THUMBNAILER_PROVIDER)
+#define COVER_THUMBNAILER_PROVIDER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_COVER_THUMBNAILER_PROVIDER, CoverThumbnailerProviderClass))
+
+typedef struct _CoverThumbnailerProviderClass CoverThumbnailerProviderClass;
+typedef struct _CoverThumbnailerProvider      CoverThumbnailerProvider;
+
+GType cover_thumbnailer_provider_get_type (void) G_GNUC_CONST;
+void  cover_thumbnailer_provider_register (TumblerProviderPlugin *plugin);
+
+G_END_DECLS
+
+#endif /* !__COVER_THUMBNAILER_PROVIDER_H__ */
diff --git a/plugins/cover-thumbnailer/cover-thumbnailer.c b/plugins/cover-thumbnailer/cover-thumbnailer.c
new file mode 100644
index 0000000..6120dc4
--- /dev/null
+++ b/plugins/cover-thumbnailer/cover-thumbnailer.c
@@ -0,0 +1,708 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*-
+ * Copyright (c) 2012 Nick Schermer <nick at xfce.org>
+ *
+ * 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.
+ *
+ * Parts are based on the gsf-office-thumbnailer in libgsf, which is
+ * written by Federico Mena-Quintero <federico at novell.com>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <tumbler/tumbler.h>
+#include <curl/curl.h>
+
+#include <cover-thumbnailer/cover-thumbnailer.h>
+
+
+
+#define SERIES_PATTERN    "\\b((?:s\\d{1,2}e\\d{1,2}|\\d{1,2}x\\d{1,2}))\\b"
+#define ABBREV_PATTERN    "\\b(\\w{1,}(?:rip|scr)|r5|hdtv|(?:480|720|1080)p|\\.(?:avi|mpe?g|mkv|ts))\\b"
+#define YEAR_PATTERN      "\\b(\\d{4})\\b"
+
+#define OMDBAPI_QUERY_URL "http://www.omdbapi.com/?t="
+
+#define TMDB_BASE_URL     "http://cf2.imgobject.com/t/p/"
+#define TMDB_API_KEY      "4be68d7eab1fbd1b6fd8a3b80a65a95e"
+#define TMDB_QUERY_URL    "http://api.themoviedb.org/3/search/movie?api_key="
+
+
+
+static void cover_thumbnailer_finalize     (GObject                    *object);
+static void cover_thumbnailer_set_property (GObject                    *object,
+                                            guint                       prop_id,
+                                            const GValue               *value,
+                                            GParamSpec                 *pspec);
+static void cover_thumbnailer_create       (TumblerAbstractThumbnailer *thumbnailer,
+                                            GCancellable               *cancellable,
+                                            TumblerFileInfo            *info);
+
+
+
+struct _CoverThumbnailerClass
+{
+  TumblerAbstractThumbnailerClass __parent__;
+};
+
+struct _CoverThumbnailer
+{
+  TumblerAbstractThumbnailer __parent__;
+
+  /* themoviedb api key */
+  gchar  *api_key;
+
+  /* whitelisted locations */
+  GSList *locations;
+
+  /* precompiled */
+  GRegex *series_regex;
+  GRegex *abbrev_regex;
+  GRegex *year_regex;
+};
+
+enum
+{
+  PROP_0,
+  PROP_LOCATIONS,
+  PROP_API_KEY
+};
+
+
+
+G_DEFINE_DYNAMIC_TYPE (CoverThumbnailer,
+                       cover_thumbnailer,
+                       TUMBLER_TYPE_ABSTRACT_THUMBNAILER);
+
+
+
+void
+cover_thumbnailer_register (TumblerProviderPlugin *plugin)
+{
+  cover_thumbnailer_register_type (G_TYPE_MODULE (plugin));
+}
+
+
+
+static void
+cover_thumbnailer_class_init (CoverThumbnailerClass *klass)
+{
+  TumblerAbstractThumbnailerClass *abstractthumbnailer_class;
+  GObjectClass                    *gobject_class;
+
+  abstractthumbnailer_class = TUMBLER_ABSTRACT_THUMBNAILER_CLASS (klass);
+  abstractthumbnailer_class->create = cover_thumbnailer_create;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = cover_thumbnailer_finalize;
+  gobject_class->set_property = cover_thumbnailer_set_property;
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_LOCATIONS,
+                                   g_param_spec_boxed ("locations",
+                                                       "locations",
+                                                       "locations",
+                                                       G_TYPE_STRV,
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_API_KEY,
+                                   g_param_spec_string ("api-key",
+                                                        "api-key",
+                                                        "api-key",
+                                                        NULL,
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_WRITABLE));
+}
+
+
+
+static void
+cover_thumbnailer_class_finalize (CoverThumbnailerClass *klass)
+{
+}
+
+
+
+static void
+cover_thumbnailer_init (CoverThumbnailer *thumbnailer)
+{
+  /* prepare the regular expressions */
+  thumbnailer->series_regex = g_regex_new (SERIES_PATTERN, G_REGEX_CASELESS, 0, NULL);
+  thumbnailer->abbrev_regex = g_regex_new (ABBREV_PATTERN, G_REGEX_CASELESS, 0, NULL);
+  thumbnailer->year_regex = g_regex_new (YEAR_PATTERN, 0, 0, NULL);
+}
+
+
+
+static void
+cover_thumbnailer_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  CoverThumbnailer     *cover = COVER_THUMBNAILER (object);
+  guint                i;
+  const gchar * const *locations;
+  GFile               *gfile;
+
+  switch (prop_id)
+    {
+    case PROP_API_KEY:
+      cover->api_key = g_value_dup_string (value);
+      break;
+
+    case PROP_LOCATIONS:
+      locations = g_value_get_boxed (value);
+      g_assert (locations != NULL);
+      for (i = 0; locations[i] != NULL; i++)
+        {
+          gfile = g_file_new_for_commandline_arg (locations[i]);
+          cover->locations = g_slist_prepend (cover->locations, gfile);
+        }
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+
+static void
+cover_thumbnailer_finalize (GObject *object)
+{
+  CoverThumbnailer *cover = COVER_THUMBNAILER (object);
+
+  /* cleanup */
+  g_regex_unref (cover->series_regex);
+  g_regex_unref (cover->abbrev_regex);
+  g_regex_unref (cover->year_regex);
+
+  g_free (cover->api_key);
+
+  g_slist_foreach (cover->locations, (GFunc) g_object_unref, NULL);
+  g_slist_free (cover->locations);
+
+  (*G_OBJECT_CLASS (cover_thumbnailer_parent_class)->finalize) (object);
+}
+
+
+
+static void
+cover_thumbnailer_size_prepared (GdkPixbufLoader        *loader,
+                                 gint                    source_width,
+                                 gint                    source_height,
+                                 TumblerThumbnailFlavor *flavor)
+{
+
+  gint    dest_width;
+  gint    dest_height;
+  gdouble hratio;
+  gdouble wratio;
+
+  g_return_if_fail (GDK_IS_PIXBUF_LOADER (loader));
+  g_return_if_fail (TUMBLER_IS_THUMBNAIL_FLAVOR (flavor));
+
+  /* get the destination size */
+  tumbler_thumbnail_flavor_get_size (flavor, &dest_width, &dest_height);
+
+  if (source_width <= dest_width && source_height <= dest_height)
+    {
+      /* do not scale the image */
+      dest_width = source_width;
+      dest_height = source_height;
+    }
+  else
+    {
+      /* determine which axis needs to be scaled down more */
+      wratio = (gdouble) source_width / (gdouble) dest_width;
+      hratio = (gdouble) source_height / (gdouble) dest_height;
+
+      /* adjust the other axis */
+      if (hratio > wratio)
+        dest_width = rint (source_width / hratio);
+     else
+        dest_height = rint (source_height / wratio);
+    }
+
+  gdk_pixbuf_loader_set_size (loader, MAX (dest_width, 1), MAX (dest_height, 1));
+}
+
+
+
+static size_t
+cover_thumbnailer_load_pixbuf_write (gpointer data,
+                                     size_t   size,
+                                     size_t   nmemb,
+                                     gpointer user_data)
+{
+  GdkPixbufLoader *loader = GDK_PIXBUF_LOADER (user_data);
+  size_t           len = size * nmemb;
+  GError          *err = NULL;
+
+  g_return_val_if_fail (GDK_IS_PIXBUF_LOADER (loader), 0);
+
+  /* write to the loader */
+  if (!gdk_pixbuf_loader_write (loader, data, len, &err))
+    {
+      g_critical ("Failed to write to pixbuf loader: %s", err->message);
+      g_error_free (err);
+    }
+
+  return len;
+}
+
+
+
+static size_t
+cover_thumbnailer_load_contents_write (gpointer data,
+                                       size_t   size,
+                                       size_t   nmemb,
+                                       gpointer user_data)
+{
+  GString *contents = user_data;
+  size_t   len = size * nmemb;
+
+  g_string_append_len (contents, data, len);
+
+  return len;
+}
+
+
+
+static gint
+cover_thumbnailer_check_progress (gpointer user_data,
+                                  gdouble  dltotal,
+                                  gdouble  dlnow,
+                                  gdouble  ultotal,
+                                  gdouble  ulnow)
+{
+  GCancellable *cancellable = G_CANCELLABLE (user_data);
+
+  g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), 1);
+
+  /* return 1 to stop the download, 0 continue */
+  return g_cancellable_is_cancelled (cancellable);
+}
+
+
+
+static CURL *
+cover_thumbnailer_load_prepare (const gchar  *url,
+                                GCancellable *cancellable)
+{
+  CURL *curl_handle;
+
+  g_return_val_if_fail (g_str_has_prefix (url, "http://"), NULL);
+  g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), NULL);
+
+  /* curl downloader */
+  curl_handle = curl_easy_init ();
+  curl_easy_setopt (curl_handle, CURLOPT_URL, url);
+  curl_easy_setopt (curl_handle, CURLOPT_USERAGENT, PACKAGE_NAME "/" PACKAGE_VERSION);
+
+  /* cancellable check */
+  curl_easy_setopt (curl_handle, CURLOPT_PROGRESSFUNCTION, cover_thumbnailer_check_progress);
+  curl_easy_setopt (curl_handle, CURLOPT_PROGRESSDATA, cancellable);
+  curl_easy_setopt (curl_handle, CURLOPT_NOPROGRESS, FALSE);
+
+  return curl_handle;
+}
+
+
+
+static GdkPixbuf *
+cover_thumbnailer_load_pixbuf (const gchar             *url,
+                               TumblerThumbnailFlavor  *flavor,
+                               GCancellable            *cancellable,
+                               GError                 **error)
+{
+  CURL            *curl_handle;
+  GdkPixbufLoader *loader;
+  GdkPixbuf       *pixbuf = NULL;
+  gint             ret;
+
+  g_return_val_if_fail (url != NULL, NULL);
+  g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+  g_return_val_if_fail (TUMBLER_IS_THUMBNAIL_FLAVOR (flavor), NULL);
+
+  if (g_cancellable_is_cancelled (cancellable))
+    return NULL;
+
+  /* create a pixbuf loader */
+  loader = gdk_pixbuf_loader_new ();
+  g_signal_connect (loader, "size-prepared",
+                    G_CALLBACK (cover_thumbnailer_size_prepared), flavor);
+
+  /* download the image into a pixbuf loader */
+  curl_handle = cover_thumbnailer_load_prepare (url, cancellable);
+  curl_easy_setopt (curl_handle, CURLOPT_WRITEFUNCTION, cover_thumbnailer_load_pixbuf_write);
+  curl_easy_setopt (curl_handle, CURLOPT_WRITEDATA, loader);
+  ret = curl_easy_perform (curl_handle);
+  curl_easy_cleanup (curl_handle);
+
+  /* check if everything went fine */
+  if (gdk_pixbuf_loader_close (loader, error)
+      && ret == 0
+      && !g_cancellable_is_cancelled (cancellable))
+    {
+      /* take the pixbuf */
+      pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+      if (G_LIKELY (pixbuf != NULL))
+        g_object_ref (pixbuf);
+    }
+  else if (ret != 0 && error == NULL)
+    {
+      /* curl error */
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   _("Failed to load the poster image \"%s\""), url);
+    }
+
+  g_object_unref (loader);
+
+  return pixbuf;
+}
+
+
+
+static gchar *
+cover_thumbnailer_load_contents (const gchar   *uri,
+                                 GCancellable  *cancellable,
+                                 GError       **error)
+{
+  GString *contents;
+  CURL    *curl_handle;
+  gint     ret;
+
+  if (g_cancellable_is_cancelled (cancellable))
+    return NULL;
+
+  /* prepare buffer */
+  contents = g_string_new (NULL);
+
+  /* download metadata */
+  curl_handle = cover_thumbnailer_load_prepare (uri, cancellable);
+  curl_easy_setopt (curl_handle, CURLOPT_WRITEFUNCTION, cover_thumbnailer_load_contents_write);
+  curl_easy_setopt (curl_handle, CURLOPT_WRITEDATA, contents);
+  ret = curl_easy_perform (curl_handle);
+  curl_easy_cleanup (curl_handle);
+
+  if (ret != 0)
+    {
+      /* curl error */
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   _("Failed to load the metadata from \"%s\""), uri);
+    }
+
+  return g_string_free (contents, ret != 0);
+}
+
+
+
+static gboolean
+cover_thumbnailer_get_title (CoverThumbnailer  *cover,
+                             GFile            *gfile,
+                             gchar           **ret_title,
+                             gchar           **ret_year)
+{
+  gchar       *basename;
+  gboolean     is_series;
+  GMatchInfo  *match_info;
+  gint         start_pos;
+  gint         end_pos;
+  gchar       *year = NULL;
+  GString     *title;
+  const gchar *p;
+  gboolean     append_space;
+  gunichar     uchar;
+  gboolean     succeed;
+
+  g_return_val_if_fail (G_IS_FILE (gfile), FALSE);
+  g_return_val_if_fail (ret_title != NULL, FALSE);
+  g_return_val_if_fail (ret_year != NULL, FALSE);
+
+  /* get the basename */
+  basename = g_file_get_basename (gfile);
+
+  /* check if the title looks like a serie */
+  is_series = g_regex_match (cover->series_regex, basename, 0, &match_info);
+
+  /* if this is not a serie, look for other filename crap */
+  if (is_series
+      || g_regex_match (cover->abbrev_regex, basename, 0, &match_info))
+    {
+      /* remove series or abbrev suffix from the filename */
+      if (g_match_info_fetch_pos (match_info, 0, &start_pos, NULL)
+          && start_pos > 0)
+        basename[start_pos] = '\0';
+      g_match_info_free (match_info);
+
+      if (!is_series)
+        {
+          /* for non-series, look for a year in the title */
+          if (g_regex_match (cover->year_regex, basename, 0, &match_info))
+            {
+              /* store year and remove the suffix from the title */
+              if (g_match_info_fetch_pos (match_info, 0, &start_pos, &end_pos)
+                  && start_pos > 0
+                  && end_pos > start_pos)
+                {
+                  year = g_strndup (basename + start_pos, end_pos - start_pos);
+                  basename[start_pos] = '\0';
+                }
+              g_match_info_free (match_info);
+            }
+        }
+    }
+
+  /* append the possible title part of the filename */
+  title = g_string_sized_new (strlen (basename));
+  for (p = basename, append_space = FALSE; *p != '\0'; p = g_utf8_next_char (p))
+    {
+      uchar = g_utf8_get_char (p);
+      if (g_unichar_isalnum (uchar)
+          || uchar == '\'')
+        {
+          if (append_space)
+            {
+              g_string_append_c (title, '+');
+              append_space = FALSE;
+            }
+
+          /* append the char */
+          g_string_append_unichar (title, uchar);
+        }
+      else if (title->len > 0)
+        {
+          /* start with a space next time we append a char */
+          append_space = TRUE;
+        }
+    }
+
+  /* finalize */
+  g_free (basename);
+  succeed = title->len > 1;
+  *ret_title = g_string_free (title, !succeed);
+  *ret_year = year;
+
+  return succeed;
+}
+
+
+
+static gchar *
+cover_thumbnailer_poster_url (CoverThumbnailer        *cover,
+                              const gchar             *title,
+                              const gchar             *year,
+                              TumblerThumbnailFlavor  *flavor,
+                              GCancellable            *cancellable,
+                              GError                 **error)
+{
+  gchar       *query;
+  const gchar *needle;
+  const gchar *p;
+  const gchar *k = NULL;
+  gchar       *url_part;
+  gchar       *url = NULL;
+  gchar       *data;
+  gint         dest_width;
+
+  g_return_val_if_fail (TUMBLER_IS_THUMBNAIL_FLAVOR (flavor), NULL);
+  g_return_val_if_fail (IS_COVER_THUMBNAILER (cover), NULL);
+
+  if (G_LIKELY (cover->api_key == NULL))
+    {
+      needle = "\"Poster\":\"http://";
+      query = g_strconcat (OMDBAPI_QUERY_URL, title,
+                           year != NULL ? "&y=" : NULL, year,
+                           NULL);
+    }
+  else
+    {
+      needle = "\"poster_path\":\"/";
+      query = g_strconcat (TMDB_QUERY_URL, cover->api_key,
+                           "&query=", title,
+                           year != NULL ? "&year=" : NULL, year,
+                           NULL);
+    }
+
+  data = cover_thumbnailer_load_contents (query, cancellable, error);
+  g_free (query);
+
+  if (data != NULL)
+    {
+      p = strstr (data, needle);
+      if (p != NULL)
+        {
+          p += strlen (needle);
+          k = strstr (p, ".jpg\"");
+        }
+
+      if (p != NULL && k != NULL)
+        {
+          /* extract poster data from the contents and build a working uri */
+          url_part = g_strndup (p, k - p);
+          if (cover->api_key == NULL)
+            {
+              /* imdb image location */
+              url = g_strconcat ("http://", url_part, ".jpg", NULL);
+            }
+          else
+            {
+              /* see http://api.themoviedb.org/3/configuration?api_key= for the values */
+              tumbler_thumbnail_flavor_get_size (flavor, &dest_width, NULL);
+              url = g_strconcat (TMDB_BASE_URL,
+                                 dest_width <= 154 ? "w154" : "w342", /* optimize for 128 or 256 */
+                                 "/", url_part, ".jpg", NULL);
+            }
+          g_free (url_part);
+        }
+      else
+        {
+          /* check for api key problems */
+          if (cover->api_key != NULL
+              && strstr (data, "Invalid API key") != NULL)
+            {
+              g_printerr ("\n%s.\n\n",
+                          _("Invalid API key, you must be granted a valid "
+                            "key. The Movie DB backend will be disabled."));
+
+              g_free (cover->api_key);
+              cover->api_key = NULL;
+            }
+
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                               _("No poster key found in metadata"));
+        }
+
+      g_free (data);
+    }
+
+  return url;
+}
+
+
+
+static void
+cover_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer,
+                          GCancellable               *cancellable,
+                          TumblerFileInfo            *info)
+{
+  CoverThumbnailer       *cover = COVER_THUMBNAILER (thumbnailer);
+  const gchar            *uri;
+  TumblerThumbnail       *thumbnail;
+  gchar                  *year;
+  gchar                  *title;
+  GdkPixbuf              *pixbuf = NULL;
+  gchar                  *poster_url;
+  GError                 *error = NULL;
+  TumblerImageData        data;
+  TumblerThumbnailFlavor *flavor;
+  GFile                  *gfile;
+  GSList                 *lp;
+
+  /* source file */
+  uri = tumbler_file_info_get_uri (info);
+  gfile = g_file_new_for_uri (uri);
+
+  /* check if file is in allowed destinations */
+  for (lp = cover->locations; lp != NULL; lp = lp->next)
+    if (g_file_has_parent (gfile, lp->data))
+      break;
+
+  if (lp == NULL)
+    {
+      /* location not white-listed */
+      g_object_unref (gfile);
+      g_signal_emit_by_name (thumbnailer, "error", uri,
+                             TUMBLER_ERROR_UNSUPPORTED,
+                             _("Location is not whitelisted in rc file"));
+      return;
+    }
+
+  /* target data */
+  thumbnail = tumbler_file_info_get_thumbnail (info);
+  flavor = tumbler_thumbnail_get_flavor (thumbnail);
+
+  /* extract title from filename */
+  if (cover_thumbnailer_get_title (cover, gfile, &title, &year))
+    {
+      /* request online metadata and return the poster url */
+      poster_url = cover_thumbnailer_poster_url (cover, title, year, flavor, cancellable, &error);
+
+      g_free (title);
+      g_free (year);
+
+      if (poster_url != NULL)
+        {
+          /* download poster and load it in a pixbuf */
+          pixbuf = cover_thumbnailer_load_pixbuf (poster_url, flavor, cancellable, &error);
+          g_free (poster_url);
+        }
+    }
+  else
+    {
+      g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME,
+                           _("Movie title is too short"));
+    }
+
+  if (pixbuf != NULL)
+    {
+      /* prepare the image data */
+      data.data = gdk_pixbuf_get_pixels (pixbuf);
+      data.has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
+      data.bits_per_sample = gdk_pixbuf_get_bits_per_sample (pixbuf);
+      data.width = gdk_pixbuf_get_width (pixbuf);
+      data.height = gdk_pixbuf_get_height (pixbuf);
+      data.rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+      data.colorspace = (TumblerColorspace) gdk_pixbuf_get_colorspace (pixbuf);
+
+      tumbler_thumbnail_save_image_data (thumbnail, &data,
+                                         tumbler_file_info_get_mtime (info),
+                                         cancellable, &error);
+
+      g_object_unref (pixbuf);
+    }
+
+  /* return the status */
+  if (error != NULL)
+    {
+      g_signal_emit_by_name (thumbnailer, "error", uri,
+                             error->code, error->message);
+      g_error_free (error);
+    }
+  else
+    {
+      g_signal_emit_by_name (thumbnailer, "ready", uri);
+    }
+
+  g_object_unref (thumbnail);
+  g_object_unref (flavor);
+  g_object_unref (gfile);
+}
diff --git a/plugins/cover-thumbnailer/cover-thumbnailer.h b/plugins/cover-thumbnailer/cover-thumbnailer.h
new file mode 100644
index 0000000..975be65
--- /dev/null
+++ b/plugins/cover-thumbnailer/cover-thumbnailer.h
@@ -0,0 +1,43 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*-
+ * Copyright (c) 2012 Nick Schermer <nick at xfce.org>
+ *
+ * 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 __COVER_THUMBNAILER_H__
+#define __COVER_THUMBNAILER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define TYPE_COVER_THUMBNAILER            (cover_thumbnailer_get_type ())
+#define COVER_THUMBNAILER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_COVER_THUMBNAILER, CoverThumbnailer))
+#define COVER_THUMBNAILER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_COVER_THUMBNAILER, CoverThumbnailerClass))
+#define IS_COVER_THUMBNAILER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_COVER_THUMBNAILER))
+#define IS_COVER_THUMBNAILER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_COVER_THUMBNAILER)
+#define COVER_THUMBNAILER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_COVER_THUMBNAILER, CoverThumbnailerClass))
+
+typedef struct _CoverThumbnailerClass   CoverThumbnailerClass;
+typedef struct _CoverThumbnailer        CoverThumbnailer;
+
+GType cover_thumbnailer_get_type (void) G_GNUC_CONST;
+void  cover_thumbnailer_register (TumblerProviderPlugin *plugin);
+
+G_END_DECLS
+
+#endif /* !__COVER_THUMBNAILER_H__ */


More information about the Xfce4-commits mailing list