[gthumb] split the gstreamer extension in utils and tools



commit f887a13c3c4242713986c4cd2354953ab9fcd281
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Tue Feb 16 10:42:58 2010 +0100

    split the gstreamer extension in utils and tools

 configure.ac                                       |    7 +-
 extensions/Makefile.am                             |    3 +-
 extensions/gstreamer_tools/Makefile.am             |   39 +
 extensions/gstreamer_tools/actions.c               |  211 ++++
 extensions/gstreamer_tools/actions.h               |   32 +
 extensions/gstreamer_tools/data/Makefile.am        |   18 +
 .../data/gthumb-gstreamer.schemas.in               |   17 +
 extensions/gstreamer_tools/data/ui/Makefile.am     |    5 +
 extensions/gstreamer_tools/data/ui/mediabar.ui     |  230 ++++
 .../gstreamer_tools/data/ui/save-screenshot.ui     |  171 +++
 .../gstreamer_tools.extension.in.in                |   12 +
 extensions/gstreamer_tools/gth-media-viewer-page.c | 1213 ++++++++++++++++++++
 extensions/gstreamer_tools/gth-media-viewer-page.h |   62 +
 .../gth-metadata-provider-gstreamer.c              |  105 ++
 .../gth-metadata-provider-gstreamer.h              |   52 +
 extensions/gstreamer_tools/main.c                  |   84 ++
 extensions/gstreamer_tools/preferences.h           |   28 +
 extensions/gstreamer_utils/Makefile.am             |   33 +
 extensions/gstreamer_utils/gstreamer-utils.c       |  792 +++++++++++++
 extensions/gstreamer_utils/gstreamer-utils.h       |   47 +
 .../gstreamer_utils.extension.in.in                |    6 +
 extensions/gstreamer_utils/gstscreenshot.c         |  259 +++++
 extensions/gstreamer_utils/gstscreenshot.h         |   36 +
 extensions/slideshow/Makefile.am                   |    3 +
 extensions/slideshow/gth-slideshow.c               |   31 +-
 gthumb/gth-main.c                                  |    3 +-
 26 files changed, 3479 insertions(+), 20 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 422fbe7..d2a9905 100644
--- a/configure.ac
+++ b/configure.ac
@@ -411,9 +411,10 @@ extensions/file_tools/Makefile
 extensions/file_tools/data/Makefile
 extensions/file_tools/data/ui/Makefile
 extensions/file_viewer/Makefile
-extensions/gstreamer/Makefile
-extensions/gstreamer/data/Makefile
-extensions/gstreamer/data/ui/Makefile
+extensions/gstreamer_tools/Makefile
+extensions/gstreamer_tools/data/Makefile
+extensions/gstreamer_tools/data/ui/Makefile
+extensions/gstreamer_utils/Makefile
 extensions/image_print/Makefile
 extensions/image_print/data/Makefile
 extensions/image_print/data/ui/Makefile
diff --git a/extensions/Makefile.am b/extensions/Makefile.am
index bfff982..c2a914c 100644
--- a/extensions/Makefile.am
+++ b/extensions/Makefile.am
@@ -10,7 +10,8 @@ SUBDIRS = 			\
 	file_manager		\
 	file_tools		\
 	file_viewer		\
-	gstreamer		\
+	gstreamer_utils		\
+	gstreamer_tools		\
 	image_print		\
 	image_rotation		\
 	image_viewer		\
