diff -u -u -r1.233 configure.ac
--- configure.ac	20 Apr 2006 17:25:17 -0000	1.233
+++ configure.ac	23 Apr 2006 08:37:00 -0000
@@ -912,6 +912,7 @@
 plugins/Makefile
 plugins/sample/Makefile
 plugins/audioscrobbler/Makefile
+plugins/gst-plugin-install/Makefile
 plugins/ipod/Makefile
 plugins/lirc/Makefile
 plugins/sample-python/Makefile
diff -u -u -r1.16 rb-marshal.list
--- lib/rb-marshal.list	17 Apr 2006 14:35:24 -0000	1.16
+++ lib/rb-marshal.list	23 Apr 2006 08:37:02 -0000
@@ -1,5 +1,6 @@
 BOOLEAN:BOOLEAN,BOOLEAN,BOOLEAN
 BOOLEAN:POINTER
+BOOLEAN:STRING,ULONG,STRING,POINTER
 INT:VOID
 OBJECT:OBJECT
 STRING:STRING
diff -u -u -r1.4 rb-metadata-dbus-client.c
--- metadata/rb-metadata-dbus-client.c	20 Apr 2006 20:43:22 -0000	1.4
+++ metadata/rb-metadata-dbus-client.c	23 Apr 2006 08:37:02 -0000
@@ -70,6 +70,8 @@
 {
 	char *uri;
 	char *mimetype;
+	gboolean has_audio;
+	gboolean has_video;
 	GHashTable *metadata;
 };
 
@@ -386,6 +388,16 @@
 				     RB_METADATA_ERROR,
 				     RB_METADATA_ERROR_INTERNAL,
 				     _("D-BUS communication error"));
+		} else if (!rb_metadata_dbus_get_boolean (&iter, &md->priv->has_audio)) {
+			g_set_error (error,
+				     RB_METADATA_ERROR,
+				     RB_METADATA_ERROR_INTERNAL,
+				     _("D-BUS communication error"));
+		} else if (!rb_metadata_dbus_get_boolean (&iter, &md->priv->has_video)) {
+			g_set_error (error,
+				     RB_METADATA_ERROR,
+				     RB_METADATA_ERROR_INTERNAL,
+				     _("D-BUS communication error"));
 		} else {
 			/* get metadata */
 			rb_debug ("got mimetype: %s", md->priv->mimetype);
@@ -405,6 +417,18 @@
 }
 
 gboolean
+rb_metadata_has_audio (RBMetaData *md)
+{
+	return md->priv->has_audio;
+}
+
+gboolean
+rb_metadata_has_video (RBMetaData *md)
+{
+	return md->priv->has_video;
+}
+
+gboolean
 rb_metadata_get (RBMetaData *md, RBMetaDataField field,
 		 GValue *ret)
 {
@@ -530,4 +554,3 @@
 		read_error_from_message (md, &iter, error);
 	}
 }
-
diff -u -u -r1.6 rb-metadata-dbus-service.c
--- metadata/rb-metadata-dbus-service.c	20 Apr 2006 20:43:22 -0000	1.6
+++ metadata/rb-metadata-dbus-service.c	23 Apr 2006 08:37:02 -0000
@@ -98,6 +98,8 @@
 	GError *error = NULL;
 	gboolean ok = TRUE;
 	const char *mimetype = NULL;
+	gboolean has_audio;
+	gboolean has_video;
 
 	if (!dbus_message_iter_init (message, &iter)) {
 		return DBUS_HANDLER_RESULT_NEED_MEMORY;
@@ -132,10 +134,15 @@
 	}
 
 	mimetype = rb_metadata_get_mime (svc->metadata);
+	has_audio = rb_metadata_has_audio (svc->metadata);
+	has_video = rb_metadata_has_video (svc->metadata);
+	
 	dbus_message_iter_init_append (reply, &iter);
 
 	if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_BOOLEAN, &ok) ||
