[Xfce4-commits] <tumbler:master> New gstreamer thumbnailer based on totem-thumbnailer.

Nick Schermer noreply at xfce.org
Sat Dec 8 22:58:01 CET 2012


Updating branch refs/heads/master
         to 27c1d836b6e14083fe6c4e0da0255a3ead40eb31 (commit)
       from df93ffb651100b18ec2896ae2f7d995ddcf155f6 (commit)

commit 27c1d836b6e14083fe6c4e0da0255a3ead40eb31
Author: Nick Schermer <nick at xfce.org>
Date:   Sat Dec 8 22:55:16 2012 +0100

    New gstreamer thumbnailer based on totem-thumbnailer.
    
    Better code that should fix most of the existing bugs
    in the bug tracker. Improved cancellation and handling
    of aborted streams.
    
    Also depend on gstreamer-1.0.

 acinclude.m4                              |    2 +-
 plugins/gst-thumbnailer/Makefile.am       |    4 +-
 plugins/gst-thumbnailer/gst-helper.c      |  194 ---------
 plugins/gst-thumbnailer/gst-helper.h      |   37 --
 plugins/gst-thumbnailer/gst-thumbnailer.c |  673 +++++++++++++++++++----------
 5 files changed, 451 insertions(+), 459 deletions(-)

diff --git a/acinclude.m4 b/acinclude.m4
index 9841563..e84b8c3 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -158,7 +158,7 @@ if test x"$ac_tumbler_gstreamer_thumbnailer" = x"yes"; then
   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])
+    PKG_CHECK_MODULES([GSTREAMER], [gstreamer-1.0], [], [ac_tumbler_gstreamer_thumbnailer=no])
   ], [ac_tumbler_gstreamer_thumbnailer=no])
 fi
 
diff --git a/plugins/gst-thumbnailer/Makefile.am b/plugins/gst-thumbnailer/Makefile.am
index a4d2ac1..87c33c3 100644
--- a/plugins/gst-thumbnailer/Makefile.am
+++ b/plugins/gst-thumbnailer/Makefile.am
@@ -28,9 +28,7 @@ tumbler_gst_thumbnailer_la_SOURCES =					\
 	gst-thumbnailer-provider.c					\
 	gst-thumbnailer-provider.h					\
 	gst-thumbnailer.c						\
-	gst-thumbnailer.h						\
-	gst-helper.c							\
-	gst-helper.h
+	gst-thumbnailer.h
 
 tumbler_gst_thumbnailer_la_CFLAGS =					\
 	-I$(top_builddir)						\
