summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorNick Schermer <nick@xfce.org>2012-12-11 20:56:25 +0100
committerNick Schermer <nick@xfce.org>2012-12-27 11:28:27 +0100
commit72a8b6ad2af67a2455ac0da78a9304138a35c429 (patch)
treed2b48f9ab4ec8c8f62ed1d491264a2399700ffc1 /plugins
parentf2dba5074031aa2faa447ec30ac107debdda7f26 (diff)
downloadtumbler-72a8b6ad2af67a2455ac0da78a9304138a35c429.tar.gz
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
Diffstat (limited to 'plugins')
-rw-r--r--plugins/Makefile.am1
-rw-r--r--plugins/cover-thumbnailer/Makefile.am64
-rw-r--r--plugins/cover-thumbnailer/cover-thumbnailer-plugin.c93
-rw-r--r--plugins/cover-thumbnailer/cover-thumbnailer-provider.c200
-rw-r--r--plugins/cover-thumbnailer/cover-thumbnailer-provider.h43
-rw-r--r--plugins/cover-thumbnailer/cover-thumbnailer.c708
-rw-r--r--plugins/cover-thumbnailer/cover-thumbnailer.h43
7 files changed, 1152 insertions, 0 deletions
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/cover-thumbnailer/Makefile.am b/plugins/cover-thumbnailer/Makefile.am
new file mode 100644
index 0000000..7ffb891
--- /dev/null
+++ b/plugins/cover-thumbnailer/Makefile.am
@@ -0,0 +1,64 @@
+# vi:set ts=8 sw=8 noet ai nocindent:
+# -
+# Copyright (c) 2012 Nick Schermer <nick@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
+# 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_COVER_THUMBNAILER
+
+tumbler_plugindir = $(libdir)/tumbler-$(TUMBLER_VERSION_API)/plugins
+tumbler_plugin_LTLIBRARIES = \
+ tumbler-cover-thumbnailer.la
+
+tumbler_cover_thumbnailer_la_SOURCES = \
+ cover-thumbnailer-plugin.c \
+ cover-thumbnailer-provider.c \
+ cover-thumbnailer-provider.h \
+ cover-thumbnailer.c \
+ cover-thumbnailer.h
+
+tumbler_cover_thumbnailer_la_CFLAGS = \
+ -I$(top_builddir) \
+ -I$(top_builddir)/plugins \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/plugins \
+ -DG_LOG_DOMAIN=\"tumbler-cover-thumbnailer\" \
+ -DPACKAGE_LOCALE_DIR=\"$(localedir)\" \
+ $(GDK_PIXBUF_CFLAGS) \
+ $(GIO_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(CURL_CFLAGS) \
+ $(PLATFORM_CFLAGS) \
+ $(PLATFORM_CPPFLAGS)
+
+tumbler_cover_thumbnailer_la_LDFLAGS = \
+ -avoid-version \
+ -export-dynamic \
+ -module \
+ $(PLATFORM_LDFLAGS)
+
+tumbler_cover_thumbnailer_la_LIBADD = \
+ $(top_builddir)/tumbler/libtumbler-$(TUMBLER_VERSION_API).la \
+ $(GDK_PIXBUF_LIBS) \
+ $(GIO_LIBS) \
+ $(GLIB_LIBS) \
+ $(CURL_LIBS) \
+ -lm
+
+tumbler_cover_thumbnailer_la_DEPENDENCIES = \
+ $(top_builddir)/tumbler/libtumbler-$(TUMBLER_VERSION_API).la
+
+endif
diff --git a/plugins/cover-thumbnailer/cover-thumbnailer-plugin.c b/plugins/cover-thumbnailer/cover-thumbnailer-plugin.c
new file mode 100644
index 0000000..b612ea6
--- /dev/null
+++ b/plugins/cover-thumbnailer/cover-thumbnailer-plugin.c
@@ -0,0 +1,93 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*-
+ * Copyright (c) 2009 Jannis Pohlmann <jannis@xfce.org>
+ * Copyright (c) 2011 Nick Schermer <nick@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/gi18n.h>
+#include <glib-object.h>
+
+#include <tumbler/tumbler.h>
+
+#include <cover-thumbnailer/cover-thumbnailer-provider.h>
+#include <cover-thumbnailer/cover-thumbnailer.h>
+
+
+
+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 Cover Thumbnailer plugin"));
+#endif
+
+ /* register the types provided by this plugin */
+ cover_thumbnailer_register (plugin);
+ cover_thumbnailer_provider_register (plugin);
+
+ /* set up the plugin provider type list */
+ type_list[0] = TYPE_COVER_THUMBNAILER_PROVIDER;
+}
+
+
+
+void
+tumbler_plugin_shutdown (void)
+{
+#ifdef DEBUG
+ g_message (_("Shutting down the Tumbler Cover 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/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@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@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@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@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@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__ */