totem r5859 - in trunk: . src
- From: pwithnall svn gnome org
- To: svn-commits-list gnome org
- Subject: totem r5859 - in trunk: . src
- Date: Mon, 15 Dec 2008 07:28:49 +0000 (UTC)
Author: pwithnall
Date: Mon Dec 15 07:28:48 2008
New Revision: 5859
URL: http://svn.gnome.org/viewvc/totem?rev=5859&view=rev
Log:
2008-12-15 Philip Withnall <philip tecnocode co uk>
* src/totem-video-thumbnailer.c (scale_pixbuf), (save_pixbuf),
(cairo_surface_to_pixbuf), (create_gallery), (main): Add an
option
to totem-video-thumbnailer to create galleries of screenshots
from
videos. (Helps: #561088)
Modified:
trunk/ChangeLog
trunk/src/totem-video-thumbnailer.c
Modified: trunk/src/totem-video-thumbnailer.c
==============================================================================
--- trunk/src/totem-video-thumbnailer.c (original)
+++ trunk/src/totem-video-thumbnailer.c Mon Dec 15 07:28:48 2008
@@ -30,13 +30,16 @@
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
+#include <cairo/cairo.h>
#include <unistd.h>
#include <string.h>
+#include <math.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "bacon-video-widget.h"
+#include "video-utils.h"
#include "totem-resources.h"
/* #define THUMB_DEBUG */
@@ -48,12 +51,16 @@
#endif
#define BORING_IMAGE_VARIANCE 256.0 /* Tweak this if necessary */
+#define GALLERY_MIN 3 /* minimum number of screenshots in a gallery */
+#define GALLERY_MAX 30 /* maximum number of screenshots in a gallery */
+#define GALLERY_HEADER_HEIGHT 66 /* header height (in pixels) for the gallery */
static gboolean jpeg_output = FALSE;
static gboolean output_size = 128;
static gboolean time_limit = TRUE;
static gboolean verbose = FALSE;
static gboolean g_fatal_warnings = FALSE;
+static gint gallery = -1;
static gint64 second_index = -1;
static char **filenames = NULL;
@@ -255,27 +262,20 @@
return (variance > BORING_IMAGE_VARIANCE);
}
-static void
-save_pixbuf (GdkPixbuf *pixbuf, const char *path,
- const char *video_path, int size, gboolean is_still)
+static GdkPixbuf *
+scale_pixbuf (GdkPixbuf *pixbuf, int size, gboolean is_still)
{
+ GdkPixbuf *result;
int width, height;
- GdkPixbuf *small, *with_holes;
- GError *err = NULL;
- char *a_width, *a_height;
- height = gdk_pixbuf_get_height (pixbuf);
- width = gdk_pixbuf_get_width (pixbuf);
-
- if (size <= 256)
- {
+ if (size <= 256) {
int d_width, d_height;
+ GdkPixbuf *small;
height = gdk_pixbuf_get_height (pixbuf);
width = gdk_pixbuf_get_width (pixbuf);
- if (width > height)
- {
+ if (width > height) {
d_width = size;
d_height = size * height / width;
} else {
@@ -283,22 +283,41 @@
d_width = size * width / height;
}
- small = gdk_pixbuf_scale_simple (pixbuf, d_width, d_height,
- GDK_INTERP_TILES);
+ small = gdk_pixbuf_scale_simple (pixbuf, d_width, d_height, GDK_INTERP_TILES);
if (is_still == FALSE) {
- with_holes = add_holes_to_pixbuf_small (small,
- d_width, d_height);
- g_return_if_fail (with_holes != NULL);
+ result = add_holes_to_pixbuf_small (small, d_width, d_height);
+ g_return_val_if_fail (result != NULL, NULL);
g_object_unref (small);
} else {
- with_holes = small;
+ result = small;
}
} else {
- with_holes = add_holes_to_pixbuf_large (pixbuf, size);
- g_return_if_fail (with_holes != NULL);
+ result = add_holes_to_pixbuf_large (pixbuf, size);
+ g_return_val_if_fail (result != NULL, NULL);
}
+ return result;
+}
+
+static void
+save_pixbuf (GdkPixbuf *pixbuf, const char *path,
+ const char *video_path, int size, gboolean is_still)
+{
+ int width, height;
+ GdkPixbuf *with_holes;
+ GError *err = NULL;
+ char *a_width, *a_height;
+
+ height = gdk_pixbuf_get_height (pixbuf);
+ width = gdk_pixbuf_get_width (pixbuf);
+
+ /* If we're outputting a gallery, don't scale the pixbuf or add borders */
+ if (gallery == -1)
+ with_holes = scale_pixbuf (pixbuf, size, is_still);
+ else
+ with_holes = g_object_ref (pixbuf);
+
a_width = g_strdup_printf ("%d", width);
a_height = g_strdup_printf ("%d", height);
@@ -311,8 +330,7 @@
g_free (a_width);
g_free (a_height);
- if (err != NULL)
- {
+ if (err != NULL) {
g_print ("totem-video-thumbnailer couldn't write the thumbnail '%s' for video '%s': %s\n", path, video_path, err->message);
g_error_free (err);
} else {
@@ -462,13 +480,250 @@
}
}
+static GdkPixbuf *
+cairo_surface_to_pixbuf (cairo_surface_t *surface)
+{
+ gint stride, width, height, x, y;
+ guchar *data, *output, *output_pixel;
+
+ /* This doesn't deal with alpha --- it simply converts the 4-byte Cairo ARGB
+ * format to the 3-byte GdkPixbuf packed RGB format. */
+ g_assert (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_RGB24);
+
+ stride = cairo_image_surface_get_stride (surface);
+ width = cairo_image_surface_get_width (surface);
+ height = cairo_image_surface_get_height (surface);
+ data = cairo_image_surface_get_data (surface);
+
+ output = g_malloc (stride * height);
+ output_pixel = output;
+
+ for (y = 0; y < height; y++) {
+ guint32 *row = (guint32*) (data + y * stride);
+
+ for (x = 0; x < width; x++) {
+ output_pixel[0] = (row[x] & 0x00ff0000) >> 16;
+ output_pixel[1] = (row[x] & 0x0000ff00) >> 8;
+ output_pixel[2] = (row[x] & 0x000000ff);
+
+ output_pixel += 3;
+ }
+ }
+
+ return gdk_pixbuf_new_from_data (output, GDK_COLORSPACE_RGB, FALSE, 8,
+ width, height, width * 3,
+ (GdkPixbufDestroyNotify) g_free, NULL);
+}
+
+
+static GdkPixbuf *
+create_gallery (BaconVideoWidget *bvw, const char *input, const char *output)
+{
+ GdkPixbuf *screenshot, *pixbuf = NULL;
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ PangoLayout *layout;
+ PangoFontDescription *font_desc;
+ gint64 stream_length, screenshot_interval, pos;
+ guint columns, rows, current_column, current_row, x, y;
+ gint screenshot_width, screenshot_height = 0, x_padding = 0, y_padding = 0;
+ gfloat scale = 1.0;
+ gchar *header_text, *duration_text, *filename;
+
+ /* Calculate how many screenshots we're going to take */
+ stream_length = bacon_video_widget_get_stream_length (bvw) / 1000;
+
+ /* As a default, we have one screenshot per minute of stream,
+ * but adjusted so we don't have any gaps in the resulting gallery. */
+ if (gallery == 0) {
+ gallery = stream_length / 60;
+
+ while (gallery % 3 != 0 &&
+ gallery % 4 != 0 &&
+ gallery % 5 != 0) {
+ gallery++;
+ }
+ }
+
+ if (gallery < GALLERY_MIN)
+ gallery = GALLERY_MIN;
+ if (gallery > GALLERY_MAX)
+ gallery = GALLERY_MAX;
+ screenshot_interval = stream_length / gallery;
+
+ PROGRESS_DEBUG ("Producing gallery of %u screenshots, taken at %" G_GINT64_FORMAT " second intervals throughout a %" G_GINT64_FORMAT " second-long stream.",
+ gallery, screenshot_interval, stream_length);
+
+ /* Calculate how to arrange the screenshots so we don't get ones orphaned on the last row.
+ * At this point, only deal with arrangements of 3, 4 or 5 columns. */
+ y = G_MAXUINT;
+ for (x = 3; x <= 5; x++) {
+ if (gallery % x == 0 || x - gallery % x < y) {
+ y = x - gallery % x;
+ columns = x;
+
+ /* Have we found an optimal solution already? */
+ if (y == x)
+ break;
+ }
+ }
+
+ rows = ceil ((gfloat) gallery / (gfloat) columns);
+
+ PROGRESS_DEBUG ("Outputting as %u rows and %u columns.", rows, columns);
+
+ /* Take the screenshots and composite them into a pixbuf */
+ current_column = current_row = x = y = 0;
+ for (pos = screenshot_interval; pos <= stream_length; pos += screenshot_interval) {
+ screenshot = capture_frame_at_time (bvw, input, output, pos);
+
+ if (pixbuf == NULL) {
+ screenshot_width = gdk_pixbuf_get_width (screenshot);
+ screenshot_height = gdk_pixbuf_get_height (screenshot);
+
+ /* Calculate a scaling factor so that screenshot_width -> output_size */
+ scale = (float) output_size / (float) screenshot_width;
+
+ x_padding = x = MAX (output_size * 0.05, 1);
+ y_padding = y = MAX (scale * screenshot_height * 0.05, 1);
+
+ PROGRESS_DEBUG ("Scaling each screenshot by %f.", scale);
+
+ /* Create our massive pixbuf */
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
+ columns * output_size + (columns + 1) * x_padding,
+ (guint) (rows * scale * screenshot_height + (rows + 1) * y_padding));
+ gdk_pixbuf_fill (pixbuf, 0x000000ff);
+
+ PROGRESS_DEBUG ("Created output pixbuf (%ux%u).", gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf));
+ }
+
+ /* Composite the screenshot into our gallery */
+ gdk_pixbuf_composite (screenshot, pixbuf,
+ x, y, output_size, scale * screenshot_height,
+ (gdouble) x, (gdouble) y, scale, scale,
+ GDK_INTERP_BILINEAR, 255);
+ g_object_unref (screenshot);
+
+ PROGRESS_DEBUG ("Composited screenshot from %" G_GINT64_FORMAT " seconds (address %u) at (%u,%u).",
+ pos, GPOINTER_TO_UINT (screenshot), x, y);
+
+ current_column = (current_column + 1) % columns;
+ x += output_size + x_padding;
+ if (current_column == 0) {
+ x = x_padding;
+ y += scale * screenshot_height + y_padding;
+ current_row++;
+ }
+ }
+
+ PROGRESS_DEBUG ("Converting pixbuf to a Cairo surface.");
+
+ /* Load the pixbuf into a Cairo surface and overlay the text. The height is the height of
+ * the gallery plus the necessary height for 3 lines of header (at ~18px each), plus some
+ * extra padding. */
+ surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf) + GALLERY_HEADER_HEIGHT + y_padding);
+ cr = cairo_create (surface);
+ cairo_surface_destroy (surface);
+
+ /* First, copy across the gallery pixbuf */
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0.0, GALLERY_HEADER_HEIGHT + y_padding);
+ cairo_rectangle (cr, 0.0, GALLERY_HEADER_HEIGHT + y_padding, gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf));
+ cairo_fill (cr);
+ g_object_unref (pixbuf);
+
+ /* Build the header information */
+ duration_text = totem_time_to_string (stream_length * 1000);
+ filename = g_path_get_basename (input);
+ header_text = g_strdup_printf (_("<b>Filename</b>: %s\n<b>Resolution</b>: %d\u00D7%d\n<b>Duration</b>: %s"),
+ filename,
+ screenshot_width,
+ screenshot_height,
+ duration_text);
+ g_free (duration_text);
+ g_free (filename);
+
+ PROGRESS_DEBUG ("Writing header text with Pango.");
+
+ /* Write out some header information */
+ layout = pango_cairo_create_layout (cr);
+ font_desc = pango_font_description_from_string ("Sans 18px");
+ pango_layout_set_font_description (layout, font_desc);
+ pango_font_description_free (font_desc);
+
+ pango_layout_set_markup (layout, header_text, -1);
+ g_free (header_text);
+
+ cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* white */
+ cairo_move_to (cr, (gdouble) x_padding, (gdouble) y_padding);
+ pango_cairo_show_layout (cr, layout);
+
+ /* Go through each screenshot and write its timestamp */
+ current_column = current_row = 0;
+ x = x_padding + output_size;
+ y = y_padding * 2 + GALLERY_HEADER_HEIGHT + scale * screenshot_height;
+
+ font_desc = pango_font_description_from_string ("Sans 10px");
+ pango_layout_set_font_description (layout, font_desc);
+ pango_font_description_free (font_desc);
+
+ PROGRESS_DEBUG ("Writing screenshot timestamps with Pango.");
+
+ for (pos = screenshot_interval; pos <= stream_length; pos += screenshot_interval) {
+ gchar *timestamp_text;
+ gint layout_width, layout_height;
+
+ timestamp_text = totem_time_to_string (pos * 1000);
+
+ pango_layout_set_text (layout, timestamp_text, -1);
+ pango_layout_get_pixel_size (layout, &layout_width, &layout_height);
+
+ /* Display the timestamp in the bottom-right corner of the current screenshot */
+ cairo_move_to (cr, x - layout_width - 0.02 * output_size, y - layout_height - 0.02 * scale * screenshot_height);
+
+ /* We have to stroke the text so it's visible against screenshots of the same
+ * foreground color. */
+ pango_cairo_layout_path (cr, layout);
+ cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
+ cairo_stroke_preserve (cr);
+ cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* white */
+ cairo_fill (cr);
+
+ PROGRESS_DEBUG ("Writing timestamp \"%s\" at (%f,%f).", timestamp_text,
+ x - layout_width - 0.02 * output_size,
+ y - layout_height - 0.02 * scale * screenshot_height);
+
+ g_free (timestamp_text);
+
+ current_column = (current_column + 1) % columns;
+ x += output_size + x_padding;
+ if (current_column == 0) {
+ x = x_padding + output_size;
+ y += scale * screenshot_height + y_padding;
+ current_row++;
+ }
+ }
+
+ g_object_unref (layout);
+
+ PROGRESS_DEBUG ("Converting Cairo surface back to pixbuf.");
+
+ /* Create a new pixbuf from the Cairo context */
+ pixbuf = cairo_surface_to_pixbuf (cairo_get_target (cr));
+ cairo_destroy (cr);
+
+ return pixbuf;
+}
+
static const GOptionEntry entries[] = {
{ "jpeg", 'j', 0, G_OPTION_ARG_NONE, &jpeg_output, "Output the thumbnail as a JPEG instead of PNG", NULL },
- { "size", 's', 0, G_OPTION_ARG_INT, &output_size, "Size of the thumbnail in pixels", NULL },
+ { "size", 's', 0, G_OPTION_ARG_INT, &output_size, "Size of the thumbnail in pixels (with --gallery sets the size of individual screenshots)", NULL },
{ "no-limit", 'l', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &time_limit, "Don't limit the thumbnailing time to 30 seconds", NULL },
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Output debug information", NULL },
- { "time", 't', 0, G_OPTION_ARG_INT64, &second_index, "Choose this time (in seconds) as the thumbnail", NULL },
- {"g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &g_fatal_warnings, "Make all warnings fatal", NULL},
+ { "time", 't', 0, G_OPTION_ARG_INT64, &second_index, "Choose this time (in seconds) as the thumbnail (can't be used with --gallery)", NULL },
+ { "g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &g_fatal_warnings, "Make all warnings fatal", NULL },
+ { "gallery", 'g', 0, G_OPTION_ARG_INT, &gallery, "Output a gallery of the given number (0 is default) of screenshots (can't be used with --time)", NULL },
{ G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, "[FILE...]" },
{ NULL }
};
@@ -513,7 +768,8 @@
g_log_set_always_fatal (fatal_mask);
}
- if (filenames == NULL || g_strv_length (filenames) != 2) {
+ if (filenames == NULL || g_strv_length (filenames) != 2 ||
+ (second_index != -1 && gallery != -1)) {
char *help;
help = g_option_context_get_help (context, FALSE, NULL);
g_print ("%s", help);
@@ -565,13 +821,18 @@
}
PROGRESS_DEBUG("Started playing file");
- /* If the user has told us to use a frame at a specific second
- * into the video, just use that frame no matter how boring it
- * is */
- if(second_index != -1)
- pixbuf = capture_frame_at_time (bvw, input, output, second_index);
- else
- pixbuf = capture_interesting_frame (bvw, input, output);
+ if (gallery == -1) {
+ /* If the user has told us to use a frame at a specific second
+ * into the video, just use that frame no matter how boring it
+ * is */
+ if (second_index != -1)
+ pixbuf = capture_frame_at_time (bvw, input, output, second_index);
+ else
+ pixbuf = capture_interesting_frame (bvw, input, output);
+ } else {
+ /* We're producing a gallery of screenshots from throughout the file */
+ pixbuf = create_gallery (bvw, input, output);
+ }
/* Cleanup */
bacon_video_widget_close (bvw);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]