diff --git a/extensions/gstreamer_tools/Makefile.am b/extensions/gstreamer_tools/Makefile.am
new file mode 100644
index 0000000..d8c8060
--- /dev/null
+++ b/extensions/gstreamer_tools/Makefile.am
@@ -0,0 +1,39 @@
+if ENABLE_GSTREAMER
+
+SUBDIRS = data
+
+extensiondir = $(pkglibdir)/extensions
+extension_LTLIBRARIES = libgstreamer_tools.la
+
+libgstreamer_tools_la_SOURCES = 		\
+	actions.c				\
+	actions.h				\
+	gth-media-viewer-page.c			\
+	gth-media-viewer-page.h			\
+	gth-metadata-provider-gstreamer.c	\
+	gth-metadata-provider-gstreamer.h	\
+	main.c					\
+	preferences.h
+
+libgstreamer_tools_la_CPPFLAGS = $(GTHUMB_CFLAGS) $(GSTREAMER_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
+libgstreamer_tools_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libgstreamer_tools_la_LIBADD = $(GTHUMB_LIBS) $(GSTREAMER_LIBS) ../gstreamer_utils/libgstreamer_utils.la
+libgstreamer_tools_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = gstreamer_tools.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+	$(AM_V_GEN)( sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+	$< > $@ )
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files) 
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/gstreamer_tools/actions.c b/extensions/gstreamer_tools/actions.c
new file mode 100644
index 0000000..3c1aa7b
--- /dev/null
+++ b/extensions/gstreamer_tools/actions.c
@@ -0,0 +1,211 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <gst/gst.h>
+#include <gthumb.h>
+#include <extensions/gstreamer_utils/gstreamer-utils.h>
+#include "gth-media-viewer-page.h"
+#include "preferences.h"
+
+#define MAX_ATTEMPTS 1024
+
+
+/* -- media_viewer_activate_action_screenshot -- */
+
+
+typedef struct {
+	GthBrowser         *browser;
+	GthMediaViewerPage *page;
+	gboolean            playing_before_screenshot;
+	GdkPixbuf          *pixbuf;
+	GthFileData        *file_data;
+} SaveData;
+
+
+static void
+save_date_free (SaveData *save_data)
+{
+	_g_object_unref (save_data->file_data);
+	_g_object_unref (save_data->pixbuf);
+	g_free (save_data);
+}
+
+
+static void
+screenshot_saved_cb (GthFileData *file_data,
+		     GError      *error,
+		     gpointer     user_data)
+{
+	SaveData           *save_data = user_data;
+	GthMediaViewerPage *page = save_data->page;
+
+	if (error != NULL)
+		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (save_data->browser), _("Could not save the file"), &error);
+	else if (save_data->playing_before_screenshot)
+		gst_element_set_state (gth_media_viewer_page_get_playbin (page), GST_STATE_PLAYING);
+
+	save_date_free (save_data);
+}
+
+
+static void
+save_as_response_cb (GtkDialog  *file_sel,
+		     int         response,
+		     SaveData   *save_data)
+{
+	GFile       *file;
+	GFile       *folder;
+	char        *folder_uri;
+	const char  *mime_type;
+
+	if (response != GTK_RESPONSE_OK) {
+		GthMediaViewerPage *page = save_data->page;
+
+		if (save_data->playing_before_screenshot)
+			gst_element_set_state (gth_media_viewer_page_get_playbin (page), GST_STATE_PLAYING);
+		save_date_free (save_data);
+		gtk_widget_destroy (GTK_WIDGET (file_sel));
+		return;
+	}
+
+	if (! gth_file_chooser_dialog_get_file (GTH_FILE_CHOOSER_DIALOG (file_sel), &file, &mime_type))
+		return;
+
+	folder = g_file_get_parent (file);
+	folder_uri = g_file_get_uri (folder);
+	eel_gconf_set_string (PREF_GSTREAMER_SCREESHOT_LOCATION, folder_uri);
+
+	save_data->file_data = gth_file_data_new (file, NULL);
+	gth_file_data_set_mime_type (save_data->file_data, mime_type);
+	_gdk_pixbuf_save_async (save_data->pixbuf,
+				save_data->file_data,
+				mime_type,
+				TRUE,
+				screenshot_saved_cb,
+				save_data);
+
+	gtk_widget_destroy (GTK_WIDGET (file_sel));
+
+	g_free (folder_uri);
+	g_object_unref (folder);
+	g_object_unref (file);
+}
+
+
+static void
+screenshot_ready_cb (GdkPixbuf *pixbuf,
+		     gpointer   user_data)
+{
+	SaveData  *save_data = user_data;
+	GtkWidget *file_sel;
+
+	if (pixbuf == NULL) {
+		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (save_data->browser), _("Could not take a screenshot"), NULL);
+		save_date_free (save_data);
+		return;
+	}
+
+	save_data->pixbuf = pixbuf;
+	file_sel = gth_file_chooser_dialog_new (_("Save Image"), GTK_WINDOW (save_data->browser), "pixbuf-saver");
+
+	{
+		char        *last_uri;
+		GFile       *last_folder;
+		GthFileData *file_data;
+		char        *prefix;
+		char        *display_name;
+		int          attempt;
+
+		last_uri = eel_gconf_get_string (PREF_GSTREAMER_SCREESHOT_LOCATION, "~");
+		if ((last_uri == NULL) || (strcmp (last_uri, "~") == 0))
+			last_folder = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP));
+		else
+			last_folder = g_file_new_for_uri (last_uri);
+		gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (file_sel), last_folder, NULL);
+
+		file_data = gth_media_viewer_page_get_file_data (save_data->page);
+		prefix = _g_utf8_remove_extension (g_file_info_get_display_name (file_data->info));
+		if (prefix == NULL)
+			prefix = g_strdup (C_("Filename", "Screenshot"));
+		display_name = NULL;
+		for (attempt = 1; attempt < MAX_ATTEMPTS; attempt++) {
+			GFile *proposed_file;
+
+			g_free (display_name);
+
+			display_name = g_strdup_printf ("%s-%02d.jpeg", prefix, attempt);
+			proposed_file = g_file_get_child_for_display_name (last_folder, display_name, NULL);
+			if ((proposed_file != NULL) && ! g_file_query_exists (proposed_file, NULL)) {
+				g_object_unref (proposed_file);
+				break;
+			}
+		}
+
+		if (display_name != NULL) {
+			gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (file_sel), display_name);
+			g_free (display_name);
+		}
+
+		g_free (prefix);
+		g_object_unref (last_folder);
+		g_free (last_uri);
+	}
+
+	g_signal_connect (GTK_DIALOG (file_sel),
+			  "response",
+			  G_CALLBACK (save_as_response_cb),
+			  save_data);
+
+	gtk_widget_show (file_sel);
+}
+
+
+void
+media_viewer_activate_action_screenshot (GtkAction          *action,
+				         GthMediaViewerPage *page)
+{
+	GstElement *playbin;
+	SaveData   *save_data;
+	int         video_fps_n;
+	int         video_fps_d;
+
+	playbin = gth_media_viewer_page_get_playbin (page);
+	if (playbin == NULL)
+		return;
+
+	save_data = g_new0 (SaveData, 1);
+	save_data->browser = gth_media_viewer_page_get_browser (page);
+	save_data->page = page;
+	save_data->playing_before_screenshot = gth_media_viewer_page_is_playing (page);
+
+	if (save_data->playing_before_screenshot)
+		gst_element_set_state (playbin, GST_STATE_PAUSED);
+	gth_media_viewer_page_get_video_fps (page, &video_fps_n, &video_fps_d);
+	_gst_playbin_get_current_frame (playbin,
+					video_fps_n,
+					video_fps_d,
+					screenshot_ready_cb,
+					save_data);
+}
diff --git a/extensions/gstreamer_tools/actions.h b/extensions/gstreamer_tools/actions.h
new file mode 100644
index 0000000..52a7759
--- /dev/null
+++ b/extensions/gstreamer_tools/actions.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ACTIONS_H
+#define ACTIONS_H
+
+#include <gtk/gtk.h>
+
+#define DEFINE_ACTION(x) void x (GtkAction *action, gpointer data);
+
+DEFINE_ACTION(media_viewer_activate_action_screenshot)
+
+#endif /* ACTIONS_H */
diff --git a/extensions/gstreamer_tools/data/Makefile.am b/extensions/gstreamer_tools/data/Makefile.am
new file mode 100644
index 0000000..ee3cb38
--- /dev/null
+++ b/extensions/gstreamer_tools/data/Makefile.am
@@ -0,0 +1,18 @@
+SUBDIRS = ui
+
+schemadir = @GCONF_SCHEMA_FILE_DIR@
+schema_in_files = gthumb-gstreamer.schemas.in
+schema_DATA = $(schema_in_files:.schemas.in=.schemas)
+
+ INTLTOOL_SCHEMAS_RULE@
+
+if GCONF_SCHEMAS_INSTALL
+install-data-local:
+	GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(top_builddir)/extensions/gstreamer/data/$(schema_DATA)
+endif
+
+EXTRA_DIST = $(schema_in_files)
+
+CLEANFILES = $(schema_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/gstreamer_tools/data/gthumb-gstreamer.schemas.in b/extensions/gstreamer_tools/data/gthumb-gstreamer.schemas.in
new file mode 100644
index 0000000..0fe9cf1
--- /dev/null
+++ b/extensions/gstreamer_tools/data/gthumb-gstreamer.schemas.in
@@ -0,0 +1,17 @@
+<gconfschemafile>
+    <schemalist>
+
+      <schema>
+	<key>/schemas/apps/gthumb/ext/gstreamer/screenshot_location</key>
+	<applyto>/apps/gthumb/ext/gstreamer/screenshot_location</applyto>
+	<owner>gthumb</owner>
+	<type>string</type>
+	<default>~</default>
+	<locale name="C">
+	  <short></short>
+	  <long></long>
+	</locale>
+      </schema>
+
+    </schemalist>
+</gconfschemafile>
diff --git a/extensions/gstreamer_tools/data/ui/Makefile.am b/extensions/gstreamer_tools/data/ui/Makefile.am
new file mode 100644
index 0000000..18ee18b
--- /dev/null
+++ b/extensions/gstreamer_tools/data/ui/Makefile.am
@@ -0,0 +1,5 @@
+uidir = $(pkgdatadir)/ui
+ui_DATA = mediabar.ui save-screenshot.ui
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/gstreamer_tools/data/ui/mediabar.ui b/extensions/gstreamer_tools/data/ui/mediabar.ui
new file mode 100644
index 0000000..bb59e91
--- /dev/null
+++ b/extensions/gstreamer_tools/data/ui/mediabar.ui
@@ -0,0 +1,230 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkVBox" id="mediabar">
+    <property name="visible">True</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkHBox" id="hbox2">
+        <property name="visible">True</property>
+        <property name="spacing">36</property>
+        <child>
+          <object class="GtkHBox" id="hbox4">
+            <property name="visible">True</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkButton" id="button_play_slower">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="tooltip_text" translatable="yes">Slower</property>
+                <property name="relief">none</property>
+                <child>
+                  <object class="GtkImage" id="image2">
+                    <property name="visible">True</property>
+                    <property name="stock">gtk-media-rewind</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button_play">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="tooltip_text" translatable="yes">Play</property>
+                <property name="relief">none</property>
+                <child>
+                  <object class="GtkImage" id="button_play_image">
+                    <property name="visible">True</property>
+                    <property name="stock">gtk-media-play</property>
+                    <property name="icon-size">3</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button_play_faster">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="tooltip_text" translatable="yes">Faster</property>
+                <property name="relief">none</property>
+                <child>
+                  <object class="GtkImage" id="image3">
+                    <property name="visible">True</property>
+                    <property name="stock">gtk-media-forward</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="hbox3">
+            <property name="visible">True</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkHBox" id="hbox6">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Time:</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkHScale" id="hscale_position">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="adjustment">adjustment_position</property>
+                    <property name="fill_level">100</property>
+                    <property name="draw_value">False</property>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkHBox" id="hbox1">
+                <property name="visible">True</property>
+                <property name="spacing">2</property>
+                <child>
+                  <object class="GtkLabel" id="label_position">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes" comments="this is an empty time">--:--</property>
+                    <property name="width_chars">6</property>
+                  </object>
+                  <packing>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label3">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes" context="time" comments="this is the separator between current position and duration of a media file">/</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label_duration">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">--:--</property>
+                    <property name="width_chars">6</property>
+                  </object>
+                  <packing>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="volume_box">
+            <property name="visible">True</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkToggleButton" id="togglebutton_volume">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="tooltip_text" translatable="yes">Toggle volume</property>
+                <property name="relief">none</property>
+                <child>
+                  <object class="GtkImage" id="togglebutton_volume_image">
+                    <property name="visible">True</property>
+                    <property name="icon_name">audio-volume-high</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkHScale" id="hscale_volume">
+                <property name="width_request">180</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="tooltip_text" translatable="yes">Change volume level</property>
+                <property name="adjustment">adjustment_volume</property>
+                <property name="digits">0</property>
+                <property name="value_pos">right</property>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkAdjustment" id="adjustment_position">
+    <property name="upper">100</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">1</property>
+    <property name="page_size">1</property>
+  </object>
+  <object class="GtkAdjustment" id="adjustment_volume">
+    <property name="upper">100</property>
+    <property name="value">100</property>
+    <property name="step_increment">1</property>
+  </object>
+</interface>
diff --git a/extensions/gstreamer_tools/data/ui/save-screenshot.ui b/extensions/gstreamer_tools/data/ui/save-screenshot.ui
new file mode 100644
index 0000000..1a254c3
--- /dev/null
+++ b/extensions/gstreamer_tools/data/ui/save-screenshot.ui
@@ -0,0 +1,171 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkDialog" id="save_screenshot_dialog">
+    <property name="border_width">5</property>
+    <property name="resizable">False</property>
+    <property name="type_hint">normal</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkHBox" id="hbox1">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkVBox" id="vbox1">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkScrolledWindow" id="preview_scrolledwindow">
+                    <property name="width_request">240</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">never</property>
+                    <property name="vscrollbar_policy">never</property>
+                    <property name="shadow_type">in</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkVBox" id="vbox2">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkTable" id="table1">
+                    <property name="visible">True</property>
+                    <property name="n_rows">2</property>
+                    <property name="n_columns">2</property>
+                    <property name="column_spacing">6</property>
+                    <property name="row_spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="label1">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">_Name:</property>
+                        <property name="use_underline">True</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label2">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">_Destination:</property>
+                        <property name="use_underline">True</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="filename_entry">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="invisible_char">&#x25CF;</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkFileChooserButton" id="destination_filechooserbutton">
+                        <property name="visible">True</property>
+                        <property name="local_only">False</property>
+                        <property name="create_folders">False</property>
+                        <property name="action">select-folder</property>
+                        <property name="title" translatable="yes">Choose a folder</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="cancel_button">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="ok_button">
+                <property name="label">gtk-save</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="0">cancel_button</action-widget>
+      <action-widget response="0">ok_button</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/extensions/gstreamer_tools/gstreamer_tools.extension.in.in b/extensions/gstreamer_tools/gstreamer_tools.extension.in.in
new file mode 100644
index 0000000..a4920a4
--- /dev/null
+++ b/extensions/gstreamer_tools/gstreamer_tools.extension.in.in
@@ -0,0 +1,12 @@
+[Extension]
+_Name=Audio/Video support
+_Description=Play audio and video files.
+_Authors=gthumb development team
+Copyright=Copyright © 2009 The Free Software Foundation, Inc.
+Version=1.0
+Icon=video-x-generic
+
+[Loader]
+Type=module
+File=%LIBRARY%
+Requires=gstreamer_utils
\ No newline at end of file
diff --git a/extensions/gstreamer_tools/gth-media-viewer-page.c b/extensions/gstreamer_tools/gth-media-viewer-page.c
new file mode 100644
index 0000000..7ce5ba5
--- /dev/null
+++ b/extensions/gstreamer_tools/gth-media-viewer-page.c
@@ -0,0 +1,1213 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <gst/gst.h>
+#include <gst/interfaces/xoverlay.h>
+#include <gthumb.h>
+#include <extensions/gstreamer_utils/gstreamer-utils.h>
+#include "actions.h"
+#include "gth-media-viewer-page.h"
+
+
+#define GTH_MEDIA_VIEWER_PAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_MEDIA_VIEWER_PAGE, GthMediaViewerPagePrivate))
+#define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
+#define PROGRESS_DELAY 500
+
+
+struct _GthMediaViewerPagePrivate {
+	GthBrowser     *browser;
+	GtkActionGroup *actions;
+	guint           merge_id;
+	GthFileData    *file_data;
+	GstElement     *playbin;
+	GtkBuilder     *builder;
+	GtkWidget      *area;
+	GtkWidget      *area_box;
+	gboolean        playing;
+	gboolean        paused;
+	gdouble         last_volume;
+	gint64          duration;
+	int             video_fps_n;
+	int             video_fps_d;
+	gboolean        has_video;
+	gboolean        has_audio;
+	gulong          update_progress_id;
+	gdouble         rate;
+	GtkWidget      *mediabar;
+	GtkWidget      *fullscreen_toolbar;
+	gboolean        xwin_assigned;
+	GdkPixbuf      *icon;
+	PangoLayout    *caption_layout;
+	gboolean        block_next_jump;
+};
+
+
+static gpointer gth_media_viewer_page_parent_class = NULL;
+
+
+static const char *media_viewer_ui_info =
+"<ui>"
+"  <toolbar name='ViewerToolBar'>"
+"    <placeholder name='ViewerCommands'>"
+"      <toolitem action='View_Fullscreen'/>"
+"      <toolitem action='MediaViewer_Screenshot'/>"
+"    </placeholder>"
+"  </toolbar>"
+"  <toolbar name='Fullscreen_ToolBar'>"
+"    <placeholder name='ViewerCommands'>"
+"      <toolitem action='MediaViewer_Screenshot'/>"
+"    </placeholder>"
+"  </toolbar>"
+"</ui>";
+
+
+static GtkActionEntry media_viewer_action_entries[] = {
+	{ "MediaViewer_Screenshot", "camera-photo",
+	  N_("Screenshot"), NULL,
+	  N_("Take a screenshot"),
+	  G_CALLBACK (media_viewer_activate_action_screenshot) },
+};
+
+
+static void
+_gth_media_viewer_page_update_caption (GthMediaViewerPage *self)
+{
+	if (self->priv->caption_layout == NULL)
+		return;
+
+	if (self->priv->file_data != NULL) {
+		const char  *text;
+		GthMetadata *metadata;
+
+		text = NULL;
+		metadata = (GthMetadata *) g_file_info_get_attribute_object (self->priv->file_data->info, "general::title");
+		if (metadata != NULL)
+			text = gth_metadata_get_formatted (metadata);
+		else
+			text = g_file_info_get_display_name (self->priv->file_data->info);
+
+		if (text != NULL)
+			pango_layout_set_text (self->priv->caption_layout, text, -1);
+	}
+	else
+		pango_layout_set_text (self->priv->caption_layout, "", -1);
+
+	gtk_widget_queue_draw (GTK_WIDGET (self->priv->area));
+}
+
+
+static void
+video_area_realize_cb (GtkWidget *widget,
+		       gpointer   user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+	self->priv->caption_layout = gtk_widget_create_pango_layout (widget, "");
+	_gth_media_viewer_page_update_caption (self);
+}
+
+
+static void
+video_area_unrealize_cb (GtkWidget *widget,
+			 gpointer   user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+	g_object_unref (self->priv->caption_layout);
+	self->priv->caption_layout = NULL;
+}
+
+
+static gboolean
+video_area_expose_event_cb (GtkWidget      *widget,
+			    GdkEventExpose *event,
+			    gpointer        user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+	if (event->count > 0)
+		return FALSE;
+
+	if (self->priv->xwin_assigned && self->priv->has_video)
+		return FALSE;
+
+	if (self->priv->icon == NULL) {
+		char  *type;
+		GIcon *icon;
+		int    size;
+
+		type = NULL;
+		if (self->priv->file_data != NULL)
+			type = g_content_type_from_mime_type (gth_file_data_get_mime_type (self->priv->file_data));
+		if (type == NULL)
+			type = g_content_type_from_mime_type ("text/plain");
+		icon = g_content_type_get_icon (type);
+		size = widget->allocation.width;
+		if (size > widget->allocation.height)
+			size = widget->allocation.height;
+		size = size / 3;
+		self->priv->icon = _g_icon_get_pixbuf (icon, size, gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)));
+
+		g_object_unref (icon);
+		g_free (type);
+	}
+
+	gdk_draw_rectangle (gtk_widget_get_window (widget),
+			    self->priv->has_video ? widget->style->black_gc : widget->style->text_gc[GTK_WIDGET_STATE (widget)],
+			    TRUE,
+			    event->area.x,
+			    event->area.y,
+			    event->area.width,
+			    event->area.height);
+
+	if (self->priv->icon != NULL) {
+		int            icon_w, icon_h;
+		int            icon_x, icon_y;
+		PangoRectangle logical_rect;
+		int            x, y;
+
+		icon_w = gdk_pixbuf_get_width (self->priv->icon);
+		icon_h = gdk_pixbuf_get_height (self->priv->icon);
+		pango_layout_set_width (self->priv->caption_layout, PANGO_SCALE * (icon_w * 3 / 2));
+		pango_layout_get_extents (self->priv->caption_layout, NULL, &logical_rect);
+		icon_x = (widget->allocation.width - icon_w) / 2;
+		x = (widget->allocation.width - PANGO_PIXELS (logical_rect.width)) / 2 + PANGO_PIXELS (logical_rect.x);
+		icon_y = (widget->allocation.height - (icon_h + PANGO_PIXELS (logical_rect.height))) / 2;
+		y = icon_y + icon_h;
+
+		gdk_draw_pixbuf (gtk_widget_get_window (widget),
+				 widget->style->base_gc[GTK_WIDGET_STATE (widget)],
+				 self->priv->icon,
+				 0, 0,
+				 icon_x, icon_y,
+				 icon_w, icon_h,
+				 GDK_RGB_DITHER_NORMAL,
+				 0, 0);
+		gdk_draw_layout (gtk_widget_get_window (widget),
+				 widget->style->base_gc[GTK_WIDGET_STATE (widget)],
+				 x, y,
+				 self->priv->caption_layout);
+	}
+
+	return FALSE;
+}
+
+
+static gboolean
+video_area_button_press_cb (GtkWidget          *widget,
+			    GdkEventButton     *event,
+			    GthMediaViewerPage *self)
+{
+	if ((event->type == GDK_BUTTON_PRESS) && (event->button == 1) ) {
+		gtk_button_clicked (GTK_BUTTON (GET_WIDGET ("button_play")));
+		return TRUE;
+	}
+
+	return gth_browser_viewer_button_press_cb (self->priv->browser, event);
+}
+
+
+static gboolean
+video_area_scroll_event_cb (GtkWidget 	       *widget,
+			    GdkEventScroll     *event,
+			    GthMediaViewerPage *self)
+{
+	return gth_browser_viewer_scroll_event_cb (self->priv->browser, event);
+}
+
+
+static gboolean
+video_area_key_press_cb (GtkWidget          *widget,
+			 GdkEventKey        *event,
+			 GthMediaViewerPage *self)
+{
+	return gth_browser_viewer_key_press_cb (self->priv->browser, event);
+}
+
+
+static void
+volume_value_changed_cb (GtkAdjustment *adjustment,
+			 gpointer       user_data)
+{
+	GthMediaViewerPage *self = user_data;
+	if (self->priv->playbin != NULL)
+		g_object_set (self->priv->playbin,
+			      "volume",
+			      gtk_adjustment_get_value (adjustment) / 10.0,
+			      NULL);
+}
+
+
+static void position_value_changed_cb (GtkAdjustment *adjustment,
+				       gpointer       user_data);
+
+
+static void
+update_current_position_bar (GthMediaViewerPage *self,
+			     gboolean            update_progressbar)
+{
+	GstFormat format;
+        gint64    current_value = 0;
+
+        format = GST_FORMAT_TIME;
+        if (gst_element_query_position (self->priv->playbin, &format, &current_value)) {
+        	char *s;
+
+        	if (self->priv->duration <= 0) {
+        		gst_element_query_duration (self->priv->playbin, &format, &self->priv->duration);
+        		s = _g_format_duration_for_display (GST_TIME_AS_MSECONDS (self->priv->duration));
+        		gtk_label_set_text (GTK_LABEL (GET_WIDGET ("label_duration")), s);
+
+        		g_free (s);
+        	}
+
+        	/*
+        	g_print ("==> %" G_GINT64_FORMAT " / %" G_GINT64_FORMAT " (%0.3g)\n" ,
+        		 current_value,
+        		 self->priv->duration,
+        		 ((double) current_value / self->priv->duration) * 100.0);
+		*/
+
+        	if (update_progressbar) {
+			g_signal_handlers_block_by_func(GET_WIDGET ("adjustment_position"), position_value_changed_cb, self);
+			gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("adjustment_position")), (self->priv->duration > 0) ? ((double) current_value / self->priv->duration) * 100.0 : 0.0);
+			g_signal_handlers_unblock_by_func(GET_WIDGET ("adjustment_position"), position_value_changed_cb, self);
+        	}
+
+        	s = _g_format_duration_for_display (GST_TIME_AS_MSECONDS (current_value));
+        	gtk_label_set_text (GTK_LABEL (GET_WIDGET ("label_position")), s);
+
+        	g_free (s);
+        }
+}
+
+
+static void
+position_value_changed_cb (GtkAdjustment *adjustment,
+			   gpointer       user_data)
+{
+	GthMediaViewerPage *self = user_data;
+	gint64              current_value;
+	char               *s;
+
+	if (self->priv->playbin == NULL)
+		return;
+
+	current_value = (gint64) (gtk_adjustment_get_value (adjustment) / 100.0 * self->priv->duration);
+	if (! gst_element_seek_simple (self->priv->playbin,
+				       GST_FORMAT_TIME,
+				       GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
+				       current_value))
+	{
+		g_warning ("seek failed");
+	}
+
+	s = _g_format_duration_for_display (GST_TIME_AS_MSECONDS (current_value));
+	gtk_label_set_text (GTK_LABEL (GET_WIDGET ("label_position")), s);
+
+	g_free (s);
+}
+
+
+static char *
+hscale_volume_format_value_cb (GtkScale *scale,
+			       double    value,
+			       gpointer  user_data)
+{
+	return g_strdup_printf ("%0.0f%%", value);
+}
+
+
+static gboolean
+hscale_position_change_value_cb (GtkRange      *range,
+				 GtkScrollType  scroll,
+				 gdouble        value,
+				 gpointer       user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+	if (self->priv->block_next_jump && (scroll == GTK_SCROLL_JUMP)) {
+		self->priv->block_next_jump = FALSE;
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+static gboolean
+hscale_position_button_release_event_cb (GtkWidget      *widget,
+					 GdkEventButton *event,
+					 gpointer        user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+	if (self->priv->playing)
+		self->priv->block_next_jump = TRUE;
+
+	return FALSE;
+}
+
+
+static void
+update_player_rate (GthMediaViewerPage *self)
+{
+	self->priv->rate = CLAMP (self->priv->rate, 0.25, 2.0);
+
+	if (self->priv->playbin == NULL)
+		return;
+
+	if (! self->priv->playing)
+		return;
+
+	if (! gst_element_seek (self->priv->playbin,
+				self->priv->rate,
+				GST_FORMAT_TIME,
+				(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE),
+				GST_SEEK_TYPE_NONE,
+				0.0,
+				GST_SEEK_TYPE_NONE,
+				0.0))
+	{
+		g_warning ("seek failed");
+	}
+}
+
+
+static void
+button_play_clicked_cb (GtkButton *button,
+			gpointer   user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+	if (self->priv->playbin == NULL)
+		return;
+	if (! self->priv->playing) {
+		if (! self->priv->paused) {
+			gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED);
+			gst_element_seek_simple (self->priv->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, 0);
+		}
+		gst_element_set_state (self->priv->playbin, GST_STATE_PLAYING);
+	}
+	else
+		gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED);
+}
+
+
+static void
+togglebutton_volume_toggled_cb (GtkToggleButton *button,
+				gpointer         user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+	if (self->priv->playbin == NULL)
+		return;
+
+	if (gtk_toggle_button_get_active (button)) {
+		g_object_get (self->priv->playbin, "volume", &self->priv->last_volume, NULL);
+		g_object_set (self->priv->playbin, "volume", 0.0, NULL);
+	}
+	else
+		g_object_set (self->priv->playbin, "volume", self->priv->last_volume, NULL);
+}
+
+
+static void
+button_play_slower_clicked_cb (GtkButton *button,
+			       gpointer   user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+	self->priv->rate -= 0.25;
+	update_player_rate (self);
+}
+
+
+static void
+button_play_faster_clicked_cb (GtkButton *button,
+			       gpointer   user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+	self->priv->rate += 0.25;
+	update_player_rate (self);
+}
+
+
+static void
+update_volume_from_playbin (GthMediaViewerPage *self)
+{
+	double volume;
+
+	if ((self->priv->builder == NULL) || (self->priv->playbin == NULL))
+		return;
+
+	g_object_get (self->priv->playbin, "volume", &volume, NULL);
+	if (volume == 0.0)
+		gtk_image_set_from_icon_name (GTK_IMAGE (GET_WIDGET ("togglebutton_volume_image")), "audio-volume-muted", GTK_ICON_SIZE_BUTTON);
+	else if (volume < 3.3)
+		gtk_image_set_from_icon_name (GTK_IMAGE (GET_WIDGET ("togglebutton_volume_image")), "audio-volume-low", GTK_ICON_SIZE_BUTTON);
+	else if (volume < 6.6)
+		gtk_image_set_from_icon_name (GTK_IMAGE (GET_WIDGET ("togglebutton_volume_image")), "audio-volume-medium", GTK_ICON_SIZE_BUTTON);
+	else
+		gtk_image_set_from_icon_name (GTK_IMAGE (GET_WIDGET ("togglebutton_volume_image")), "audio-volume-high", GTK_ICON_SIZE_BUTTON);
+
+	g_signal_handlers_block_by_func(GET_WIDGET ("adjustment_volume"), volume_value_changed_cb, self);
+	gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("adjustment_volume")), volume * 10.0);
+	g_signal_handlers_unblock_by_func(GET_WIDGET ("adjustment_volume"), volume_value_changed_cb, self);
+}
+
+
+static gboolean
+update_progress_cb (gpointer user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+        if (self->priv->update_progress_id != 0) {
+                g_source_remove (self->priv->update_progress_id);
+                self->priv->update_progress_id = 0;
+        }
+
+        update_current_position_bar (self, TRUE);
+
+        self->priv->update_progress_id = gdk_threads_add_timeout (PROGRESS_DELAY, update_progress_cb, self);
+
+        return FALSE;
+}
+
+
+static void
+update_play_button (GthMediaViewerPage *self,
+		    GstState            new_state)
+{
+	if (! self->priv->playing && (new_state == GST_STATE_PLAYING)) {
+		self->priv->playing = TRUE;
+		gtk_image_set_from_stock (GTK_IMAGE (GET_WIDGET ("button_play_image")), GTK_STOCK_MEDIA_PAUSE, GTK_ICON_SIZE_LARGE_TOOLBAR);
+		gtk_widget_set_tooltip_text (GET_WIDGET ("button_play_image"), _("Pause"));
+
+		if (self->priv->update_progress_id == 0)
+			self->priv->update_progress_id = gdk_threads_add_timeout (PROGRESS_DELAY, update_progress_cb, self);
+	}
+	else if (self->priv->playing && (new_state != GST_STATE_PLAYING)) {
+		self->priv->playing = FALSE;
+		gtk_image_set_from_stock (GTK_IMAGE (GET_WIDGET ("button_play_image")), GTK_STOCK_MEDIA_PLAY, GTK_ICON_SIZE_LARGE_TOOLBAR);
+		gtk_widget_set_tooltip_text (GET_WIDGET ("button_play_image"), _("Play"));
+
+		if (self->priv->update_progress_id != 0) {
+			 g_source_remove (self->priv->update_progress_id);
+			 self->priv->update_progress_id = 0;
+		}
+	}
+
+	gth_viewer_page_update_sensitivity (GTH_VIEWER_PAGE (self));
+}
+
+
+static void
+gth_media_viewer_page_real_activate (GthViewerPage *base,
+				     GthBrowser    *browser)
+{
+	GthMediaViewerPage *self;
+
+	if (! gstreamer_init ())
+		return;
+
+	self = (GthMediaViewerPage*) base;
+
+	self->priv->browser = browser;
+
+	self->priv->actions = gtk_action_group_new ("Video Viewer Actions");
+	gtk_action_group_set_translation_domain (self->priv->actions, NULL);
+	gtk_action_group_add_actions (self->priv->actions,
+				      media_viewer_action_entries,
+				      G_N_ELEMENTS (media_viewer_action_entries),
+				      self);
+	gtk_ui_manager_insert_action_group (gth_browser_get_ui_manager (browser), self->priv->actions, 0);
+
+	self->priv->area_box = gtk_vbox_new (FALSE, 0);
+
+	/* video area */
+
+	self->priv->area = gtk_drawing_area_new ();
+	gtk_widget_set_double_buffered (self->priv->area, FALSE);
+	gtk_widget_add_events (self->priv->area, (gtk_widget_get_events (self->priv->area)
+						  | GDK_EXPOSURE_MASK
+						  | GDK_BUTTON_PRESS_MASK
+						  | GDK_BUTTON_RELEASE_MASK
+						  | GDK_POINTER_MOTION_MASK
+						  | GDK_POINTER_MOTION_HINT_MASK
+						  | GDK_BUTTON_MOTION_MASK));
+	GTK_WIDGET_SET_FLAGS (self->priv->area, GTK_CAN_FOCUS);
+	gtk_widget_show (self->priv->area);
+	gtk_box_pack_start (GTK_BOX (self->priv->area_box), self->priv->area, TRUE, TRUE, 0);
+
+	g_signal_connect (G_OBJECT (self->priv->area),
+			  "realize",
+			  G_CALLBACK (video_area_realize_cb),
+			  self);
+	g_signal_connect (G_OBJECT (self->priv->area),
+			  "unrealize",
+			  G_CALLBACK (video_area_unrealize_cb),
+			  self);
+	g_signal_connect (G_OBJECT (self->priv->area),
+			  "expose_event",
+			  G_CALLBACK (video_area_expose_event_cb),
+			  self);
+	g_signal_connect (G_OBJECT (self->priv->area),
+			  "button_press_event",
+			  G_CALLBACK (video_area_button_press_cb),
+			  self);
+	g_signal_connect (G_OBJECT (self->priv->area),
+			  "scroll_event",
+			  G_CALLBACK (video_area_scroll_event_cb),
+			  self);
+	g_signal_connect (G_OBJECT (self->priv->area),
+			  "key_press_event",
+			  G_CALLBACK (video_area_key_press_cb),
+			  self);
+
+	/* mediabar */
+
+	self->priv->builder = _gtk_builder_new_from_file ("mediabar.ui", "gstreamer");
+	self->priv->mediabar = GET_WIDGET ("mediabar");
+	gtk_widget_show (self->priv->mediabar);
+	gtk_box_pack_start (GTK_BOX (self->priv->area_box), self->priv->mediabar, FALSE, FALSE, 0);
+
+	g_signal_connect (GET_WIDGET ("adjustment_volume"), "value-changed", G_CALLBACK (volume_value_changed_cb), self);
+	g_signal_connect (GET_WIDGET ("adjustment_position"), "value-changed", G_CALLBACK (position_value_changed_cb), self);
+	g_signal_connect (GET_WIDGET ("hscale_volume"), "format-value", G_CALLBACK (hscale_volume_format_value_cb), self);
+	g_signal_connect (GET_WIDGET ("hscale_position"), "change-value", G_CALLBACK (hscale_position_change_value_cb), self);
+	g_signal_connect (GET_WIDGET ("hscale_position"), "button-release-event", G_CALLBACK (hscale_position_button_release_event_cb), self);
+	g_signal_connect (GET_WIDGET ("button_play"), "clicked", G_CALLBACK (button_play_clicked_cb), self);
+	g_signal_connect (GET_WIDGET ("togglebutton_volume"), "toggled", G_CALLBACK (togglebutton_volume_toggled_cb), self);
+	g_signal_connect (GET_WIDGET ("button_play_slower"), "clicked", G_CALLBACK (button_play_slower_clicked_cb), self);
+	g_signal_connect (GET_WIDGET ("button_play_faster"), "clicked", G_CALLBACK (button_play_faster_clicked_cb), self);
+
+	gtk_widget_show (self->priv->area_box);
+	gth_browser_set_viewer_widget (browser, self->priv->area_box);
+
+	gtk_widget_realize (self->priv->area);
+	gdk_window_ensure_native (gtk_widget_get_window (self->priv->area));
+	gth_viewer_page_focus (GTH_VIEWER_PAGE (self));
+}
+
+
+static void
+gth_media_viewer_page_real_deactivate (GthViewerPage *base)
+{
+	GthMediaViewerPage *self;
+
+	self = (GthMediaViewerPage*) base;
+
+	if (self->priv->builder != NULL) {
+		g_object_unref (self->priv->builder);
+		self->priv->builder = NULL;
+	}
+
+        if (self->priv->update_progress_id != 0) {
+                g_source_remove (self->priv->update_progress_id);
+                self->priv->update_progress_id = 0;
+        }
+
+	if (self->priv->playbin != NULL) {
+		gst_element_set_state (self->priv->playbin, GST_STATE_NULL);
+		gst_object_unref (GST_OBJECT (self->priv->playbin));
+		self->priv->playbin = NULL;
+	}
+
+	gth_browser_set_viewer_widget (self->priv->browser, NULL);
+}
+
+
+static GstBusSyncReply
+set_playbin_window (GstBus             *bus,
+		    GstMessage         *message,
+		    GthMediaViewerPage *self)
+{
+	GstXOverlay *image_sink;
+
+	/* ignore anything but 'prepare-xwindow-id' element messages */
+
+	if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
+		return GST_BUS_PASS;
+	if (! gst_structure_has_name (message->structure, "prepare-xwindow-id"))
+		return GST_BUS_PASS;
+
+	image_sink = GST_X_OVERLAY (GST_MESSAGE_SRC (message));
+	gst_x_overlay_set_xwindow_id (image_sink, GDK_WINDOW_XID (gtk_widget_get_window (self->priv->area)));
+	g_object_set (image_sink, "force-aspect-ratio", TRUE, NULL);
+	self->priv->xwin_assigned = TRUE;
+
+	gst_message_unref (message);
+
+	return GST_BUS_DROP;
+}
+
+
+static void
+reset_player_state (GthMediaViewerPage *self)
+{
+        if (self->priv->update_progress_id != 0) {
+                g_source_remove (self->priv->update_progress_id);
+                self->priv->update_progress_id = 0;
+        }
+
+	update_play_button (self, GST_STATE_NULL);
+	self->priv->playing = FALSE;
+	self->priv->rate = 1.0;
+}
+
+
+static void
+update_stream_info (GthMediaViewerPage *self)
+{
+	GList  *streaminfo;
+	GstPad *videopad;
+
+	streaminfo = NULL;
+	videopad = NULL;
+
+	g_object_get (self->priv->playbin, "stream-info", &streaminfo, NULL);
+	streaminfo = g_list_copy (streaminfo);
+	g_list_foreach (streaminfo, (GFunc) g_object_ref, NULL);
+
+	for (/* void */ ; streaminfo; streaminfo = streaminfo->next) {
+		GObject    *info;
+		int         type;
+		GParamSpec *pspec;
+		GEnumValue *val;
+
+		info = streaminfo->data;
+		if (info == NULL)
+			continue;
+
+                type = -1;
+
+		g_object_get (info, "type", &type, NULL);
+		pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info), "type");
+		val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type);
+
+		if (strcmp (val->value_nick, "audio") == 0) {
+			self->priv->has_audio = TRUE;
+		}
+		else if (strcmp (val->value_nick, "video") == 0) {
+			self->priv->has_video = TRUE;
+			if (videopad == NULL)
+				g_object_get (info, "object", &videopad, NULL);
+		}
+	}
+
+	if (videopad != NULL) {
+		GstCaps *caps;
+
+		if ((caps = gst_pad_get_negotiated_caps (videopad)) != NULL) {
+			GstStructure *structure;
+
+			structure = gst_caps_get_structure (caps, 0);
+			gst_structure_get_fraction (structure, "framerate", &self->priv->video_fps_n, &self->priv->video_fps_d);
+
+			gst_caps_unref (caps);
+		}
+	}
+
+	g_list_foreach (streaminfo, (GFunc) g_object_unref, NULL);
+	g_list_free (streaminfo);
+}
+
+
+static void
+bus_message_cb (GstBus     *bus,
+                GstMessage *message,
+                gpointer    user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+	switch (GST_MESSAGE_TYPE (message)) {
+	case GST_MESSAGE_STATE_CHANGED: {
+		GstState old_state;
+		GstState new_state;
+		GstState pending_state;
+
+		old_state = new_state = GST_STATE_NULL;
+		gst_message_parse_state_changed (message, &old_state, &new_state, &pending_state);
+
+		self->priv->paused = (new_state == GST_STATE_PAUSED);
+
+		if (old_state == new_state)
+			break;
+		if (GST_MESSAGE_SRC (message) != GST_OBJECT (self->priv->playbin))
+			break;
+
+		update_current_position_bar (self, TRUE);
+
+		if ((old_state == GST_STATE_READY) && (new_state == GST_STATE_PAUSED)) {
+			update_stream_info (self);
+			gth_viewer_page_update_sensitivity (GTH_VIEWER_PAGE (self));
+		}
+		if ((old_state == GST_STATE_READY) || (new_state == GST_STATE_PAUSED))
+			update_volume_from_playbin (self);
+		if ((old_state == GST_STATE_PLAYING) || (new_state == GST_STATE_PLAYING))
+			update_play_button (self, new_state);
+		break;
+	}
+
+	case GST_MESSAGE_DURATION: {
+		GstFormat format;
+
+		format = GST_FORMAT_TIME;
+		gst_message_parse_duration (message, &format, &self->priv->duration);
+		update_current_position_bar (self, TRUE);
+		break;
+	}
+
+	case GST_MESSAGE_EOS:
+		reset_player_state (self);
+		break;
+
+	case GST_MESSAGE_BUFFERING: {
+		int percent = 0;
+		gst_message_parse_buffering (message, &percent);
+		g_print ("Buffering (%%%u percent done)", percent);
+		break;
+	}
+
+	default:
+		break;
+	}
+}
+
+
+static void
+playbin_notify_volume_cb (GObject    *playbin,
+			  GParamSpec *pspec,
+			  gpointer    user_data)
+{
+	update_volume_from_playbin ((GthMediaViewerPage *) user_data);
+}
+
+
+static void
+gth_media_viewer_page_real_show (GthViewerPage *base)
+{
+	GthMediaViewerPage *self;
+	GError             *error = NULL;
+	GstBus             *bus;
+	char               *uri;
+
+	self = (GthMediaViewerPage*) base;
+
+	if (self->priv->merge_id != 0)
+		return;
+
+	self->priv->merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (self->priv->browser), media_viewer_ui_info, -1, &error);
+	if (self->priv->merge_id == 0) {
+		g_warning ("ui building failed: %s", error->message);
+		g_error_free (error);
+	}
+
+	gth_viewer_page_focus (GTH_VIEWER_PAGE (self));
+
+	if (self->priv->playbin != NULL)
+		return;
+
+	self->priv->playbin = gst_element_factory_make ("playbin", "playbin");
+	g_signal_connect (self->priv->playbin, "notify::volume", G_CALLBACK (playbin_notify_volume_cb), self);
+
+	bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->playbin));
+	gst_bus_enable_sync_message_emission (bus);
+	gst_bus_set_sync_handler (bus, (GstBusSyncHandler) set_playbin_window, self);
+	gst_bus_add_signal_watch (bus);
+	g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), self);
+
+	if (self->priv->file_data == NULL)
+		return;
+
+	uri = g_file_get_uri (self->priv->file_data->file);
+	g_object_set (G_OBJECT (self->priv->playbin), "uri", uri, NULL);
+	gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED);
+
+	g_free (uri);
+}
+
+
+static void
+gth_media_viewer_page_real_hide (GthViewerPage *base)
+{
+	GthMediaViewerPage *self;
+
+	self = (GthMediaViewerPage*) base;
+
+	if (self->priv->merge_id != 0) {
+		gtk_ui_manager_remove_ui (gth_browser_get_ui_manager (self->priv->browser), self->priv->merge_id);
+		self->priv->merge_id = 0;
+	}
+
+	if ((self->priv->playbin != NULL) && self->priv->playing)
+		gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED);
+}
+
+
+static gboolean
+gth_media_viewer_page_real_can_view (GthViewerPage *base,
+				     GthFileData   *file_data)
+{
+	GthMediaViewerPage *self;
+
+	self = (GthMediaViewerPage*) base;
+	g_return_val_if_fail (file_data != NULL, FALSE);
+
+	return _g_mime_type_is_video (gth_file_data_get_mime_type (file_data)) || _g_mime_type_is_audio (gth_file_data_get_mime_type (file_data));
+}
+
+
+static gboolean
+set_to_paused (gpointer user_data)
+{
+	GthMediaViewerPage *self = user_data;
+
+	if (self->priv->playbin != NULL)
+		gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED);
+	return FALSE;
+}
+
+
+static void
+gth_media_viewer_page_real_view (GthViewerPage *base,
+				 GthFileData   *file_data)
+{
+	GthMediaViewerPage *self;
+	char               *uri;
+
+	self = (GthMediaViewerPage*) base;
+	g_return_if_fail (file_data != NULL);
+
+	if (! gstreamer_init ())
+		return;
+
+	gth_viewer_page_focus (GTH_VIEWER_PAGE (self));
+
+	if ((self->priv->file_data != NULL)
+	    && g_file_equal (file_data->file, self->priv->file_data->file)
+	    && (gth_file_data_get_mtime (file_data) == gth_file_data_get_mtime (self->priv->file_data)))
+	{
+		return;
+	}
+
+	/**/
+
+	_g_object_unref (self->priv->file_data);
+	self->priv->file_data = gth_file_data_dup (file_data);
+
+	self->priv->duration = 0;
+
+	_g_object_unref (self->priv->icon);
+	self->priv->icon = NULL;
+
+	_gth_media_viewer_page_update_caption (self);
+
+	/**/
+
+	gth_viewer_page_file_loaded (GTH_VIEWER_PAGE (self), TRUE);
+	g_signal_handlers_block_by_func(GET_WIDGET ("adjustment_position"), position_value_changed_cb, self);
+	gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("adjustment_position")), 0.0);
+	g_signal_handlers_unblock_by_func(GET_WIDGET ("adjustment_position"), position_value_changed_cb, self);
+	reset_player_state (self);
+
+	if (self->priv->playbin == NULL)
+		return;
+
+	gst_element_set_state (self->priv->playbin, GST_STATE_NULL);
+	uri = g_file_get_uri (self->priv->file_data->file);
+	g_object_set (G_OBJECT (self->priv->playbin), "uri", uri, NULL);
+
+	gdk_threads_add_idle (set_to_paused, self);
+
+	g_free (uri);
+}
+
+
+static void
+gth_media_viewer_page_real_focus (GthViewerPage *base)
+{
+	GtkWidget *widget;
+
+	widget = GTH_MEDIA_VIEWER_PAGE (base)->priv->area;
+	if (GTK_WIDGET_REALIZED (widget))
+		gtk_widget_grab_focus (widget);
+}
+
+
+static void
+gth_media_viewer_page_real_fullscreen (GthViewerPage *base,
+				       gboolean       active)
+{
+	GthMediaViewerPage *self = (GthMediaViewerPage*) base;
+	GdkScreen          *screen;
+
+	if (! active) {
+		g_object_ref (self->priv->mediabar);
+		gtk_container_remove (GTK_CONTAINER (self->priv->fullscreen_toolbar), self->priv->mediabar);
+		gtk_box_pack_start (GTK_BOX (self->priv->area_box), self->priv->mediabar, FALSE, FALSE, 0);
+		g_object_unref (self->priv->mediabar);
+
+		gtk_widget_destroy (self->priv->fullscreen_toolbar);
+		self->priv->fullscreen_toolbar = NULL;
+
+		return;
+	}
+
+	/* active == TRUE */
+
+	screen = gtk_widget_get_screen (GTK_WIDGET (self->priv->browser));
+
+	if (self->priv->fullscreen_toolbar == NULL) {
+		self->priv->fullscreen_toolbar = gtk_window_new (GTK_WINDOW_POPUP);
+		gtk_window_set_screen (GTK_WINDOW (self->priv->fullscreen_toolbar), screen);
+		gtk_window_set_default_size (GTK_WINDOW (self->priv->fullscreen_toolbar), gdk_screen_get_width (screen), -1);
+		gtk_container_set_border_width (GTK_CONTAINER (self->priv->fullscreen_toolbar), 0);
+	}
+
+	g_object_ref (self->priv->mediabar);
+	gtk_container_remove (GTK_CONTAINER (self->priv->area_box), self->priv->mediabar);
+	gtk_container_add (GTK_CONTAINER (self->priv->fullscreen_toolbar), self->priv->mediabar);
+	g_object_unref (self->priv->mediabar);
+
+	gtk_widget_realize (self->priv->mediabar);
+	gtk_window_set_gravity (GTK_WINDOW (self->priv->fullscreen_toolbar), GDK_GRAVITY_SOUTH_EAST);
+	gtk_window_move (GTK_WINDOW (self->priv->fullscreen_toolbar), 0, gdk_screen_get_height (screen) - self->priv->mediabar->allocation.height);
+
+	gth_browser_register_fullscreen_control (self->priv->browser, self->priv->fullscreen_toolbar);
+}
+
+
+static void
+gth_media_viewer_page_real_show_pointer (GthViewerPage *base,
+				         gboolean       show)
+{
+	GthMediaViewerPage *self = (GthMediaViewerPage*) base;
+
+	if (self->priv->fullscreen_toolbar != NULL) {
+		if (show && ! GTK_WIDGET_VISIBLE (self->priv->fullscreen_toolbar)) {
+			gtk_widget_show (self->priv->fullscreen_toolbar);
+		}
+		else if (! show && GTK_WIDGET_VISIBLE (self->priv->fullscreen_toolbar)) {
+			gtk_widget_hide (self->priv->fullscreen_toolbar);
+		}
+	}
+}
+
+
+static void
+set_action_sensitive (GthMediaViewerPage *self,
+		      const char         *action_name,
+		      gboolean            sensitive)
+{
+	GtkAction *action;
+
+	action = gtk_action_group_get_action (self->priv->actions, action_name);
+	g_object_set (action, "sensitive", sensitive, NULL);
+}
+
+
+static void
+gth_media_viewer_page_real_update_sensitivity (GthViewerPage *base)
+{
+	GthMediaViewerPage *self = (GthMediaViewerPage *) base;
+
+	gtk_widget_set_sensitive (GET_WIDGET ("button_play_slower"), self->priv->playing);
+	gtk_widget_set_sensitive (GET_WIDGET ("button_play_faster"), self->priv->playing);
+	gtk_widget_set_sensitive (GET_WIDGET ("volume_box"), self->priv->has_audio);
+	set_action_sensitive (self, "MediaViewer_Screenshot", self->priv->has_video);
+}
+
+
+static gboolean
+gth_media_viewer_page_real_can_save (GthViewerPage *base)
+{
+	return FALSE;
+}
+
+
+static void
+gth_media_viewer_page_real_save (GthViewerPage *base,
+				 GFile         *file,
+				 FileSavedFunc  func,
+				 gpointer       user_data)
+{
+	/* void */
+}
+
+
+static void
+gth_media_viewer_page_real_save_as (GthViewerPage *base,
+				    FileSavedFunc  func,
+				    gpointer       user_data)
+{
+	/* void */
+}
+
+
+static void
+gth_media_viewer_page_real_revert (GthViewerPage *base)
+{
+	/* void */
+}
+
+
+static void
+gth_media_viewer_page_finalize (GObject *obj)
+{
+	GthMediaViewerPage *self;
+
+	self = GTH_MEDIA_VIEWER_PAGE (obj);
+
+        if (self->priv->update_progress_id != 0) {
+                g_source_remove (self->priv->update_progress_id);
+                self->priv->update_progress_id = 0;
+        }
+
+	if (self->priv->playbin != NULL) {
+		gst_element_set_state (self->priv->playbin, GST_STATE_NULL);
+		gst_object_unref (GST_OBJECT (self->priv->playbin));
+		self->priv->playbin = NULL;
+	}
+	_g_object_unref (self->priv->icon);
+	_g_object_unref (self->priv->file_data);
+
+	G_OBJECT_CLASS (gth_media_viewer_page_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_media_viewer_page_class_init (GthMediaViewerPageClass *klass)
+{
+	gth_media_viewer_page_parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (GthMediaViewerPagePrivate));
+
+	G_OBJECT_CLASS (klass)->finalize = gth_media_viewer_page_finalize;
+}
+
+
+static void
+gth_viewer_page_interface_init (GthViewerPageIface *iface)
+{
+	iface->activate = gth_media_viewer_page_real_activate;
+	iface->deactivate = gth_media_viewer_page_real_deactivate;
+	iface->show = gth_media_viewer_page_real_show;
+	iface->hide = gth_media_viewer_page_real_hide;
+	iface->can_view = gth_media_viewer_page_real_can_view;
+	iface->view = gth_media_viewer_page_real_view;
+	iface->focus = gth_media_viewer_page_real_focus;
+	iface->fullscreen = gth_media_viewer_page_real_fullscreen;
+	iface->show_pointer = gth_media_viewer_page_real_show_pointer;
+	iface->update_sensitivity = gth_media_viewer_page_real_update_sensitivity;
+	iface->can_save = gth_media_viewer_page_real_can_save;
+	iface->save = gth_media_viewer_page_real_save;
+	iface->save_as = gth_media_viewer_page_real_save_as;
+	iface->revert = gth_media_viewer_page_real_revert;
+}
+
+
+static void
+gth_media_viewer_page_instance_init (GthMediaViewerPage *self)
+{
+	self->priv = GTH_MEDIA_VIEWER_PAGE_GET_PRIVATE (self);
+	self->priv->update_progress_id = 0;
+	self->priv->xwin_assigned = FALSE;
+	self->priv->has_video = FALSE;
+	self->priv->has_audio = FALSE;
+	self->priv->video_fps_n = 0;
+	self->priv->video_fps_d = 0;
+	self->priv->icon = NULL;
+}
+
+
+GType
+gth_media_viewer_page_get_type (void) {
+	static GType gth_media_viewer_page_type_id = 0;
+	if (gth_media_viewer_page_type_id == 0) {
+		static const GTypeInfo g_define_type_info = {
+			sizeof (GthMediaViewerPageClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) gth_media_viewer_page_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,
+			sizeof (GthMediaViewerPage),
+			0,
+			(GInstanceInitFunc) gth_media_viewer_page_instance_init,
+			NULL
+		};
+		static const GInterfaceInfo gth_viewer_page_info = {
+			(GInterfaceInitFunc) gth_viewer_page_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+		gth_media_viewer_page_type_id = g_type_register_static (G_TYPE_OBJECT, "GthMediaViewerPage", &g_define_type_info, 0);
+		g_type_add_interface_static (gth_media_viewer_page_type_id, GTH_TYPE_VIEWER_PAGE, &gth_viewer_page_info);
+	}
+	return gth_media_viewer_page_type_id;
+}
+
+
+GthBrowser *
+gth_media_viewer_page_get_browser (GthMediaViewerPage *self)
+{
+	return self->priv->browser;
+}
+
+
+GstElement *
+gth_media_viewer_page_get_playbin (GthMediaViewerPage *self)
+{
+	return self->priv->playbin;
+}
+
+
+gboolean
+gth_media_viewer_page_is_playing (GthMediaViewerPage *self)
+{
+	return self->priv->playing;
+}
+
+
+void
+gth_media_viewer_page_get_video_fps (GthMediaViewerPage *self,
+				     int                *video_fps_n,
+				     int                *video_fps_d)
+{
+	if (video_fps_n != NULL)
+		*video_fps_n = self->priv->video_fps_n;
+	if (video_fps_d != NULL)
+		*video_fps_d = self->priv->video_fps_d;
+}
+
+
+GthFileData *
+gth_media_viewer_page_get_file_data (GthMediaViewerPage *self)
+{
+	return self->priv->file_data;
+}
diff --git a/extensions/gstreamer_tools/gth-media-viewer-page.h b/extensions/gstreamer_tools/gth-media-viewer-page.h
new file mode 100644
index 0000000..942a45a
--- /dev/null
+++ b/extensions/gstreamer_tools/gth-media-viewer-page.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_MEDIA_VIEWER_PAGE_H
+#define GTH_MEDIA_VIEWER_PAGE_H
+
+#include <gst/gst.h>
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_MEDIA_VIEWER_PAGE (gth_media_viewer_page_get_type ())
+#define GTH_MEDIA_VIEWER_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_MEDIA_VIEWER_PAGE, GthMediaViewerPage))
+#define GTH_MEDIA_VIEWER_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_MEDIA_VIEWER_PAGE, GthMediaViewerPageClass))
+#define GTH_IS_MEDIA_VIEWER_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_MEDIA_VIEWER_PAGE))
+#define GTH_IS_MEDIA_VIEWER_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_MEDIA_VIEWER_PAGE))
+#define GTH_MEDIA_VIEWER_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_MEDIA_VIEWER_PAGE, GthMediaViewerPageClass))
+
+typedef struct _GthMediaViewerPage GthMediaViewerPage;
+typedef struct _GthMediaViewerPageClass GthMediaViewerPageClass;
+typedef struct _GthMediaViewerPagePrivate GthMediaViewerPagePrivate;
+
+struct _GthMediaViewerPage {
+	GObject parent_instance;
+	GthMediaViewerPagePrivate * priv;
+};
+
+struct _GthMediaViewerPageClass {
+	GObjectClass parent_class;
+};
+
+GType          gth_media_viewer_page_get_type      (void);
+GthBrowser *   gth_media_viewer_page_get_browser    (GthMediaViewerPage *self);
+GstElement *   gth_media_viewer_page_get_playbin    (GthMediaViewerPage *self);
+gboolean       gth_media_viewer_page_is_playing     (GthMediaViewerPage *self);
+void           gth_media_viewer_page_get_video_fps  (GthMediaViewerPage *self,
+						     int                *video_fps_n,
+						     int                *video_fps_d);
+GthFileData * gth_media_viewer_page_get_file_data   (GthMediaViewerPage *self);
+
+G_END_DECLS
+
+#endif /* GTH_MEDIA_VIEWER_PAGE_H */
diff --git a/extensions/gstreamer_tools/gth-metadata-provider-gstreamer.c b/extensions/gstreamer_tools/gth-metadata-provider-gstreamer.c
new file mode 100644
index 0000000..fd5675f
--- /dev/null
+++ b/extensions/gstreamer_tools/gth-metadata-provider-gstreamer.c
@@ -0,0 +1,105 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <gthumb.h>
+#include <extensions/gstreamer_utils/gstreamer-utils.h>
+#include "gth-metadata-provider-gstreamer.h"
+
+
+static GthMetadataProviderClass *parent_class = NULL;
+
+
+static gboolean
+gth_metadata_provider_gstreamer_can_read (GthMetadataProvider  *self,
+				          const char           *mime_type,
+				          char                **attribute_v)
+{
+	if (! _g_content_type_is_a (mime_type, "audio/*")
+	    && ! _g_content_type_is_a (mime_type, "video/*"))
+	{
+		return FALSE;
+	}
+
+	return _g_file_attributes_matches_any_v ("general::title,"
+						 "general::format,"
+						 "general::dimensions,"
+						 "audio-video::*",
+					         attribute_v);
+}
+
+
+static void
+gth_metadata_provider_gstreamer_read (GthMetadataProvider *self,
+				      GthFileData         *file_data,
+				      const char          *attributes)
+{
+	if (! g_content_type_is_a (gth_file_data_get_mime_type (file_data), "audio/*")
+	    && ! g_content_type_is_a (gth_file_data_get_mime_type (file_data), "video/*"))
+	{
+		return;
+	}
+
+	/* this function is executed in a secondary thread, so calling
+	 * slow sync functions is not a problem. */
+
+	gstreamer_read_metadata_from_file (file_data->file, file_data->info, NULL);
+}
+
+
+static void
+gth_metadata_provider_gstreamer_class_init (GthMetadataProviderGstreamerClass *klass)
+{
+	parent_class = g_type_class_peek_parent (klass);
+
+	GTH_METADATA_PROVIDER_CLASS (klass)->can_read = gth_metadata_provider_gstreamer_can_read;
+	GTH_METADATA_PROVIDER_CLASS (klass)->read = gth_metadata_provider_gstreamer_read;
+}
+
+
+GType
+gth_metadata_provider_gstreamer_get_type (void)
+{
+	static GType type = 0;
+
+	if (! type) {
+		GTypeInfo type_info = {
+			sizeof (GthMetadataProviderGstreamerClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) gth_metadata_provider_gstreamer_class_init,
+			NULL,
+			NULL,
+			sizeof (GthMetadataProviderGstreamer),
+			0,
+			(GInstanceInitFunc) NULL
+		};
+
+		type = g_type_register_static (GTH_TYPE_METADATA_PROVIDER,
+					       "GthMetadataProviderGstreamer",
+					       &type_info,
+					       0);
+	}
+
+	return type;
+}
diff --git a/extensions/gstreamer_tools/gth-metadata-provider-gstreamer.h b/extensions/gstreamer_tools/gth-metadata-provider-gstreamer.h
new file mode 100644
index 0000000..1dd5b50
--- /dev/null
+++ b/extensions/gstreamer_tools/gth-metadata-provider-gstreamer.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_METADATA_PROVIDER_GSTREAMER_H
+#define GTH_METADATA_PROVIDER_GSTREAMER_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gthumb.h>
+
+#define GTH_TYPE_METADATA_PROVIDER_GSTREAMER         (gth_metadata_provider_gstreamer_get_type ())
+#define GTH_METADATA_PROVIDER_GSTREAMER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_METADATA_PROVIDER_GSTREAMER, GthMetadataProviderGstreamer))
+#define GTH_METADATA_PROVIDER_GSTREAMER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_METADATA_PROVIDER_GSTREAMER, GthMetadataProviderGstreamerClass))
+#define GTH_IS_METADATA_PROVIDER_GSTREAMER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_METADATA_PROVIDER_GSTREAMER))
+#define GTH_IS_METADATA_PROVIDER_GSTREAMER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_METADATA_PROVIDER_GSTREAMER))
+#define GTH_METADATA_PROVIDER_GSTREAMER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_METADATA_PROVIDER_GSTREAMER, GthMetadataProviderGstreamerClass))
+
+typedef struct _GthMetadataProviderGstreamer         GthMetadataProviderGstreamer;
+typedef struct _GthMetadataProviderGstreamerClass    GthMetadataProviderGstreamerClass;
+
+struct _GthMetadataProviderGstreamer
+{
+	GthMetadataProvider __parent;
+};
+
+struct _GthMetadataProviderGstreamerClass
+{
+	GthMetadataProviderClass __parent_class;	
+};
+
+GType gth_metadata_provider_gstreamer_get_type (void) G_GNUC_CONST;
+
+#endif /* GTH_METADATA_PROVIDER_GSTREAMER_H */
diff --git a/extensions/gstreamer_tools/main.c b/extensions/gstreamer_tools/main.c
new file mode 100644
index 0000000..a6939d7
--- /dev/null
+++ b/extensions/gstreamer_tools/main.c
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-metadata-provider-gstreamer.h"
+#include "gth-media-viewer-page.h"
+
+
+GthMetadataCategory gstreamer_metadata_category[] = {
+	{ "audio-video::video", N_("Video"), 40 },
+	{ "audio-video::audio", N_("Audio"), 50 },
+	{ "audio-video::other", N_("Other"), 60 },
+	{ NULL, NULL, 0 }
+};
+
+
+GthMetadataInfo gstreamer_metadata_info[] = {
+	{ "audio-video::general::artist", N_("Artist"), "general", 2, GTH_METADATA_ALLOW_EVERYWHERE },
+	{ "audio-video::general::album", N_("Album"), "general", 3, GTH_METADATA_ALLOW_EVERYWHERE },
+	{ "audio-video::general::bitrate", N_("Bitrate"), "general", 20, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+	{ "audio-video::general::encoder", N_("Encoder"), "general", 21, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+
+	{ "audio-video::video::codec", N_("Codec"), "audio-video::video", 2, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+	{ "audio-video::video::framerate", N_("Framerate"), "audio-video::video", 3, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+	{ "audio-video::video::width", N_("Width"), "audio-video::video", 0, GTH_METADATA_ALLOW_NOWHERE },
+	{ "audio-video::video::height", N_("Height"), "audio-video::video", 0, GTH_METADATA_ALLOW_NOWHERE },
+
+	{ "audio-video::audio::codec", N_("Codec"), "audio-video::audio", 1, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+	{ "audio-video::audio::channels", N_("Channels"), "audio-video::audio", 2, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+	{ "audio-video::audio::samplerate", N_("Sample rate"), "audio-video::audio", 3, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+
+	{ NULL, NULL, NULL, 0, 0 }
+};
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+	gth_main_register_object (GTH_TYPE_VIEWER_PAGE, NULL, GTH_TYPE_MEDIA_VIEWER_PAGE, NULL);
+	gth_main_register_metadata_category (gstreamer_metadata_category);
+	gth_main_register_metadata_info_v (gstreamer_metadata_info);
+	gth_main_register_metadata_provider (GTH_TYPE_METADATA_PROVIDER_GSTREAMER);
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+	return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/gstreamer_tools/preferences.h b/extensions/gstreamer_tools/preferences.h
new file mode 100644
index 0000000..0cad7b1
--- /dev/null
+++ b/extensions/gstreamer_tools/preferences.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef PREFERENCES_H
+#define PREFERENCES_H
+
+#define  PREF_GSTREAMER_SCREESHOT_LOCATION "/apps/gthumb/ext/gstreamer/screenshot_location"
+
+#endif /* PREFERENCES_H */
diff --git a/extensions/gstreamer_utils/Makefile.am b/extensions/gstreamer_utils/Makefile.am
new file mode 100644
index 0000000..d38e6d5
--- /dev/null
+++ b/extensions/gstreamer_utils/Makefile.am
@@ -0,0 +1,33 @@
+if ENABLE_GSTREAMER
+
+extensiondir = $(pkglibdir)/extensions
+extension_LTLIBRARIES = libgstreamer_utils.la
+
+libgstreamer_utils_la_SOURCES = 		\
+	gstreamer-utils.c			\
+	gstreamer-utils.h			\
+	gstscreenshot.c				\
+	gstscreenshot.h
+
+libgstreamer_utils_la_CPPFLAGS = $(GTHUMB_CFLAGS) $(GSTREAMER_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
+libgstreamer_utils_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libgstreamer_utils_la_LIBADD = $(GTHUMB_LIBS) $(GSTREAMER_LIBS)
+libgstreamer_utils_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = gstreamer_utils.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+	$(AM_V_GEN)( sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+	$< > $@ )
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files) 
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/gstreamer_utils/gstreamer-utils.c b/extensions/gstreamer_utils/gstreamer-utils.c
new file mode 100644
index 0000000..6db6a21
--- /dev/null
+++ b/extensions/gstreamer_utils/gstreamer-utils.c
@@ -0,0 +1,792 @@
+/* -*- Mode: CPP; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2008-2009 Free Software Foundation, Inc.
+ *
+ *  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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+/* This was based on a file from Tracker */
+/*
+ * Tracker - audio/video metadata extraction based on GStreamer
+ * Copyright (C) 2006, Laurent Aguerreche (laurent aguerreche free fr)
+ *
+ * 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.
+ */
+
+#include <config.h>
+#include <gst/gst.h>
+#include <gthumb.h>
+#include "gstreamer-utils.h"
+#include "gstscreenshot.h"
+
+
+static gboolean gstreamer_initialized = FALSE;
+
+
+typedef struct {
+	GstElement *playbin;
+	GstTagList *tagcache;
+	gboolean    has_audio;
+	gboolean    has_video;
+	gint        video_height;
+	gint        video_width;
+	gint        video_fps_n;
+	gint        video_fps_d;
+	gint        video_bitrate;
+	char       *video_codec;
+	gint        audio_channels;
+	gint        audio_samplerate;
+	gint        audio_bitrate;
+	char       *audio_codec;
+} MetadataExtractor;
+
+
+static void
+reset_extractor_data (MetadataExtractor *extractor)
+{
+	if (extractor->tagcache != NULL) {
+		gst_tag_list_free (extractor->tagcache);
+		extractor->tagcache = NULL;
+	}
+
+	g_free (extractor->audio_codec);
+	extractor->audio_codec = NULL;
+
+	g_free (extractor->video_codec);
+	extractor->video_codec = NULL;
+
+	extractor->has_audio = FALSE;
+	extractor->has_video = FALSE;
+	extractor->video_fps_n = -1;
+	extractor->video_fps_d = -1;
+	extractor->video_height = -1;
+	extractor->video_width = -1;
+	extractor->video_bitrate = -1;
+	extractor->audio_channels = -1;
+	extractor->audio_samplerate = -1;
+	extractor->audio_bitrate = -1;
+}
+
+
+static void
+metadata_extractor_free (MetadataExtractor *extractor)
+{
+	reset_extractor_data (extractor);
+	gst_element_set_state (extractor->playbin, GST_STATE_NULL);
+	gst_object_unref (GST_OBJECT (extractor->playbin));
+	g_slice_free (MetadataExtractor, extractor);
+}
+
+
+gboolean
+gstreamer_init (void)
+{
+	if (! gstreamer_initialized) {
+		if (! gst_init_check (NULL, NULL, NULL))
+			return FALSE;
+		gstreamer_initialized = TRUE;
+	}
+
+	return TRUE;
+}
+
+
+static void
+add_metadata (GFileInfo  *info,
+              const char *key,
+              char       *raw,
+              char       *formatted)
+{
+	GthMetadata *metadata;
+
+	if (raw == NULL)
+		return;
+
+	if (strcmp (key, "general::dimensions") == 0) {
+		g_file_info_set_attribute_string (info, key, raw);
+		return;
+	}
+	else if (strcmp (key, "general::duration") == 0) {
+		int secs;
+
+		g_free (formatted);
+		sscanf (raw, "%i", &secs);
+		formatted = _g_format_duration_for_display (secs * 1000);
+	}
+	else if (strcmp (key, "audio-video::general::bitrate") == 0) {
+		int bps;
+
+		g_free (formatted);
+		sscanf (raw, "%i", &bps);
+		formatted = g_strdup_printf ("%d kbps", bps / 1000);
+	}
+
+	metadata = gth_metadata_new ();
+	g_object_set (metadata,
+		      "id", key,
+		      "formatted", formatted != NULL ? formatted : raw,
+		      "raw", raw,
+		      NULL);
+	g_file_info_set_attribute_object (info, key, G_OBJECT (metadata));
+
+	g_object_unref (metadata);
+	g_free (raw);
+	g_free (formatted);
+}
+
+
+static void
+add_metadata_from_tag (GFileInfo         *info,
+		       const GstTagList  *list,
+		       const char        *tag,
+		       const char        *tag_key)
+{
+	GType tag_type;
+
+	tag_type = gst_tag_get_type (tag);
+
+	if (tag_type == G_TYPE_BOOLEAN) {
+		gboolean ret;
+		if (gst_tag_list_get_boolean (list, tag, &ret)) {
+			if (ret)
+				add_metadata (info, tag_key, g_strdup ("TRUE"), NULL);
+			else
+				add_metadata (info, tag_key, g_strdup ("FALSE"), NULL);
+		}
+	}
+
+	if (tag_type == G_TYPE_STRING) {
+		char *ret = NULL;
+		if (gst_tag_list_get_string (list, tag, &ret))
+			add_metadata (info, tag_key, ret, NULL);
+	}
+
+        if (tag_type == G_TYPE_UCHAR) {
+                guchar ret = 0;
+                if (gst_tag_list_get_uchar (list, tag, &ret))
+                        add_metadata (info, tag_key, g_strdup_printf ("%u", ret), NULL);
+        }
+
+        if (tag_type == G_TYPE_CHAR) {
+                gchar ret = 0;
+                if (gst_tag_list_get_char (list, tag, &ret))
+                        add_metadata (info, tag_key, g_strdup_printf ("%d", ret), NULL);
+        }
+
+        if (tag_type == G_TYPE_UINT) {
+                guint ret = 0;
+                if (gst_tag_list_get_uint (list, tag, &ret))
+                        add_metadata (info, tag_key, g_strdup_printf ("%u", ret), NULL);
+        }
+
+        if (tag_type == G_TYPE_INT) {
+                gint ret = 0;
+                if (gst_tag_list_get_int (list, tag, &ret))
+                        add_metadata (info, tag_key, g_strdup_printf ("%d", ret), NULL);
+        }
+
+        if (tag_type == G_TYPE_ULONG) {
+                gulong ret = 0;
+                if (gst_tag_list_get_ulong (list, tag, &ret))
+                        add_metadata (info, tag_key, g_strdup_printf ("%lu", ret), NULL);
+        }
+
+        if (tag_type == G_TYPE_LONG) {
+                glong ret = 0;
+                if (gst_tag_list_get_long (list, tag, &ret))
+                        add_metadata (info, tag_key, g_strdup_printf ("%ld", ret), NULL);
+        }
+
+        if (tag_type == G_TYPE_INT64) {
+                gint64 ret = 0;
+                if (gst_tag_list_get_int64 (list, tag, &ret))
+                        add_metadata (info, tag_key, g_strdup_printf ("%" G_GINT64_FORMAT, ret), NULL);
+        }
+
+        if (tag_type == G_TYPE_UINT64) {
+                guint64 ret = 0;
+                if (gst_tag_list_get_uint64 (list, tag, &ret))
+                        add_metadata (info, tag_key, g_strdup_printf ("%" G_GUINT64_FORMAT, ret), NULL);
+        }
+
+        if (tag_type == G_TYPE_DOUBLE) {
+                gdouble ret = 0;
+                if (gst_tag_list_get_double (list, tag, &ret))
+                        add_metadata (info, tag_key, g_strdup_printf ("%f", ret), NULL);
+        }
+
+        if (tag_type == G_TYPE_FLOAT) {
+                gfloat ret = 0;
+                if (gst_tag_list_get_float (list, tag, &ret))
+                        add_metadata (info, tag_key, g_strdup_printf ("%f", ret), NULL);
+        }
+
+        if (tag_type == G_TYPE_DATE) {
+                GDate *ret = NULL;
+                if (gst_tag_list_get_date (list, tag, &ret)) {
+			if (ret != NULL) {
+				char  buf[128];
+				char *raw;
+				char *formatted;
+
+				g_date_strftime (buf, 10, "%F %T", ret);
+				raw = g_strdup (buf);
+
+				g_date_strftime (buf, 10, "%x %X", ret);
+				formatted = g_strdup (buf);
+				add_metadata (info, tag_key, raw, formatted);
+			}
+			g_free (ret);
+		}
+        }
+}
+
+
+static void
+tag_iterate (const GstTagList *list,
+	     const char       *tag,
+	     GFileInfo        *info)
+{
+	const char *tag_key;
+	char       *attribute = NULL;
+
+	tag_key = NULL;
+
+	if (strcmp (tag, "container-format") == 0) {
+		tag_key = "general::format";
+	}
+	else if (strcmp (tag, "bitrate") == 0) {
+		tag_key = "audio-video::general::bitrate";
+	}
+	else if (strcmp (tag, "encoder") == 0) {
+		tag_key = "audio-video::general::encoder";
+	}
+	else if (strcmp (tag, "title") == 0) {
+		tag_key = "general::title";
+	}
+	else if (strcmp (tag, "artist") == 0) {
+		tag_key = "audio-video::general::artist";
+	}
+	else if (strcmp (tag, "album") == 0) {
+		tag_key = "audio-video::general::album";
+	}
+	else if (strcmp (tag, "audio-codec") == 0) {
+		tag_key = "audio-video::audio::codec";
+	}
+	else if (strcmp (tag, "video-codec") == 0) {
+		tag_key = "audio-video::video::codec";
+	}
+
+	if (tag_key == NULL) {
+		GthMetadataInfo *metadata_info;
+
+		attribute = g_strconcat ("audio-video::other::", tag, NULL);
+		metadata_info = gth_main_get_metadata_info (attribute);
+		if (metadata_info == NULL) {
+			GthMetadataInfo info;
+
+			info.id = attribute;
+			info.display_name = gst_tag_get_nick (tag);
+			info.category = "audio-video::other";
+			info.sort_order = 500;
+			info.flags = GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW;
+			metadata_info = gth_main_register_metadata_info (&info);
+		}
+
+		tag_key = attribute;
+	}
+
+	add_metadata_from_tag (info, list, tag, tag_key);
+
+	g_free (attribute);
+}
+
+
+static gint64
+get_media_duration (MetadataExtractor *extractor)
+{
+	GstFormat fmt;
+	gint64	  duration;
+
+	g_return_val_if_fail (extractor, -1);
+	g_return_val_if_fail (extractor->playbin, -1);
+
+	fmt = GST_FORMAT_TIME;
+	duration = -1;
+	if (gst_element_query_duration (extractor->playbin, &fmt, &duration) && (duration >= 0))
+		return duration / GST_SECOND;
+	else
+		return -1;
+}
+
+
+static void
+extract_metadata (MetadataExtractor *extractor,
+		  GFileInfo         *info)
+{
+	gint64 duration;
+
+	if (extractor->audio_channels >= 0)
+		add_metadata (info,
+			      "audio-video::audio::channels",
+			      g_strdup_printf ("%d", (guint) extractor->audio_channels),
+			      g_strdup (extractor->audio_channels == 2 ? _("Stereo") : _("Mono")));
+
+        if (extractor->audio_samplerate >= 0)
+                add_metadata (info,
+                              "audio-video::audio::samplerate",
+                              g_strdup_printf ("%d", (guint) extractor->audio_samplerate),
+                              g_strdup_printf ("%d Hz", (guint) extractor->audio_samplerate));
+
+        if (extractor->audio_bitrate >= 0)
+                add_metadata (info,
+                              "audio-video::audio::bitrate",
+                              g_strdup_printf ("%d", (guint) extractor->audio_bitrate),
+                              g_strdup_printf ("%d bps", (guint) extractor->audio_bitrate));
+
+        if (extractor->video_height >= 0)
+                add_metadata (info,
+                	      "audio-video::video::height",
+                	      g_strdup_printf ("%d", (guint) extractor->video_height),
+                	      NULL);
+
+        if (extractor->video_width >= 0)
+                add_metadata (info,
+                	      "audio-video::video::width",
+                	      g_strdup_printf ("%d", (guint) extractor->video_width),
+                	      NULL);
+
+        if ((extractor->video_height >= 0) && (extractor->video_width >= 0))
+                add_metadata (info,
+                	      "general::dimensions",
+                	      g_strdup_printf ("%d x %d", (guint) extractor->video_width, (guint) extractor->video_height),
+                	      NULL);
+
+        if ((extractor->video_fps_n >= 0) && (extractor->video_fps_d >= 0))
+                add_metadata (info,
+                              "audio-video::video::framerate",
+                              g_strdup_printf ("%.7g", (gdouble) extractor->video_fps_n / (gdouble) extractor->video_fps_d),
+                              g_strdup_printf ("%.7g fps", (gdouble) extractor->video_fps_n / (gdouble) extractor->video_fps_d));
+
+        if (extractor->video_bitrate >= 0)
+                add_metadata (info,
+                              "audio-video::video::bitrate",
+                              g_strdup_printf ("%d", (guint) extractor->video_bitrate),
+                              g_strdup_printf ("%d bps", (guint) extractor->video_bitrate));
+
+	duration = get_media_duration (extractor);
+	if (duration >= 0)
+		add_metadata (info,
+			      "general::duration",
+			      g_strdup_printf ("%" G_GINT64_FORMAT, duration),
+			      g_strdup_printf ("%" G_GINT64_FORMAT " sec", duration));
+
+	if (extractor->tagcache != NULL)
+		gst_tag_list_foreach (extractor->tagcache, (GstTagForeachFunc) tag_iterate, info);
+}
+
+
+static void
+caps_set (GstPad           *pad,
+	  MetadataExtractor *extractor,
+	  const char        *type)
+{
+	GstCaps	     *caps;
+	GstStructure *structure;
+
+	if ((caps = gst_pad_get_negotiated_caps (pad)) == NULL)
+		return;
+
+	structure = gst_caps_get_structure (caps, 0);
+	if (structure == NULL) {
+		gst_caps_unref (caps);
+		return;
+	}
+
+	if (strcmp (type, "audio") == 0) {
+		gst_structure_get_int (structure, "channels", &extractor->audio_channels);
+		gst_structure_get_int (structure, "rate", &extractor->audio_samplerate);
+		gst_structure_get_int (structure, "bitrate", &extractor->audio_bitrate);
+	}
+	else if (strcmp (type, "video") == 0) {
+		gst_structure_get_fraction (structure, "framerate", &extractor->video_fps_n, &extractor->video_fps_d);
+		gst_structure_get_int (structure, "bitrate", &extractor->video_bitrate);
+		gst_structure_get_int (structure, "width", &extractor->video_width);
+		gst_structure_get_int (structure, "height", &extractor->video_height);
+	}
+
+	gst_caps_unref (caps);
+}
+
+
+static void
+update_stream_info (MetadataExtractor *extractor)
+{
+	GList  *streaminfo;
+	GstPad *audiopad;
+	GstPad *videopad;
+
+	g_return_if_fail (extractor);
+
+	streaminfo = NULL;
+	audiopad = videopad = NULL;
+
+	g_object_get (extractor->playbin, "stream-info", &streaminfo, NULL);
+	streaminfo = g_list_copy (streaminfo);
+	g_list_foreach (streaminfo, (GFunc) g_object_ref, NULL);
+
+	for (/* void */ ; streaminfo; streaminfo = streaminfo->next) {
+		GObject    *info;
+		int         type;
+		GParamSpec *pspec;
+		GEnumValue *val;
+
+		info = streaminfo->data;
+		if (info == NULL)
+			continue;
+
+                type = -1;
+
+		g_object_get (info, "type", &type, NULL);
+		pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info), "type");
+		val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type);
+
+		if (strcmp (val->value_nick, "audio") == 0) {
+			extractor->has_audio = TRUE;
+			if (audiopad == NULL)
+				g_object_get (info, "object", &audiopad, NULL);
+		}
+		else if (strcmp (val->value_nick, "video") == 0) {
+			extractor->has_video = TRUE;
+			if (videopad == NULL)
+				g_object_get (info, "object", &videopad, NULL);
+		}
+	}
+
+	if (audiopad != NULL) {
+		GstCaps *caps;
+
+		if ((caps = gst_pad_get_negotiated_caps (audiopad)) != NULL) {
+			caps_set (audiopad, extractor, "audio");
+			gst_caps_unref (caps);
+		}
+	}
+
+	if (videopad != NULL) {
+		GstCaps *caps;
+
+		if ((caps = gst_pad_get_negotiated_caps (videopad)) != NULL) {
+			caps_set (videopad, extractor, "video");
+			gst_caps_unref (caps);
+		}
+	}
+
+	g_list_foreach (streaminfo, (GFunc) g_object_unref, NULL);
+	g_list_free (streaminfo);
+}
+
+
+static gboolean
+message_loop_to_state_change (MetadataExtractor *extractor,
+			      GstState           state)
+{
+	GstBus         *bus;
+	GstMessageType  events;
+
+	g_return_val_if_fail (extractor, FALSE);
+	g_return_val_if_fail (extractor->playbin, FALSE);
+
+	bus = gst_element_get_bus (extractor->playbin);
+
+	events = (GST_MESSAGE_TAG | GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
+
+	for (;;) {
+		GstMessage *message;
+
+		message = gst_bus_timed_pop_filtered (bus, GST_SECOND * 5, events);
+		if (message == NULL)
+			goto timed_out;
+
+		switch (GST_MESSAGE_TYPE (message)) {
+		case GST_MESSAGE_STATE_CHANGED: {
+			GstState old_state;
+			GstState new_state;
+
+			old_state = new_state = GST_STATE_NULL;
+
+			gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
+			if (old_state == new_state)
+				break;
+
+			/* we only care about playbin (pipeline) state changes */
+			if (GST_MESSAGE_SRC (message) != GST_OBJECT (extractor->playbin))
+				break;
+
+			if ((old_state == GST_STATE_READY) && (new_state == GST_STATE_PAUSED))
+				update_stream_info (extractor);
+			else if ((old_state == GST_STATE_PAUSED) && (new_state == GST_STATE_READY))
+				reset_extractor_data (extractor);
+
+			if (new_state == state) {
+				gst_message_unref (message);
+				goto success;
+			}
+
+			break;
+		}
+
+		case GST_MESSAGE_TAG: {
+			GstTagList *tag_list;
+			GstTagList *result;
+
+			tag_list = NULL;
+			gst_message_parse_tag (message, &tag_list);
+			result = gst_tag_list_merge (extractor->tagcache, tag_list, GST_TAG_MERGE_KEEP);
+			if (extractor->tagcache != NULL)
+				gst_tag_list_free (extractor->tagcache);
+			extractor->tagcache = result;
+
+			gst_tag_list_free (tag_list);
+
+			break;
+		}
+
+		case GST_MESSAGE_ERROR: {
+			gchar  *debug    = NULL;
+			GError *gsterror = NULL;
+
+			gst_message_parse_error (message, &gsterror, &debug);
+
+			/*g_warning ("Error: %s (%s)", gsterror->message, debug);*/
+
+			g_error_free (gsterror);
+			gst_message_unref (message);
+			g_free (debug);
+			goto error;
+		}
+			break;
+
+		case GST_MESSAGE_EOS: {
+			g_warning ("Media file could not be played.");
+			gst_message_unref (message);
+			goto error;
+		}
+			break;
+
+		default:
+			g_assert_not_reached ();
+			break;
+		}
+
+		gst_message_unref (message);
+	}
+
+	g_assert_not_reached ();
+
+ success:
+	/* state change succeeded */
+	GST_DEBUG ("state change to %s succeeded", gst_element_state_get_name (state));
+	return TRUE;
+
+ timed_out:
+	/* it's taking a long time to open  */
+	GST_DEBUG ("state change to %s timed out, returning success", gst_element_state_get_name (state));
+	return TRUE;
+
+ error:
+	GST_DEBUG ("error while waiting for state change to %s", gst_element_state_get_name (state));
+	/* already set *error */
+	return FALSE;
+}
+
+
+gboolean
+gstreamer_read_metadata_from_file (GFile       *file,
+				   GFileInfo   *info,
+				   GError     **error)
+{
+	char              *uri;
+	MetadataExtractor *extractor;
+
+	if (! gstreamer_init ())
+		return FALSE;
+
+	uri = g_file_get_uri (file);
+	g_return_val_if_fail (uri != NULL, FALSE);
+
+	extractor = g_slice_new0 (MetadataExtractor);
+	reset_extractor_data (extractor);
+
+	extractor->playbin = gst_element_factory_make ("playbin", "playbin");
+	g_object_set (G_OBJECT (extractor->playbin),
+		      "uri", uri,
+		      "audio-sink", gst_element_factory_make ("fakesink", "fakesink-audio"),
+		      "video-sink", gst_element_factory_make ("fakesink", "fakesink-video"),
+		      NULL);
+
+	gst_element_set_state (extractor->playbin, GST_STATE_PAUSED);
+	message_loop_to_state_change (extractor, GST_STATE_PAUSED);
+	extract_metadata (extractor, info);
+
+	metadata_extractor_free (extractor);
+	g_free (uri);
+
+	return TRUE;
+}
+
+
+/* -- _gst_playbin_get_current_frame -- */
+
+/* this is voodoo code taken from totem, kudos to the authors, license is GPL */
+
+
+typedef struct {
+
+	GdkPixbuf          *pixbuf;
+	FrameReadyCallback  cb;
+	gpointer            user_data;
+} ScreenshotData;
+
+
+static void
+screenshot_data_finalize (ScreenshotData *data)
+{
+	if (data->cb != NULL)
+		data->cb (data->pixbuf, data->user_data);
+	g_free (data);
+}
+
+
+static void
+destroy_pixbuf (guchar *pix, gpointer data)
+{
+	gst_buffer_unref (GST_BUFFER (data));
+}
+
+
+static void
+get_current_frame_step2 (GstBuffer *buf,
+			 gpointer   user_data)
+{
+	ScreenshotData *data = user_data;
+	GstStructure   *s;
+	int             outwidth = 0;
+	int             outheight = 0;
+
+	if (buf == NULL) {
+		g_warning ("Could not take screenshot: %s", "conversion failed");
+		screenshot_data_finalize (data);
+		return;
+	}
+
+	if (GST_BUFFER_CAPS (buf) == NULL) {
+		g_warning ("Could not take screenshot: %s", "no caps on output buffer");
+		screenshot_data_finalize (data);
+		return;
+	}
+
+	s = gst_caps_get_structure (GST_BUFFER_CAPS (buf), 0);
+	gst_structure_get_int (s, "width", &outwidth);
+	gst_structure_get_int (s, "height", &outheight);
+
+	g_return_if_fail (outwidth > 0 && outheight > 0);
+
+	/* create pixbuf from that - use our own destroy function */
+
+	data->pixbuf = gdk_pixbuf_new_from_data (GST_BUFFER_DATA (buf),
+						 GDK_COLORSPACE_RGB,
+						 FALSE,
+						 8,
+						 outwidth,
+						 outheight,
+						 GST_ROUND_UP_4 (outwidth * 3),
+						 destroy_pixbuf,
+						 buf);
+	if (data->pixbuf == NULL) {
+		g_warning ("Could not take screenshot: %s", "could not create pixbuf");
+		gst_buffer_unref (buf);
+	}
+
+	screenshot_data_finalize (data);
+}
+
+
+gboolean
+_gst_playbin_get_current_frame (GstElement          *playbin,
+				int                  video_fps_n,
+				int                  video_fps_d,
+				FrameReadyCallback   cb,
+				gpointer             user_data)
+{
+	ScreenshotData *data;
+	GstBuffer      *buf;
+	GstCaps        *to_caps;
+
+	g_object_get (playbin, "frame", &buf, NULL);
+
+	if (buf == NULL) {
+		g_warning ("Could not take screenshot: %s", "no last video frame");
+		return FALSE;
+	}
+
+	if (GST_BUFFER_CAPS (buf) == NULL) {
+		g_warning ("Could not take screenshot: %s", "no caps on buffer");
+		return FALSE;
+	}
+
+	/* convert to our desired format (RGB24) */
+
+	data = g_new0 (ScreenshotData, 1);
+	data->cb = cb;
+	data->user_data = user_data;
+
+	to_caps = gst_caps_new_simple ("video/x-raw-rgb",
+				       "bpp", G_TYPE_INT, 24,
+				       "depth", G_TYPE_INT, 24,
+				       /* Note: we don't ask for a specific width/height here, so that
+				        * videoscale can adjust dimensions from a non-1/1 pixel aspect
+				        * ratio to a 1/1 pixel-aspect-ratio */
+				       "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
+				       "endianness", G_TYPE_INT, G_BIG_ENDIAN,
+				       "red_mask", G_TYPE_INT, 0xff0000,
+				       "green_mask", G_TYPE_INT, 0x00ff00,
+				       "blue_mask", G_TYPE_INT, 0x0000ff,
+				       NULL);
+
+	if (video_fps_n > 0 && video_fps_d > 0) {
+		gst_caps_set_simple (to_caps, "framerate",
+				     GST_TYPE_FRACTION, video_fps_n, video_fps_d,
+				     NULL);
+	}
+
+	return bvw_frame_conv_convert (buf, to_caps, get_current_frame_step2, data);
+}
+
diff --git a/extensions/gstreamer_utils/gstreamer-utils.h b/extensions/gstreamer_utils/gstreamer-utils.h
new file mode 100644
index 0000000..033642d
--- /dev/null
+++ b/extensions/gstreamer_utils/gstreamer-utils.h
@@ -0,0 +1,47 @@
+/* -*- Mode: CPP; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GSTREAMER_UTILS_H
+#define GSTREAMER_UTILS_H
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gst/gst.h>
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+typedef void (*FrameReadyCallback) (GdkPixbuf *, gpointer user_data);
+
+gboolean    gstreamer_init                    (void);
+gboolean    gstreamer_read_metadata_from_file (GFile               *file,
+					       GFileInfo           *info,
+					       GError             **error);
+gboolean    _gst_playbin_get_current_frame    (GstElement          *playbin,
+					       int                  video_fps_n,
+					       int                  video_fps_d,
+					       FrameReadyCallback   cb,
+					       gpointer             user_data);
+
+G_END_DECLS
+
+#endif /* GSTREAMER_UTILS_H */
diff --git a/extensions/gstreamer_utils/gstreamer_utils.extension.in.in b/extensions/gstreamer_utils/gstreamer_utils.extension.in.in
new file mode 100644
index 0000000..bb60f9b
--- /dev/null
+++ b/extensions/gstreamer_utils/gstreamer_utils.extension.in.in
@@ -0,0 +1,6 @@
+[Extension]
+Hidden=true
+
+[Loader]
+Type=module
+File=%LIBRARY%
diff --git a/extensions/gstreamer_utils/gstscreenshot.c b/extensions/gstreamer_utils/gstscreenshot.c
new file mode 100644
index 0000000..f7f177e
--- /dev/null
+++ b/extensions/gstreamer_utils/gstscreenshot.c
@@ -0,0 +1,259 @@
+/* Small helper element for format conversion
+ * (c) 2004 Ronald Bultje <rbultje ronald bitfreak net>
+ * Portion Copyright © 2009 Nokia Corporation and/or its
+ * subsidiary(-ies).* All rights reserved. *
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <string.h>
+
+#include "gstscreenshot.h"
+
+typedef struct {
+	GstBuffer *result;
+	GstElement *src;
+	GstElement *sink;
+	GstElement *pipeline;
+	BvwFrameConvCb cb;
+	gpointer cb_data;
+} GstScreenshotData;
+
+/* GST_DEBUG_CATEGORY_EXTERN (_totem_gst_debug_cat); */
+/* #define GST_CAT_DEFAULT _totem_gst_debug_cat */
+
+static void feed_fakesrc(GstElement *src, GstBuffer *buf, GstPad *pad,
+			 gpointer data)
+{
+	GstBuffer *in_buf = GST_BUFFER(data);
+
+	g_assert(GST_BUFFER_SIZE(buf) >= GST_BUFFER_SIZE(in_buf));
+	g_assert(!GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_READONLY));
+
+	gst_buffer_set_caps(buf, GST_BUFFER_CAPS(in_buf));
+
+	memcpy(GST_BUFFER_DATA(buf), GST_BUFFER_DATA(in_buf),
+	       GST_BUFFER_SIZE(in_buf));
+
+	GST_BUFFER_SIZE(buf) = GST_BUFFER_SIZE(in_buf);
+
+	GST_DEBUG("feeding buffer %p, size %u, caps %" GST_PTR_FORMAT,
+		  buf, GST_BUFFER_SIZE(buf), GST_BUFFER_CAPS(buf));
+
+	gst_buffer_unref(in_buf);
+}
+
+static void save_result(GstElement *sink, GstBuffer *buf, GstPad *pad,
+			gpointer data)
+{
+	GstScreenshotData *gsd = data;
+
+	gsd->result = gst_buffer_ref(buf);
+
+	GST_DEBUG("received converted buffer %p with caps %" GST_PTR_FORMAT,
+		  gsd->result, GST_BUFFER_CAPS(gsd->result));
+}
+
+static gboolean create_element(const gchar *factory_name, GstElement **element,
+			       GError **err)
+{
+	*element = gst_element_factory_make(factory_name, NULL);
+	if (*element)
+		return TRUE;
+
+	if (err && *err == NULL) {
+		*err = g_error_new(
+			GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN,
+			"cannot create element '%s' - please check your "
+			"GStreamer installation", factory_name);
+	}
+
+	return FALSE;
+}
+
+static gboolean finalize_process(GstScreenshotData *gsd)
+{
+	g_signal_handlers_disconnect_matched(gsd->sink, (GSignalMatchType)
+					     G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+					     save_result, NULL);
+	g_signal_handlers_disconnect_matched(gsd->src, (GSignalMatchType)
+					     G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+					     feed_fakesrc, NULL);
+	gst_element_set_state(gsd->pipeline, GST_STATE_NULL);
+
+	g_free(gsd);
+
+	return FALSE;
+}
+
+static gboolean async_bus_handler(GstBus *bus, GstMessage *msg,
+				  gpointer data)
+{
+	GstScreenshotData *gsd = data;
+	gboolean keep_watch = TRUE;
+
+	switch (GST_MESSAGE_TYPE(msg)) {
+	case GST_MESSAGE_EOS: {
+		if (gsd->result != NULL) {
+			GST_DEBUG("conversion successful: result = %p",
+				  gsd->result);
+		} else {
+			GST_WARNING("EOS but no result frame?!");
+		}
+		gsd->cb(gsd->result, gsd->cb_data);
+		keep_watch = finalize_process(gsd);
+		break;
+	}
+	case GST_MESSAGE_ERROR: {
+		gchar *dbg = NULL;
+		GError *error = NULL;
+
+		gst_message_parse_error(msg, &error, &dbg);
+		if (error != NULL) {
+			g_warning("Could not take screenshot: %s",
+				  error->message);
+			GST_DEBUG("%s [debug: %s]", error->message,
+				  GST_STR_NULL(dbg));
+			g_error_free(error);
+		} else {
+			g_warning("Could not take screenshot(and "
+				  "NULL error!)");
+		}
+		g_free(dbg);
+		gsd->result = NULL;
+		gsd->cb(gsd->result, gsd->cb_data);
+		keep_watch = finalize_process(gsd);
+		break;
+	}
+	default:
+		break;
+	}
+
+	return keep_watch;
+}
+
+/* takes ownership of the input buffer */
+gboolean bvw_frame_conv_convert(GstBuffer *buf, GstCaps *to_caps,
+				BvwFrameConvCb cb, gpointer cb_data)
+{
+	static GstElement *src = NULL, *sink = NULL, *pipeline = NULL,
+		*filter1 = NULL, *filter2 = NULL;
+	static GstBus *bus;
+	GError *error = NULL;
+	GstCaps *to_caps_no_par;
+	GstScreenshotData *gsd;
+
+	g_return_val_if_fail(GST_BUFFER_CAPS(buf) != NULL, FALSE);
+	g_return_val_if_fail(cb != NULL, FALSE);
+
+	if (pipeline == NULL) {
+		GstElement *csp, *vscale;
+
+		pipeline = gst_pipeline_new("screenshot-pipeline");
+		if(pipeline == NULL) {
+			g_warning("Could not take screenshot: "
+				  "no pipeline (unknown error)");
+			return FALSE;
+		}
+
+		/* videoscale is here to correct for the
+		 * pixel-aspect-ratio for us */
+		GST_DEBUG("creating elements");
+		if(!create_element("fakesrc", &src, &error) ||
+		   !create_element("ffmpegcolorspace", &csp, &error) ||
+		   !create_element("videoscale", &vscale, &error) ||
+		   !create_element("capsfilter", &filter1, &error) ||
+		   !create_element("capsfilter", &filter2, &error) ||
+		   !create_element("fakesink", &sink, &error)) {
+			g_warning("Could not take screenshot: %s",
+				  error->message);
+			g_error_free(error);
+			return FALSE;
+		}
+
+		GST_DEBUG("adding elements");
+		gst_bin_add_many(GST_BIN(pipeline), src, csp, filter1, vscale,
+				 filter2, sink, NULL);
+
+		g_object_set(sink, "preroll-queue-len", 1,
+			     "signal-handoffs", TRUE, NULL);
+
+		/* set to 'fixed' sizetype */
+		g_object_set(src, "sizetype", 2, "num-buffers", 1,
+			     "signal-handoffs", TRUE, NULL);
+
+		/* FIXME: linking is still way too expensive, profile
+		 * this properly */
+		GST_DEBUG("linking src->csp");
+		if(!gst_element_link_pads(src, "src", csp, "sink"))
+			return FALSE;
+
+		GST_DEBUG("linking csp->filter1");
+		if(!gst_element_link_pads(csp, "src", filter1, "sink"))
+			return FALSE;
+
+		GST_DEBUG("linking filter1->vscale");
+		if(!gst_element_link_pads(filter1, "src", vscale, "sink"))
+			return FALSE;
+
+		GST_DEBUG("linking vscale->capsfilter");
+		if(!gst_element_link_pads(vscale, "src", filter2, "sink"))
+			return FALSE;
+
+		GST_DEBUG("linking capsfilter->sink");
+		if(!gst_element_link_pads(filter2, "src", sink, "sink"))
+			return FALSE;
+
+		bus = gst_element_get_bus(pipeline);
+	}
+
+	/* adding this superfluous capsfilter makes linking cheaper */
+	to_caps_no_par = gst_caps_copy(to_caps);
+	gst_structure_remove_field(gst_caps_get_structure(to_caps_no_par, 0),
+				   "pixel-aspect-ratio");
+	g_object_set(filter1, "caps", to_caps_no_par, NULL);
+	gst_caps_unref(to_caps_no_par);
+
+	g_object_set(filter2, "caps", to_caps, NULL);
+	gst_caps_unref(to_caps);
+
+	gsd = g_new0(GstScreenshotData, 1);
+
+	gsd->src = src;
+	gsd->sink = sink;
+	gsd->pipeline = pipeline;
+	gsd->cb = cb;
+	gsd->cb_data = cb_data;
+
+	g_signal_connect(sink, "handoff", G_CALLBACK(save_result), gsd);
+
+	g_signal_connect(src, "handoff", G_CALLBACK(feed_fakesrc), buf);
+
+	gst_bus_add_watch(bus, async_bus_handler, gsd);
+
+	/* set to 'fixed' sizetype */
+	g_object_set(src, "sizemax", GST_BUFFER_SIZE(buf), NULL);
+
+	GST_DEBUG("running conversion pipeline");
+	gst_element_set_state(pipeline, GST_STATE_PLAYING);
+
+	return TRUE;
+}
diff --git a/extensions/gstreamer_utils/gstscreenshot.h b/extensions/gstreamer_utils/gstscreenshot.h
new file mode 100644
index 0000000..d3cf23c
--- /dev/null
+++ b/extensions/gstreamer_utils/gstscreenshot.h
@@ -0,0 +1,36 @@
+/* Small helper element for format conversion
+ * (c) 2004 Ronald Bultje <rbultje ronald bitfreak net>
+ * Portion Copyright © 2009 Nokia Corporation and/or its
+ * subsidiary(-ies).* All rights reserved. *
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __BVW_FRAME_CONV_H__
+#define __BVW_FRAME_CONV_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+typedef void (*BvwFrameConvCb)(GstBuffer *result, gpointer user_data);
+
+gboolean bvw_frame_conv_convert (GstBuffer *buf, GstCaps *to,
+				 BvwFrameConvCb cb, gpointer cb_data);
+
+G_END_DECLS
+
+#endif /* __BVW_FRAME_CONV_H__ */
diff --git a/extensions/slideshow/Makefile.am b/extensions/slideshow/Makefile.am
index e511d8d..8e5419d 100644
--- a/extensions/slideshow/Makefile.am
+++ b/extensions/slideshow/Makefile.am
@@ -23,6 +23,9 @@ libslideshow_la_SOURCES = 		\
 libslideshow_la_CFLAGS = $(GTHUMB_CFLAGS) $(CLUTTER_CFLAGS) $(GSTREAMER_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
 libslideshow_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
 libslideshow_la_LIBADD = $(GTHUMB_LIBS) $(CLUTTER_LIBS) $(GSTREAMER_LIBS) ../catalogs/libcatalogs.la
+if ENABLE_GSTREAMER
+libslideshow_la_LIBADD += ../gstreamer_utils/libgstreamer_utils.la
+endif
 libslideshow_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
 
 extensioninidir = $(extensiondir)
diff --git a/extensions/slideshow/gth-slideshow.c b/extensions/slideshow/gth-slideshow.c
index d0da6db..e728e4d 100644
--- a/extensions/slideshow/gth-slideshow.c
+++ b/extensions/slideshow/gth-slideshow.c
@@ -26,6 +26,7 @@
 #include <clutter-gtk/clutter-gtk.h>
 #if HAVE_GSTREAMER
 #include <gst/gst.h>
+#include <extensions/gstreamer_utils/gstreamer-utils.h>
 #endif
 #include "gth-slideshow.h"
 #include "gth-transition.h"
@@ -552,22 +553,22 @@ gth_slideshow_show_cb (GtkWidget    *widget,
 		return;
 
 #if HAVE_GSTREAMER
-	if ((self->priv->audio_files != NULL) && (self->priv->audio_files[0] != NULL)) {
-		self->priv->current_audio_file = 0;
-		if (self->priv->playbin == NULL) {
-			GstBus *bus;
-
-			gst_init_check (NULL, NULL, NULL); /* FIXME */
-
-			self->priv->playbin = gst_element_factory_make ("playbin", "playbin");
-			bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->playbin));
-			gst_bus_add_signal_watch (bus);
-			g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), self);
+	if (gstreamer_init()) {
+		if ((self->priv->audio_files != NULL) && (self->priv->audio_files[0] != NULL)) {
+			self->priv->current_audio_file = 0;
+			if (self->priv->playbin == NULL) {
+				GstBus *bus;
+
+				self->priv->playbin = gst_element_factory_make ("playbin", "playbin");
+				bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->playbin));
+				gst_bus_add_signal_watch (bus);
+				g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), self);
+			}
+			else
+				gst_element_set_state (self->priv->playbin, GST_STATE_NULL);
+			g_object_set (G_OBJECT (self->priv->playbin), "uri", self->priv->audio_files[self->priv->current_audio_file], NULL);
+			gst_element_set_state (self->priv->playbin, GST_STATE_PLAYING);
 		}
-		else
-			gst_element_set_state (self->priv->playbin, GST_STATE_NULL);
-		g_object_set (G_OBJECT (self->priv->playbin), "uri", self->priv->audio_files[self->priv->current_audio_file], NULL);
-		gst_element_set_state (self->priv->playbin, GST_STATE_PLAYING);
 	}
 #endif
 
diff --git a/gthumb/gth-main.c b/gthumb/gth-main.c
index 3b38407..8568e37 100644
--- a/gthumb/gth-main.c
+++ b/gthumb/gth-main.c
@@ -1198,7 +1198,8 @@ gth_main_activate_extensions (void)
 						"exiv2_tools",
 						"file_manager",
 						"file_tools",
-						"gstreamer",
+						"gstreamer_tools",
+						"gstreamer_utils",
 						"image_print",
 						"image_rotation",
 						"image_viewer",



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]