diff --git a/plugins/gst-thumbnailer/gst-helper.c b/plugins/gst-thumbnailer/gst-helper.c
deleted file mode 100644
index eab8185..0000000
--- a/plugins/gst-thumbnailer/gst-helper.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/* vi:set et ai sw=2 sts=2 ts=2: */
-/*
- * Originally from Bickley - a meta data management framework.
- * Copyright © 2008, 2011 Intel Corporation.
- *
- * 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.
- */
-
-#include "config.h"
-#include <string.h>
-
-#include <gdk-pixbuf/gdk-pixbuf.h>
-#include <gst/gst.h>
-#include <tumbler/tumbler.h>
-#include "gst-helper.h"
-
-static void
-push_buffer (GstElement *element,
-             GstBuffer  *out_buffer,
-             GstPad     *pad,
-             GstBuffer  *in_buffer)
-{
-  gst_buffer_set_caps (out_buffer, GST_BUFFER_CAPS (in_buffer));
-  GST_BUFFER_SIZE (out_buffer) = GST_BUFFER_SIZE (in_buffer);
-  memcpy (GST_BUFFER_DATA (out_buffer), GST_BUFFER_DATA (in_buffer),
-          GST_BUFFER_SIZE (in_buffer));
-}
-
-static void
-pull_buffer (GstElement *element,
-             GstBuffer  *in_buffer,
-             GstPad     *pad,
-             GstBuffer **out_buffer)
-{
-  *out_buffer = gst_buffer_ref (in_buffer);
-}
-
-GdkPixbuf *
-gst_helper_convert_buffer_to_pixbuf (GstBuffer    *buffer,
-                                     GCancellable *cancellable,
-                                     TumblerThumbnailFlavor *flavour)
-{
-  GstCaps *pb_caps;
-  GstElement *pipeline;
-  GstBuffer *out_buffer = NULL;
-  GstElement *src, *sink, *colorspace, *scale, *filter;
-  GstBus *bus;
-  GstMessage *msg;
-  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));
-
-  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
deleted file mode 100644
index 85fa25a..0000000
--- a/plugins/gst-thumbnailer/gst-helper.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/* vi:set et ai sw=2 sts=2 ts=2: */
-/*
- * Originally from Bickley - a meta data management framework.
- * Copyright © 2008, 2011 Intel Corporation.
- *
- * 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_HELPER_H__
-#define __GST_HELPER_H__
-
-#include <gdk-pixbuf/gdk-pixbuf.h>
-#include <gst/gst.h>
-#include <tumbler/tumbler.h>
-
-G_BEGIN_DECLS
-
-GdkPixbuf *gst_helper_convert_buffer_to_pixbuf (GstBuffer              *buffer,
-                                                GCancellable           *cancellable,
-                                                TumblerThumbnailFlavor *flavour);
-
-G_END_DECLS
-
-#endif
diff --git a/plugins/gst-thumbnailer/gst-thumbnailer.c b/plugins/gst-thumbnailer/gst-thumbnailer.c
index 5dcef19..4db1d72 100644
--- a/plugins/gst-thumbnailer/gst-thumbnailer.c
+++ b/plugins/gst-thumbnailer/gst-thumbnailer.c
@@ -1,8 +1,6 @@
 /* vi:set et ai sw=2 sts=2 ts=2: */
 /*
- * Copyright (c) 2011 Intel Corporation
- *
- * Author: Ross Burton <ross at linux.intel.com>
+ * Copyright (C) 2003,2004 Bastien Nocera <hadess at hadess.net>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -11,13 +9,16 @@
  *
  * 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 
+ * 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 
+ * 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.
+ *
+ * Most of the code is taken from the totem-video-thumbnailer and
+ * made suitable for Tumbler by Nick Schermer.
  */
 
 #ifdef HAVE_CONFIG_H
@@ -31,18 +32,17 @@
 #include <glib-object.h>
 #include <gdk-pixbuf/gdk-pixbuf.h>
 #include <tumbler/tumbler.h>
+
 #include <gst/gst.h>
+#include <gst/tag/tag.h>
 
 #include "gst-thumbnailer.h"
-#include "gst-helper.h"
 
 
 
-#ifdef DEBUG
-#define LOG(...) g_message (__VA_ARGS__)
-#else
-#define LOG(...)
-#endif
+#define BORING_IMAGE_VARIANCE       256.0    /* tweak this if necessary */
+#define TUMBLER_GST_PLAY_FLAG_VIDEO (1 << 0) /* from GstPlayFlags */
+#define TUMBLER_GST_PLAY_FLAG_AUDIO (1 << 1) /* from GstPlayFlags */
 
 
 
@@ -103,298 +103,523 @@ 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)
+static GdkPixbuf *
+gst_thumbnailer_buffer_to_pixbuf (GstBuffer *buffer)
 {
-  int width, height, y, rowstride;
-  gboolean has_alpha;
-  guint32 histogram[4][4][4] = {{{0,},},};
-  guchar *pixels;
-  guint pxl_count = 0, count, i;
+  GstMapInfo       info;
+  GdkPixbuf       *pixbuf = NULL;
+  GdkPixbufLoader *loader;
 
-  g_assert (GDK_IS_PIXBUF (pixbuf));
+  if (!gst_buffer_map (buffer, &info, GST_MAP_READ))
+    return NULL;
 
-  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);
+  loader = gdk_pixbuf_loader_new ();
 
-  pixels = gdk_pixbuf_get_pixels (pixbuf);
-  for (y = 0; y < height; y++)
+  if (gdk_pixbuf_loader_write (loader, info.data, info.size, NULL)
+      && gdk_pixbuf_loader_close (loader, NULL))
     {
-      guchar *row = pixels + (y * rowstride);
-      int c;
+      pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+      if (pixbuf != NULL)
+        g_object_ref (pixbuf);
+    }
 
-      for (c = 0; c < width; c++)
-        {
-          guchar r, g, b;
+  g_object_unref (loader);
 
-          r = row[0];
-          g = row[1];
-          b = row[2];
+  gst_buffer_unmap (buffer, &info);
 
-          histogram[r / 64][g / 64][b / 64]++;
+  return pixbuf;
+}
 
-          if (has_alpha)
-            {
-              row += 4;
-            }
-          else
-            {
-              row += 3;
-            }
 