-	    !dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &mimetype)) {
+	    !dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &mimetype) ||
+	    !dbus_message_iter_append_basic (&iter, DBUS_TYPE_BOOLEAN, &has_audio) ||
+	    !dbus_message_iter_append_basic (&iter, DBUS_TYPE_BOOLEAN, &has_video)) {
 		rb_debug ("out of memory adding data to return message");
 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 	}
@@ -394,9 +401,7 @@
 	md = rb_metadata_new ();
 	rb_metadata_load (md, uri, &error);
 	if (error) {
-		if (error->code == RB_METADATA_ERROR_NOT_AUDIO_IGNORE) {
-			g_print ("%s is not an audio stream (ignored)\n", uri);
-		} else if (error->code == RB_METADATA_ERROR_NOT_AUDIO) {
+		if (error->code == RB_METADATA_ERROR_NOT_AUDIO) {
 			g_print ("%s is not an audio stream\n", uri);
 		} else {
 			g_print ("Error loading metadata from %s: %s\n", uri, error->message);
diff -u -u -r1.70 rb-metadata-gst.c
--- metadata/rb-metadata-gst.c	20 Apr 2006 20:43:22 -0000	1.70
+++ metadata/rb-metadata-gst.c	23 Apr 2006 08:37:04 -0000
@@ -50,23 +50,6 @@
 typedef GstElement *(*RBAddTaggerElem) (GstBin *, GstElement *);
 
 
-/*
- * The list of mine type prefixes for files that shouldn't display errors about being non-audio.
- * Useful for people who have cover art, et cetera, in their music directories
- */
-const char * ignore_mime_types[] = {
-	"image/",
-	"text/",
-	"application/xml"
-};
-
-/*
- * File size below which we will simply ignore files that can't be identified.
- * This is mostly here so we ignore the various text files that are packaged
- * with many netlabel releases and other downloads.
- */
-#define REALLY_SMALL_FILE_SIZE	(4096)
-
 struct RBMetadataGstType
 {
 	char *mimetype;
@@ -621,8 +604,8 @@
 rb_metadata_gst_typefind_cb (GstElement *typefind, guint probability, GstCaps *caps, RBMetaData *md)
 {
 	if (!(gst_caps_is_empty (caps) || gst_caps_is_any (caps))) {
-		g_free (md->priv->type);
-		md->priv->type = g_strdup (gst_structure_get_name (gst_caps_get_structure (caps, 0)));
+		if (md->priv->type == NULL)
+			md->priv->type = g_strdup (gst_structure_get_name (gst_caps_get_structure (caps, 0)));
 		rb_debug ("found type %s", md->priv->type);
 	}
 
@@ -830,8 +813,6 @@
 	GstElement *urisrc = NULL;
 	GstElement *decodebin = NULL;
 	GstElement *typefind = NULL;
-	gint64 file_size = -1;
-	GstFormat file_size_format = GST_FORMAT_BYTES;
 #ifdef HAVE_GSTREAMER_0_10
 	GstStateChangeReturn state_ret;
 	int change_timeout;
@@ -1009,13 +990,9 @@
 	 * the pipeline to NULL state.
 	 */
 #ifdef HAVE_GSTREAMER_0_8
-	gst_element_query (urisrc, GST_QUERY_TOTAL, &file_size_format, &file_size);
 	gst_element_set_state (pipeline, GST_STATE_NULL);
 #endif
 #ifdef HAVE_GSTREAMER_0_10
-	if (gst_element_query_duration (urisrc, &file_size_format, &file_size))
-		g_assert (file_size_format == GST_FORMAT_BYTES);
-	
 	state_ret = gst_element_set_state (pipeline, GST_STATE_NULL);
 	if (state_ret == GST_STATE_CHANGE_ASYNC) {
 		g_warning ("Failed to return metadata reader to NULL state");
@@ -1030,46 +1007,18 @@
 	if (md->priv->error) {
 		g_propagate_error (error, md->priv->error);
 	} else if (!md->priv->type) {
-		/* ignore really small files that can't be identified */
-		gint error_code = RB_METADATA_ERROR_UNRECOGNIZED;
-		if (file_size > 0 && file_size < REALLY_SMALL_FILE_SIZE) {
-			rb_debug ("ignoring %s because it's too small to care about", md->priv->uri);
-			error_code = RB_METADATA_ERROR_NOT_AUDIO_IGNORE;
-		}
-		
 		g_clear_error (error);
 		g_set_error (error,
 			     RB_METADATA_ERROR,
-			     error_code,
+			     RB_METADATA_ERROR_UNRECOGNIZED,
 			     _("The MIME type of the file could not be identified"));
 	} else if (md->priv->non_audio || !md->priv->handoff) {
-		gboolean ignore = FALSE;
-		int i;
-
-		if (md->priv->has_video) {
-			ignore = TRUE;
-		} else {
-			for (i = 0; i < G_N_ELEMENTS (ignore_mime_types); i++) {
-				if (g_str_has_prefix (md->priv->type, ignore_mime_types[i])) {
-					ignore = TRUE;
-					break;
-				}
-			}
-		}
-
-		if (!ignore) {
-			char *msg = make_undecodable_error (md);
-			g_set_error (error,
-				     RB_METADATA_ERROR,
-				     RB_METADATA_ERROR_NOT_AUDIO,
-				     "%s", msg);
-			g_free (msg);
-		} else {
-			g_set_error (error,
-				     RB_METADATA_ERROR,
-				     RB_METADATA_ERROR_NOT_AUDIO_IGNORE,
-				     NULL);
-		}
+		char *msg = make_undecodable_error (md);
+		g_set_error (error,
+			     RB_METADATA_ERROR,
+			     RB_METADATA_ERROR_NOT_AUDIO,
+			     "%s", msg);
+		g_free (msg);
 	} else {
 		/* yay, it worked */
 		rb_debug ("successfully read metadata for %s", uri);
@@ -1340,5 +1289,17 @@
 	g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (field),
 			     newval);
 	return TRUE;
+}
+
+gboolean
+rb_metadata_has_video (RBMetaData *md)
+{
+	return md->priv->has_video;
+}
+
+gboolean
+rb_metadata_has_audio (RBMetaData *md)
+{
+	return !md->priv->non_audio;
 }
 
diff -u -u -r1.13 rb-metadata.h
--- metadata/rb-metadata.h	20 Apr 2006 20:43:22 -0000	1.13
+++ metadata/rb-metadata.h	23 Apr 2006 08:37:04 -0000
@@ -69,7 +69,6 @@
 	RB_METADATA_ERROR_GENERAL,
 	RB_METADATA_ERROR_INTERNAL,
 	RB_METADATA_ERROR_NOT_AUDIO,
-	RB_METADATA_ERROR_NOT_AUDIO_IGNORE,
 } RBMetaDataError;
 
 #define RB_METADATA_ERROR rb_metadata_error_quark ()
@@ -115,6 +114,8 @@
 					 GError **error);
 
 const char *	rb_metadata_get_mime	(RBMetaData *md);
+gboolean	rb_metadata_has_video	(RBMetaData *md);
+gboolean	rb_metadata_has_audio	(RBMetaData *md);
 
 gboolean	rb_metadata_get		(RBMetaData *md, RBMetaDataField field,
 					 GValue *val);
diff -u -u -r1.5 test-metadata.c
--- metadata/test-metadata.c	20 Apr 2006 20:43:22 -0000	1.5
+++ metadata/test-metadata.c	23 Apr 2006 08:37:04 -0000
@@ -89,14 +89,7 @@
 	rb_metadata_load (md, (const char *)uri, &error);
 	
 	if (error) {
-		switch (error->code) {
-		case RB_METADATA_ERROR_NOT_AUDIO_IGNORE:
-			printf ("file ignored: %s\n", error->message);
-			break;
-		default:
-			printf ("error: %s\n", error->message);
-			break;
-		}
+		printf ("error: %s\n", error->message);
 		g_clear_error (&error);
 	} else {
 		printf ("type: %s\n", rb_metadata_get_mime (md));
diff -u -u -r1.5 Makefile.am
--- plugins/Makefile.am	20 Apr 2006 17:25:18 -0000	1.5
+++ plugins/Makefile.am	23 Apr 2006 08:37:04 -0000
@@ -11,7 +11,7 @@
 endif
 
 if ENABLE_PYTHON
-SUBDIRS += pythonconsole sample-python artdisplay
+SUBDIRS += pythonconsole sample-python artdisplay gst-plugin-install
 endif
 
 if USE_IPOD
diff -u -u -r1.210 rhythmdb.c
--- rhythmdb/rhythmdb.c	21 Apr 2006 08:26:04 -0000	1.210
+++ rhythmdb/rhythmdb.c	23 Apr 2006 08:37:12 -0000
@@ -73,6 +73,13 @@
 
 GType rhythmdb_property_type_map[RHYTHMDB_NUM_PROPERTIES];
 
+/*
+ * File size below which we will simply ignore files that can't be identified.
+ * This is mostly here so we ignore the various text files that are packaged
+ * with many netlabel releases and other downloads.
+ */
+#define REALLY_SMALL_FILE_SIZE	(4096)
+
 struct RhythmDBPrivate
 {
 	char *name;
@@ -252,6 +259,7 @@
 	SAVE_COMPLETE,
 	SAVE_ERROR,
 	READ_ONLY,
+	IMPORT_ERROR,
 	LAST_SIGNAL
 };
 
@@ -351,7 +359,6 @@
 			      G_TYPE_STRING,
 			      G_TYPE_POINTER);
 
-
 	rhythmdb_signals[READ_ONLY] =
 		g_signal_new ("read-only",
 			      G_OBJECT_CLASS_TYPE (object_class),
@@ -362,6 +369,18 @@
 			      G_TYPE_NONE,
 			      1,
 			      G_TYPE_BOOLEAN);
+	
+	rhythmdb_signals[IMPORT_ERROR] =
+		g_signal_new ("import-error",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RhythmDBClass, import_error),
+			      g_signal_accumulator_true_handled, NULL,
+			      rb_marshal_BOOLEAN__STRING_ULONG_STRING_POINTER,
+			      G_TYPE_BOOLEAN,
+			      4,
+			      G_TYPE_STRING, RHYTHMDB_TYPE_ENTRY_TYPE,
+			      G_TYPE_STRING, G_TYPE_POINTER);
 
 	g_type_class_add_private (klass, sizeof (RhythmDBPrivate));
 }
@@ -1662,20 +1681,58 @@
 	char *msg;
 } RhythmDBLoadErrorData;
 
