[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