-          pxl_count++;
+
+static GdkPixbuf *
+gst_thumbnailer_cover_from_tags (GstTagList   *tags,
+                                 GCancellable *cancellable)
+{
+  GstSample          *cover = NULL;
+  guint               i;
+  GstSample          *sample;
+  GstCaps            *caps;
+  const GstStructure *caps_struct;
+  gint                type;
+  GstBuffer          *buffer;
+  GdkPixbuf          *pixbuf = NULL;
+
+  for (i = 0; ; i++)
+    {
+      if (g_cancellable_is_cancelled (cancellable))
+        break;
+
+      /* look for image in the tags */
+      if (!gst_tag_list_get_sample_index (tags, GST_TAG_IMAGE, i, &sample))
+        break;
+
+      caps = gst_sample_get_caps (sample);
+      caps_struct = gst_caps_get_structure (caps, 0);
+      gst_structure_get_enum (caps_struct,
+                              "image-type",
+                              GST_TYPE_TAG_IMAGE_TYPE,
+                              &type);
+
+      if (type == GST_TAG_IMAGE_TYPE_FRONT_COVER)
+        {
+          /* found the cover */
+          cover = sample;
+          break;
         }
+
+      gst_sample_unref (sample);
     }
 
-  count = 0;
-  for (i = 0; i < 4; i++)
+  if (cover == NULL
+      && !g_cancellable_is_cancelled (cancellable))
     {
-      int j;
-      for (j = 0; j < 4; j++)
-        {
-          int k;
+      /* look for preview image */
+      gst_tag_list_get_sample_index (tags, GST_TAG_PREVIEW_IMAGE, 0, &cover);
+    }
 
-          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++;
-            }
-        }
+  if (cover != NULL)
+    {
+      /* create image */
+      buffer = gst_sample_get_buffer (cover);
+      pixbuf = gst_thumbnailer_buffer_to_pixbuf (buffer);
+      gst_sample_unref (cover);
     }
 
-  /* Image is boring if there is only 1 bin with > 1% of pixels */
-  return count > 1;
+  return pixbuf;
 }
 
 
 
-/*
- * 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)
+static GdkPixbuf *
+gst_thumbnailer_cover_by_name (GstElement   *play,
+                               const gchar  *signal_name,
+                               GCancellable *cancellable)
 {
-  GstStateChangeReturn state;
-  GstElement          *audio_sink;
-  GstElement          *playbin;
-  GstElement          *video_sink;
-  gint                 count = 0;
-  gint                 n_video = 0;
+  GstTagList *tags = NULL;
+  GdkPixbuf  *cover;
 
-  g_assert (info);
+  g_signal_emit_by_name (G_OBJECT (play), signal_name, 0, &tags);
 
-  playbin = gst_element_factory_make ("playbin2", "playbin");
-  g_assert (playbin);
+  if (tags == NULL)
+    return FALSE;
 
-  audio_sink = gst_element_factory_make ("fakesink", "audiosink");
-  g_assert (audio_sink);
+  /* check the tags for a cover */
+  cover = gst_thumbnailer_cover_from_tags (tags, cancellable);
+  gst_tag_list_free (tags);
 
-  video_sink = gst_element_factory_make ("fakesink", "videosink");
-  g_assert (video_sink);
+  return cover;
+}
 