+/*
+ * The list of mine type prefixes for files that shouldn't display errors about being non-audio.
+ * Useful for people who have cover art, et cetera, in their music directories
+ */
+const char * ignore_mime_types[] = {
+	"image/",
+	"text/",
+	"application/xml",
+	"application/x-gzip",
+};
+
 static void
-rhythmdb_add_import_error_entry (RhythmDB *db, RhythmDBEvent *event)
-{
+rhythmdb_add_import_error_entry (RhythmDB *db, RhythmDBEvent *event) {
 	RhythmDBEntry *entry;
 	GValue value = {0,};
 	RhythmDBEntryType error_entry_type = RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR;
+	gboolean ignore = FALSE;
+		const char *mimetype;
 
-	if (g_error_matches (event->error, RB_METADATA_ERROR, RB_METADATA_ERROR_NOT_AUDIO_IGNORE)) {
-		/* only add an ignore entry for the main library */
-		if (event->entry_type != RHYTHMDB_ENTRY_TYPE_SONG)
+	mimetype = rb_metadata_get_mime (event->metadata);
+
+	if (event->vfsinfo->size < REALLY_SMALL_FILE_SIZE) {
+		ignore = TRUE;
+	} else if (mimetype != NULL){
+		gboolean handled = FALSE;
+		int i;
+	
+		/* allow plugins to affect error reporting */
+		g_signal_emit (db, rhythmdb_signals[IMPORT_ERROR], 0,
+			       event->real_uri, event->entry_type,
+			       mimetype, event->error,
+			       &handled);
+		if (handled)
 			return;
+	
+		/* ignore files if they have known video, or are a blacklisted type */
+		if (event->entry_type != RHYTHMDB_ENTRY_TYPE_SONG) {
+			if (rb_metadata_has_video (event->metadata)) {
+				ignore = TRUE;
+			} else {		
+				for (i = 0; i < G_N_ELEMENTS (ignore_mime_types); i++) {
+					if (g_str_has_prefix (mimetype, ignore_mime_types[i])) {
+						ignore = TRUE;
+						break;
+					}
+				}
+			}
+		}
+	}
 
+	if (ignore)
 		error_entry_type = RHYTHMDB_ENTRY_TYPE_IGNORE;
-	}
 	
 	entry = rhythmdb_entry_lookup_by_location (db, event->real_uri);
 	if (entry) {
@@ -1754,13 +1811,14 @@
 		return FALSE;
 	}
 
+	mime = rb_metadata_get_mime (event->metadata);
+
 	if (event->error) {
 		rhythmdb_add_import_error_entry (db, event);
 		return TRUE;
 	}
 
 	/* do we really need to do this? */
-	mime = rb_metadata_get_mime (event->metadata);
 	if (!mime) {
 		rb_debug ("unsupported file");
 		return TRUE;
diff -u -u -r1.81 rhythmdb.h
--- rhythmdb/rhythmdb.h	20 Apr 2006 20:43:28 -0000	1.81
+++ rhythmdb/rhythmdb.h	23 Apr 2006 08:37:13 -0000
@@ -227,6 +227,9 @@
 	void	(*load_error)		(RhythmDB *db, const char *uri, const char *msg);
 	void	(*save_error)		(RhythmDB *db, const char *uri, const GError *error);
 	void	(*read_only)		(RhythmDB *db, gboolean readonly);
+	gboolean (*import_error)	(RhythmDB *db,
+					 const char *uri, RhythmDBEntryType entry_type,
+					 const char *mimetype, GError *error);
 
 	/* virtual methods */
 
--- /dev/null	2006-04-19 19:48:42.000000000 +1000
+++ plugins/gst-plugin-install/Makefile.am	2006-04-23 17:30:47.000000000 +1000
@@ -0,0 +1,12 @@
+plugindir = $(PLUGINDIR)
+plugin_PYTHON = gst-plugin-install.py
+
+plugin_in_files = gst-plugin-install.rb-plugin.desktop.in
+%.rb-plugin: %.rb-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.rb-plugin.desktop.in=.rb-plugin)
+
+EXTRA_DIST = $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
--- /dev/null	2006-04-19 19:48:42.000000000 +1000
+++ plugins/gst-plugin-install/gst-plugin-install.rb-plugin.desktop.in	2006-04-23 17:30:41.000000000 +1000
@@ -0,0 +1,9 @@
+[RB Plugin]
+Loader=python
+Module=gst-plugin-install
+IAge=1
+_Name=GStreamer plugin installer
+_Description=Automatically installs GStreamer plugins when needed
+Authors=James Livingston <jrl@ids.org.au>
+Copyright=Copyright © 2006 james Livingston
+Website=http://www.rhythmbox.org
--- /dev/null	2006-04-19 19:48:42.000000000 +1000
+++ plugins/gst-plugin-install/gst-plugin-install.py	2006-04-23 17:27:27.000000000 +1000
@@ -0,0 +1,162 @@
+import rhythmdb, rb
+import gtk
+import gst
+from gettext import gettext as _
+
+class GstPluginInstall (rb.Plugin):
+
+	def __init__ (self):
+		rb.Plugin.__init__ (self)
+
+		self.awaiting_install = {}		
+		self.mime_plugin_map = {
+			'audio/mpeg': 'mad',
+			'application/x-id3': 'id3demux'
+		}
+			
+	def activate (self, shell):
+		self.db = shell.get_property('db')
+		self.ie_id = self.db.connect('import-error', self.import_error)
+	
+	def deactivate (self, shell):
+		self.db.disconnect (self.ie_id)
+		self.db = None
+
+	def import_error (self, db, uri, type, mimetype, error):
+		if not mimetype in self.mime_plugin_map:
+			# we don't know what plugin to use, so display the error
+			return False
+		plugin = self.mime_plugin_map[mimetype]
+
+		if plugin in self.awaiting_install:
+			# just add it to the list of files to reload
+			track = {'db': db, 'uri': uri, 'entry-type': type}
+			list = self.awaiting_install[plugin]
+			list.append(track)
+			return True
+
+		if not self.can_install (plugin):
+			# it should already be installed, but isn't working
+			return False
+
+		# ask the user whether they want to install it
+		dialog = gtk.MessageDialog(message_format=_("Install GStreamer Plugin"),
+					   type=gtk.MESSAGE_QUESTION,
+					   buttons=gtk.BUTTONS_YES_NO)
+		dialog.format_secondary_text((_("The GStreamer plugin '%s' is necessary to play some of your tracks. Would you like to install it?") % plugin))
+
+		response = dialog.run()
+		dialog.hide()
+		dialog = None
+		if response == gtk.RESPONSE_NO:
+			return False
+
+		# put it into the "awaiting files" list
+		track = {'db': db, 'uri': uri, 'entry-type': type}
+		list = [track]
+		self.awaiting_install[plugin] = list
+
+		return self.install(plugin)
+
+	def plugin_installed (self, plugin):
+		# scan for new plugins
+		registry = gst.registry_get_default ()
+		registry.scan_path ('/usr/lib/gstreamer-0.10')
+
+		for track in self.awaiting_install[plugin]:
+			track['db'].add_uri_with_type (track['uri'], track['entry-type'])
+		del self.awaiting_install[plugin]
+
+
+import apt, apt_pkg
+
+class GstPluginInstallApt (GstPluginInstall):
+
+	def __init__ (self):
+		GstPluginInstall.__init__ (self)
+
+		self.cache = None
+		self.plugin_package_map = {
+			'mad': 'gstreamer0.10-plugins-ugly',
+			'id3demux': 'gstreamer0.10-plugins-good'
+		}
+
+	def activate (self, shell):
+		GstPluginInstall.activate (self, shell)
+
+	def deactivate (self, shell):
+		self.cache = None
+
+		GstPluginInstall.deactivate (self, shell)
+
+	def can_install (self, plugin):
+		package = self.plugin_package_map[plugin]
+
+		if self.cache is None:
+			self.cache = apt.Cache()
+
+		if not self.cache.has_key (package):
+			return False
+
+		candidate = self.cache[package].candidateVersion
+		installed = self.cache[package].installedVersion
+
+		if candidate is None:
+			return False
+		elif installed is None:
+			return True
+		else:
+			return (apt_pkg.VersionCompare(candidate, installed) > 0)
+
+	def install (self, plugin):
+		if not plugin in self.plugin_package_map:
+			return False
+		package = self.plugin_package_map[plugin]
+
+		# should be done asynchronously
+		worker = PackageWorker()
+		worker.perform_action (package)
+
+		self.plugin_installed (plugin)
+		return True
+
+
+
+# stolen from gnome-app-install, and then hack up
+# (c) 2005 Canonical, GPL
+
+import subprocess
+import thread
+import time
+import gtk
+import tempfile
+
+class PackageWorker:
+    def run_synaptic(self, lock, package):
+        cmd = ["/usr/bin/gksu",
+               "--",
+               "/usr/sbin/synaptic",
+               "--hide-main-window",
+               "--non-interactive"]
+
+        # create tempfile for install (here because it must survive
+        # durng the synaptic call
+        f = tempfile.NamedTemporaryFile()
+        f.write("%s\tinstall\n" % package)
+        cmd.append("--set-selections-file")
+        cmd.append("%s" % f.name)
+        f.flush()
+
+        retcode = subprocess.call(cmd)
+
+        f.close()
+        lock.release()
+    
+    def perform_action(self, package):
+        lock = thread.allocate_lock()
+        lock.acquire()
+        t = thread.start_new_thread(self.run_synaptic,(lock,package))
+        while lock.locked():
+            while gtk.events_pending():
+                gtk.main_iteration()
+            time.sleep(0.05)