-  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++;
+static GdkPixbuf *
+gst_thumbnailer_cover (GstElement   *play,
+                       GCancellable *cancellable)
+{
+  GdkPixbuf *cover;
+
+  cover = gst_thumbnailer_cover_by_name (play, "get-audio-tags", cancellable);
+  if (cover == NULL)
+    cover = gst_thumbnailer_cover_by_name (play, "get-video-tags", cancellable);
+
+  return cover;
+}
+
+
+
+static gboolean
+gst_thumbnailer_has_video (GstElement *play)
+{
+  guint n_video;
+  g_object_get (play, "n-video", &n_video, NULL);
+  return n_video > 0;
+}
+
+
+
+static void
+gst_thumbnailer_destroy_pixbuf (guchar   *pixbuf,
+                                gpointer  data)
+{
+  gst_sample_unref (GST_SAMPLE (data));
+}
+
+
+
+static gboolean
+gst_thumbnailer_pixbuf_interesting (GdkPixbuf *pixbuf)
+{
+  gint    rowstride;
+  gint    height;
+  guchar *buffer;
+  gint    length;
+  gint    i;
+  gfloat  x_bar = 0.0f;
+  gfloat  variance = 0.0f;
+  gfloat  temp;
+
+  rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+  height = gdk_pixbuf_get_height (pixbuf);
+  length = (rowstride * height);
+
+  buffer = gdk_pixbuf_get_pixels (pixbuf);
 
-      /* Spin mainloop so we can pick up the cancels */
-      while (g_main_context_pending (NULL))
-        g_main_context_iteration (NULL, FALSE);
+  /* calculate the x-bar */
+  for (i = 0; i < length; i++)
+    x_bar += (gfloat) buffer[i];
+  x_bar /= (gfloat) length;
+
+  /* calculate the variance */
+  for (i = 0; i < length; i++)
+    {
+      temp = ((gfloat) buffer[i] - x_bar);
+      variance += temp * temp;
     }
 
-  if (state == GST_STATE_CHANGE_FAILURE || state == GST_STATE_CHANGE_ASYNC)
+  return (variance > BORING_IMAGE_VARIANCE);
+}
+
+
+
+static GdkPixbuf *
+gst_thumbnailer_capture_frame (GstElement *play)
+{
+  GstCaps      *to_caps;
+  GstSample    *sample = NULL;
+  GdkPixbuf    *pixbuf = NULL;
+  GstStructure *s;
+  GstCaps      *sample_caps;
+  gint          width = 0, height = 0;
+  GstMemory    *memory;
+  GstMapInfo    info;
+
+  /* desired output format (RGB24) */
+  to_caps = gst_caps_new_simple ("video/x-raw",
+                                 "format", G_TYPE_STRING, "RGB",
+                                 "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
+                                 NULL);
+
+  /* get the frame */
+  g_signal_emit_by_name (play, "convert-sample", to_caps, &sample);
+  gst_caps_unref (to_caps);
+
+  if (sample == NULL)
+    return NULL;
+
+  sample_caps = gst_sample_get_caps (sample);
+  if (sample_caps == NULL)
     {
-      LOG ("failed to or still changing state, aborting (state change %d)", state);
-      g_object_unref (playbin);
+      /* no caps on output buffer */
+      gst_sample_unref (sample);
       return NULL;
     }
 
-  g_object_get (playbin, "n-video", &n_video, NULL);
-  if (n_video == 0)
+  /* size of the frame */
+  s = gst_caps_get_structure (sample_caps, 0);
+  gst_structure_get_int (s, "width", &width);
+  gst_structure_get_int (s, "height", &height);
+  if (width <= 0 || height <= 0)
     {
-      LOG ("no video stream, aborting");
-      g_object_unref (playbin);
+      /* invalid size */
+      gst_sample_unref (sample);
       return NULL;
     }
 
-  return playbin;
+  /* get the memory block of the buffer */
+  memory = gst_buffer_get_memory (gst_sample_get_buffer (sample), 0);
+  if (gst_memory_map (memory, &info, GST_MAP_READ))
+    {
+      /* create pixmap for the data */
+      pixbuf = gdk_pixbuf_new_from_data (info.data,
+                                         GDK_COLORSPACE_RGB, FALSE, 8,
+                                         width, height,
+                                         GST_ROUND_UP_4 (width * 3),
+                                         gst_thumbnailer_destroy_pixbuf,
+                                         sample);
+
+      /* release memory */
+      gst_memory_unmap (memory, &info);
+    }
+
+  gst_memory_unref (memory);
+
+  /* release sample if pixbuf failed */
+  if (pixbuf == NULL)
+    gst_sample_unref (sample);
+
+  return pixbuf;
 }
 
 
 
-/*
- * Get the total duration in nanoseconds of the stream.
- */
-static gint64
-get_duration (GstElement *playbin)
+static GdkPixbuf *
+gst_thumbnailer_capture_interesting_frame (GstElement   *play,
+                                           gint64        duration,
+                                           GCancellable *cancellable)
 {
-  GstFormat format = GST_FORMAT_TIME;
-  gint64 duration = 0;
+  GdkPixbuf     *pixbuf = NULL;
+  guint          n;
+  const gdouble  offsets[] = { 1.0 / 3.0, 2.0 / 3.0, 0.1, 0.9, 0.5 };
+  gint64         seek_time;
+
+  /* video has no duration, capture 1st frame */
+  if (duration == -1)
+    {
+      if (!g_cancellable_is_cancelled (cancellable))
+        return gst_thumbnailer_capture_frame (play);
+      else
+        return NULL;
+    }
+
+  for (n = 0; n < G_N_ELEMENTS (offsets); n++)
+    {
+      /* check if we should abort */
+      if (g_cancellable_is_cancelled (cancellable))
+        break;
 
-  g_assert (playbin);
+      /* seek to offset */
+      seek_time = offsets[n] * duration;
+      gst_element_seek (play, 1.0,
+                        GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
+                        GST_SEEK_TYPE_SET, seek_time * GST_MSECOND,
+                        GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
 
-  gst_element_query_duration (playbin, &format, &duration);
+      /* wait for the seek to complete */
+      gst_element_get_state (play, NULL, NULL, GST_CLOCK_TIME_NONE);
 
-  return duration;
-}
+      /* check if we should abort */
+      if (g_cancellable_is_cancelled (cancellable))
+        break;
 
+      /* get the frame */
+      pixbuf = gst_thumbnailer_capture_frame (play);
+      if (pixbuf == NULL)
+        continue;
 
+      /* check if image is interesting or end of loop */
+      if (n + 1 == G_N_ELEMENTS (offsets)
+          || gst_thumbnailer_pixbuf_interesting (pixbuf))
+        break;
 
-/*
- * Generate a thumbnail for @info.
- */
-static void
-gst_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer,
-                        GCancellable               *cancellable,
-                        TumblerFileInfo            *info)
-{
-  TumblerThumbnailFlavor *flavour;
-  TumblerThumbnail       *thumbnail;
-  TumblerImageData        data;
-  /* These positions are taken from Totem */
-  const gdouble           positions[] = {
-    1.0 / 3.0,
-    2.0 / 3.0,
-    0.1,
-    0.9,
-    0.5
-  };
-  GstElement             *playbin;
-  GdkPixbuf              *shot;
-  GstBuffer              *frame;
-  GError                 *error = NULL;
-  gint64                  duration;
-  guint                   i;
+      /* continue looking for something better */
+      g_object_unref (pixbuf);
+      pixbuf = NULL;
+    }
+
+  return pixbuf;
+}
 
-  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)
+static GstBusSyncReply
+gst_thumbnailer_error_handler (GstBus     *bus,
+                               GstMessage *message,
+                               gpointer    user_data)
+{
+  GCancellable *cancellable = user_data;
+
+  switch (GST_MESSAGE_TYPE (message))
     {
-      /* TODO: emit an error, but the specification won't let me. */
-      return;
+      case GST_MESSAGE_ERROR:
+      case GST_MESSAGE_EOS:
+        /* stop */
+        g_cancellable_cancel (cancellable);
+        return GST_BUS_DROP;
+
+      default:
+        return GST_BUS_PASS;
     }
+}
 
-  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]);
+static gboolean
+gst_thumbnailer_play_start (GstElement   *play,
+                            GCancellable *cancellable)
+{
+  GstBus     *bus;
+  gboolean    terminate = FALSE;
+  GstMessage *message;
+  gboolean    async_received = FALSE;
 
-      gst_element_seek_simple (playbin,
-                               GST_FORMAT_TIME,
-                               GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
-                               (gint64)(positions[i] * duration));
+  /* pause to prepare for seeking */
+  gst_element_set_state (play, GST_STATE_PAUSED);
 
-      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;
-        }
+  bus = gst_element_get_bus (play);
 
-      g_object_get (playbin, "frame", &frame, NULL);
+  while (!terminate
+         && !g_cancellable_is_cancelled (cancellable))
+    {
+      message = gst_bus_timed_pop_filtered (bus,
+                                            GST_CLOCK_TIME_NONE,
+                                            GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR);
 
-      if (frame == NULL)
+      switch (GST_MESSAGE_TYPE (message))
         {
-          LOG ("No frame found!");
-          gst_element_set_state (playbin, GST_STATE_NULL);
-          g_object_unref (playbin);
-          continue;
-        }
+        case GST_MESSAGE_ASYNC_DONE:
+          if (GST_MESSAGE_SRC (message) == GST_OBJECT (play))
+            {
+              async_received = TRUE;
+              terminate = TRUE;
+            }
+          break;
 
-      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);
+        case GST_MESSAGE_ERROR:
+          terminate = TRUE;
+          break;
 
-      /* 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");
+        default:
           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)
+      gst_message_unref (message);
+    }
+
+  /* setup the error handler */
+  if (async_received)
+    gst_bus_set_sync_handler (bus, gst_thumbnailer_error_handler, cancellable, NULL);
+
+  gst_object_unref (bus);
+
+  return async_received;
+}
+
+
+
+static GstElement *
+gst_thumbnailer_play_init (TumblerFileInfo *info)
+{
+  GstElement *play;
+  GstElement *audio_sink;
+  GstElement *video_sink;
+
+  /* prepare play factory */
+  play = gst_element_factory_make ("playbin", "play");
+  audio_sink = gst_element_factory_make ("fakesink", "audio-fake-sink");
+  video_sink = gst_element_factory_make ("fakesink", "video-fake-sink");
+  g_object_set (video_sink, "sync", TRUE, NULL);
+
+  g_object_set (play,
+                "uri", tumbler_file_info_get_uri (info),
+                "audio-sink", audio_sink,
+                "video-sink", video_sink,
+                "flags", TUMBLER_GST_PLAY_FLAG_VIDEO | TUMBLER_GST_PLAY_FLAG_AUDIO,
+                NULL);
+
+  return play;
+}
+
+
+
+static GdkPixbuf *
+gst_thumbnailer_scale_pixbuf (GdkPixbuf *source,
+                              gint       dest_width,
+                              gint       dest_height)
+{
+  gdouble wratio;
+  gdouble hratio;
+  gint    source_width;
+  gint    source_height;
+
+  /* determine source pixbuf dimensions */
+  source_width  = gdk_pixbuf_get_width  (source);
+  source_height = gdk_pixbuf_get_height (source);
+
+  /* don't do anything if there is no need to resize */
+  if (source_width <= dest_width && source_height <= dest_height)
+    return g_object_ref (source);
+
+  /* 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);
+
+  /* scale the pixbuf down to the desired size */
+  return gdk_pixbuf_scale_simple (source, MAX (dest_width, 1),
+                                  MAX (dest_height, 1),
+                                  GDK_INTERP_BILINEAR);
+}
+
+
+
+static void
+gst_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer,
+                        GCancellable               *cancellable,
+                        TumblerFileInfo            *info)
+{
+  GstElement             *play;
+  GdkPixbuf              *pixbuf = NULL;
+  gint64                  duration;
+  TumblerImageData        data;
+  GError                 *error = NULL;
+  TumblerThumbnail       *thumbnail;
+  gint                    width, height;
+  TumblerThumbnailFlavor *flavor;
+  GdkPixbuf              *scaled;
+
+  /* check for early cancellation */
+  if (g_cancellable_is_cancelled (cancellable))
+    return;
+
+  /* prepare factory */
+  play = gst_thumbnailer_play_init (info);
+
+  if (gst_thumbnailer_play_start (play, cancellable))
+    {
+      /* check for covers in the file */
+      pixbuf = gst_thumbnailer_cover (play, cancellable);
+
+      /* extract cover from video stream */
+      if (pixbuf == NULL
+          && gst_thumbnailer_has_video (play))
         {
-          g_object_unref (shot);
-          shot = NULL;
-        }
+          /* get the length of the video track */
+          if (gst_element_query_duration (play, GST_FORMAT_TIME, &duration)
+              && duration != -1)
+            duration /= GST_MSECOND;
+          else
+            duration = -1;
 
-      /* Spin mainloop so we can pick up the cancels */
-      while (g_main_context_pending (NULL))
-        g_main_context_iteration (NULL, FALSE);
+          pixbuf = gst_thumbnailer_capture_interesting_frame (play, duration, cancellable);
+        }
     }
 
-  gst_element_set_state (playbin, GST_STATE_NULL);
-  g_object_unref (playbin);
+  /* stop factory */
+  gst_element_set_state (play, GST_STATE_NULL);
+  g_object_unref (play);
 
-  if (shot)
+  if (G_LIKELY (pixbuf != NULL))
     {
-      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);
+      thumbnail = tumbler_file_info_get_thumbnail (info);
+
+      /* get size of dest thumb */
+      flavor = tumbler_thumbnail_get_flavor (thumbnail);
+      tumbler_thumbnail_flavor_get_size (flavor, &width, &height);
+
+      /* scale to correct size */
+      scaled = gst_thumbnailer_scale_pixbuf (pixbuf, width, height);
+      g_object_unref (pixbuf);
+      pixbuf = scaled;
+
+      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),
                                          NULL, &error);
 
-      g_object_unref (shot);
+      g_object_unref (pixbuf);
 
       if (error != NULL)
         {


More information about the Xfce4-commits mailing list