[rhythmbox] Replace metadata and encoder backends



commit 90b7496c0572a680e37b68cecf7d4013cbfeee4c
Author: Jonathan Matthew <jonathan d14n org>
Date:   Sun Jul 17 21:56:44 2011 +1000

    Replace metadata and encoder backends
    
    Use GstDiscoverer for reading metadata.  The media type we report is now based
    on the audio encoding, rather than the top level container type as previously.
    For string tags, return the first valid utf-8 string, or a later string of
    which it is a prefix.  This should work better for badly (multiply) tagged MP3
    files in particular.
    
    Use a decodebin2-based pipeline for writing metadata.  We allow autoplugging to
    continue until we get to a decoder, then connect tagging and muxing elements
    for the encoding type.  This removes extra ID3 and APE tags from MP3 files.
    
    The 'mimetype' database field is now called 'media-type', and hopefully we're
    more consistent about what's a media type as opposed to a MIME type.  The
    database version number has been bumped, and all file metadata will be re-read
    to populate the media-type field.
    
    Use encodebin and encoding profiles instead of libgnome-media-profiles.  We
    have a set of profiles for common media types (ogg vorbis, mp3, flac, aac)
    which we use to build targets for the library and for devices.  We don't yet do
    anything with presets, so all encoding will use the default encoder settings
    for now.  Users can override the default set of profiles by copying
    rhythmbox.gep to ~/.local/share/rhythmbox/ and editing it.
    
    Since the new profile system doesn't hide profiles that require additional
    plugins from us, we can do automatic installation of encoders and muxers either
    when selecting a preferred encoding type, or when starting a transfer.
    
    Drop all compatibility code for old GStreamer versions.  We now require GStreamer
    0.10.32.
    
    Fixes too many bugs to list here.

 backends/gstreamer/rb-encoder-gst.c                |  854 +++--------
 backends/gstreamer/rb-encoder-gst.h                |   15 +-
 backends/gstreamer/rb-player-gst-xfade.c           |   34 +-
 backends/gstreamer/rb-player-gst.c                 |   10 -
 backends/rb-encoder.c                              |   65 +-
 backends/rb-encoder.h                              |   26 +-
 configure.ac                                       |   14 +-
 data/Makefile.am                                   |    4 +
 data/org.gnome.rhythmbox.gschema.xml               |    9 +-
 data/rhythmbox.gep                                 |   46 +
 data/ui/library-prefs.ui                           |  115 +-
 doc/reference/rhythmbox-sections.txt               |    3 +-
 lib/Makefile.am                                    |    2 +
 lib/rb-gst-media-types.c                           |  309 ++++
 lib/rb-gst-media-types.h                           |   75 +
 lib/rb-util.c                                      |   21 -
 lib/rb-util.h                                      |    1 -
 metadata/rb-metadata-dbus-client.c                 |   28 +-
 metadata/rb-metadata-dbus-service.c                |    4 +-
 metadata/rb-metadata-dbus.c                        |    2 +-
 metadata/rb-metadata-gst-common.c                  |   60 -
 metadata/rb-metadata-gst-common.h                  |   10 -
 metadata/rb-metadata-gst.c                         | 1675 ++++++++------------
 metadata/rb-metadata.h                             |    4 +-
 metadata/test-metadata.c                           |   10 +-
 plugins/audiocd/rb-audiocd-source.c                |    2 +-
 plugins/daap/rb-daap-record.c                      |    2 +-
 .../rb-dbus-media-server-plugin.c                  |   11 +-
 plugins/generic-player/rb-generic-player-source.c  |   44 +-
 plugins/ipod/rb-ipod-source.c                      |   44 +-
 plugins/mpris/rb-mpris-plugin.c                    |    2 +-
 plugins/mtpdevice/rb-mtp-source.c                  |  100 +-
 plugins/rbzeitgeist/rbzeitgeist.py                 |    2 +-
 podcast/rb-feed-podcast-properties-dialog.c        |    7 +-
 podcast/rb-podcast-manager.c                       |   10 +-
 rhythmdb/rhythmdb-private.h                        |    2 +-
 rhythmdb/rhythmdb-song-entry-types.c               |    6 +-
 rhythmdb/rhythmdb-tree.c                           |   17 +-
 rhythmdb/rhythmdb.c                                |   45 +-
 rhythmdb/rhythmdb.h                                |    2 +-
 shell/rb-track-transfer-batch.c                    |  286 +++--
 shell/rb-track-transfer-batch.h                    |    9 +-
 shell/rb-track-transfer-queue.c                    |  235 ++-
 sources/rb-import-errors-source.c                  |    5 +-
 sources/rb-library-source.c                        |  231 +++-
 sources/rb-removable-media-source.c                |  119 +-
 sources/rb-removable-media-source.h                |   10 +-
 47 files changed, 2211 insertions(+), 2376 deletions(-)
---
diff --git a/backends/gstreamer/rb-encoder-gst.c b/backends/gstreamer/rb-encoder-gst.c
index e3641c3..72a6eb4 100644
--- a/backends/gstreamer/rb-encoder-gst.c
+++ b/backends/gstreamer/rb-encoder-gst.c
@@ -1,9 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
- * Based on Sound-Juicer's ripping code
- *
- * Copyright (C) 2003 Ross Burton <ross burtonini com>
- * Copyright (C) 2006 James Livingston <doclivingston gmail com>
+ * Copyright (C) 2010 Jonathan Matthew  <jonathan d14n org>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -35,10 +32,10 @@
 #include <gst/gst.h>
 #include <gst/tag/tag.h>
 #include <string.h>
-#include <libgnome-media-profiles/gnome-media-profiles.h>
 #include <gtk/gtk.h>
 #include <gio/gio.h>
 #include <gst/pbutils/missing-plugins.h>
+#include <gst/pbutils/encoding-profile.h>
 
 #include "rhythmdb.h"
 #include "rb-encoder.h"
@@ -46,14 +43,15 @@
 #include "rb-debug.h"
 #include "rb-util.h"
 #include "rb-file-helpers.h"
+#include "rb-gst-media-types.h"
 
-static void rb_encoder_gst_class_init (RBEncoderGstClass *klass);
-static void rb_encoder_gst_init       (RBEncoderGst *encoder);
-static void rb_encoder_gst_finalize   (GObject *object);
+static void rb_encoder_gst_init (RBEncoderGst *encoder);
 static void rb_encoder_init (RBEncoderIface *iface);
 
 struct _RBEncoderGstPrivate {
-	GstElement *enc;
+	GstEncodingProfile *profile;
+
+	GstElement *encodebin;
 	GstElement *pipeline;
 
 	gboolean transcoding;
@@ -66,7 +64,7 @@ struct _RBEncoderGstPrivate {
 	gint64 total_length;
 	guint progress_id;
 	char *dest_uri;
-	char *dest_mediatype;
+	char *dest_media_type;
 
 	GOutputStream *outstream;
 
@@ -76,144 +74,6 @@ struct _RBEncoderGstPrivate {
 G_DEFINE_TYPE_WITH_CODE(RBEncoderGst, rb_encoder_gst, G_TYPE_OBJECT,
 			G_IMPLEMENT_INTERFACE(RB_TYPE_ENCODER,
 					      rb_encoder_init))
-#define RB_ENCODER_GST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_ENCODER_GST, RBEncoderGstPrivate))
-
-static void rb_encoder_gst_encode (RBEncoder *encoder,
-				   RhythmDBEntry *entry,
-				   const char *dest,
-				   const char *dest_media_type);
-static void rb_encoder_gst_cancel (RBEncoder *encoder);
-static gboolean rb_encoder_gst_get_media_type (RBEncoder *encoder,
-					       RhythmDBEntry *entry,
-					       GList *dest_media_types,
-					       char **media_type,
-					       char **extension);
-static gboolean rb_encoder_gst_get_missing_plugins (RBEncoder *encoder,
-						    const char *media_type,
-						    char ***details);
-static void rb_encoder_gst_emit_completed (RBEncoderGst *encoder);
-
-
-static const char *
-get_entry_media_type (RhythmDBEntry *entry)
-{
-	const char *entry_media_type;
-
-	/* hackish mapping of gstreamer container media types to actual
-	 * encoding media types; this should be unnecessary when we do proper
-	 * (deep) typefinding.
-	 */
-	entry_media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
-	if (rb_safe_strcmp (entry_media_type, "audio/x-wav") == 0) {
-		/* if it has a bitrate, assume it's mp3-in-wav */
-		if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE) != 0) {
-			entry_media_type = "audio/mpeg";
-		}
-	} else if (rb_safe_strcmp (entry_media_type, "application/x-id3") == 0) {
-		entry_media_type = "audio/mpeg";
-	} else if (rb_safe_strcmp (entry_media_type, "audio/x-flac") == 0) {
-		entry_media_type = "audio/flac";
-	}
-
-	return entry_media_type;
-}
-
-static void
-rb_encoder_gst_class_init (RBEncoderGstClass *klass)
-{
-        GObjectClass *object_class = (GObjectClass *) klass;
-	GstCaps *caps;
-
-        object_class->finalize = rb_encoder_gst_finalize;
-
-        g_type_class_add_private (klass, sizeof (RBEncoderGstPrivate));
-
-	/* create the media type -> GstCaps lookup table
-	 *
-	 * The strings are static data for now, but if we allow dynamic changing
-	 * we need to change this to use g_strdup/g_free
-	 */
-	klass->media_caps_table = g_hash_table_new_full (g_str_hash, g_str_equal,
-							 NULL,
-							 (GDestroyNotify)gst_caps_unref);
-
-	/* M4A/AAC */
-	caps = gst_caps_new_simple ("audio/mpeg",
-				    "mpegversion", G_TYPE_INT, 4,
-				    NULL);
-	g_hash_table_insert (klass->media_caps_table, "audio/aac", caps);
-	g_hash_table_insert (klass->media_caps_table, "audio/x-m4a", caps);
-
-	/* MP3 */
-	caps = gst_caps_new_simple ("audio/mpeg",
-				    "mpegversion", G_TYPE_INT, 1,
-				    "layer", G_TYPE_INT, 3,
-				    NULL);
-	g_hash_table_insert (klass->media_caps_table, "audio/mpeg", caps);
-
-	/* hack for HAL's application/ogg reporting, assume it's audio/vorbis */
-	caps = gst_caps_new_simple ("audio/x-vorbis",
-				    NULL);
-	g_hash_table_insert (klass->media_caps_table, "application/ogg", caps);
-
-	/* FLAC */
-	caps = gst_caps_new_simple ("audio/x-flac", NULL);
-	g_hash_table_insert (klass->media_caps_table, "audio/flac", caps);
-
-	/* create the media type -> extension fallback mapping table */
-	klass->media_extension_table = g_hash_table_new (g_str_hash, g_str_equal);
-	g_hash_table_insert (klass->media_extension_table, "audio/mpeg", "mp3");
-	g_hash_table_insert (klass->media_extension_table, "audio/x-vorbis", "ogg");
-	g_hash_table_insert (klass->media_extension_table, "audio/x-flac", "flac");
-	g_hash_table_insert (klass->media_extension_table, "audio/x-m4a", "m4a");
-}
-
-static void
-rb_encoder_gst_init (RBEncoderGst *encoder)
-{
-        encoder->priv = RB_ENCODER_GST_GET_PRIVATE (encoder);
-}
-
-static void
-rb_encoder_init (RBEncoderIface *iface)
-{
-	iface->encode = rb_encoder_gst_encode;
-	iface->cancel = rb_encoder_gst_cancel;
-	iface->get_media_type = rb_encoder_gst_get_media_type;
-	iface->get_missing_plugins = rb_encoder_gst_get_missing_plugins;
-}
-
-static void
-rb_encoder_gst_finalize (GObject *object)
-{
-	RBEncoderGst *encoder = RB_ENCODER_GST (object);
-
-	if (encoder->priv->progress_id != 0)
-		g_source_remove (encoder->priv->progress_id);
-
-	if (encoder->priv->pipeline) {
-		gst_element_set_state (encoder->priv->pipeline, GST_STATE_NULL);
-		g_object_unref (encoder->priv->pipeline);
-		encoder->priv->pipeline = NULL;
-	}
-
-	if (encoder->priv->outstream) {
-		g_output_stream_close (encoder->priv->outstream, NULL, NULL);
-		g_object_unref (encoder->priv->outstream);
-		encoder->priv->outstream = NULL;
-	}
-
-	g_free (encoder->priv->dest_uri);
-	g_free (encoder->priv->dest_mediatype);
-
-        G_OBJECT_CLASS (rb_encoder_gst_parent_class)->finalize (object);
-}
-
-RBEncoder*
-rb_encoder_gst_new (void)
-{
-	return RB_ENCODER (g_object_new (RB_TYPE_ENCODER_GST, NULL));
-}
 
 static void
 set_error (RBEncoderGst *encoder, GError *error)
@@ -250,8 +110,9 @@ rb_encoder_gst_emit_completed (RBEncoderGst *encoder)
 		encoder->priv->progress_id = 0;
 	}
 
-	/* emit an error if no audio pad has been found and it wasn't due to an
-	 * error */
+	/* emit an error if no audio pad has been found
+	 * and it wasn't due to an error
+	 */
 	if (encoder->priv->error == NULL &&
 	    encoder->priv->transcoding &&
 	    encoder->priv->decoded_pads == 0) {
@@ -283,7 +144,7 @@ rb_encoder_gst_emit_completed (RBEncoderGst *encoder)
 	g_object_unref (file);
 
 	encoder->priv->completion_emitted = TRUE;
-	_rb_encoder_emit_completed (RB_ENCODER (encoder), dest_size, encoder->priv->dest_mediatype, encoder->priv->error);
+	_rb_encoder_emit_completed (RB_ENCODER (encoder), dest_size, encoder->priv->dest_media_type, encoder->priv->error);
 }
 
 static void
@@ -313,7 +174,7 @@ bus_watch_cb (GstBus *bus, GstMessage *message, gpointer data)
 	GError *error = NULL;
 
 	/* ref ourselves, in case one of the signal handler unrefs us */
-	g_object_ref (G_OBJECT (encoder));
+	g_object_ref (encoder);
 
 	switch (GST_MESSAGE_TYPE (message)) {
 	case GST_MESSAGE_ERROR:
@@ -358,7 +219,7 @@ bus_watch_cb (GstBus *bus, GstMessage *message, gpointer data)
 		break;
 	}
 
-	g_object_unref (G_OBJECT (encoder));
+	g_object_unref (encoder);
 	return TRUE;
 }
 
@@ -426,70 +287,6 @@ start_pipeline (RBEncoderGst *encoder)
 	}
 }
 
-static const char *GST_ENCODING_PROFILE = "audioconvert ! audioresample ! %s";
-
-static GstElement*
-add_encoding_pipeline (RBEncoderGst *encoder,
-		       GMAudioProfile *profile,
-		       GError **error)
-{
-	GstElement *queue, *encoding_bin, *queue2;
-	GstPad *pad;
-	char *tmp;
-
-	queue = gst_element_factory_make ("queue2", NULL);
-	if (queue == NULL) {
-		g_set_error (error,
-			     RB_ENCODER_ERROR, RB_ENCODER_ERROR_INTERNAL,
-			     "Could not create queue2 element");
-		return NULL;
-	}
-	gst_bin_add (GST_BIN (encoder->priv->pipeline), queue);
-
-	queue2 = gst_element_factory_make ("queue2", NULL);
-	if (queue2 == NULL) {
-		g_set_error (error,
-			     RB_ENCODER_ERROR, RB_ENCODER_ERROR_INTERNAL,
-			     "Could not create queue2 element");
-		return NULL;
-	}
-	gst_bin_add (GST_BIN (encoder->priv->pipeline), queue2);
-
-	/* Nice big buffers... */
-	g_object_set (queue, "max-size-time", 30 * GST_SECOND, "max-size-buffers", 0, "max-size-bytes", 0, NULL);
-
-	tmp = g_strdup_printf (GST_ENCODING_PROFILE, gm_audio_profile_get_pipeline (profile));
-	rb_debug ("constructing encoding bin from pipeline string %s", tmp);
-	encoding_bin = GST_ELEMENT (gst_parse_launch (tmp, error));
-	g_free (tmp);
-
-	if (encoding_bin == NULL) {
-		rb_debug ("unable to construct encoding bin");
-		return NULL;
-	}
-
-	/* find pads and ghost them if necessary */
-	if ((pad = gst_bin_find_unconnected_pad (GST_BIN (encoding_bin), GST_PAD_SRC)))
-		gst_element_add_pad (encoding_bin, gst_ghost_pad_new ("src", pad));
-	if ((pad = gst_bin_find_unconnected_pad (GST_BIN (encoding_bin), GST_PAD_SINK)))
-		gst_element_add_pad (encoding_bin, gst_ghost_pad_new ("sink", pad));
-
-	gst_bin_add (GST_BIN (encoder->priv->pipeline), encoding_bin);
-
-	if (gst_element_link_many (queue, encoding_bin, queue2, NULL) == FALSE) {
-		g_set_error (error,
-			     RB_ENCODER_ERROR, RB_ENCODER_ERROR_INTERNAL,
-			     "Could not link encoding bin to queues");
-		return NULL;
-	}
-
-	/* store the first element of the encoding graph. new_decoded_pad_cb
-	 * will link to this once a decoded pad is found */
-	encoder->priv->enc = queue;
-
-	return queue2;
-}
-
 static void
 add_string_tag (GstTagList *tags, GstTagMergeMode mode, const char *tag, RhythmDBEntry *entry, RhythmDBPropType property)
 {
@@ -506,9 +303,11 @@ add_tags_from_entry (RBEncoderGst *encoder,
 		     GError **error)
 {
 	GstTagList *tags;
-	gboolean result = TRUE;
+	GstTagSetter *tag_setter;
+	GstIterator *iter;
 	gulong day;
 	gdouble bpm;
+	gboolean done;
 
 	tags = gst_tag_list_new ();
 
@@ -549,43 +348,32 @@ add_tags_from_entry (RBEncoderGst *encoder,
 		gst_tag_list_add (tags, GST_TAG_MERGE_APPEND, GST_TAG_BEATS_PER_MINUTE, bpm, NULL);
 	}
 
-	{
-		GstIterator *iter;
-		gboolean done;
-
-		iter = gst_bin_iterate_all_by_interface (GST_BIN (encoder->priv->pipeline), GST_TYPE_TAG_SETTER);
-		done = FALSE;
-		while (!done) {
-			GstTagSetter *tagger = NULL;
-			GstTagSetter **tagger_ptr = &tagger;
-
-			switch (gst_iterator_next (iter, (gpointer*)tagger_ptr)) {
-			case GST_ITERATOR_OK:
-				gst_tag_setter_merge_tags (tagger, tags, GST_TAG_MERGE_REPLACE_ALL);
-				break;
-			case GST_ITERATOR_RESYNC:
-				gst_iterator_resync (iter);
-				break;
-			case GST_ITERATOR_ERROR:
-				g_set_error (error,
-					     RB_ENCODER_ERROR, RB_ENCODER_ERROR_INTERNAL,
-					     "Could not add tags to tag-setter");
-				result = FALSE;
-				done = TRUE;
-				break;
-			case GST_ITERATOR_DONE:
-				done = TRUE;
-				break;
-			}
-
-			if (tagger)
-				gst_object_unref (tagger);
+	/* XXX encodebin isn't a tag setter yet */
+	/*
+	gst_tag_setter_merge_tags (GST_TAG_SETTER (encoder->priv->encodebin), tags, GST_TAG_MERGE_REPLACE_ALL);
+	*/
+	iter = gst_bin_iterate_all_by_interface (GST_BIN (encoder->priv->encodebin), GST_TYPE_TAG_SETTER);
+	done = FALSE;
+	while (!done) {
+		switch (gst_iterator_next (iter, (gpointer) & tag_setter)) {
+		case GST_ITERATOR_OK:
+			gst_tag_setter_merge_tags (tag_setter, tags, GST_TAG_MERGE_REPLACE_ALL);
+			gst_object_unref (tag_setter);
+			break;
+		case GST_ITERATOR_RESYNC:
+			gst_iterator_resync (iter);
+			break;
+		case GST_ITERATOR_ERROR:
+			done = TRUE;
+			break;
+		case GST_ITERATOR_DONE:
+			done = TRUE;
+			break;
 		}
-		gst_iterator_free (iter);
 	}
 
 	gst_tag_list_free (tags);
-	return result;
+	return TRUE;
 }
 
 static void
@@ -609,8 +397,7 @@ new_decoded_pad_cb (GstElement *decodebin, GstPad *new_pad, gboolean arg1, RBEnc
 	/* only process audio data */
 	if (strncmp (caps_string, "audio/", 6) == 0) {
 		encoder->priv->decoded_pads++;
-		enc_sinkpad = gst_element_get_static_pad (encoder->priv->enc,
-				"sink");
+		enc_sinkpad = gst_element_get_static_pad (encoder->priv->encodebin, "audio_0");
 		if (gst_pad_link (new_pad, enc_sinkpad) != GST_PAD_LINK_OK)
 			rb_debug ("error linking pads");
 	}
@@ -620,7 +407,7 @@ new_decoded_pad_cb (GstElement *decodebin, GstPad *new_pad, gboolean arg1, RBEnc
 
 static GstElement *
 add_decoding_pipeline (RBEncoderGst *encoder,
-			GError **error)
+		       GError **error)
 {
 	GstElement *decodebin;
 
@@ -629,11 +416,11 @@ add_decoding_pipeline (RBEncoderGst *encoder,
 	encoder->priv->transcoding = TRUE;
 	decodebin = gst_element_factory_make ("decodebin2", NULL);
 	if (decodebin == NULL) {
+		rb_debug ("couldn't create decodebin2");
 		g_set_error (error,
 				RB_ENCODER_ERROR,
 				RB_ENCODER_ERROR_INTERNAL,
-				"Could not create decodebin");
-
+				"Could not create decodebin2");
 		return NULL;
 	}
 
@@ -722,201 +509,6 @@ attach_output_pipeline (RBEncoderGst *encoder,
 	return TRUE;
 }
 
-static gboolean
-encoder_match_media_type (RBEncoderGst *rbencoder, GstElement *encoder, const gchar *media_type)
-{
-	GstPad *srcpad;
-	GstCaps *element_caps = NULL;
-	GstCaps *desired_caps = NULL;
-	GstCaps *intersect_caps = NULL;
-	gboolean match = FALSE;
-	char *tmp;
-
-	srcpad = gst_element_get_static_pad (encoder, "src");
-	element_caps = gst_pad_get_caps (srcpad);
-
-	if (element_caps == NULL) {
-		g_warning ("couldn't create any element caps");
-		goto end;
-	}
-
-	desired_caps = g_hash_table_lookup (RB_ENCODER_GST_GET_CLASS (rbencoder)->media_caps_table, media_type);
-	if (desired_caps != NULL) {
-		gst_caps_ref (desired_caps);
-	} else {
-		desired_caps = gst_caps_new_simple (media_type, NULL);
-	}
-
-	if (desired_caps == NULL) {
-		g_warning ("couldn't create any desired caps for media type: %s", media_type);
-		goto end;
-	}
-
-	intersect_caps = gst_caps_intersect (desired_caps, element_caps);
-	match = !gst_caps_is_empty (intersect_caps);
-
-	tmp = gst_caps_to_string (desired_caps);
-	rb_debug ("desired caps are: %s", tmp);
-	g_free (tmp);
-
-	tmp = gst_caps_to_string (element_caps);
-	rb_debug ("element caps are: %s", tmp);
-	g_free (tmp);
-
-	tmp = gst_caps_to_string (intersect_caps);
-	rb_debug ("intersect caps are: %s", tmp);
-	g_free (tmp);
-
-end:
-	if (intersect_caps != NULL)
-		gst_caps_unref (intersect_caps);
-	if (desired_caps != NULL)
-		gst_caps_unref (desired_caps);
-	if (element_caps != NULL)
-		gst_caps_unref (element_caps);
-	if (srcpad != NULL)
-		gst_object_unref (GST_OBJECT (srcpad));
-
-	return match;
-}
-
-static GstElement *
-profile_bin_find_encoder (GstBin *profile_bin)
-{
-	GstElementFactory *factory;
-	GstElement *encoder = NULL;
-	GstIterator *iter;
-	gboolean done = FALSE;
-
-	iter = gst_bin_iterate_elements (profile_bin);
-	while (!done) {
-		gpointer data;
-
-		switch (gst_iterator_next (iter, &data)) {
-			case GST_ITERATOR_OK:
-				factory = gst_element_get_factory (GST_ELEMENT (data));
-				if (rb_safe_strcmp (factory->details.klass,
-						"Codec/Encoder/Audio") == 0) {
-					encoder = GST_ELEMENT (data);
-					done = TRUE;
-				}
-				break;
-			case GST_ITERATOR_RESYNC:
-				gst_iterator_resync (iter);
-				break;
-			case GST_ITERATOR_ERROR:
-				/* !?? */
-				rb_debug ("iterator error");
-				done = TRUE;
-				break;
-			case GST_ITERATOR_DONE:
-				done = TRUE;
-				break;
-		}
-	}
-	gst_iterator_free (iter);
-
-	if (encoder == NULL) {
-		rb_debug ("unable to find encoder element");
-	}
-	return encoder;
-}
-
-static const char *
-get_media_type_from_profile (RBEncoderGst *rbencoder, GMAudioProfile *profile)
-{
-	GHashTableIter iter;
-	GstElement *pipeline;
-	GstElement *encoder;
-	char *pipeline_description;
-	GError *error = NULL;
-	gpointer key;
-	gpointer value;
-
-	pipeline_description =
-		g_strdup_printf ("fakesrc ! %s ! fakesink",
-			gm_audio_profile_get_pipeline (profile));
-	pipeline = gst_parse_launch (pipeline_description, &error);
-	g_free (pipeline_description);
-	if (error) {
-		g_warning ("unable to get media type for profile %s: %s",
-			   gm_audio_profile_get_name (profile),
-			   error->message);
-		g_clear_error (&error);
-		return NULL;
-	}
-
-	encoder = profile_bin_find_encoder (GST_BIN (pipeline));
-	if (encoder == NULL) {
-		g_object_unref (pipeline);
-		g_warning ("Unable to get media type for profile %s: couldn't find encoder",
-			   gm_audio_profile_get_name (profile));
-		return NULL;
-	}
-
-	g_hash_table_iter_init (&iter, RB_ENCODER_GST_GET_CLASS (rbencoder)->media_caps_table);
-	while (g_hash_table_iter_next (&iter, &key, &value)) {
-		const char *media_type = (const char *)key;
-		if (encoder_match_media_type (rbencoder, encoder, media_type)) {
-			return media_type;
-		}
-	}
-
-	g_warning ("couldn't identify media type for profile %s", gm_audio_profile_get_name (profile));
-	return NULL;
-}
-
-static GMAudioProfile*
-get_profile_from_media_type (RBEncoderGst *rbencoder, const char *media_type)
-{
-	GList *profiles, *walk;
-	gchar *pipeline_description;
-	GstElement *pipeline;
-	GstElement *encoder;
-	GMAudioProfile *profile;
-	GMAudioProfile *matching_profile = NULL;
-	GError *error = NULL;
-
-	rb_debug ("Looking up profile for media type '%s'", media_type);
-
-	profiles = gm_audio_profile_get_active_list ();
-	for (walk = profiles; walk; walk = g_list_next (walk)) {
-		profile = (GMAudioProfile *) walk->data;
-		pipeline_description =
-			g_strdup_printf ("fakesrc ! %s ! fakesink",
-				gm_audio_profile_get_pipeline (profile));
-		pipeline = gst_parse_launch (pipeline_description, &error);
-		g_free (pipeline_description);
-		if (error) {
-			g_error_free (error);
-			error = NULL;
-			continue;
-		}
-
-		encoder = profile_bin_find_encoder (GST_BIN (pipeline));
-		if (encoder == NULL) {
-			g_object_unref (pipeline);
-			continue;
-		}
-
-		if (encoder_match_media_type (rbencoder, encoder, media_type)) {
-			matching_profile = profile;
-			gst_object_unref (GST_OBJECT (encoder));
-			gst_object_unref (GST_OBJECT (pipeline));
-			break;
-		}
-
-		gst_object_unref (GST_OBJECT (encoder));
-		gst_object_unref (GST_OBJECT (pipeline));
-	}
-
-	if (matching_profile)
-		g_object_ref (matching_profile);
-	g_list_free (profiles);
-
-	return matching_profile;
-}
-
 static GstElement *
 create_pipeline_and_source (RBEncoderGst *encoder,
 			    RhythmDBEntry *entry,
@@ -981,25 +573,14 @@ transcode_track (RBEncoderGst *encoder,
 		 const char *dest,
 		 GError **error)
 {
-	/* src ! decodebin ! queue ! encoding_profile ! queue ! sink */
-	GMAudioProfile *profile;
-	GstElement *src, *decoder, *end;
+	/* src ! decodebin2 ! encodebin ! sink */
+	GstElement *src;
+	GstElement *decoder;
 
 	g_assert (encoder->priv->pipeline == NULL);
-	g_assert (encoder->priv->dest_mediatype != NULL);
+	g_assert (encoder->priv->profile != NULL);
 
-	rb_debug ("transcoding to %s, media type %s", dest, encoder->priv->dest_mediatype);
-	profile = get_profile_from_media_type (encoder, encoder->priv->dest_mediatype);
-	if (profile == NULL) {
-		g_set_error (error,
-			     RB_ENCODER_ERROR,
-			     RB_ENCODER_ERROR_FORMAT_UNSUPPORTED,
-			     "Unable to locate encoding profile for media-type %s",
-			     encoder->priv->dest_mediatype);
-		goto error;
-	}
-
-	rb_debug ("selected profile %s", gm_audio_profile_get_name (profile));
+	rb_debug ("transcoding to %s, profile %s", dest, gst_encoding_profile_get_name (encoder->priv->profile));
 
 	src = create_pipeline_and_source (encoder, entry, error);
 	if (src == NULL)
@@ -1018,11 +599,24 @@ transcode_track (RBEncoderGst *encoder,
 		goto error;
 	}
 
-	end = add_encoding_pipeline (encoder, profile, error);
-	if (end == NULL)
+	encoder->priv->encodebin = gst_element_factory_make ("encodebin", NULL);
+	if (encoder->priv->encodebin == NULL) {
+		rb_debug ("unable to create encodebin");
+		g_set_error (error,
+				RB_ENCODER_ERROR,
+				RB_ENCODER_ERROR_INTERNAL,
+				"Could not create encodebin");
 		goto error;
+	}
+	g_object_set (encoder->priv->encodebin,
+		      "profile", encoder->priv->profile,
+		      "queue-bytes-max", 0,
+		      "queue-buffers-max", 0,
+		      "queue-time-max", 30 * GST_SECOND,
+		      NULL);
+	gst_bin_add (GST_BIN (encoder->priv->pipeline), encoder->priv->encodebin);
 
-	if (!attach_output_pipeline (encoder, end, dest, error))
+	if (!attach_output_pipeline (encoder, encoder->priv->encodebin, dest, error))
 		goto error;
 	if (!add_tags_from_entry (encoder, entry, error))
 		goto error;
@@ -1030,37 +624,34 @@ transcode_track (RBEncoderGst *encoder,
 	start_pipeline (encoder);
 	return TRUE;
 error:
-	if (profile)
-		g_object_unref (profile);
-
 	return FALSE;
 }
 
 static void
-rb_encoder_gst_cancel (RBEncoder *encoder)
+impl_cancel (RBEncoder *bencoder)
 {
-	RBEncoderGstPrivate *priv = RB_ENCODER_GST (encoder)->priv;
+	RBEncoderGst *encoder = RB_ENCODER_GST (bencoder);
 
-	if (priv->pipeline != NULL) {
-		gst_element_set_state (priv->pipeline, GST_STATE_NULL);
-		g_object_unref (priv->pipeline);
-		priv->pipeline = NULL;
+	if (encoder->priv->pipeline != NULL) {
+		gst_element_set_state (encoder->priv->pipeline, GST_STATE_NULL);
+		g_object_unref (encoder->priv->pipeline);
+		encoder->priv->pipeline = NULL;
 	}
 
-	if (priv->outstream != NULL) {
+	if (encoder->priv->outstream != NULL) {
 		GError *error = NULL;
 		GFile *f;
-		g_output_stream_close (priv->outstream, NULL, &error);
+		g_output_stream_close (encoder->priv->outstream, NULL, &error);
 		if (error != NULL) {
 			rb_debug ("error closing output stream: %s", error->message);
 			g_error_free (error);
 		}
-		g_object_unref (priv->outstream);
-		priv->outstream = NULL;
+		g_object_unref (encoder->priv->outstream);
+		encoder->priv->outstream = NULL;
 
 		/* try to delete the output file, since it's incomplete */
 		error = NULL;
-		f = g_file_new_for_uri (priv->dest_uri);
+		f = g_file_new_for_uri (encoder->priv->dest_uri);
 		if (g_file_delete (f, NULL, &error) == FALSE) {
 			rb_debug ("error deleting incomplete output file: %s", error->message);
 			g_error_free (error);
@@ -1068,38 +659,34 @@ rb_encoder_gst_cancel (RBEncoder *encoder)
 		g_object_unref (f);
 	}
 
-	if (priv->error == NULL) {
+	if (encoder->priv->error == NULL) {
 		/* should never be displayed to the user anyway */
-		priv->error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, " ");
+		encoder->priv->error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, " ");
 	}
 
-	priv->cancelled = TRUE;
-	rb_encoder_gst_emit_completed (RB_ENCODER_GST (encoder));
+	encoder->priv->cancelled = TRUE;
+	rb_encoder_gst_emit_completed (encoder);
 }
 
 static gboolean
 cancel_idle (RBEncoder *encoder)
 {
-	rb_encoder_gst_cancel (encoder);
+	impl_cancel (encoder);
 	g_object_unref (encoder);
 	return FALSE;
 }
 
 static void
-rb_encoder_gst_encode (RBEncoder *bencoder,
-		       RhythmDBEntry *entry,
-		       const char *dest,
-		       const char *dest_media_type)
+impl_encode (RBEncoder *bencoder,
+	     RhythmDBEntry *entry,
+	     const char *dest,
+	     GstEncodingProfile *profile)
 {
 	RBEncoderGst *encoder = RB_ENCODER_GST (bencoder);
-	const char *entry_media_type;
-	GError *error = NULL;
 	gboolean result;
+	GError *error = NULL;
 
 	g_return_if_fail (encoder->priv->pipeline == NULL);
-	g_return_if_fail (dest_media_type != NULL);
-
-	entry_media_type = get_entry_media_type (entry);
 
 	if (rb_uri_create_parent_dirs (dest, &error) == FALSE) {
 		error = g_error_new_literal (RB_ENCODER_ERROR,
@@ -1112,7 +699,7 @@ rb_encoder_gst_encode (RBEncoder *bencoder,
 		return;
 	}
 
-	g_free (encoder->priv->dest_mediatype);
+	g_free (encoder->priv->dest_media_type);
 	g_free (encoder->priv->dest_uri);
 	encoder->priv->dest_uri = g_strdup (dest);
 
@@ -1120,17 +707,18 @@ rb_encoder_gst_encode (RBEncoder *bencoder,
 	g_object_ref (encoder);
 
 	/* if destination and source media types are the same, copy it */
-	if (g_strcmp0 (entry_media_type, dest_media_type) == 0) {
-		rb_debug ("source file already has required media type %s, copying rather than transcoding", dest_media_type);
+	if (profile == NULL) {
 		encoder->priv->total_length = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
 		encoder->priv->position_format = GST_FORMAT_BYTES;
+		encoder->priv->dest_media_type = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
 
 		result = copy_track (encoder, entry, dest, &error);
-		encoder->priv->dest_mediatype = g_strdup (entry_media_type);
 	} else {
+		gst_encoding_profile_ref (profile);
+		encoder->priv->profile = profile;
 		encoder->priv->total_length = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
 		encoder->priv->position_format = GST_FORMAT_TIME;
-		encoder->priv->dest_mediatype = g_strdup (dest_media_type);
+		encoder->priv->dest_media_type = rb_gst_encoding_profile_get_media_type (profile);
 
 		result = transcode_track (encoder, entry, dest, &error);
 	}
@@ -1147,169 +735,145 @@ rb_encoder_gst_encode (RBEncoder *bencoder,
 	g_object_unref (encoder);
 }
 
-static char *
-get_extension_for_media_type (RBEncoder *encoder, const char *media_type)
+static gboolean
+impl_get_missing_plugins (RBEncoder *encoder,
+			  GstEncodingProfile *profile,
+			  char ***details,
+			  char ***descriptions)
 {
-	char *ext;
-	GMAudioProfile *profile;
-
-	profile = get_profile_from_media_type (RB_ENCODER_GST (encoder), media_type);
-	if (profile) {
-		ext = g_strdup (gm_audio_profile_get_extension (profile));
-		rb_debug ("got extension %s from profile", ext);
-		g_object_unref (profile);
-	} else {
-		GHashTable *map = RB_ENCODER_GST_GET_CLASS (encoder)->media_extension_table;
-		ext = g_strdup (g_hash_table_lookup (map, media_type));
-		rb_debug ("got extension %s from fallback extension map", ext);
-	}
+	GstElement *encodebin;
+	GstBus *bus;
+	GstPad *pad;
+	gboolean ret;
 
-	return ext;
-}
+	ret = FALSE;
+	rb_debug ("trying to check profile %s for missing plugins", gst_encoding_profile_get_name (profile));
 
-static gboolean
-rb_encoder_gst_get_media_type (RBEncoder *encoder,
-			       RhythmDBEntry *entry,
-			       GList *dest_media_types,
-			       char **media_type,
-			       char **extension)
-{
-	GList *l;
-	GMAudioProfile *profile;
-	const char *src_media_type;
+	encodebin = gst_element_factory_make ("encodebin", NULL);
+	if (encodebin == NULL) {
+		g_warning ("Unable to create encodebin");
+		return FALSE;
+	}
 
-	src_media_type = get_entry_media_type (entry);
-	g_return_val_if_fail (src_media_type != NULL, FALSE);
+	bus = gst_bus_new ();
+	gst_element_set_bus (encodebin, bus);
+	gst_bus_set_flushing (bus, FALSE);		/* necessary? */
 
-	if (media_type != NULL)
-		*media_type = NULL;
-	if (extension != NULL)
-		*extension = NULL;
+	g_object_set (encodebin, "profile", profile, NULL);
+	pad = gst_element_get_static_pad (encodebin, "audio_0");
+	if (pad == NULL) {
+		GstMessage *message;
+		GList *messages = NULL;
+		GList *m;
+		int i;
 
+		rb_debug ("didn't get request pad, profile %s doesn't work", gst_encoding_profile_get_name (profile));
+		message = gst_bus_pop (bus);
+		while (message != NULL) {
+			if (gst_is_missing_plugin_message (message)) {
+				messages = g_list_append (messages, message);
+			} else {
+				gst_message_unref (message);
+			}
+			message = gst_bus_pop (bus);
+		}
 
-	/* if we don't have any destination format requirements,
-	 * use preferred encoding for raw files, otherwise accept as is
-	 */
-	if (dest_media_types == NULL) {
-		if (g_str_has_prefix (src_media_type, "audio/x-raw")) {
-			/* hopefully encodebin stuff will land before i need to replace this with something gsettings wise */
-			const char *profile_name = "cdlossy"; /* eel_gconf_get_string (CONF_LIBRARY_PREFERRED_FORMAT); */
-			const char *mt;
-			profile = gm_audio_profile_lookup (profile_name);
-
-			mt = get_media_type_from_profile (RB_ENCODER_GST (encoder), profile);
-			if (mt == NULL) {
-				/* ugh */
-				return FALSE;
+		if (messages != NULL) {
+			if (details != NULL) {
+				*details = g_new0(char *, g_list_length (messages)+1);
+			}
+			if (descriptions != NULL) {
+				*descriptions = g_new0(char *, g_list_length (messages)+1);
+			}
+			i = 0;
+			for (m = messages; m != NULL; m = m->next) {
+				char *str;
+				if (details != NULL) {
+					str = gst_missing_plugin_message_get_installer_detail (m->data);
+					rb_debug ("missing plugin for profile %s: %s",
+						  gst_encoding_profile_get_name (profile),
+						  str);
+					(*details)[i] = str;
+				}
+				if (descriptions != NULL) {
+					str = gst_missing_plugin_message_get_description (m->data);
+					(*descriptions)[i] = str;
+				}
+				i++;
 			}
-			rb_debug ("selected preferred media type %s (extension %s)",
-				  mt,
-				  gm_audio_profile_get_extension (profile));
-			if (media_type != NULL)
-				*media_type = g_strdup (mt);
-			if (extension != NULL)
-				*extension = g_strdup (gm_audio_profile_get_extension (profile));
-		} else {
-			if (media_type != NULL)
-				*media_type = g_strdup (src_media_type);
 
-			if (extension != NULL)
-				*extension = get_extension_for_media_type (encoder, src_media_type);
+			ret = TRUE;
+			rb_list_destroy_free (messages, (GDestroyNotify)gst_message_unref);
 		}
-		return TRUE;
+
+	} else {
+		rb_debug ("got request pad, profile %s works", gst_encoding_profile_get_name (profile));
+		gst_element_release_request_pad (encodebin, pad);
+		gst_object_unref (pad);
 	}
 
-	/* check if the source media type is in the destination list */
-	if (rb_string_list_contains (dest_media_types, src_media_type)) {
-		rb_debug ("found source media type %s in destination type list", src_media_type);
-		if (media_type != NULL)
-			*media_type = g_strdup (src_media_type);
-		if (extension != NULL)
-			*extension = get_extension_for_media_type (encoder, src_media_type);
-		return TRUE;
+	gst_object_unref (encodebin);
+	gst_object_unref (bus);
+	return ret;
+}
+
+static void
+impl_finalize (GObject *object)
+{
+	RBEncoderGst *encoder = RB_ENCODER_GST (object);
+
+	if (encoder->priv->progress_id != 0)
+		g_source_remove (encoder->priv->progress_id);
+
+	if (encoder->priv->pipeline) {
+		gst_element_set_state (encoder->priv->pipeline, GST_STATE_NULL);
+		g_object_unref (encoder->priv->pipeline);
+		encoder->priv->pipeline = NULL;
 	}
 
-	/* now find the type in the destination media type list that we have a
-	 * profile for.
-	 */
-	for (l = dest_media_types; l != NULL; l = g_list_next (l)) {
-		GMAudioProfile *profile;
-		const char *mt;
-
-		mt = (const char *)l->data;
-		profile = get_profile_from_media_type (RB_ENCODER_GST (encoder), mt);
-		if (profile) {
-			rb_debug ("selected destination media type %s (extension %s)",
-				  mt,
-				  gm_audio_profile_get_extension (profile));
-			if (extension != NULL)
-				*extension = g_strdup (gm_audio_profile_get_extension (profile));
-
-			if (media_type != NULL)
-				*media_type = g_strdup (mt);
-			g_object_unref (profile);
-			return TRUE;
-		}
+	if (encoder->priv->outstream) {
+		g_output_stream_close (encoder->priv->outstream, NULL, NULL);
+		g_object_unref (encoder->priv->outstream);
+		encoder->priv->outstream = NULL;
 	}
 
-	return FALSE;
+	if (encoder->priv->profile) {
+		gst_encoding_profile_unref (encoder->priv->profile);
+		encoder->priv->profile = NULL;
+	}
+
+	g_free (encoder->priv->dest_uri);
+	g_free (encoder->priv->dest_media_type);
+
+        G_OBJECT_CLASS (rb_encoder_gst_parent_class)->finalize (object);
 }
 
-static int
-add_element_if_missing (char ***details, int index, const char *element_name)
+static void
+rb_encoder_gst_init (RBEncoderGst *encoder)
 {
-	GstElementFactory *factory;
-	factory = gst_element_factory_find (element_name);
-	if (factory != NULL) {
-		rb_debug ("element factory %s is available", element_name);
-		gst_object_unref (factory);
-	} else {
-		rb_debug ("element factory %s not available, adding detail string", element_name);
-		(*details)[index++] = gst_missing_element_installer_detail_new (element_name);
-	}
-	return index;
+        encoder->priv = (G_TYPE_INSTANCE_GET_PRIVATE ((encoder), RB_TYPE_ENCODER_GST, RBEncoderGstPrivate));
 }
 
-static gboolean
-rb_encoder_gst_get_missing_plugins (RBEncoder *encoder,
-				    const char *media_type,
-				    char ***details)
+static void
+rb_encoder_init (RBEncoderIface *iface)
 {
-	/*
-	 * since encoding profiles use explicit element names, we need to
-	 * check for and request installation of exactly the element names
-	 * used in the profile, rather than requesting an encoder for a media
-	 * type.  parsing the profile pipeline description is too much work,
-	 * so we'll just use the element names from the default profiles.
-	 *
-	 * mp3: 	lame, id3v2mux
-	 * aac:		faac, ffmux_mp4
-	 * ogg vorbis:	vorbisenc, oggmux
-	 * flac:	flacenc
-	 * mp2:		twolame, id3v2mux   (we don't have a media type for this)
-	 */
+	iface->encode = impl_encode;
+	iface->cancel = impl_cancel;
+	iface->get_missing_plugins = impl_get_missing_plugins;
+}
 
-	int i = 0;
-	*details = g_new0(char *, 3);
-
-	if (g_strcmp0 (media_type, "audio/mpeg") == 0) {
-		i = add_element_if_missing (details, i, "lame");
-		i = add_element_if_missing (details, i, "id3v2mux");
-	} else if (g_strcmp0 (media_type, "audio/x-aac") == 0) {
-		i = add_element_if_missing (details, i, "faac");
-		i = add_element_if_missing (details, i, "ffmux_mp4");
-	} else if (g_strcmp0 (media_type, "application/ogg") == 0) {
-		i = add_element_if_missing (details, i, "vorbisenc");
-		i = add_element_if_missing (details, i, "oggmux");
-	} else if (g_strcmp0 (media_type, "audio/x-flac") == 0) {
-		i = add_element_if_missing (details, i, "flacenc");
-	} else {
-		rb_debug ("unable to provide missing plugin details for unknown media type %s",
-			  media_type);
-		g_strfreev (*details);
-		*details = NULL;
-		return FALSE;
-	}
-	rb_debug ("have %d missing plugin detail strings", i);
-	return TRUE;
+static void
+rb_encoder_gst_class_init (RBEncoderGstClass *klass)
+{
+        GObjectClass *object_class = (GObjectClass *) klass;
+
+        object_class->finalize = impl_finalize;
+
+        g_type_class_add_private (klass, sizeof (RBEncoderGstPrivate));
+}
+
+RBEncoder*
+rb_encoder_gst_new (void)
+{
+	return RB_ENCODER (g_object_new (RB_TYPE_ENCODER_GST, NULL));
 }
diff --git a/backends/gstreamer/rb-encoder-gst.h b/backends/gstreamer/rb-encoder-gst.h
index 749920d..913b77f 100644
--- a/backends/gstreamer/rb-encoder-gst.h
+++ b/backends/gstreamer/rb-encoder-gst.h
@@ -35,21 +35,18 @@
 
 G_BEGIN_DECLS
 
-#define RB_TYPE_ENCODER_GST            (rb_encoder_gst_get_type ())
-#define RB_ENCODER_GST(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), RB_TYPE_ENCODER, RBEncoderGst))
-#define RB_ENCODER_GST_CLASS(k)        (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_ENCODER_GST, RBEncoderGstClass))
-#define RB_IS_ENCODER_GST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), RB_TYPE_ENCODER))
-#define RB_IS_ENCODER_GST_CLASS(k)     (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_ENCODER_GST))
-#define RB_ENCODER_GST_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_ENCODER_GST, RBEncoderGstClass))
+#define RB_TYPE_ENCODER_GST           (rb_encoder_gst_get_type ())
+#define RB_ENCODER_GST(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), RB_TYPE_ENCODER, RBEncoderGst))
+#define RB_ENCODER_GST_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_ENCODER_GST, RBEncoderGstClass))
+#define RB_IS_ENCODER_GST(obj)        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), RB_TYPE_ENCODER))
+#define RB_IS_ENCODER_GST_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_ENCODER_GST))
+#define RB_ENCODER_GST_GET_CLASS(o)   (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_ENCODER_GST, RBEncoderGstClass))
 
 typedef struct _RBEncoderGstPrivate RBEncoderGstPrivate;
 
 typedef struct
 {
 	GObjectClass obj_class;
-
-	GHashTable *media_caps_table;
-	GHashTable *media_extension_table;
 } RBEncoderGstClass;
 
 typedef struct
diff --git a/backends/gstreamer/rb-player-gst-xfade.c b/backends/gstreamer/rb-player-gst-xfade.c
index b24c826..4cf011e 100644
--- a/backends/gstreamer/rb-player-gst-xfade.c
+++ b/backends/gstreamer/rb-player-gst-xfade.c
@@ -144,9 +144,7 @@
 #include <gst/gst.h>
 #include <gst/controller/gstcontroller.h>
 #include <gst/base/gstbasetransform.h>
-#if GST_CHECK_VERSION(0,10,25)
 #include <gst/interfaces/streamvolume.h>
-#endif
 #include <gst/pbutils/pbutils.h>
 
 #include "rb-player.h"
@@ -339,7 +337,7 @@ typedef struct
 
 	gint64 seek_target;
 
-	GstController *fader;
+	GstInterpolationControlSource *fader;
 	StreamState state;
 	RBPlayerPlayType play_type;
 	gint64 crossfade;
@@ -1010,21 +1008,21 @@ start_stream_fade (RBXFadeStream *stream, double start, double end, gint64 time)
 	stream->fade_end = end;
 	g_object_set (stream->volume, "volume", start, NULL);
 
-	gst_controller_unset_all (stream->fader, "volume");
+	gst_interpolation_control_source_unset_all (stream->fader);
 
 	g_value_init (&v, G_TYPE_DOUBLE);
 	g_value_set_double (&v, start);
-	if (gst_controller_set (stream->fader, "volume", pos, &v) == FALSE) {
+	if (gst_interpolation_control_source_set (stream->fader, pos, &v) == FALSE) {
 		rb_debug ("controller didn't like our start point");
 	}
-	if (gst_controller_set (stream->fader, "volume", 0, &v) == FALSE) {
+	if (gst_interpolation_control_source_set (stream->fader, 0, &v) == FALSE) {
 		rb_debug ("controller didn't like our 0 start point");
 	}
 	g_value_unset (&v);
 
 	g_value_init (&v, G_TYPE_DOUBLE);
 	g_value_set_double (&v, end);
-	if (gst_controller_set (stream->fader, "volume", pos + time, &v) == FALSE) {
+	if (gst_interpolation_control_source_set (stream->fader, pos + time, &v) == FALSE) {
 		rb_debug ("controller didn't like our end point");
 	}
 	g_value_unset (&v);
@@ -2007,6 +2005,7 @@ create_stream (RBPlayerGstXFade *player, const char *uri, gpointer stream_data,
 	GstCaps *caps;
 	GValueArray *stream_filters = NULL;
 	GstElement *tail;
+	GstController *controller;
 
 	rb_debug ("creating new stream for %s (stream data %p)", uri, stream_data);
 	stream = g_object_new (RB_TYPE_XFADE_STREAM, NULL, NULL);
@@ -2099,13 +2098,16 @@ create_stream (RBPlayerGstXFade *player, const char *uri, gpointer stream_data,
 				 G_CALLBACK (volume_changed_cb),
 				 player, 0);
 
-	stream->fader = gst_object_control_properties (G_OBJECT (stream->volume), "volume", NULL);
-	if (stream->fader == NULL) {
+	controller = gst_object_control_properties (G_OBJECT (stream->volume), "volume", NULL);
+	if (controller == NULL) {
 		rb_debug ("unable to create volume controller");
 		g_object_unref (stream);
 		return NULL;
 	}
-	gst_controller_set_interpolation_mode (stream->fader, "volume", GST_INTERPOLATE_LINEAR);
+
+	stream->fader = gst_interpolation_control_source_new ();
+	gst_interpolation_control_source_set_interpolation_mode (stream->fader, GST_INTERPOLATE_LINEAR);
+	gst_controller_set_control_source (controller, "volume", GST_CONTROL_SOURCE (stream->fader));
 
 	stream->preroll = gst_element_factory_make ("queue", NULL);
 	if (stream->preroll == NULL) {
@@ -2283,7 +2285,7 @@ actually_start_stream (RBXFadeStream *stream, GError **error)
 			rb_debug ("stream isn't fading; setting volume to 1.0");
 			g_value_init (&v, G_TYPE_DOUBLE);
 			g_value_set_double (&v, 1.0);
-			if (gst_controller_set (stream->fader, "volume", 0, &v) == FALSE) {
+			if (gst_interpolation_control_source_set (stream->fader, 0, &v) == FALSE) {
 				rb_debug ("controller didn't like our start point");
 			}
 			g_value_unset (&v);
@@ -2591,16 +2593,12 @@ emit_volume_changed_idle (RBPlayerGstXFade *player)
 {
 	double vol;
 
-#if GST_CHECK_VERSION(0,10,25)
 	if (gst_element_implements_interface (player->priv->volume_handler, GST_TYPE_STREAM_VOLUME)) {
 		vol = gst_stream_volume_get_volume (GST_STREAM_VOLUME (player->priv->volume_handler),
 						    GST_STREAM_VOLUME_FORMAT_CUBIC);
 	} else {
 		vol = player->priv->cur_volume;
 	}
-#else
-	vol = player->priv->cur_volume;
-#endif
 
 	_rb_player_emit_volume_changed (RB_PLAYER (player), vol);
 	return FALSE;
@@ -3597,7 +3595,6 @@ rb_player_gst_xfade_set_volume (RBPlayer *iplayer, float volume)
 		gdouble v = (gdouble)volume;
 
 		/* maybe use a controller here for smoother changes? */
-#if GST_CHECK_VERSION(0,10,25)
 		if (gst_element_implements_interface (player->priv->volume_handler,
 						      GST_TYPE_STREAM_VOLUME)) {
 			gst_stream_volume_set_volume (GST_STREAM_VOLUME (player->priv->volume_handler),
@@ -3605,9 +3602,6 @@ rb_player_gst_xfade_set_volume (RBPlayer *iplayer, float volume)
 		} else {
 			g_object_set (player->priv->volume_handler, "volume", v, NULL);
 		}
-#else
-		g_object_set (player->priv->volume_handler, "volume", v, NULL);
-#endif
 		player->priv->volume_applied = player->priv->volume_changed;
 	}
 	player->priv->cur_volume = volume;
@@ -3619,11 +3613,9 @@ rb_player_gst_xfade_get_volume (RBPlayer *iplayer)
 {
 	RBPlayerGstXFade *player = RB_PLAYER_GST_XFADE (iplayer);
 
-#if GST_CHECK_VERSION(0,10,25)
 	if (gst_element_implements_interface (player->priv->volume_handler, GST_TYPE_STREAM_VOLUME))
 		return gst_stream_volume_get_volume (GST_STREAM_VOLUME (player->priv->volume_handler),
 						     GST_STREAM_VOLUME_FORMAT_CUBIC);
-#endif
 
 	return player->priv->cur_volume;
 }
diff --git a/backends/gstreamer/rb-player-gst.c b/backends/gstreamer/rb-player-gst.c
index 6cc54dc..d4f888e 100644
--- a/backends/gstreamer/rb-player-gst.c
+++ b/backends/gstreamer/rb-player-gst.c
@@ -36,9 +36,7 @@
 #include <glib/gi18n.h>
 #include <gdk/gdk.h>
 #include <gst/tag/tag.h>
-#if GST_CHECK_VERSION(0,10,25)
 #include <gst/interfaces/streamvolume.h>
-#endif
 #include <gst/pbutils/pbutils.h>
 
 #include "rb-debug.h"
@@ -177,16 +175,12 @@ emit_volume_changed_idle (RBPlayerGst *player)
 {
 	double vol;
 
-#if GST_CHECK_VERSION(0,10,25)
 	if (gst_element_implements_interface (player->priv->playbin, GST_TYPE_STREAM_VOLUME)) {
 		vol = gst_stream_volume_get_volume (GST_STREAM_VOLUME (player->priv->playbin),
 						    GST_STREAM_VOLUME_FORMAT_CUBIC);
 	} else {
 		vol = player->priv->cur_volume;
 	}
-#else
-	vol = player->priv->cur_volume;
-#endif
 
 	_rb_player_emit_volume_changed (RB_PLAYER (player), vol);
 	return FALSE;
@@ -321,15 +315,11 @@ set_playbin_volume (RBPlayerGst *player, float volume)
 	 * we still get another one anyway.
 	 */
 	g_signal_handlers_block_by_func (player->priv->playbin, volume_notify_cb, player);
-#if GST_CHECK_VERSION(0,10,25)
 	if (gst_element_implements_interface (player->priv->playbin, GST_TYPE_STREAM_VOLUME))
 		gst_stream_volume_set_volume (GST_STREAM_VOLUME (player->priv->playbin),
 					      GST_STREAM_VOLUME_FORMAT_CUBIC, volume);
 	else
 		g_object_set (player->priv->playbin, "volume", volume, NULL);
-#else
-	g_object_set (player->priv->playbin, "volume", volume, NULL);
-#endif
 	g_signal_handlers_unblock_by_func (player->priv->playbin, volume_notify_cb, player);
 }
 
diff --git a/backends/rb-encoder.c b/backends/rb-encoder.c
index 6c6d327..37fff53 100644
--- a/backends/rb-encoder.c
+++ b/backends/rb-encoder.c
@@ -37,15 +37,10 @@
  * @short_description: audio transcoder interface
  *
  * The RBEncoder interface provides transcoding between audio formats based on
- * media types.  Media types are conceptually similar to MIME types, and overlap
- * in many cases, but the media type for an encoding is not always the same as the
- * MIME type for files using that encoding.
- *
- * The encoder picks the output format from a list provided by the caller,
- * limited by the available codecs.  It operatees asynchronously and provides
- * status updates in the form of signals.
- *
- * A new encoder instance should be created for each file that is transcoded.
+ * encoding profiles.  The encoder implementation operates asynchronously and
+ * provides status updates in the form of signals emitted on the main thread.
+ * A new encoder instance should be created for each file that is transcoded
+ * or copied.
  */
 
 static void rb_encoder_factory_class_init (RBEncoderFactoryClass *klass);
@@ -226,24 +221,22 @@ rb_encoder_factory_get ()
  * @encoder: the #RBEncoder
  * @entry: the #RhythmDBEntry to transcode
  * @dest: destination file URI
- * @dest_media_type: destination media type, or NULL to just copy it
+ * @profile: encoding profile to use, or NULL to just copy
  *
- * Initiates encoding, transcoding to the specified media type if it doesn't match
- * the current media type of the entry.  The caller should use rb_encoder_get_media_type
- * to select a destination media type.
+ * Initiates encoding, transcoding to the specified profile if specified.
  *
- * Encoding and error reporting takes places asynchronously.  The caller should wait
+ * Encoding and error reporting takes place asynchronously.  The caller should wait
  * for the 'completed' signal which indicates it has either completed or failed.
  */
 void
 rb_encoder_encode (RBEncoder *encoder,
 		   RhythmDBEntry *entry,
 		   const char *dest,
-		   const char *dest_media_type)
+		   GstEncodingProfile *profile)
 {
 	RBEncoderIface *iface = RB_ENCODER_GET_IFACE (encoder);
 
-	iface->encode (encoder, entry, dest, dest_media_type);
+	iface->encode (encoder, entry, dest, profile);
 }
 
 /**
@@ -263,49 +256,25 @@ rb_encoder_cancel (RBEncoder *encoder)
 }
 
 /**
- * rb_encoder_get_media_type:
- * @encoder: a #RBEncoder
- * @entry: the source #RhythmDBEntry
- * @dest_media_types: (element-type utf8): media type strings in order of preference
- * @media_type: (out callee-allocates) (allow-none): returns the selected media type, if any
- * @extension: (out callee-allocates) (allow-none): returns the file extension associated with the selected media type, if any
- *
- * Identifies the first media type in the list that the encoder can actually encode to.
- * The file extension (eg. '.mp3' for audio/mpeg) associated with the selected type is
- * also returned.
- *
- * Return value: TRUE if a format was identified
- */
-gboolean
-rb_encoder_get_media_type (RBEncoder *encoder,
-			   RhythmDBEntry *entry,
-			   GList *dest_media_types,
-			   char **media_type,
-			   char **extension)
-{
-	RBEncoderIface *iface = RB_ENCODER_GET_IFACE (encoder);
-
-	return iface->get_media_type (encoder, entry, dest_media_types, media_type, extension);
-}
-
-/**
  * rb_encoder_get_missing_plugins:
  * @encoder: a #RBEncoder
- * @media_type: the media type required
+ * @profile: an encoding profile
  * @details: (out callee-allocates): returns plugin installer detail strings
+ * @descriptions: (out callee-allocates): returns plugin descriptions
  *
- * Retrieves the plugin installer detail strings for any missing plugins
- * required to encode the specified media type.
+ * Retrieves the plugin installer detail strings and descriptions
+ * for any missing plugins required to use the specified encoding profile.
  *
  * Return value: %TRUE if some detail strings are returned, %FALSE otherwise
  */
 gboolean
 rb_encoder_get_missing_plugins (RBEncoder *encoder,
-				const char *media_type,
-				char ***details)
+				GstEncodingProfile *profile,
+				char ***details,
+				char ***descriptions)
 {
 	RBEncoderIface *iface = RB_ENCODER_GET_IFACE (encoder);
-	return iface->get_missing_plugins (encoder, media_type, details);
+	return iface->get_missing_plugins (encoder, profile, details, descriptions);
 }
 
 /**
diff --git a/backends/rb-encoder.h b/backends/rb-encoder.h
index 4bef216..fa82dc0 100644
--- a/backends/rb-encoder.h
+++ b/backends/rb-encoder.h
@@ -31,6 +31,8 @@
 
 #include <glib-object.h>
 
+#include <gst/pbutils/encoding-profile.h>
+
 #include <rhythmdb/rhythmdb.h>
 
 G_BEGIN_DECLS
@@ -74,16 +76,12 @@ struct _RBEncoderIface
 	void		(*encode)	(RBEncoder *encoder,
 					 RhythmDBEntry *entry,
 					 const char *dest,
-					 const char *dest_media_type);
+					 GstEncodingProfile *profile);
 	void		(*cancel)	(RBEncoder *encoder);
-	gboolean	(*get_media_type) (RBEncoder *encoder,
-					 RhythmDBEntry *entry,
-					 GList *dest_media_types,
-					 char **media_type,
-					 char **extension);
 	gboolean	(*get_missing_plugins) (RBEncoder *encoder,
-					 const char *media_type,
-					 char ***details);
+					 GstEncodingProfile *profile,
+					 char ***details,
+					 char ***descriptions);
 
 	/* signals */
 	void (*progress) (RBEncoder *encoder,  double fraction);
@@ -115,17 +113,13 @@ GType 		rb_encoder_get_type 	(void);
 void		rb_encoder_encode	(RBEncoder *encoder,
 					 RhythmDBEntry *entry,
 					 const char *dest,
-					 const char *dest_media_type);
+					 GstEncodingProfile *profile);
 void		rb_encoder_cancel	(RBEncoder *encoder);
 
-gboolean	rb_encoder_get_media_type (RBEncoder *encoder,
-					 RhythmDBEntry *entry,
-					 GList *dest_media_types,
-					 char **media_type,
-					 char **extension);
 gboolean	rb_encoder_get_missing_plugins (RBEncoder *encoder,
-					 const char *media_type,
-					 char ***details);
+					 GstEncodingProfile *profile,
+					 char ***details,
+					 char ***descriptions);
 
 /* only to be used by subclasses */
 void	_rb_encoder_emit_progress (RBEncoder *encoder, double fraction);
diff --git a/configure.ac b/configure.ac
index 27d77f0..de3b6ed 100644
--- a/configure.ac
+++ b/configure.ac
@@ -46,7 +46,7 @@ AC_CHECK_SIZEOF(long)
 GTK_REQS=2.91.4
 
 DBUS_MIN_REQS=0.35
-GST_0_10_REQS=0.10.20
+GST_0_10_REQS=0.10.32
 GDK_PIXBUF_REQS=2.18.0
 GLIB_REQS=2.26.0
 LIBGPOD_REQS=0.6
@@ -59,7 +59,6 @@ GUDEV_REQS=143
 LIBMTP_REQS=0.3.0
 LIBPEAS_REQS=0.7.3
 
-GNOME_MEDIA_PROFILES_REQS=2.91.0
 LIBNOTIFY_REQS=0.7.0
 BRASERO_MIN_REQS=2.31.5
 WEBKIT_MIN_REQS=1.3.9
@@ -101,7 +100,6 @@ PKG_CHECK_MODULES(RHYTHMBOX,				\
 		  glib-2.0 >= $GLIB_REQS		\
 		  gio-2.0 >= $GLIB_REQS			\
 		  gio-unix-2.0 >= $GLIB_REQS		\
-		  libgnome-media-profiles-3.0 >= $GNOME_MEDIA_PROFILES_REQS \
 		  libsoup-2.4 >= $LIBSOUP_REQS		\
 		  libsoup-gnome-2.4 >= $LIBSOUP_REQS	\
 		  libpeas-1.0 >= $LIBPEAS_REQS
@@ -263,10 +261,12 @@ dnl And we can also ask for the right version of gstreamer
 PKG_CHECK_MODULES(GSTREAMER_0_10, \
 	gstreamer-0.10 >= $GST_0_10_REQS
 	gstreamer-base-0.10 >= $GST_0_10_REQS
-	gstreamer-plugins-base-0.10 >= $GST_0_10_REQS)
+	gstreamer-plugins-base-0.10 >= $GST_0_10_REQS
+	gstreamer-interfaces-0.10 >= $GST_0_10_REQS
+	gstreamer-pbutils-0.10 >= $GST_0_10_REQS)
 
 RHYTHMBOX_CFLAGS="$RHYTHMBOX_CFLAGS $GSTREAMER_0_10_CFLAGS"
-RHYTHMBOX_LIBS="$RHYTHMBOX_LIBS $GSTREAMER_0_10_LIBS -lgstinterfaces-0.10"
+RHYTHMBOX_LIBS="$RHYTHMBOX_LIBS $GSTREAMER_0_10_LIBS"
 
 AC_ARG_WITH(mdns,
    AC_HELP_STRING([--with-mdns=auto|avahi],
@@ -352,7 +352,7 @@ have_sj_metadata_getter=no
 AC_ARG_ENABLE(musicbrainz, AC_HELP_STRING([--disable-musicbrainz],
 				[don't build with MusicBrainz support]))
 if test x"$enable_musicbrainz" != "xno"; then
-	PKG_CHECK_MODULES(MUSICBRAINZ3, libmusicbrainz3 >= $MUSICBRAINZ3_REQS, [have_musicbrainz3=yes], [have_musicbrainz3=no])
+	PKG_CHECK_MODULES(MUSICBRAINZ3, libmusicbrainz3 >= $MUSICBRAINZ3_REQS gconf-2.0, [have_musicbrainz3=yes], [have_musicbrainz3=no])
 
 	AC_SUBST(MUSICBRAINZ3_CFLAGS)
 	AC_SUBST(MUSICBRAINZ3_LIBS)
@@ -589,7 +589,7 @@ if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then
 	PKG_CHECK_EXISTS(gtk+-2.0 >= 2.20.0, [CFLAGS="$CFLAGS -DGSEAL_ENABLE"], [])
 
 	dnl disable deprecated stuff
-	CFLAGS="$CFLAGS $DISABLE_DEPRECATED"
+	dnl CFLAGS="$CFLAGS $DISABLE_DEPRECATED"
 else
 	AC_MSG_RESULT(no)
 fi
diff --git a/data/Makefile.am b/data/Makefile.am
index 333fdae..33beb5f 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -13,6 +13,9 @@ playlistsdir = $(datadir)/rhythmbox
 
 man_MANS = rhythmbox.1 rhythmbox-client.1
 
+profilesdir = $(datadir)
+profiles_DATA = rhythmbox.gep
+
 # GSettings schemas
 # (summaries and descriptions are not translated, so no .xml.in)
 gsettings_SCHEMAS = org.gnome.rhythmbox.gschema.xml
@@ -52,6 +55,7 @@ EXTRA_DIST = $(service_in_files)    \
 	     $(schema_DATA)	    \
 	     $(man_MANS)            \
 	     $(playlists_in_files)  \
+	     $(profiles_DATA)       \
 	     rhythmbox.desktop.in.in \
 	     rhythmbox-device.desktop.in.in
 
diff --git a/data/org.gnome.rhythmbox.gschema.xml b/data/org.gnome.rhythmbox.gschema.xml
index 9eeb447..b080d8d 100644
--- a/data/org.gnome.rhythmbox.gschema.xml
+++ b/data/org.gnome.rhythmbox.gschema.xml
@@ -152,11 +152,12 @@
       <summary>File name of tracks</summary>
       <description>The path used to store track in, under the chosen library. Various substitutions are done</description>
     </key>
-    <key name="preferred-format" type="s">
-      <default>'cdlossy'</default>
-      <summary>id of gnome-media audio profile</summary>
-      <description>The ID of the preferred gnome audio profile, used for ripping CDs</description>
+    <key name="preferred-media-type" type="s">
+      <default>'audio/x-vorbis'</default>
+      <summary>Preferred media type for encoding audio extracted from CD</summary>
+      <description>Preferred media type for encoding audio extracted from CD. 'audio/x-vorbis' for Ogg Vorbis, or 'audio/mpeg' for MP3, for example. This is not a MIME type.</description>
     </key>
+    <!-- and something about presets too -->
     <key name="strip-chars" type="b">
       <default>false</default>
       <summary>Strip special characters</summary>
diff --git a/data/rhythmbox.gep b/data/rhythmbox.gep
new file mode 100644
index 0000000..6383d96
--- /dev/null
+++ b/data/rhythmbox.gep
@@ -0,0 +1,46 @@
+[GStreamer Encoding Target]
+name = rhythmbox
+category = muh
+description = Common encoding profiles for Rhythmbox
+
+[profile-mp3]
+name = mp3
+description = MPEG Layer 3 Audio
+format = application/x-id3
+type = container
+
+[streamprofile-mp3-1]
+parent = mp3
+type = audio
+format = audio/mpeg, mpegversion=1, layer=3
+presence = 1
+
+[profile-oggvorbis]
+name = oggvorbis
+description = Ogg Vorbis
+format = application/ogg
+type = container
+
+[streamprofile-oggvorbis-1]
+parent = oggvorbis
+type = audio
+format = audio/x-vorbis
+presence = 1
+
+[profile-flac]
+name = flac
+description = FLAC
+format = audio/x-flac
+type = audio
+
+[profile-m4a]
+name = m4a
+description = MPEG 4 Audio
+format = video/quicktime, variant=iso
+type = container
+
+[streamprofile-m4a-1]
+parent = m4a
+type = audio
+format = audio/mpeg, mpegversion=4, stream-format=raw
+presence = 1
diff --git a/data/ui/library-prefs.ui b/data/ui/library-prefs.ui
index 57d0ddf..d6dece9 100644
--- a/data/ui/library-prefs.ui
+++ b/data/ui/library-prefs.ui
@@ -1,24 +1,20 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 2.12 -->
-  <!-- interface-naming-policy toplevel-contextual -->
-  <object class="GtkImage" id="edit_button_image">
-    <property name="stock">gtk-edit</property>
-    <property name="icon_size">4</property>
-  </object>
   <object class="GtkVBox" id="library_vbox">
     <property name="visible">True</property>
+    <property name="can_focus">False</property>
     <property name="border_width">12</property>
-    <property name="orientation">vertical</property>
     <property name="spacing">18</property>
     <child>
       <object class="GtkVBox" id="vbox11">
         <property name="visible">True</property>
-        <property name="orientation">vertical</property>
+        <property name="can_focus">False</property>
         <property name="spacing">6</property>
         <child>
           <object class="GtkLabel" id="library_location_label">
             <property name="visible">True</property>
+            <property name="can_focus">False</property>
             <property name="xalign">0</property>
             <property name="label" translatable="yes">Library Location</property>
           </object>
@@ -31,10 +27,12 @@
         <child>
           <object class="GtkHBox" id="hbox14">
             <property name="visible">True</property>
+            <property name="can_focus">False</property>
             <property name="spacing">6</property>
             <child>
               <object class="GtkLabel" id="label20">
                 <property name="visible">True</property>
+                <property name="can_focus">False</property>
                 <property name="label">    </property>
               </object>
               <packing>
@@ -46,15 +44,17 @@
             <child>
               <object class="GtkVBox" id="vbox12">
                 <property name="visible">True</property>
-                <property name="orientation">vertical</property>
+                <property name="can_focus">False</property>
                 <property name="spacing">3</property>
                 <child>
                   <object class="GtkHBox" id="hbox15">
                     <property name="visible">True</property>
+                    <property name="can_focus">False</property>
                     <property name="spacing">6</property>
                     <child>
                       <object class="GtkLabel" id="label25">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
                         <property name="label" translatable="yes">_Music files are placed in:</property>
                         <property name="use_underline">True</property>
                         <property name="mnemonic_widget">library_location_entry</property>
@@ -69,11 +69,11 @@
                       <object class="GtkEntry" id="library_location_entry">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
-                        <property name="invisible_char">&#x25CF;</property>
+                        <property name="invisible_char">â</property>
                       </object>
                       <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
                         <property name="position">1</property>
                       </packing>
                     </child>
@@ -82,18 +82,22 @@
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">True</property>
+                        <property name="use_action_appearance">False</property>
                         <child>
                           <object class="GtkAlignment" id="alignment3">
                             <property name="visible">True</property>
+                            <property name="can_focus">False</property>
                             <property name="xscale">0</property>
                             <property name="yscale">0</property>
                             <child>
                               <object class="GtkHBox" id="hbox16">
                                 <property name="visible">True</property>
+                                <property name="can_focus">False</property>
                                 <property name="spacing">2</property>
                                 <child>
                                   <object class="GtkImage" id="image1">
                                     <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
                                     <property name="stock">gtk-directory</property>
                                   </object>
                                   <packing>
@@ -105,6 +109,7 @@
                                 <child>
                                   <object class="GtkLabel" id="label21">
                                     <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
                                     <property name="label" translatable="yes">_Browse...</property>
                                     <property name="use_underline">True</property>
                                   </object>
@@ -138,6 +143,7 @@
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
                     <property name="receives_default">False</property>
+                    <property name="use_action_appearance">False</property>
                     <property name="use_underline">True</property>
                     <property name="draw_indicator">True</property>
                   </object>
@@ -149,11 +155,15 @@
                 </child>
               </object>
               <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
                 <property name="position">1</property>
               </packing>
             </child>
           </object>
           <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
             <property name="position">1</property>
           </packing>
         </child>
@@ -167,11 +177,12 @@
     <child>
       <object class="GtkVBox" id="library_structure_vbox">
         <property name="visible">True</property>
-        <property name="orientation">vertical</property>
+        <property name="can_focus">False</property>
         <property name="spacing">6</property>
         <child>
           <object class="GtkLabel" id="library_structure_label">
             <property name="visible">True</property>
+            <property name="can_focus">False</property>
             <property name="xalign">0</property>
             <property name="label" translatable="yes">Library Structure</property>
             <property name="use_underline">True</property>
@@ -185,10 +196,12 @@
         <child>
           <object class="GtkHBox" id="hbox17">
             <property name="visible">True</property>
+            <property name="can_focus">False</property>
             <property name="spacing">6</property>
             <child>
               <object class="GtkLabel" id="label23">
                 <property name="visible">True</property>
+                <property name="can_focus">False</property>
                 <property name="label">    </property>
               </object>
               <packing>
@@ -200,6 +213,7 @@
             <child>
               <object class="GtkTable" id="table1">
                 <property name="visible">True</property>
+                <property name="can_focus">False</property>
                 <property name="n_rows">5</property>
                 <property name="n_columns">2</property>
                 <property name="column_spacing">6</property>
@@ -207,6 +221,7 @@
                 <child>
                   <object class="GtkLabel" id="layout_path_menu_label">
                     <property name="visible">True</property>
+                    <property name="can_focus">False</property>
                     <property name="xalign">0</property>
                     <property name="label" translatable="yes">F_older hierarchy:</property>
                     <property name="use_underline">True</property>
@@ -219,6 +234,7 @@
                 <child>
                   <object class="GtkLabel" id="layout_filename_menu_label">
                     <property name="visible">True</property>
+                    <property name="can_focus">False</property>
                     <property name="xalign">0</property>
                     <property name="label" translatable="yes">_File name:</property>
                     <property name="use_underline">True</property>
@@ -233,13 +249,14 @@
                 <child>
                   <object class="GtkLabel" id="preferred_format_menu_label">
                     <property name="visible">True</property>
+                    <property name="can_focus">False</property>
                     <property name="xalign">0</property>
                     <property name="label" translatable="yes">_Preferred format:</property>
                     <property name="use_underline">True</property>
                   </object>
                   <packing>
-                    <property name="top_attach">4</property>
-                    <property name="bottom_attach">5</property>
+                    <property name="top_attach">3</property>
+                    <property name="bottom_attach">4</property>
                     <property name="x_options">GTK_FILL</property>
                     <property name="y_options"></property>
                   </packing>
@@ -247,6 +264,7 @@
                 <child>
                   <object class="GtkLabel" id="layout_example_label">
                     <property name="visible">True</property>
+                    <property name="can_focus">False</property>
                     <property name="xalign">0</property>
                     <property name="label" translatable="yes">Artist/Artist - Album/Artist (Album) - 01 - Title.ogg</property>
                   </object>
@@ -262,6 +280,7 @@
                 <child>
                   <object class="GtkHBox" id="layout_path_menu_box">
                     <property name="visible">True</property>
+                    <property name="can_focus">False</property>
                     <child>
                       <placeholder/>
                     </child>
@@ -276,6 +295,7 @@
                 <child>
                   <object class="GtkHBox" id="layout_filename_menu_box">
                     <property name="visible">True</property>
+                    <property name="can_focus">False</property>
                     <child>
                       <placeholder/>
                     </child>
@@ -292,25 +312,26 @@
                 <child>
                   <object class="GtkHBox" id="hbox18">
                     <property name="visible">True</property>
+                    <property name="can_focus">False</property>
                     <property name="spacing">6</property>
                     <child>
-                      <object class="GtkHBox" id="preferred_format_menu_box">
+                      <object class="GtkComboBox" id="format_select_combo">
                         <property name="visible">True</property>
-                        <child>
-                          <placeholder/>
-                        </child>
+                        <property name="can_focus">False</property>
                       </object>
                       <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
                         <property name="position">0</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkButton" id="edit_profile_button">
+                      <object class="GtkButton" id="profile_settings_button">
+                        <property name="label" translatable="yes">_Settings</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">True</property>
-                        <property name="image">edit_button_image</property>
-                        <property name="label" translatable="yes">_Edit...</property>
+                        <property name="use_action_appearance">False</property>
                         <property name="use_underline">True</property>
                       </object>
                       <packing>
@@ -323,38 +344,72 @@
                   <packing>
                     <property name="left_attach">1</property>
                     <property name="right_attach">2</property>
-                    <property name="top_attach">4</property>
-                    <property name="bottom_attach">5</property>
+                    <property name="top_attach">3</property>
+                    <property name="bottom_attach">4</property>
                     <property name="x_options">GTK_FILL</property>
                     <property name="y_options">GTK_FILL</property>
                   </packing>
                 </child>
                 <child>
-                  <placeholder/>
+                  <object class="GtkLabel" id="blank">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                  <packing>
+                    <property name="top_attach">2</property>
+                    <property name="bottom_attach">3</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
                 </child>
                 <child>
-                  <placeholder/>
+                  <object class="GtkLabel" id="blank2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                  <packing>
+                    <property name="top_attach">4</property>
+                    <property name="bottom_attach">5</property>
+                    <property name="y_options"></property>
+                  </packing>
                 </child>
                 <child>
-                  <placeholder/>
+                  <object class="GtkButton" id="install_plugins_button">
+                    <property name="label" translatable="yes">_Install additional software required to use this format</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="use_underline">True</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">4</property>
+                    <property name="bottom_attach">5</property>
+                    <property name="y_options"></property>
+                  </packing>
                 </child>
               </object>
               <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
                 <property name="position">1</property>
               </packing>
             </child>
           </object>
           <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
             <property name="position">1</property>
           </packing>
         </child>
       </object>
       <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
         <property name="position">1</property>
       </packing>
     </child>
-    <child>
-      <placeholder/>
-    </child>
   </object>
 </interface>
diff --git a/doc/reference/rhythmbox-sections.txt b/doc/reference/rhythmbox-sections.txt
index b7e338c..9d05793 100644
--- a/doc/reference/rhythmbox-sections.txt
+++ b/doc/reference/rhythmbox-sections.txt
@@ -39,7 +39,7 @@ rb_metadata_get_field_name
 rb_metadata_can_save
 rb_metadata_load
 rb_metadata_save
-rb_metadata_get_mime
+rb_metadata_get_media_type
 rb_metadata_has_missing_plugins
 rb_metadata_get_missing_plugins
 rb_metadata_get
@@ -425,7 +425,6 @@ RBRemovableMediaSourceClass
 rb_removable_media_source_build_dest_uri
 rb_removable_media_source_track_added
 rb_removable_media_source_track_add_error
-rb_removable_media_source_get_mime_types
 rb_removable_media_source_get_format_descriptions
 rb_removable_media_source_should_paste
 rb_removable_media_source_should_paste_no_duplicate
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 76b9cc5..6381ec5 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -41,6 +41,8 @@ librb_la_SOURCES =					\
 	rb-async-queue-watch.h				\
 	rb-text-helpers.c				\
 	rb-text-helpers.h				\
+	rb-gst-media-types.c				\
+	rb-gst-media-types.h				\
 	rb-missing-plugins.c				\
 	rb-missing-plugins.h
 
diff --git a/lib/rb-gst-media-types.c b/lib/rb-gst-media-types.c
new file mode 100644
index 0000000..65ea345
--- /dev/null
+++ b/lib/rb-gst-media-types.c
@@ -0,0 +1,309 @@
+/*
+ *  Copyright (C) 2010  Jonathan Matthew  <jonathan d14n org>
+ *
+ *  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, or (at your option)
+ *  any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#include <memory.h>
+
+#include <gst/pbutils/encoding-target.h>
+
+#include <lib/rb-gst-media-types.h>
+#include <lib/rb-debug.h>
+#include <lib/rb-file-helpers.h>
+
+/* don't like this much, but it's all we can do for now.
+ * these media types are copied from gst-plugins-base/gst-libs/gst/pbutils/descriptions.c.
+ * these are only the media types that don't start with 'audio/' or 'video/', which are
+ * identified fairly accurately by the filters for those prefixes.
+ */
+static const char *container_formats[] = {
+	"application/ogg",
+	"application/vnd.rn-realmedia",
+	"application/x-id3",
+	"application/x-ape",
+	"application/x-icy"
+};
+
+#define ENCODING_TARGET_FILE "rhythmbox.gep"
+static GstEncodingTarget *default_target = NULL;
+
+RBGstMediaType
+rb_gst_get_missing_plugin_type (const GstStructure *structure)
+{
+	const char *media_type;
+	const char *missing_type;
+	const GstCaps *caps;
+	const GValue *val;
+	int i;
+
+	missing_type = gst_structure_get_string (structure, "type");
+	if (missing_type == NULL || strcmp (missing_type, "decoder") != 0) {
+		rb_debug ("missing plugin is not a decoder");
+		return MEDIA_TYPE_NONE;
+	}
+
+	val = gst_structure_get_value (structure, "detail");
+	caps = gst_value_get_caps (val);
+
+	media_type = gst_structure_get_name (gst_caps_get_structure (caps, 0));
+	for (i = 0; i < G_N_ELEMENTS (container_formats); i++) {
+		if (strcmp (media_type, container_formats[i]) == 0) {
+			rb_debug ("missing plugin is a container demuxer");
+			return MEDIA_TYPE_CONTAINER;
+		}
+	}
+
+	if (g_str_has_prefix (media_type, "audio/")) {
+		rb_debug ("missing plugin is an audio decoder");
+		return MEDIA_TYPE_AUDIO;
+	} else if (g_str_has_prefix (media_type, "video/")) {
+		rb_debug ("missing plugin is (probably) a video decoder");
+		return MEDIA_TYPE_VIDEO;
+	} else {
+		rb_debug ("missing plugin is neither a video nor audio decoder");
+		return MEDIA_TYPE_OTHER;
+	}
+}
+
+char *
+rb_gst_caps_to_media_type (const GstCaps *caps)
+{
+	GstStructure *s;
+	const char *media_type;
+
+	/* the aim here is to reduce the caps to a single mimetype-like
+	 * string, describing the audio encoding (for audio files) or the
+	 * file type (for everything else).  raw media types are ignored.
+	 *
+	 * there are only a couple of special cases.
+	 */
+
+	if (gst_caps_get_size (caps) == 0)
+		return NULL;
+
+	s = gst_caps_get_structure (caps, 0);
+	media_type = gst_structure_get_name (s);
+	if (media_type == NULL) {
+		return NULL;
+	} else if (g_str_has_prefix (media_type, "audio/x-raw-") ||
+	    g_str_has_prefix (media_type, "video/x-raw-")) {
+		/* ignore raw types */
+		return NULL;
+	} else if (g_str_equal (media_type, "audio/mpeg")) {
+		/* need to distinguish between mpeg 1 layer 3 and
+		 * mpeg 2 or 4 here.
+		 */
+		int mpegversion = 0;
+		gst_structure_get_int (s, "mpegversion", &mpegversion);
+		switch (mpegversion) {
+		case 2:
+		case 4:
+			return g_strdup ("audio/x-aac");		/* hmm. */
+
+		case 1:
+		default:
+			return g_strdup ("audio/mpeg");
+		}
+	} else {
+		/* everything else is fine as-is */
+		return g_strdup (media_type);
+	}
+}
+
+GstCaps *
+rb_gst_media_type_to_caps (const char *media_type)
+{
+	/* special cases: */
+	if (strcmp (media_type, "audio/mpeg") == 0) {
+		return gst_caps_from_string ("audio/mpeg, mpegversion=(int)1");
+	} else if (strcmp (media_type, "audio/x-aac") == 0) {
+		return gst_caps_from_string ("audio/mpeg, mpegversion=(int){ 2, 4 }");
+	} else {
+		/* otherwise, the media type is enough */
+		return gst_caps_from_string (media_type);
+	}
+}
+
+const char *
+rb_gst_media_type_to_extension (const char *media_type)
+{
+	if (media_type == NULL) {
+		return NULL;
+	} else if (!strcmp (media_type, "audio/mpeg")) {
+		return "mp3";
+	} else if (!strcmp (media_type, "audio/x-vorbis") || !strcmp (media_type, "application/ogg")) {
+		return "ogg";
+	} else if (!strcmp (media_type, "audio/x-flac") || !strcmp (media_type, "audio/flac")) {
+		return "flac";
+	} else if (!strcmp (media_type, "audio/x-aac") || !strcmp (media_type, "audio/aac") || !strcmp (media_type, "audio/x-alac")) {
+		return "m4a";
+	} else if (!strcmp (media_type, "audio/x-wavpack")) {
+		return "wv";
+	} else {
+		return NULL;
+	}
+}
+
+const char *
+rb_gst_mime_type_to_media_type (const char *mime_type)
+{
+	if (!strcmp (mime_type, "application/x-id3") || !strcmp (mime_type, "audio/mpeg")) {
+		return "audio/mpeg";
+	} else if (!strcmp (mime_type, "application/ogg") || !strcmp (mime_type, "audio/x-vorbis")) {
+		return "audio/x-vorbis";
+	} else if (!strcmp (mime_type, "audio/flac")) {
+		return "audio/x-flac";
+	} else if (!strcmp (mime_type, "audio/aac") || !strcmp (mime_type, "audio/mp4") || !strcmp (mime_type, "audio/m4a")) {
+		return "audio/x-aac";
+	}
+	return mime_type;
+}
+
+const char *
+rb_gst_media_type_to_mime_type (const char *media_type)
+{
+	if (!strcmp (media_type, "audio/x-vorbis")) {
+		return "application/ogg";
+	} else if (!strcmp (media_type, "audio/x-flac")) {
+		return "audio/flac";
+	} else if (!strcmp (media_type, "audio/x-aac")) {
+		return "audio/mp4";	/* probably */
+	} else {
+		return media_type;
+	}
+}
+
+gboolean
+rb_gst_media_type_matches_profile (GstEncodingProfile *profile, const char *media_type)
+{
+	const GstCaps *pcaps;
+	const GList *cl;
+	GstCaps *caps;
+	gboolean matches = FALSE;
+
+	caps = rb_gst_media_type_to_caps (media_type);
+	if (caps == NULL) {
+		return FALSE;
+	}
+
+	pcaps = gst_encoding_profile_get_format (profile);
+	if (gst_caps_can_intersect (caps, pcaps)) {
+		matches = TRUE;
+	}
+
+	if (matches == FALSE && GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
+		for (cl = gst_encoding_container_profile_get_profiles (GST_ENCODING_CONTAINER_PROFILE (profile)); cl != NULL; cl = cl->next) {
+			GstEncodingProfile *cp = cl->data;
+			pcaps = gst_encoding_profile_get_format (cp);
+			if (gst_caps_can_intersect (caps, pcaps)) {
+				matches = TRUE;
+				break;
+			}
+		}
+	}
+	return matches;
+}
+
+char *
+rb_gst_encoding_profile_get_media_type (GstEncodingProfile *profile)
+{
+	if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
+		const GList *cl = gst_encoding_container_profile_get_profiles (GST_ENCODING_CONTAINER_PROFILE (profile));
+		for (; cl != NULL; cl = cl->next) {
+			GstEncodingProfile *p = cl->data;
+			if (GST_IS_ENCODING_AUDIO_PROFILE (p)) {
+				return rb_gst_caps_to_media_type (gst_encoding_profile_get_format (p));
+			}
+
+		}
+
+		/* now what? */
+		return NULL;
+	} else {
+		return rb_gst_caps_to_media_type (gst_encoding_profile_get_format (profile));
+	}
+}
+
+GstEncodingTarget *
+rb_gst_get_default_encoding_target ()
+{
+	if (default_target == NULL) {
+		char *target_file;
+		GError *error = NULL;
+
+		target_file = rb_find_user_data_file (ENCODING_TARGET_FILE);
+		if (g_file_test (target_file, G_FILE_TEST_EXISTS) == FALSE) {
+			target_file = g_strdup (rb_file (ENCODING_TARGET_FILE));
+		}
+
+		default_target = gst_encoding_target_load_from_file (target_file, &error);
+		if (default_target == NULL) {
+			g_warning ("Unable to load encoding profiles from %s: %s", target_file, error ? error->message : "no error");
+			g_clear_error (&error);
+			return NULL;
+		}
+	}
+
+	return default_target;
+}
+
+GstEncodingProfile *
+rb_gst_get_encoding_profile (const char *media_type)
+{
+	const GList *l;
+	GstEncodingTarget *target;
+	target = rb_gst_get_default_encoding_target ();
+
+	for (l = gst_encoding_target_get_profiles (target); l != NULL; l = l->next) {
+		GstEncodingProfile *profile = l->data;
+		if (rb_gst_media_type_matches_profile (profile, media_type)) {
+			gst_encoding_profile_ref (profile);
+			return profile;
+		}
+	}
+
+	return NULL;
+}
+
+gboolean
+rb_gst_media_type_is_lossless (const char *media_type)
+{
+	int i;
+	const char *lossless_types[] = {
+		"audio/x-flac",
+		"audio/x-alac",
+		"audio/x-shorten",
+		"audio/x-wavpack"	/* not completely sure */
+	};
+
+	for (i = 0; i < G_N_ELEMENTS (lossless_types); i++) {
+		if (strcmp (media_type, lossless_types[i]) == 0) {
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
diff --git a/lib/rb-gst-media-types.h b/lib/rb-gst-media-types.h
new file mode 100644
index 0000000..836a6b3
--- /dev/null
+++ b/lib/rb-gst-media-types.h
@@ -0,0 +1,75 @@
+/*
+ *  Copyright (C) 2010  Jonathan Matthew <jonathan d14n org>
+ *
+ *  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, or (at your option)
+ *  any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef __RB_GST_MEDIA_TYPES_H
+#define __RB_GST_MEDIA_TYPES_H
+
+#include <gst/gst.h>
+#include <gst/pbutils/encoding-target.h>
+#include <gst/pbutils/encoding-profile.h>
+
+G_BEGIN_DECLS
+
+/* some common media types */
+#define RB_GST_MEDIA_TYPE_MP3		"audio/mpeg"
+#define RB_GST_MEDIA_TYPE_OGG_VORBIS 	"audio/x-vorbis"
+#define RB_GST_MEDIA_TYPE_FLAC 		"audio/x-flac"
+#define RB_GST_MEDIA_TYPE_AAC 		"audio/x-aac"
+
+/* media type categories */
+typedef enum {
+	MEDIA_TYPE_NONE = 0,
+	MEDIA_TYPE_CONTAINER,
+	MEDIA_TYPE_AUDIO,
+	MEDIA_TYPE_VIDEO,
+	MEDIA_TYPE_OTHER
+} RBGstMediaType;
+
+char *		rb_gst_caps_to_media_type (const GstCaps *caps);
+GstCaps *	rb_gst_media_type_to_caps (const char *media_type);
+
+const char *	rb_gst_media_type_to_extension (const char *media_type);
+
+const char *	rb_gst_mime_type_to_media_type (const char *mime_type);
+
+const char *	rb_gst_media_type_to_mime_type (const char *media_type);
+
+RBGstMediaType	rb_gst_get_missing_plugin_type (const GstStructure *structure);
+
+GstEncodingTarget *rb_gst_get_default_encoding_target (void);
+
+GstEncodingProfile *rb_gst_get_encoding_profile (const char *media_type);
+
+gboolean	rb_gst_media_type_matches_profile (GstEncodingProfile *profile, const char *media_type);
+
+char *		rb_gst_encoding_profile_get_media_type (GstEncodingProfile *profile);
+
+gboolean	rb_gst_media_type_is_lossless (const char *media_type);
+
+G_END_DECLS
+
+#endif /* __RB_GST_MEDIA_TYPES_H */
diff --git a/lib/rb-util.c b/lib/rb-util.c
index e5168e0..a4341e4 100644
--- a/lib/rb-util.c
+++ b/lib/rb-util.c
@@ -1005,27 +1005,6 @@ rb_uri_list_parse (const char *uri_list)
 }
 
 /**
- * rb_mime_get_friendly_name:
- * @mime_type: a MIME type
- *
- * Returns a human-friendly description of the MIME type @mime_type.
- *
- * Return value: type description, must be freed by caller
- */
-char*
-rb_mime_get_friendly_name (const char *mime_type)
-{
-	gchar *name = NULL;
-	
-	if (name == NULL && mime_type)
-		name = g_content_type_get_description (mime_type);
-	if (name == NULL)
-		name = g_strdup (_("Unknown"));
-
-	return name;
-}
-
-/**
  * rb_signal_accumulator_object_handled: (skip):
  * @hint: a #GSignalInvocationHint
  * @return_accu: holds the accumulated return value
diff --git a/lib/rb-util.h b/lib/rb-util.h
index 126bd3a..d72765b 100644
--- a/lib/rb-util.h
+++ b/lib/rb-util.h
@@ -79,7 +79,6 @@ GList* rb_collate_hash_table_keys (GHashTable *table);
 GList* rb_collate_hash_table_values (GHashTable *table);
 
 GList* rb_uri_list_parse (const char *uri_list);
-char* rb_mime_get_friendly_name (const char *mime_type);
 
 gboolean rb_signal_accumulator_object_handled (GSignalInvocationHint *hint,
 					       GValue *return_accu,
diff --git a/metadata/rb-metadata-dbus-client.c b/metadata/rb-metadata-dbus-client.c
index 4acff09..7a73ad1 100644
--- a/metadata/rb-metadata-dbus-client.c
+++ b/metadata/rb-metadata-dbus-client.c
@@ -84,7 +84,7 @@ static char **saveable_types = NULL;
 
 struct RBMetaDataPrivate
 {
-	char       *mimetype;
+	char       *media_type;
 	char      **missing_plugins;
 	char      **plugin_descriptions;
 	gboolean    has_audio;
@@ -120,7 +120,7 @@ rb_metadata_finalize (GObject *object)
 
 	md = RB_METADATA (object);
 
-	g_free (md->priv->mimetype);
+	g_free (md->priv->media_type);
 	if (md->priv->metadata)
 		g_hash_table_destroy (md->priv->metadata);
 
@@ -351,8 +351,8 @@ start_metadata_service (GError **error)
 void
 rb_metadata_reset (RBMetaData *md)
 {
-	g_free (md->priv->mimetype);
-	md->priv->mimetype = NULL;
+	g_free (md->priv->media_type);
+	md->priv->media_type = NULL;
 
 	if (md->priv->metadata)
 		g_hash_table_destroy (md->priv->metadata);
@@ -370,7 +370,7 @@ rb_metadata_reset (RBMetaData *md)
  *
  * Reads metadata information from the specified URI.
  * Once this has returned successfully (with *error == NULL),
- * rb_metadata_get, rb_metadata_get_mime, rb_metadata_has_missing_plugins,
+ * rb_metadata_get, rb_metadata_get_media_type, rb_metadata_has_missing_plugins,
  * and rb_metadata_get_missing_plugins can usefully be called.
  */
 void
@@ -419,7 +419,7 @@ rb_metadata_load (RBMetaData *md,
 			       &md->priv->has_audio,
 			       &md->priv->has_video,
 			       &md->priv->has_other_data,
-			       &md->priv->mimetype,
+			       &md->priv->media_type,
 			       &ok,
 			       &error_code,
 			       &error_string,
@@ -476,18 +476,18 @@ rb_metadata_load (RBMetaData *md,
 }
 
 /**
- * rb_metadata_get_mime:
+ * rb_metadata_get_media_type:
  * @md: a #RBMetaData
  *
  * Returns the type of the file from which metadata was read.
- * This isn't really a MIME type, but it looks like one.
+ * This may look like a MIME type, but it isn't.
  *
- * Return value: MIME type-ish string
+ * Return value: media type string
  */
 const char *
-rb_metadata_get_mime (RBMetaData *md)
+rb_metadata_get_media_type (RBMetaData *md)
 {
-	return md->priv->mimetype;
+	return md->priv->media_type;
 }
 
 /**
@@ -597,7 +597,7 @@ rb_metadata_set (RBMetaData *md, RBMetaDataField field,
 /**
  * rb_metadata_can_save:
  * @md: a #RBMetaData
- * @mimetype: the MIME type-ish string to check
+ * @media_type: the media type string to check
  *
  * Checks if the metadata writer is capable of updating file metadata
  * for a given media type.
@@ -605,7 +605,7 @@ rb_metadata_set (RBMetaData *md, RBMetaDataField field,
  * Return value: TRUE if the file metadata for the given media type can be updated
  */
 gboolean
-rb_metadata_can_save (RBMetaData *md, const char *mimetype)
+rb_metadata_can_save (RBMetaData *md, const char *media_type)
 {
 	GError *error = NULL;
 	gboolean result = FALSE;
@@ -622,7 +622,7 @@ rb_metadata_can_save (RBMetaData *md, const char *mimetype)
 
 	if (saveable_types != NULL) {
 		for (i = 0; saveable_types[i] != NULL; i++) {
-			if (g_str_equal (mimetype, saveable_types[i])) {
+			if (g_str_equal (media_type, saveable_types[i])) {
 				result = TRUE;
 				break;
 			}
diff --git a/metadata/rb-metadata-dbus-service.c b/metadata/rb-metadata-dbus-service.c
index 37bf58d..1eb1f1d 100644
--- a/metadata/rb-metadata-dbus-service.c
+++ b/metadata/rb-metadata-dbus-service.c
@@ -73,7 +73,7 @@ rb_metadata_dbus_load (GVariant *parameters,
 
 	rb_debug ("loading metadata from %s", uri);
 	rb_metadata_load (svc->metadata, uri, &error);
-	mediatype = rb_metadata_get_mime (svc->metadata);
+	mediatype = rb_metadata_get_media_type (svc->metadata);
 	rb_debug ("metadata load finished (type %s)", mediatype);
 
 	rb_metadata_get_missing_plugins (svc->metadata, &missing_plugins, &plugin_descriptions);
@@ -284,7 +284,7 @@ test_load (const char *uri)
 		rv = -1;
 	} else {
 		int i;
-		g_print ("mimetype: %s\n", rb_metadata_get_mime (md));
+		g_print ("media type: %s\n", rb_metadata_get_media_type (md));
 		for (i=0; i<RB_METADATA_FIELD_LAST; i++) {
 			GValue v = {0,};
 			GValue sv = {0,};
diff --git a/metadata/rb-metadata-dbus.c b/metadata/rb-metadata-dbus.c
index 48c3224..799b9d2 100644
--- a/metadata/rb-metadata-dbus.c
+++ b/metadata/rb-metadata-dbus.c
@@ -49,7 +49,7 @@ const char *rb_metadata_iface_xml = "				\
       <arg direction='out' type='b' name='hasAudio'/>		\
       <arg direction='out' type='b' name='hasVideo'/>		\
       <arg direction='out' type='b' name='hasOtherData'/>	\
-      <arg direction='out' type='s' name='mimeType'/>		\
+      <arg direction='out' type='s' name='mediaType'/>		\
       <arg direction='out' type='b' name='ok'/>			\
       <arg direction='out' type='i' name='errorCode'/>		\
       <arg direction='out' type='s' name='errorString'/>	\
diff --git a/metadata/rb-metadata-gst-common.c b/metadata/rb-metadata-gst-common.c
index 63386cc..a78a7a5 100644
--- a/metadata/rb-metadata-gst-common.c
+++ b/metadata/rb-metadata-gst-common.c
@@ -129,12 +129,10 @@ rb_metadata_gst_tag_to_field (const char *tag)
 		return RB_METADATA_FIELD_ARTIST_SORTNAME;
 	else if (!strcmp (tag, GST_TAG_ALBUM_SORTNAME))
 		return RB_METADATA_FIELD_ALBUM_SORTNAME;
-#if GST_CHECK_VERSION(0,10,25)
 	else if (!strcmp (tag, GST_TAG_ALBUM_ARTIST))
 		return RB_METADATA_FIELD_ALBUM_ARTIST;
 	else if (!strcmp (tag, GST_TAG_ALBUM_ARTIST_SORTNAME))
 		return RB_METADATA_FIELD_ALBUM_ARTIST_SORTNAME;
-#endif
 	else
 		return -1;
 }
@@ -208,69 +206,11 @@ rb_metadata_gst_field_to_gst_tag (RBMetaDataField field)
 		return GST_TAG_ARTIST_SORTNAME;
 	case RB_METADATA_FIELD_ALBUM_SORTNAME:
 		return GST_TAG_ALBUM_SORTNAME;
-#if GST_CHECK_VERSION(0,10,25)
 	case RB_METADATA_FIELD_ALBUM_ARTIST:
 		return GST_TAG_ALBUM_ARTIST;
 	case RB_METADATA_FIELD_ALBUM_ARTIST_SORTNAME:
 		return GST_TAG_ALBUM_ARTIST_SORTNAME;
-#endif
 	default:
 		return NULL;
 	}
 }
-
-
-/* don't like this much, but it's all we can do for now.
- * these media types are copied from gst-plugins-base/gst-libs/gst/pbutils/descriptions.c.
- * these are only the media types that don't start with 'audio/' or 'video/', which are
- * identified fairly accurately by the filters for those prefixes.
- */
-static const char *container_formats[] = {
-	"application/ogg",
-	"application/vnd.rn-realmedia",
-	"application/x-id3",
-	"application/x-ape",
-	"application/x-icy"
-};
-
-
-RBGstMediaType
-rb_metadata_gst_get_missing_plugin_type (GstMessage *message)
-{
-	const char *media_type;
-	const char *missing_type;
-	const GstStructure *structure;
-	const GstCaps *caps;
-	const GValue *val;
-	int i;
-
-	structure = gst_message_get_structure (message);
-	missing_type = gst_structure_get_string (structure, "type");
-	if (missing_type == NULL || strcmp (missing_type, "decoder") != 0) {
-		rb_debug ("missing plugin is not a decoder");
-		return MEDIA_TYPE_NONE;
-	}
-
-	val = gst_structure_get_value (structure, "detail");
-	caps = gst_value_get_caps (val);
-
-	media_type = gst_structure_get_name (gst_caps_get_structure (caps, 0));
-	for (i = 0; i < G_N_ELEMENTS (container_formats); i++) {
-		if (strcmp (media_type, container_formats[i]) == 0) {
-			rb_debug ("missing plugin is a container demuxer");
-			return MEDIA_TYPE_CONTAINER;
-		}
-	}
-
-	if (g_str_has_prefix (media_type, "audio/")) {
-		rb_debug ("missing plugin is an audio decoder");
-		return MEDIA_TYPE_AUDIO;
-	} else if (g_str_has_prefix (media_type, "video/")) {
-		rb_debug ("missing plugin is (probably) a video decoder");
-		return MEDIA_TYPE_VIDEO;
-	} else {
-		rb_debug ("missing plugin is neither a video nor audio decoder");
-		return MEDIA_TYPE_OTHER;
-	}
-}
-
diff --git a/metadata/rb-metadata-gst-common.h b/metadata/rb-metadata-gst-common.h
index b1e8dad..29f97ff 100644
--- a/metadata/rb-metadata-gst-common.h
+++ b/metadata/rb-metadata-gst-common.h
@@ -34,21 +34,11 @@ G_BEGIN_DECLS
 
 #include <metadata/rb-metadata.h>
 
-typedef enum {
-	MEDIA_TYPE_NONE = 0,
-	MEDIA_TYPE_CONTAINER,
-	MEDIA_TYPE_AUDIO,
-	MEDIA_TYPE_VIDEO,
-	MEDIA_TYPE_OTHER
-} RBGstMediaType;
-
 const char *		rb_metadata_gst_field_to_gst_tag (RBMetaDataField field);
 RBMetaDataField		rb_metadata_gst_tag_to_field (const char *tag);
 
 void			rb_metadata_gst_register_transforms (void);
 
-RBGstMediaType		rb_metadata_gst_get_missing_plugin_type (GstMessage *msg);
-
 G_END_DECLS
 
 #endif /* RB_METADATA_GST_COMMON_H */
diff --git a/metadata/rb-metadata-gst.c b/metadata/rb-metadata-gst.c
index f5245e8..cdfcae2 100644
--- a/metadata/rb-metadata-gst.c
+++ b/metadata/rb-metadata-gst.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- *  Copyright (C) 2003,2004 Colin Walters <walters verbum org>
+ *  Copyright (C) 2010 Jonathan Matthew  <jonathan d14n org>
  *
  *  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
@@ -28,940 +28,544 @@
 
 #include <config.h>
 
-#include <string.h>
-
 #include <glib/gi18n.h>
-#include <gst/gsttagsetter.h>
-#include <gst/tag/tag.h>
-#include <gst/gsturi.h>
 #include <gio/gio.h>
+#include <gst/gst.h>
+#include <gst/pbutils/gstdiscoverer.h>
 #include <gst/pbutils/pbutils.h>
 
 #include "rb-metadata.h"
 #include "rb-metadata-gst-common.h"
+#include "rb-gst-media-types.h"
 #include "rb-debug.h"
-#include "rb-util.h"
 #include "rb-file-helpers.h"
 
-G_DEFINE_TYPE(RBMetaData, rb_metadata, G_TYPE_OBJECT)
+/* copied from gstplay-enum.h */
+typedef enum {
+  GST_AUTOPLUG_SELECT_TRY,
+  GST_AUTOPLUG_SELECT_EXPOSE,
+  GST_AUTOPLUG_SELECT_SKIP
+} GstAutoplugSelectResult;
 
-typedef GstElement *(*RBAddTaggerElem) (GstElement *pipeline, GstElement *source, GstTagList *tags);
+typedef GstElement *(*RBAddTaggerElem) (GstElement *pipeline, GstPad *srcpad, GstTagList *tags);
 
-static void rb_metadata_finalize (GObject *object);
+static gboolean check_gst_plugin_version (const char *plugin, const char *element, gint major, gint minor, gint micro);
+G_DEFINE_TYPE(RBMetaData, rb_metadata, G_TYPE_OBJECT)
 
 struct RBMetaDataPrivate
 {
-	GHashTable *metadata;
-
-	GstElement *pipeline;
-	GstElement *sink;
-	gulong typefind_cb_id;
-	GstTagList *tags;
-
-	GHashTable *taggers;
+	GstDiscovererInfo *info;
 
-	char *type;
-	gboolean eos;
+	char *mediatype;
 	gboolean has_audio;
 	gboolean has_non_audio;
 	gboolean has_video;
-	GSList *missing_plugins;
-	GError *error;
-};
+	guint audio_bitrate;
+	GstCaps *jpeg_image_caps;
 
-#define RB_METADATA_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_METADATA, RBMetaDataPrivate))
+	/* writing */
+	GstElement *pipeline;
+	GstElement *sink;
+	GHashTable *taggers;
+	GstTagList *tags;
+	gboolean sink_linked;
+};
 
 void
 rb_metadata_reset (RBMetaData *md)
 {
-	if (md->priv->metadata)
-		g_hash_table_destroy (md->priv->metadata);
-	md->priv->metadata = g_hash_table_new_full (g_direct_hash, g_direct_equal,
-						    NULL, (GDestroyNotify) rb_value_free);
-
-	if (md->priv->pipeline) {
-		gst_object_unref (md->priv->pipeline);
-		md->priv->pipeline = NULL;
+	if (md->priv->tags != NULL) {
+		g_object_unref (md->priv->tags);
 	}
-	if (md->priv->sink) {
-		md->priv->sink = NULL;
-	}
-	md->priv->typefind_cb_id = 0;
-	if (md->priv->tags) {
-		gst_tag_list_free (md->priv->tags);
-		md->priv->tags = NULL;
+
+	if (md->priv->info != NULL) {
+		gst_discoverer_info_unref (md->priv->info);
+		md->priv->info = NULL;
 	}
+	g_free (md->priv->mediatype);
+	md->priv->mediatype = NULL;
 
-	g_free (md->priv->type);
-	md->priv->type = NULL;
-	md->priv->eos = FALSE;
 	md->priv->has_audio = FALSE;
 	md->priv->has_non_audio = FALSE;
 	md->priv->has_video = FALSE;
-	if (md->priv->missing_plugins != NULL) {
-		GSList *t;
-		for (t = md->priv->missing_plugins; t != NULL; t = t->next) {
-			gst_message_unref (GST_MESSAGE (t->data));
-		}
-		g_slist_free (md->priv->missing_plugins);
-		md->priv->missing_plugins = NULL;
-	}
-
-	g_clear_error (&md->priv->error);
 }
 
-
-static GstElement *
-flac_tagger (GstElement *pipeline, GstElement *link_to, GstTagList *tags)
+void
+rb_metadata_load (RBMetaData *md, const char *uri, GError **error)
 {
-	GstElement *tagger = NULL;
+	GList *streams;
+	GList *l;
+	GstDiscoverer *discoverer;
+	GstCaps *caps;
 
-	tagger = gst_element_factory_make ("flactag", NULL);
-	if (tagger == NULL)
-		return NULL;
+	rb_metadata_reset (md);
 
-	gst_bin_add (GST_BIN (pipeline), tagger);
-	gst_element_link_many (link_to, tagger, NULL);
-	gst_element_set_state (tagger, GST_STATE_PAUSED);
+	discoverer = gst_discoverer_new (30 * GST_SECOND, NULL);
+	md->priv->info = gst_discoverer_discover_uri (discoverer, g_strdup (uri), error);
+	g_object_unref (discoverer);
 
-	gst_tag_setter_merge_tags (GST_TAG_SETTER (tagger), tags, GST_TAG_MERGE_REPLACE_ALL);
-	return tagger;
-}
+	/* figure out if we've got audio, non-audio, or video streams */
+	streams = gst_discoverer_info_get_streams (md->priv->info, GST_TYPE_DISCOVERER_STREAM_INFO);
+	for (l = streams; l != NULL; l = l->next) {
+		GstDiscovererStreamInfo *s = (GstDiscovererStreamInfo *)l->data;
+		const char *mediatype;
+		caps = gst_discoverer_stream_info_get_caps (s);
+		mediatype = gst_structure_get_name (gst_caps_get_structure (caps, 0));
 
-static void
-id3_pad_added_cb (GstElement *demux, GstPad *pad, GstElement *mux)
-{
-	GstPad *mux_pad;
-
-	mux_pad = gst_element_get_compatible_pad (mux, pad, NULL);
-	if (gst_pad_link (pad, mux_pad) != GST_PAD_LINK_OK)
-		rb_debug ("unable to link pad from id3demux to id3v2mux");
-	else
-		rb_debug ("linked pad from id3demux to id3v2mux");
-}
-
-static GstElement *
-id3_tagger (GstElement *pipeline, GstElement *link_to, GstTagList *tags)
-{
-	GstElement *demux = NULL;
-	GstElement *mux = NULL;
-	GstPad *link_src = NULL;
-	GstStructure *link_type;
+		if (GST_IS_DISCOVERER_AUDIO_INFO (s)) {
+			md->priv->has_audio = TRUE;
 
-	/* TODO use new id3tag element here once it's in gst-plugins-good */
-	mux = gst_element_factory_make ("id3v2mux", NULL);
-	if (mux == NULL) {
-		return NULL;
-	}
+			md->priv->audio_bitrate = gst_discoverer_audio_info_get_bitrate (GST_DISCOVERER_AUDIO_INFO (s));
 
-	/* if the input stream doesn't contain id3 tags already, we can just link the
-	 * tag writer to it and we're done.
-	 */
-	link_src = gst_element_get_static_pad (link_to, "src");
-	if (link_src == NULL) {
-		rb_debug ("couldn't get src pad to link to?");
-		goto error;
-	}
-
-	link_type = gst_caps_get_structure (GST_PAD_CAPS (link_src), 0);
-	if (gst_structure_has_name (link_type, "application/x-id3") == FALSE) {
-		rb_debug ("input file has no tags, plugging id3 muxer directly");
-		gst_bin_add (GST_BIN (pipeline), mux);
-		if (!gst_element_link (link_to, mux)) {
-			gst_bin_remove (GST_BIN (pipeline), mux);
-			goto error;
-		}
-	} else {
-		rb_debug ("input file already has tags, using id3demux first");
-		demux = gst_element_factory_make ("id3demux", NULL);
-		if (demux == NULL)
-			goto error;
-
-		gst_bin_add_many (GST_BIN (pipeline), demux, mux, NULL);
-		if (!gst_element_link (link_to, demux)) {
-			gst_bin_remove_many (GST_BIN (pipeline), demux, mux, NULL);
-			goto error;
+			g_free (md->priv->mediatype);
+			md->priv->mediatype = rb_gst_caps_to_media_type (caps);
+			rb_debug ("found audio stream, media type %s", md->priv->mediatype);
+		} else if (GST_IS_DISCOVERER_CONTAINER_INFO (s)) {
+			if (md->priv->mediatype == NULL) {
+				md->priv->mediatype = g_strdup (mediatype);
+				rb_debug ("found container, media type %s", md->priv->mediatype);
+			} else {
+				rb_debug ("found container, ignoring media type");
+			}
+		} else if (g_strcmp0 (mediatype, "application/x-id3") == 0 ||
+			   g_strcmp0 (mediatype, "application/x-apetag") == 0) {
+			rb_debug ("found tag type, ignoring");
+		} else {
+			if (GST_IS_DISCOVERER_VIDEO_INFO (s)) {
+				/* pretend low-framerate jpeg isn't video */
+				if (gst_caps_can_intersect (caps, md->priv->jpeg_image_caps)) {
+					rb_debug ("found a jpeg image stream, not actual video");
+				} else {
+					md->priv->has_video = TRUE;
+				}
+			}
+			md->priv->has_non_audio = TRUE;
+			if (md->priv->mediatype == NULL) {
+				md->priv->mediatype = g_strdup (mediatype);
+				rb_debug ("found video/image/other stream, media type %s", md->priv->mediatype);
+			} else {
+				rb_debug ("found video/image/other stream, ignoring media type (%s)", mediatype);
+			}
+			rb_debug ("video caps: %s", gst_caps_to_string (caps));
 		}
 
-		g_signal_connect (demux, "pad-added", (GCallback)id3_pad_added_cb, mux);
-		gst_element_set_state (demux, GST_STATE_PAUSED);
+		gst_caps_unref (caps);
 	}
+	gst_discoverer_stream_info_list_free (streams);
 
-	gst_element_set_state (mux, GST_STATE_PAUSED);
-	gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), tags, GST_TAG_MERGE_REPLACE_ALL);
-	return mux;
-
-error:
-	if (link_src != NULL) {
-		gst_object_unref (link_src);
-	}
-	if (demux != NULL) {
-		g_object_unref (demux);
-	}
-	g_object_unref (mux);
-	return NULL;
-}
-
-static void
-ogg_pad_added_cb (GstElement *demux, GstPad *pad, GstTagList *tags)
-{
-	GstCaps *caps;
-	GstStructure *structure;
-	const gchar *mimetype;
-	GstPad *conn_pad = NULL;
-	GstElement *mux;
-
-	caps = gst_pad_get_caps (pad);
-	structure = gst_caps_get_structure (caps, 0);
-	mimetype = gst_structure_get_name (structure);
-
-	mux = g_object_get_data (G_OBJECT (demux), "mux");
-
-	if (strcmp (mimetype, "audio/x-vorbis") == 0) {
-		GstElement *tagger, *parser;
-		GstBin *bin;
-		GstState state;
-
-		rb_debug ("found vorbis stream in ogg container, using vorbistag");
-
-		parser = gst_element_factory_make ("vorbisparse", NULL);
-		if (parser == NULL) {
-			rb_debug ("could not create vorbisparse element");
-			goto end;
-		}
-
-		tagger = gst_element_factory_make ("vorbistag", NULL);
-		if (tagger == NULL) {
-			rb_debug ("could not create vorbistag element");
-			gst_object_unref (parser);
-			goto end;
+	/* look at missing plugin information too */
+	if (rb_metadata_has_missing_plugins (md)) {
+		switch (rb_gst_get_missing_plugin_type (gst_discoverer_info_get_misc (md->priv->info))) {
+		case MEDIA_TYPE_NONE:
+			break;
+		case MEDIA_TYPE_CONTAINER:
+			/* hm, maybe we need a way to say 'we don't even know what's in here'.
+			 * but for now, the things we actually identify as containers are mostly
+			 * used for audio, so pretending they actually are is good enough.
+			 */
+		case MEDIA_TYPE_AUDIO:
+			md->priv->has_audio = TRUE;
+			break;
+		case MEDIA_TYPE_VIDEO:
+			md->priv->has_video = TRUE;
+			break;
+		case MEDIA_TYPE_OTHER:
+			md->priv->has_non_audio = TRUE;
+			break;
+		default:
+			g_assert_not_reached ();
 		}
-
-		bin = GST_BIN (gst_element_get_parent (mux));
-		gst_bin_add_many (bin, tagger, parser, NULL);
-		gst_object_unref (GST_OBJECT (bin));
-
-		/* connect and bring them up to the same state */
-		gst_element_link_many (tagger, parser, mux, NULL);
-		gst_element_get_state (mux, &state, NULL, 0);
-		gst_element_set_state (parser, state);
-		gst_element_set_state (tagger, state);
-
-		conn_pad = gst_element_get_compatible_pad (tagger, pad, NULL);
-		gst_pad_link (pad, conn_pad);
-
-		gst_tag_setter_merge_tags (GST_TAG_SETTER (tagger), tags, GST_TAG_MERGE_REPLACE_ALL);
-	} else {
-		conn_pad = gst_element_get_compatible_pad (mux, pad, NULL);
-		gst_pad_link (pad, conn_pad);
-		rb_debug ("found stream in ogg container with no known tagging element");
 	}
-
-end:
-	gst_caps_unref (caps);
-}
-
-static GstElement *
-vorbis_tagger (GstElement *pipeline, GstElement *link_to, GstTagList *tags)
-{
-	GstElement *demux = NULL;
-	GstElement *mux = NULL;
-
-	demux = gst_element_factory_make ("oggdemux", NULL);
-	mux =  gst_element_factory_make ("oggmux", NULL);
-
-	if (demux == NULL || mux == NULL)
-		goto error;
-
-	gst_bin_add_many (GST_BIN (pipeline), demux, mux, NULL);
-	if (!gst_element_link (link_to, demux))
-		goto error;
-
-	gst_element_set_state (demux, GST_STATE_PAUSED);
-	g_object_set_data (G_OBJECT (demux), "mux", mux);
-	g_signal_connect (demux, "pad-added", (GCallback)ogg_pad_added_cb, tags);
-	return mux;
-
-error:
-	g_object_unref (demux);
-	g_object_unref (mux);
-	return NULL;
-}
-
-static void
-mp4_pad_added_cb (GstElement *demux, GstPad *demuxpad, GstPad *muxpad)
-{
-	if (gst_pad_link (demuxpad, muxpad) != GST_PAD_LINK_OK)
-		rb_debug ("unable to link pad from qtdemux to mp4mux");
-	else
-		rb_debug ("linked pad from qtdemux to mp4mux");
-}
-
-
-static GstElement *
-mp4_tagger (GstElement *pipeline, GstElement *link_to, GstTagList *tags)
-{
-	GstElement *demux;
-	GstElement *mux;
-	GstPad *muxpad;
-
-	demux = gst_element_factory_make ("qtdemux", NULL);
-	mux = gst_element_factory_make ("mp4mux", NULL);
-	if (demux == NULL || mux == NULL)
-		goto error;
-
-	gst_bin_add_many (GST_BIN (pipeline), demux, mux, NULL);
-	if (!gst_element_link (link_to, demux))
-		goto error;
-
-	muxpad = gst_element_get_request_pad (mux, "audio_%d");
-	g_signal_connect (demux, "pad-added", G_CALLBACK (mp4_pad_added_cb), muxpad);
-	gst_element_set_state (demux, GST_STATE_PAUSED);
-	gst_element_set_state (mux, GST_STATE_PAUSED);
-
-	gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), tags, GST_TAG_MERGE_REPLACE_ALL);
-
-	return mux;
-
-error:
-	if (demux != NULL)
-		g_object_unref (demux);
-	if (mux != NULL)
-		g_object_unref (mux);
-	return NULL;
 }
 
-
-
-static void
-rb_metadata_class_init (RBMetaDataClass *klass)
+gboolean
+rb_metadata_has_missing_plugins (RBMetaData *md)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->finalize = rb_metadata_finalize;
+	GstDiscovererResult result;
+	if (md->priv->info == NULL) {
+		return FALSE;
+	}
 
-	g_type_class_add_private (klass, sizeof (RBMetaDataPrivate));
-	rb_metadata_gst_register_transforms ();
+	result = gst_discoverer_info_get_result (md->priv->info);
+	return ((result & GST_DISCOVERER_MISSING_PLUGINS) != 0);
 }
 
-static void
-rb_metadata_init (RBMetaData *md)
+gboolean
+rb_metadata_get_missing_plugins (RBMetaData *md, char ***missing_plugins, char ***plugin_descriptions)
 {
-	md->priv = RB_METADATA_GET_PRIVATE (md);
-
-	md->priv->taggers = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+	char **mp;
+	char **pd;
+	GstMessage *msg;
+	const GstStructure *misc;
 
-	if (gst_element_factory_find ("giostreamsink") == FALSE) {
-		rb_debug ("giostreamsink not found, can't tag anything");
-	} else {
-		if (gst_element_factory_find ("vorbistag") &&
-		    gst_element_factory_find ("vorbisparse") &&
-		    gst_element_factory_find ("oggdemux") &&
-		    gst_element_factory_find ("oggmux")) {
-			rb_debug ("ogg vorbis tagging available");
-			g_hash_table_insert (md->priv->taggers, "application/ogg", vorbis_tagger);
-			g_hash_table_insert (md->priv->taggers, "audio/x-vorbis", vorbis_tagger);
-		}
+	if (rb_metadata_has_missing_plugins (md) == FALSE) {
+		return FALSE;
+	}
 
-		if (gst_element_factory_find ("flactag")) {
-			rb_debug ("flac tagging available");
-			g_hash_table_insert (md->priv->taggers, "audio/x-flac", flac_tagger);
-		}
+	mp = g_new0 (char *, 2);
+	pd = g_new0 (char *, 2);
 
-		/* TODO check for new id3 tag element too */
-		if (gst_element_factory_find ("id3v2mux") && gst_element_factory_find ("id3demux")) {
-			rb_debug ("id3 tagging available");
-			g_hash_table_insert (md->priv->taggers, "application/x-id3", id3_tagger);
-			g_hash_table_insert (md->priv->taggers, "audio/mpeg", id3_tagger);
-		}
+	/* wrap the structure in a new message so we can use the
+	 * pbutils functions to look at it.
+	 */
+	misc = gst_discoverer_info_get_misc (md->priv->info);
+	msg = gst_message_new_element (NULL, gst_structure_copy (misc));
+	mp[0] = gst_missing_plugin_message_get_installer_detail (msg);
+	pd[0] = gst_missing_plugin_message_get_description (msg);
+	gst_message_unref (msg);
 
-		if (gst_element_factory_find ("qtdemux") && gst_element_factory_find ("mp4mux")) {
-			rb_debug ("mp4 tagging available");
-			g_hash_table_insert (md->priv->taggers, "audio/x-m4a", mp4_tagger);
-			g_hash_table_insert (md->priv->taggers, "video/quicktime", mp4_tagger);
-		}
-	}
+	*missing_plugins = mp;
+	*plugin_descriptions = pd;
+	return TRUE;
 }
 
-static void
-rb_metadata_finalize (GObject *object)
+gboolean
+rb_metadata_has_audio (RBMetaData *md)
 {
-	RBMetaData *md;
-
-	md = RB_METADATA (object);
-
-	rb_metadata_reset (md);
-	if (md->priv->taggers)
-		g_hash_table_destroy (md->priv->taggers);
-
-
-	G_OBJECT_CLASS (rb_metadata_parent_class)->finalize (object);
+	return md->priv->has_audio;
 }
 
-RBMetaData *
-rb_metadata_new (void)
+gboolean
+rb_metadata_has_video (RBMetaData *md)
 {
-	return RB_METADATA (g_object_new (RB_TYPE_METADATA, NULL, NULL));
+	return md->priv->has_video;
 }
 
-static void
-rb_metadata_gst_load_tag (const GstTagList *list, const gchar *tag, RBMetaData *md)
+gboolean
+rb_metadata_has_other_data (RBMetaData *md)
 {
-	int count, tem, type;
-	RBMetaDataField field;
-	GValue *newval;
-	const GValue *val;
-
-	count = gst_tag_list_get_tag_size (list, tag);
-	if (count < 1)
-		return;
-
-	tem = rb_metadata_gst_tag_to_field (tag);
-	if (tem < 0) {
-		rb_debug ("no metadata field for tag \"%s\"", tag);
-		return;
-	}
-	field = (RBMetaDataField) tem;
-
-	type = rb_metadata_get_field_type (field);
-	val = gst_tag_list_get_value_index (list, tag, 0);
-	newval = g_slice_new0 (GValue);
-	g_value_init (newval, type);
-	if (!g_value_transform (val, newval)) {
-
-		rb_debug ("Could not transform tag value type %s into %s",
-			  g_type_name (G_VALUE_TYPE (val)),
-			  g_type_name (G_VALUE_TYPE (newval)));
-		g_value_unset (newval);
-		g_slice_free (GValue, newval);
-		return;
-	}
-
-	switch (type) {
-	case G_TYPE_STRING: {
-		/* Reject invalid utf-8 strings, shorter duplicated tags
-		 * and then remove leading and trailing whitespace.
-		 */
-		char *str;
-
-		str = g_value_dup_string (newval);
-
-		if (!g_utf8_validate (str, -1, NULL)) {
-			rb_debug ("Got invalid UTF-8 tag data");
-			g_free (str);
-			g_value_unset (newval);
-			g_slice_free (GValue, newval);
-			return;
-		}
-		str = g_strstrip (str);
-
-		/* Check whether we have a shorter duplicate tag,
-		 * Doesn't work with non-normalised UTF-8 strings */
-		val = g_hash_table_lookup (md->priv->metadata,
-					   GINT_TO_POINTER (field));
-		if (val != NULL) {
-			const char *old_str;
-			old_str = g_value_get_string (val);
-			if (old_str != NULL
-			    && g_utf8_strlen (old_str, -1) > g_utf8_strlen (str, -1)) {
-				if (g_str_has_prefix (old_str, str) != FALSE) {
-					rb_debug ("Got shorter duplicate tag");
-					g_free (str);
-					g_value_unset (newval);
-					g_slice_free (GValue, newval);
-					return;
-				}
-			}
-		}
-
-		rb_debug ("processed string tag \"%s\": \"%s\"", tag, str);
-
-		g_value_take_string (newval, str);
-		break;
-	}
-	default:
-		break;
-	}
-
-	switch (field) {
-	case RB_METADATA_FIELD_BITRATE: {
-		/* GStreamer sends us bitrate in bps, but we need it in kbps*/
-		gulong bitrate;
-		bitrate = g_value_get_ulong (newval);
-		g_value_set_ulong (newval, bitrate/1000);
-		rb_debug ("processed bitrate value: %lu", g_value_get_ulong (newval));
-		break;
-	}
-
-	case RB_METADATA_FIELD_DURATION: {
-		/* GStreamer sends us duration in ns,
-		 * but we need it in seconds
-		 */
-		guint64 duration;
-		duration = g_value_get_uint64 (val);
-		g_value_set_ulong (newval, duration/(1000*1000*1000));
-		rb_debug ("processed duration value: %lu", g_value_get_ulong (newval));
-		break;
-	}
-
-	default:
-		break;
-	}
-
-	g_hash_table_insert (md->priv->metadata,
-			     GINT_TO_POINTER (field),
-			     newval);
+	return md->priv->has_non_audio;
 }
 
-static void
-rb_metadata_gst_typefind_cb (GstElement *typefind, guint probability, GstCaps *caps, RBMetaData *md)
+gboolean
+rb_metadata_can_save (RBMetaData *md, const char *mediatype)
 {
-	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)));
-		rb_debug ("found type %s", md->priv->type);
-	}
-
-	g_signal_handler_disconnect (typefind, md->priv->typefind_cb_id);
-	md->priv->typefind_cb_id = 0;
+	return FALSE;
 }
 
-static void
-rb_metadata_gst_new_decoded_pad_cb (GstElement *decodebin, GstPad *pad, gboolean last, RBMetaData *md)
+char **
+rb_metadata_get_saveable_types (RBMetaData *md)
 {
-	GstCaps *caps;
-	GstStructure *structure;
-	const gchar *mimetype;
-	gboolean cancel = FALSE;
-
-	caps = gst_pad_get_caps (pad);
-
-	/* we get "ANY" caps for text/plain files etc. */
-	if (gst_caps_is_empty (caps) || gst_caps_is_any (caps)) {
-		rb_debug ("decoded pad with no caps or any caps.  this file is boring.");
-		md->priv->has_non_audio = TRUE;
-		cancel = TRUE;
-	} else {
-		GstPad *sink_pad;
-
-		sink_pad = gst_element_get_static_pad (md->priv->sink, "sink");
-		gst_pad_link (pad, sink_pad);
-		gst_object_unref (sink_pad);
-
-		/* is this pad audio? */
-		structure = gst_caps_get_structure (caps, 0);
-		mimetype = gst_structure_get_name (structure);
+	char **types;
+	GList *taggers;
+	GList *t;
+	int i;
 
-		if (g_str_has_prefix (mimetype, "audio/x-raw")) {
-			rb_debug ("got decoded audio pad of type %s", mimetype);
-			md->priv->has_audio = TRUE;
-		} else if (g_str_has_prefix (mimetype, "video/")) {
-			rb_debug ("got decoded video pad of type %s", mimetype);
-			md->priv->has_video = TRUE;
-		} else {
-			rb_debug ("got decoded pad of non-audio type %s", mimetype);
-			md->priv->has_non_audio = TRUE;
-		}
+	taggers = g_hash_table_get_keys (md->priv->taggers);
+	types = g_new0 (char *, g_list_length (taggers) + 1);
+	i = 0;
+	for (t = taggers; t != NULL; t = t->next) {
+		types[i++] = g_strdup (t->data);
 	}
 
-	gst_caps_unref (caps);
-
-	/* If this is non-audio, cancel the operation.
-	 * This seems to cause some deadlocks with video files, so only do it
-	 * when we get no/any caps.
-	 */
-	if (cancel)
-		gst_element_set_state (md->priv->pipeline, GST_STATE_NULL);
+	g_list_free (taggers);
+	return types;
 }
 
-static GstElement *make_pipeline_element (GstElement *pipeline, const char *element, GError **error)
+static gboolean
+check_gst_plugin_version (const char *plugin, const char *element, int major, int minor, int micro)
 {
-	GstElement *elem = gst_element_factory_make (element, element);
-	if (elem == NULL) {
-		g_set_error (error,
-			     RB_METADATA_ERROR,
-			     RB_METADATA_ERROR_MISSING_PLUGIN,
-			     _("Failed to create %s element; check your installation"),
-			     element);
-		return NULL;
-	}
+	const char *version;
+	GstPlugin *p;
+	guint i;
+	guint count;
 
-	gst_bin_add (GST_BIN (pipeline), elem);
-	return elem;
-}
+	if (gst_default_registry_check_feature_version (element, major, minor, micro + 1))
+		return TRUE;
 
-static void
-rb_metadata_handle_missing_plugin_message (RBMetaData *md, GstMessage *message)
-{
-	char *detail;
+	if (!gst_default_registry_check_feature_version (element, major, minor, micro))
+		return FALSE;
 
-	detail = gst_missing_plugin_message_get_installer_detail (message);
-	rb_debug ("got missing-plugin message from %s: %s",
-		  GST_OBJECT_NAME (GST_MESSAGE_SRC (message)),
-		  detail);
-	g_free (detail);
+	p = gst_default_registry_find_plugin (plugin);
+	if (p == NULL)
+		return FALSE;
 
-	md->priv->missing_plugins = g_slist_prepend (md->priv->missing_plugins, gst_message_ref (message));
+	version = gst_plugin_get_version (p);
 
-	/* update our information on what's in the stream based on
-	 * what we're missing.
-	 */
-	switch (rb_metadata_gst_get_missing_plugin_type (message)) {
-	case MEDIA_TYPE_NONE:
-		break;
-	case MEDIA_TYPE_CONTAINER:
-		/* hm, maybe we need a way to say 'we don't even know what's in here'.
-		 * but for now, the things we actually identify as containers are mostly
-		 * used for audio, so pretending they actually are is good enough.
-		 */
-	case MEDIA_TYPE_AUDIO:
-		md->priv->has_audio = TRUE;
-		break;
-	case MEDIA_TYPE_VIDEO:
-		md->priv->has_video = TRUE;
-		break;
-	case MEDIA_TYPE_OTHER:
-		md->priv->has_non_audio = TRUE;
-		break;
-	default:
-		g_assert_not_reached ();
-	}
+	/* check if it's not a release */	/* what */
+	count = sscanf (version, "%u.%u.%u.%u", &i, &i, &i, &i);
+	return (count > 3);
 }
 
 static gboolean
-rb_metadata_bus_handler (GstBus *bus, GstMessage *message, RBMetaData *md)
+link_named_pad (GstPad *srcpad, GstElement *element, const char *sinkpadname)
 {
-	switch (GST_MESSAGE_TYPE (message)) {
-	case GST_MESSAGE_EOS:
-		rb_debug ("EOS reached");
-		md->priv->eos = TRUE;
-		return TRUE;
-
-	case GST_MESSAGE_ERROR:
-	{
-		GError *gerror;
-		gchar *debug;
-		char *src;
-
-		src = gst_element_get_name (GST_MESSAGE_SRC (message));
-		rb_debug ("got error message from %s", src);
-		g_free (src);
-
-		gst_message_parse_error (message, &gerror, &debug);
-		if (gerror->domain == GST_STREAM_ERROR &&
-		    gerror->code == GST_STREAM_ERROR_TYPE_NOT_FOUND) {
-			rb_debug ("caught type not found error");
-		} else if (gerror->domain == GST_STREAM_ERROR &&
-			   gerror->code == GST_STREAM_ERROR_WRONG_TYPE &&
-			   md->priv->type != NULL &&
-			   strcmp (md->priv->type, "text/plain") == 0) {
-			rb_debug ("got WRONG_TYPE error for text/plain: setting non-audio flag");
-			md->priv->has_non_audio = TRUE;
-		} else if (md->priv->error) {
-			rb_debug ("caught error: %s, but we've already got one", gerror->message);
-		} else {
-			rb_debug ("caught error: %s ", gerror->message);
-
-			g_clear_error (&md->priv->error);
-			md->priv->error = g_error_new_literal (RB_METADATA_ERROR,
-							       RB_METADATA_ERROR_GENERAL,
-							       gerror->message);
-		}
+	GstPad *sinkpad;
+	GstPadLinkReturn result;
 
-		/* treat this as equivalent to EOS */
-		md->priv->eos = TRUE;
-
-		g_error_free (gerror);
-		g_free (debug);
-		return TRUE;
+	sinkpad = gst_element_get_static_pad (element, sinkpadname);
+	if (sinkpad == NULL) {
+		sinkpad = gst_element_get_request_pad (element, sinkpadname);
 	}
-	case GST_MESSAGE_TAG:
-	{
-		GstTagList *tags;
-
-		gst_message_parse_tag (message, &tags);
-		if (tags) {
-			gst_tag_list_foreach (tags, (GstTagForeachFunc) rb_metadata_gst_load_tag, md);
-			gst_tag_list_free (tags);
-		} else {
-			const gchar *errmsg = "Could not retrieve tag list";
-
-			rb_debug ("caught error: %s ", errmsg);
-			md->priv->error = g_error_new_literal (RB_METADATA_ERROR,
-							       RB_METADATA_ERROR_GENERAL,
-							       errmsg);
-		}
-		break;
-	}
-	case GST_MESSAGE_ELEMENT:
-	{
-		if (gst_is_missing_plugin_message (message)) {
-			rb_metadata_handle_missing_plugin_message (md, message);
-		}
-		break;
-	}
-	default:
-	{
-		char *src;
+	result = gst_pad_link (srcpad, sinkpad);
+	gst_object_unref (sinkpad);
 
-		src = gst_element_get_name (GST_MESSAGE_SRC (message));
-		rb_debug ("message of type %s from %s",
-			  GST_MESSAGE_TYPE_NAME (message), src);
-		g_free (src);
-		break;
-	}
+	if (GST_PAD_LINK_SUCCESSFUL (result)) {
+		return TRUE;
+	} else {
+		char *srcname = gst_pad_get_name (srcpad);
+		char *sinkname = gst_pad_get_name (sinkpad);
+		rb_debug ("couldn't link %s to %s: %d",
+			  srcname, sinkname, result);
+		return FALSE;
 	}
-
-	return FALSE;
 }
 
-static void
-rb_metadata_event_loop (RBMetaData *md, GstElement *element, gboolean block)
+static GstElement *
+flac_tagger (GstElement *pipeline, GstPad *srcpad, GstTagList *tags)
 {
-	GstBus *bus;
-	gboolean done = FALSE;
-
-	bus = gst_element_get_bus (element);
-	g_return_if_fail (bus != NULL);
-
-	while (!done && !md->priv->eos) {
-		GstMessage *message;
+	GstElement *tagger = NULL;
 
-		if (block)
-			message = gst_bus_timed_pop (bus, GST_CLOCK_TIME_NONE);
-		else
-			message = gst_bus_pop (bus);
+	tagger = gst_element_factory_make ("flactag", NULL);
+	if (tagger == NULL)
+		return NULL;
 
-		if (message == NULL) {
-			gst_object_unref (bus);
-			return;
-		}
+	gst_bin_add (GST_BIN (pipeline), tagger);
+	if (!link_named_pad (srcpad, tagger, "sink"))
+		goto error;
 
-		done = rb_metadata_bus_handler (bus, message, md);
-		gst_message_unref (message);
+	gst_element_set_state (tagger, GST_STATE_PAUSED);
+	if (tags != NULL) {
+		gst_tag_setter_merge_tags (GST_TAG_SETTER (tagger), tags, GST_TAG_MERGE_REPLACE_ALL);
 	}
-	gst_object_unref (bus);
+	return tagger;
+
+error:
+	gst_object_unref (tagger);
+	return NULL;
 }
 
-void
-rb_metadata_load (RBMetaData *md,
-		  const char *uri,
-		  GError **error)
+static GstElement *
+mp3_tagger (GstElement *pipeline, GstPad *srcpad, GstTagList *tags)
 {
-	GstElement *pipeline = NULL;
-	GstElement *urisrc = NULL;
-	GstElement *decodebin = NULL;
-	GstElement *typefind = NULL;
-	gint64 file_size = -1;
-	GstFormat file_size_format = GST_FORMAT_BYTES;
-	GstStateChangeReturn state_ret;
-	int change_timeout;
-	GstBus *bus;
+	GstElement *mux = NULL;
 
-	rb_metadata_reset (md);
-	if (uri == NULL)
-		return;
+	/* try id3mux first, since it's more supported and writes id3v2.3 tags rather than v2.4.  */
+	if (check_gst_plugin_version ("id3mux", "id3mux", 0, 10, 12)) {
+		mux = gst_element_factory_make ("id3mux", NULL);
+	}
 
-	rb_debug ("loading metadata for uri: %s", uri);
+	if (mux == NULL)
+		mux = gst_element_factory_make ("id3v2mux", NULL);
 
-	/* The main tagfinding pipeline looks like this:
- 	 * <src> ! decodebin ! fakesink
- 	 *
- 	 * but we can only link the fakesink in when the decodebin
- 	 * creates an audio source pad.  we do this in the 'new-decoded-pad'
- 	 * signal handler.
- 	 */
-	pipeline = gst_pipeline_new ("pipeline");
+	if (mux == NULL)
+		goto error;
 
-	urisrc = gst_element_make_from_uri (GST_URI_SRC, uri, "urisrc");
-	if (urisrc == NULL) {
-		g_set_error (error,
-			     RB_METADATA_ERROR,
-			     RB_METADATA_ERROR_MISSING_PLUGIN,
-			     _("Failed to create a source element; check your installation"));
-		rb_debug ("missing an element to load the uri, sadly");
-		goto out;
+	gst_bin_add (GST_BIN (pipeline), mux);
+	if (!link_named_pad (srcpad, mux, "sink")) {
+		rb_debug ("couldn't link decoded pad to id3 muxer");
+		goto error;
 	}
-	gst_bin_add (GST_BIN (pipeline), urisrc);
-
- 	decodebin = make_pipeline_element (pipeline, "decodebin", error);
- 	md->priv->sink = make_pipeline_element (pipeline, "fakesink", error);
- 	if (!(urisrc && decodebin && md->priv->sink)) {
- 		rb_debug ("missing an element, sadly");
- 		goto out;
- 	}
-
- 	g_signal_connect_object (decodebin, "new-decoded-pad", G_CALLBACK (rb_metadata_gst_new_decoded_pad_cb), md, 0);
-
- 	/* locate the decodebin's typefind, so we can get the have_type signal too.
- 	 * this is kind of nasty, since it relies on an essentially arbitrary string
- 	 * in the decodebin code not changing.  the alternative is to have our own
- 	 * typefind instance before the decodebin.  it might not like that.
- 	 */
- 	typefind = gst_bin_get_by_name (GST_BIN (decodebin), "typefind");
- 	g_assert (typefind != NULL);
-	md->priv->typefind_cb_id = g_signal_connect_object (typefind,
-							    "have_type",
-							    G_CALLBACK (rb_metadata_gst_typefind_cb),
-							    md,
-							    0);
-	gst_object_unref (GST_OBJECT (typefind));
-
- 	gst_element_link (urisrc, decodebin);
 
-	md->priv->pipeline = pipeline;
-	rb_debug ("going to PAUSED for metadata, uri: %s", uri);
-	state_ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
-	bus = gst_element_get_bus (GST_ELEMENT (pipeline));
-	change_timeout = 0;
-	while (state_ret == GST_STATE_CHANGE_ASYNC &&
-	       !md->priv->eos &&
-	       change_timeout < 5) {
-		GstMessage *msg;
-
-		msg = gst_bus_timed_pop (bus, 1 * GST_SECOND);
-		if (msg) {
-			rb_metadata_bus_handler (bus, msg, md);
-			gst_message_unref (msg);
-			change_timeout = 0;
-		} else {
-			change_timeout++;
-		}
-
-		state_ret = gst_element_get_state (pipeline, NULL, NULL, 0);
+	gst_element_set_state (mux, GST_STATE_PAUSED);
+	if (tags != NULL) {
+		gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), tags, GST_TAG_MERGE_REPLACE_ALL);
 	}
-	gst_object_unref (GST_OBJECT (bus));
-
-	rb_metadata_event_loop (md, GST_ELEMENT (pipeline), FALSE);
-
-	if (state_ret != GST_STATE_CHANGE_SUCCESS) {
-		rb_debug ("failed to go to PAUSED for %s", uri);
-		if (!md->priv->has_non_audio && md->priv->error == NULL)
-			g_set_error (error,
-				     RB_METADATA_ERROR,
-				     RB_METADATA_ERROR_INTERNAL,
-				     _("GStreamer error: failed to change state"));
-	} else
-		rb_debug ("gone to PAUSED for %s", uri);
-
-	if (state_ret == GST_STATE_CHANGE_SUCCESS) {
+	rb_debug ("id3 tagger created");
+	return mux;
 
-		/* The pipeline went to PAUSED,
-		 * which means the decoder should have read all
-		 * of the metadata, and should know the length now.
-		 */
-		if (g_hash_table_lookup (md->priv->metadata, GINT_TO_POINTER (RB_METADATA_FIELD_DURATION)) == NULL) {
-			GstFormat format = GST_FORMAT_TIME;
-			gint64 length;
-			GValue *newval;
-
-			if (gst_element_query_duration (md->priv->sink, &format, &length)) {
-				g_assert (format == GST_FORMAT_TIME);
-				newval = g_slice_new0 (GValue);
-
-				rb_debug ("duration query succeeded");
-
-				g_value_init (newval, G_TYPE_ULONG);
-				/* FIXME - use guint64 for duration? */
-				g_value_set_ulong (newval, (long) (length / GST_SECOND));
-				g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (RB_METADATA_FIELD_DURATION),
-						     newval);
-			} else {
-				rb_debug ("duration query failed!");
-			}
-		}
+error:
+	if (mux != NULL) {
+		g_object_unref (mux);
 	}
+	return NULL;
+}
 
-	/* Get the file size, since it might be interesting, and return
-	 * the pipeline to NULL state.
-	 */
-	if (gst_element_query_duration (urisrc, &file_size_format, &file_size))
-		g_assert (file_size_format == GST_FORMAT_BYTES);
+static GstElement *
+vorbis_tagger (GstElement *pipeline, GstPad *srcpad, GstTagList *tags)
+{
+	GstElement *mux;
+	GstElement *tagger;
+	GstElement *parser;
 
-	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");
-	}
+	mux = gst_element_factory_make ("oggmux", NULL);
+	parser = gst_element_factory_make ("vorbisparse", NULL);
+	tagger = gst_element_factory_make ("vorbistag", NULL);
+	if (mux == NULL || parser == NULL || tagger == NULL)
+		goto error;
 
-	if (md->priv->error != NULL) {
-		g_propagate_error (error, md->priv->error);
-		md->priv->error = NULL;
-	} else if (!md->priv->type) {
-		g_clear_error (error);
-		g_set_error (error,
-			     RB_METADATA_ERROR,
-			     RB_METADATA_ERROR_UNRECOGNIZED,
-			     _("The MIME type of the file could not be identified"));
-	} else {
-		/* yay, it worked */
-		rb_debug ("successfully read metadata for %s", uri);
+	gst_bin_add_many (GST_BIN (pipeline), parser, tagger, mux, NULL);
+	if (!link_named_pad (srcpad, parser, "sink"))
+		goto error;
+	if (!gst_element_link_many (parser, tagger, mux, NULL))
+		goto error;
+
+	gst_element_set_state (parser, GST_STATE_PAUSED);
+	gst_element_set_state (tagger, GST_STATE_PAUSED);
+	gst_element_set_state (mux, GST_STATE_PAUSED);
+	if (tags != NULL) {
+		gst_tag_setter_merge_tags (GST_TAG_SETTER (tagger), tags, GST_TAG_MERGE_REPLACE_ALL);
 	}
+	return mux;
 
- out:
-	if (pipeline != NULL)
-		gst_object_unref (GST_OBJECT (pipeline));
-	md->priv->pipeline = NULL;
+error:
+	if (parser != NULL)
+		g_object_unref (parser);
+	if (tagger != NULL)
+		g_object_unref (tagger);
+	if (mux != NULL)
+		g_object_unref (mux);
+	return NULL;
 }
 
-gboolean
-rb_metadata_can_save (RBMetaData *md, const char *mimetype)
+static GstElement *
+mp4_tagger (GstElement *pipeline, GstPad *srcpad, GstTagList *tags)
 {
-	return g_hash_table_lookup (md->priv->taggers, mimetype) != NULL;
+	GstElement *mux;
+
+	mux = gst_element_factory_make ("mp4mux", NULL);
+	if (mux == NULL)
+		return NULL;
+
+	gst_bin_add (GST_BIN (pipeline), mux);
+	if (!link_named_pad (srcpad, mux, "audio_%d"))
+		goto error;
+
+	gst_element_set_state (mux, GST_STATE_PAUSED);
+	if (tags != NULL) {
+		gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), tags, GST_TAG_MERGE_REPLACE_ALL);
+	}
+	return mux;
+
+error:
+	g_object_unref (mux);
+	return NULL;
 }
 
-char **
-rb_metadata_get_saveable_types (RBMetaData *md)
+static void
+metadata_save_new_decoded_pad_cb (GstElement *decodebin, GstPad *pad, gboolean last, RBMetaData *md)
 {
+	RBAddTaggerElem add_tagger_func = NULL;
+	GstElement *retag_end;
+	GstCaps *caps;
+	char *caps_str;
 	GHashTableIter iter;
 	gpointer key;
 	gpointer value;
-	char **types;
-	int i;
 
-	types = g_new0 (char *, g_hash_table_size (md->priv->taggers) + 1);
-	i = 0;
+	if (md->priv->sink_linked) {
+		GError *error;
+		error = g_error_new (GST_STREAM_ERROR,
+				     GST_STREAM_ERROR_FORMAT,
+				     _("Unable to write tags to this file as it contains multiple streams"));
+		gst_element_post_message (decodebin,
+					  gst_message_new_error (GST_OBJECT (decodebin),
+								 error,
+								 NULL));
+		g_error_free (error);
+		return;
+	}
+
+	/* find a tagger function that accepts the caps */
+	caps = gst_pad_get_caps (pad);
+	caps_str = gst_caps_to_string (caps);
+	rb_debug ("finding tagger for src caps %s", caps_str);
+	g_free (caps_str);
+
 	g_hash_table_iter_init (&iter, md->priv->taggers);
 	while (g_hash_table_iter_next (&iter, &key, &value)) {
-		types[i++] = g_strdup ((const char *) key);
+		GstCaps *tagger_caps;
+		const char *media_type = (const char *)key;
+
+		tagger_caps = rb_gst_media_type_to_caps (media_type);
+		/* not sure this is right, really */
+		if (gst_caps_is_always_compatible (caps, tagger_caps)) {
+			caps_str = gst_caps_to_string (tagger_caps);
+			rb_debug ("matched sink caps %s", caps_str);
+			g_free (caps_str);
+
+			gst_caps_unref (tagger_caps);
+			add_tagger_func = (RBAddTaggerElem)value;
+			break;
+		}
+		gst_caps_unref (tagger_caps);
+	}
+	gst_caps_unref (caps);
+
+	/* add retagging element(s) */
+	if (add_tagger_func == NULL) {
+		GError *error;
+		error = g_error_new (GST_STREAM_ERROR,
+				     GST_STREAM_ERROR_FORMAT,
+				     _("Unable to write tags to this file as it is not encoded in a supported format"));
+		gst_element_post_message (decodebin,
+					  gst_message_new_error (GST_OBJECT (decodebin),
+								 error,
+								 NULL));
+		g_error_free (error);
+		return;
 	}
+	retag_end = add_tagger_func (md->priv->pipeline, pad, md->priv->tags);
 
-	return types;
+	/* link to the sink */
+	gst_element_link (retag_end, md->priv->sink);
+	md->priv->sink_linked = TRUE;
 }
 
-static void
-rb_metadata_gst_add_tag_data (gpointer key, const GValue *val, RBMetaData *md)
+static gboolean
+factory_src_caps_intersect (GstElementFactory *factory, GstCaps *caps)
 {
-	RBMetaDataField field = GPOINTER_TO_INT (key);
-	const char *tag = rb_metadata_gst_field_to_gst_tag (field);
-
-	/* don't write this out */
-	if (field == RB_METADATA_FIELD_DURATION)
-		return;
+	const GList *templates;
+	const GList *l;
 
-	if (tag) {
-		if (field == RB_METADATA_FIELD_DATE && g_value_get_ulong (val) == 0) {
-			/* we should ask gstreamer to remove the tag,
-			 * but there is no easy way of doing so
-			 */
-		} else {
-			GValue newval = {0,};
+	templates = gst_element_factory_get_static_pad_templates (factory);
+	for (l = templates; l != NULL; l = l->next) {
+		GstStaticPadTemplate *t = l->data;
+		GstCaps *tcaps;
 
-			g_value_init (&newval, gst_tag_get_type (tag));
-			if (g_value_transform (val, &newval)) {
-				rb_debug("Setting %s",tag);
+		if (t->direction != GST_PAD_SRC) {
+			continue;
+		}
 
-				gst_tag_list_add_values (md->priv->tags,
-							 GST_TAG_MERGE_APPEND,
-							 tag, &newval,
-							 NULL);
-			}
-			g_value_unset (&newval);
+		tcaps = gst_static_pad_template_get_caps (t);
+		if (gst_caps_can_intersect (tcaps, caps)) {
+			gst_caps_unref (tcaps);
+			return TRUE;
 		}
+		gst_caps_unref (tcaps);
+	}
+	return FALSE;
+}
+
+static GstAutoplugSelectResult
+metadata_save_autoplug_select_cb (GstElement *decodebin, GstPad *pad, GstCaps *caps, GstElementFactory *factory, RBMetaData *md)
+{
+	GstCaps *src_caps;
+	gboolean is_any;
+	gboolean is_raw;
+	gboolean is_demuxer;
+
+	is_demuxer = (strstr (gst_element_factory_get_klass (factory), "Demuxer") != NULL);
+	if (is_demuxer) {
+		/* allow demuxers, since we're going to remux later */
+		return GST_AUTOPLUG_SELECT_TRY;
+	}
+
+	src_caps = gst_caps_new_any ();
+	is_any = gst_element_factory_can_src_caps (factory, src_caps);
+	gst_caps_unref (src_caps);
+	if (is_any) {
+		/* this is something like id3demux (though that will match the
+		 * above check), allow it so we can get to the actual decoder later
+		 */
+		return GST_AUTOPLUG_SELECT_TRY;
 	}
+
+	src_caps = gst_caps_from_string ("audio/x-raw-int; audio/x-raw-float; video/x-raw-yuv; video/x-raw-rgb; video/x-raw-gray");
+	is_raw = factory_src_caps_intersect (factory, src_caps);
+	gst_caps_unref (src_caps);
+
+	if (is_raw == FALSE) {
+		/* this is probably a parser or something, allow it */
+		return GST_AUTOPLUG_SELECT_TRY;
+	}
+
+	/* don't allow decoders */
+	return GST_AUTOPLUG_SELECT_EXPOSE;
 }
 
 static gboolean
-rb_metadata_file_valid (const char *original, const char *newfile)
+check_file_valid (const char *original, const char *newfile)
 {
 	RBMetaData *md = rb_metadata_new ();
 	GError *error = NULL;
@@ -974,76 +578,26 @@ rb_metadata_file_valid (const char *original, const char *newfile)
 
 	if (error != NULL)
 		g_error_free (error);
-	g_object_unref (G_OBJECT (md));
+	g_object_unref (md);
 	return ret;
 }
 
-static void
-metadata_save_type_found (GstElement *typefind, guint probability, GstCaps *caps, RBMetaData *md)
-{
-	RBAddTaggerElem add_tagger_func;
-        GstElement *retag_end;
-	GstMessage *message;
-	const char *type;
-	GError *error = NULL;
-
-	g_signal_handler_disconnect (typefind, md->priv->typefind_cb_id);
-	md->priv->typefind_cb_id = 0;
-
-	if (gst_caps_is_empty (caps) || gst_caps_is_any (caps)) {
-		rb_debug ("got empty/no caps from typefind");
-		g_set_error (&error,
-			     RB_METADATA_ERROR,
-			     RB_METADATA_ERROR_UNSUPPORTED,
-			     _("Unable to identify file type"));
-		goto out_error;
-	}
-
-	type = gst_structure_get_name (gst_caps_get_structure (caps, 0));
-
-	/* Tagger element(s) */
-	add_tagger_func = g_hash_table_lookup (md->priv->taggers, type);
-	if (!add_tagger_func) {
-		rb_debug ("found unsupported type %s", type);
-		g_set_error (&error,
-			     RB_METADATA_ERROR,
-			     RB_METADATA_ERROR_UNSUPPORTED,
-			     _("Unsupported file type: %s"), type);
-		goto out_error;
-	}
-
-	rb_debug ("adding tagger for type %s", type);
-	retag_end = add_tagger_func (md->priv->pipeline, typefind, md->priv->tags);
-	if (!retag_end) {
-		g_set_error (&error,
-			     RB_METADATA_ERROR,
-			     RB_METADATA_ERROR_UNSUPPORTED,
-			     _("Unable to create tag-writing elements"));
-		goto out_error;
-	}
-
-	gst_element_link_many (retag_end, md->priv->sink, NULL);
-	return;
-
-out_error:
-	message = gst_message_new_error (GST_OBJECT (typefind), error, NULL);
-	gst_element_post_message (typefind, message);
-	g_clear_error (&error);
-}
 
 void
 rb_metadata_save (RBMetaData *md, const char *uri, GError **error)
 {
 	GstElement *pipeline = NULL;
-	GstElement *source = NULL;
-	GstElement *typefind = NULL;
-	const char *plugin_name = NULL;
+	GstElement *urisrc = NULL;
+	GstElement *decodebin = NULL;
 	char *tmpname_prefix = NULL;
 	char *tmpname = NULL;
 	GOutputStream *stream = NULL;
 	GError *io_error = NULL;
+	GstBus *bus;
+	gboolean done;
+	GFile *src;
+	GFile *dest;
 
-	g_return_if_fail (uri != NULL);
 	rb_debug ("saving metadata for uri: %s", uri);
 
 	tmpname_prefix = rb_uri_make_hidden (uri);
@@ -1055,74 +609,113 @@ rb_metadata_save (RBMetaData *md, const char *uri, GError **error)
 		goto gio_error;
 	}
 
+	/* set up pipeline */
 	pipeline = gst_pipeline_new ("pipeline");
 	md->priv->pipeline = pipeline;
+	md->priv->sink_linked = FALSE;
+
+	urisrc = gst_element_make_from_uri (GST_URI_SRC, uri, "urisrc");
+	if (urisrc == NULL) {
+		g_set_error (error,
+			     RB_METADATA_ERROR,
+			     RB_METADATA_ERROR_MISSING_PLUGIN,
+			     _("Failed to create a source element; check your installation"));
+		rb_debug ("missing an element to load the uri, sadly");
+		goto out;
+	}
 
-	/* Source */
-	source = gst_element_make_from_uri (GST_URI_SRC, uri, "urisrc");
-	if (source == NULL) {
-		plugin_name = "urisrc";
-		goto missing_plugin;
+	decodebin = gst_element_factory_make ("decodebin2", "decoder");
+	if (decodebin == NULL) {
+		g_set_error (error,
+			     RB_METADATA_ERROR,
+			     RB_METADATA_ERROR_MISSING_PLUGIN,
+			     _("Failed to create the 'decodebin2' element; check your GStreamer installation"));
+		goto out;
 	}
-	gst_bin_add (GST_BIN (pipeline), source);
-
-	/* typefind */
-	plugin_name = "typefind";
-	typefind = gst_element_factory_make (plugin_name, NULL);
-	if (typefind == NULL)
-		goto missing_plugin;
-	gst_bin_add (GST_BIN (pipeline), typefind);
-	md->priv->typefind_cb_id = g_signal_connect_object (typefind,
-							    "have_type",
-							    G_CALLBACK (metadata_save_type_found),
-							    md,
-							    0);
-
-	/* Sink */
-	plugin_name = "giostreamsink";
-	if (!(md->priv->sink = gst_element_factory_make (plugin_name, NULL)))
-		goto missing_plugin;
 
+	md->priv->sink = gst_element_factory_make ("giostreamsink", "sink");
+	if (md->priv->sink == NULL) {
+		g_set_error (error,
+			     RB_METADATA_ERROR,
+			     RB_METADATA_ERROR_MISSING_PLUGIN,
+			     _("Failed to create the 'giostreamsink' element; check your GStreamer installation"));
+		goto out;
+	}
 	g_object_set (md->priv->sink, "stream", stream, NULL);
 
-	md->priv->tags = gst_tag_list_new ();
-	g_hash_table_foreach (md->priv->metadata,
-			      (GHFunc) rb_metadata_gst_add_tag_data,
-			      md);
+	gst_bin_add_many (GST_BIN (pipeline), urisrc, decodebin, md->priv->sink, NULL);
+	gst_element_link (urisrc, decodebin);
 
-	gst_bin_add (GST_BIN (pipeline), md->priv->sink);
-	gst_element_link (source, typefind);
+	g_signal_connect_object (decodebin,
+				 "new-decoded-pad",
+				 G_CALLBACK (metadata_save_new_decoded_pad_cb),
+				 md, 0);
+	g_signal_connect_object (decodebin,
+				 "autoplug-select",
+				 G_CALLBACK (metadata_save_autoplug_select_cb),
+				 md, 0);
 
+	/* run pipeline .. */
 	gst_element_set_state (pipeline, GST_STATE_PLAYING);
+	bus = gst_element_get_bus (pipeline);
+	done = FALSE;
+	while (done == FALSE) {
+		GstMessage *message;
 
-	rb_metadata_event_loop (md, GST_ELEMENT (pipeline), TRUE);
-	if (gst_element_set_state (pipeline, GST_STATE_NULL) == GST_STATE_CHANGE_ASYNC) {
-		if (gst_element_get_state (pipeline, NULL, NULL, 3 * GST_SECOND) != GST_STATE_CHANGE_SUCCESS) {
-			g_set_error (error,
-				     RB_METADATA_ERROR,
-				     RB_METADATA_ERROR_INTERNAL,
-				     _("Timeout while setting pipeline to NULL"));
-			goto out_error;
+		message = gst_bus_timed_pop (bus, GST_CLOCK_TIME_NONE);
+		if (message == NULL) {
+			/* can this even happen? */
+			rb_debug ("breaking out of bus polling loop");
+			break;
 		}
-	}
 
-	if (md->priv->error) {
-		g_propagate_error (error, md->priv->error);
-		md->priv->error = NULL;
-		goto out_error;
-	}
-	if (stream != NULL) {
-		GFile *src;
-		GFile *dest;
+		switch (GST_MESSAGE_TYPE (message)) {
+		case GST_MESSAGE_ERROR:
+			{
+				GError *gerror;
+				char *debug;
+
+				gst_message_parse_error (message, &gerror, &debug);
+				if (*error != NULL) {
+					rb_debug ("caught error: %s (%s), but we've already got one", gerror->message, debug);
+				} else {
+					rb_debug ("caught error: %s (%s)", gerror->message, debug);
+
+					g_clear_error (error);
+					*error = g_error_new_literal (RB_METADATA_ERROR,
+								      RB_METADATA_ERROR_GENERAL,
+								      gerror->message);
+				}
+				done = TRUE;
 
-		if (g_output_stream_close (stream, NULL, &io_error) == FALSE) {
-			goto gio_error;
+				g_error_free (gerror);
+				g_free (debug);
+			}
+			break;
+
+		case GST_MESSAGE_EOS:
+			rb_debug ("got eos message");
+			done = TRUE;
+			break;
+
+		default:
+			break;
 		}
-		g_object_unref (stream);
-		stream = NULL;
 
+		gst_message_unref (message);
+	}
+	gst_object_unref (bus);
+	gst_element_set_state (pipeline, GST_STATE_NULL);
+
+	if (g_output_stream_close (stream, NULL, &io_error) == FALSE) {
+		goto gio_error;
+	}
+	g_object_unref (stream);
+	stream = NULL;
+
+	if (*error == NULL) {
 		/* check to ensure the file isn't corrupt */
-		if (!rb_metadata_file_valid (uri, tmpname)) {
+		if (!check_file_valid (uri, tmpname)) {
 			g_set_error (error,
 				     RB_METADATA_ERROR,
 				     RB_METADATA_ERROR_INTERNAL,
@@ -1132,154 +725,224 @@ rb_metadata_save (RBMetaData *md, const char *uri, GError **error)
 
 		src = g_file_new_for_uri (tmpname);
 		dest = g_file_new_for_uri (uri);
+
+		/* try to copy attributes over, not likely to help much though */
+		g_file_copy_attributes (dest, src, G_FILE_COPY_ALL_METADATA, NULL, NULL);
+
 		g_file_move (src, dest, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &io_error);
 		if (io_error != NULL) {
 			goto gio_error;
 		}
+		goto out;
 	}
 
-	goto out;
 gio_error:
-	g_set_error (error,
-		     RB_METADATA_ERROR,
-		     RB_METADATA_ERROR_IO,
-		     "%s",
-		     io_error->message);
-	goto out_error;
-missing_plugin:
-	g_set_error (error,
-		     RB_METADATA_ERROR,
-		     RB_METADATA_ERROR_MISSING_PLUGIN,
-		     _("Failed to create %s element; check your installation"),
-		     plugin_name);
-out_error:
-	if (stream != NULL) {
-		g_output_stream_close (stream, NULL, NULL);
-		g_object_unref (stream);
+	if (*error == NULL) {
+		g_set_error (error,
+			     RB_METADATA_ERROR,
+			     RB_METADATA_ERROR_IO,
+			     "%s",
+			     io_error->message);
 	}
-
+out_error:
 	if (tmpname != NULL) {
-		GFile *del;
-		del = g_file_new_for_uri (tmpname);
-		g_file_delete (del, NULL, NULL);
-		g_object_unref (del);
+		/* clean up temporary file */
+		rb_debug ("removing temporary file %s", tmpname);
+		dest = g_file_new_for_uri (tmpname);
+		if (g_file_delete (dest, NULL, NULL) == FALSE) {
+			rb_debug ("unable to remove temporary file?");
+		}
+		g_object_unref (dest);
 	}
-
 out:
-	if (md->priv->tags)
-		gst_tag_list_free (md->priv->tags);
-	md->priv->tags = NULL;
-
+	g_free (tmpname);
 	if (pipeline != NULL)
 		gst_object_unref (GST_OBJECT (pipeline));
-	md->priv->pipeline = NULL;
 }
 
 gboolean
-rb_metadata_get (RBMetaData *md, RBMetaDataField field,
-		 GValue *ret)
+rb_metadata_get (RBMetaData *md, RBMetaDataField field, GValue *ret)
 {
-	GValue *val;
-	if ((val = g_hash_table_lookup (md->priv->metadata,
-					GINT_TO_POINTER (field)))) {
-		g_value_init (ret, G_VALUE_TYPE (val));
-		g_value_copy (val, ret);
+	const GstTagList *tags;
+	const char *tag;
+	GValue gstvalue = {0, };
+	GstClockTime duration;
+
+	/* special cases: mostly duration */
+	switch (field) {
+	case RB_METADATA_FIELD_DURATION:
+		duration = gst_discoverer_info_get_duration (md->priv->info);
+		if (duration != 0) {
+			g_value_init (ret, G_TYPE_ULONG);
+			g_value_set_ulong (ret, duration / (1000 * 1000 * 1000));
+			return TRUE;
+		} else {
+			rb_debug ("no duration available");
+			return FALSE;
+		}
+		break;
+	case RB_METADATA_FIELD_BITRATE:
+		if (md->priv->audio_bitrate != 0) {
+			g_value_init (ret, G_TYPE_ULONG);
+			g_value_set_ulong (ret, md->priv->audio_bitrate / 1000);
+			return TRUE;
+		} else {
+			return FALSE;
+		}
+	default:
+		break;
+	}
+
+	tags = gst_discoverer_info_get_tags (md->priv->info);
+	if (tags == NULL) {
+		return FALSE;
+	}
+
+	tag = rb_metadata_gst_field_to_gst_tag (field);
+	if (tag == NULL) {
+		return FALSE;
+	}
+
+
+	if (rb_metadata_get_field_type (field) == G_TYPE_STRING) {
+		char *str = NULL;
+		const char *v;
+		int i;
+
+		/* pick the first valid utf8 string, or if we find a later
+		 * string of which the first is a prefix, pick that.
+		 */
+		i = 0;
+		while (gst_tag_list_peek_string_index (tags, tag, i, &v)) {
+			if (g_utf8_validate (v, -1, NULL) && (str == NULL || g_str_has_prefix (v, str))) {
+				g_free (str);
+				str = g_strdup (v);
+			} else {
+				rb_debug ("ignoring %s", v);
+			}
+			i++;
+		}
+
+		if (str != NULL) {
+			str = g_strstrip (str);
+			g_value_init (ret, G_TYPE_STRING);
+			g_value_take_string (ret, str);
+			return TRUE;
+		} else {
+			return FALSE;
+		}
+	} else {
+		if (gst_tag_list_copy_value (&gstvalue, tags, tag) == FALSE) {
+			return FALSE;
+		}
+		g_value_init (ret, rb_metadata_get_field_type (field));
+		g_value_transform (&gstvalue, ret);
+		g_value_unset (&gstvalue);
 		return TRUE;
 	}
-	return FALSE;
 }
 
 const char *
-rb_metadata_get_mime (RBMetaData *md)
+rb_metadata_get_media_type (RBMetaData *md)
 {
-	return md->priv->type;
+	return md->priv->mediatype;
 }
 
 gboolean
-rb_metadata_set (RBMetaData *md, RBMetaDataField field,
-		 const GValue *val)
+rb_metadata_set (RBMetaData *md, RBMetaDataField field, const GValue *val)
 {
-	GValue *newval;
-	GType type;
+	/* don't write this out */
+	if (field == RB_METADATA_FIELD_DURATION)
+		return TRUE;
 
-	if (rb_metadata_gst_field_to_gst_tag (field) == NULL) {
-		return FALSE;
-	}
+	if (field == RB_METADATA_FIELD_DATE && g_value_get_ulong (val) == 0) {
+		/* we should ask gstreamer to remove the tag,
+		 * but there is no easy way of doing so
+		 */
+	} else {
+		const char *tag;
+		GValue newval = {0,};
+
+		tag = rb_metadata_gst_field_to_gst_tag (field);
+		g_value_init (&newval, gst_tag_get_type (tag));
+		if (g_value_transform (val, &newval)) {
+			rb_debug ("Setting %s",tag);
 
-	type = rb_metadata_get_field_type (field);
-	g_return_val_if_fail (type == G_VALUE_TYPE (val), FALSE);
+			if (md->priv->tags == NULL) {
+				md->priv->tags = gst_tag_list_new ();
+			}
 
-	newval = g_slice_new0 (GValue);
-	g_value_init (newval, type);
-	g_value_copy (val, newval);
+			gst_tag_list_add_values (md->priv->tags,
+						 GST_TAG_MERGE_APPEND,
+						 tag, &newval,
+						 NULL);
+		}
+		g_value_unset (&newval);
+	}
 
-	g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (field),
-			     newval);
 	return TRUE;
 }
 
-gboolean
-rb_metadata_has_missing_plugins (RBMetaData *md)
+static void
+rb_metadata_finalize (GObject *object)
 {
-	return (g_slist_length (md->priv->missing_plugins) > 0);
+	RBMetaData *md;
+	md = RB_METADATA (object);
+	rb_metadata_reset (md);
+
+	G_OBJECT_CLASS (rb_metadata_parent_class)->finalize (object);
 }
 
-gboolean
-rb_metadata_get_missing_plugins (RBMetaData *md,
-				 char ***missing_plugins,
-				 char ***plugin_descriptions)
+static void
+rb_metadata_init (RBMetaData *md)
 {
-	char **mp;
-	char **pd;
-	int count;
-	int i;
-	GSList *t;
+	md->priv = (G_TYPE_INSTANCE_GET_PRIVATE ((md), RB_TYPE_METADATA, RBMetaDataPrivate));
 
-	count = g_slist_length (md->priv->missing_plugins);
-	if (count == 0) {
-		return FALSE;
-	}
+	md->priv->taggers = g_hash_table_new (g_str_hash, g_str_equal);
 
-	mp = g_new0 (char *, count + 1);
-	pd = g_new0 (char *, count + 1);
-	i = 0;
-	for (t = md->priv->missing_plugins; t != NULL; t = t->next) {
-		GstMessage *msg = GST_MESSAGE (t->data);
-		char *detail;
-		char *description;
-
-		detail = gst_missing_plugin_message_get_installer_detail (msg);
-		description = gst_missing_plugin_message_get_description (msg);
-		rb_debug ("adding [%s,%s] to return data", detail, description);
-		mp[i] = g_strdup (detail);
-		pd[i] = g_strdup (description);
-		i++;
-
-		gst_message_unref (msg);
+	if (gst_element_factory_find ("giostreamsink") == FALSE) {
+		rb_debug ("giostreamsink not found, can't tag anything");
+	} else {
+		if (gst_element_factory_find ("vorbistag") &&
+		    gst_element_factory_find ("vorbisparse") &&
+		    gst_element_factory_find ("oggmux")) {
+			rb_debug ("ogg vorbis tagging available");
+			g_hash_table_insert (md->priv->taggers, "audio/x-vorbis", (gpointer)vorbis_tagger);
+		}
+
+		if (gst_element_factory_find ("flactag")) {
+			rb_debug ("flac tagging available");
+			g_hash_table_insert (md->priv->taggers, "audio/x-flac", flac_tagger);
+		}
+
+		/* id3mux > 0.10.12 is the new one, not the broken old one. */
+		if (gst_element_factory_find ("id3v2mux") || check_gst_plugin_version ("id3mux", "id3mux", 0, 10, 12)) {
+			rb_debug ("id3 tagging available");
+			g_hash_table_insert (md->priv->taggers, "audio/mpeg", mp3_tagger);
+		}
+
+		if (gst_element_factory_find ("mp4mux")) {
+			rb_debug ("mp4 tagging available");
+			g_hash_table_insert (md->priv->taggers, "audio/x-aac", mp4_tagger);
+		}
 	}
-	g_slist_free (md->priv->missing_plugins);
-	md->priv->missing_plugins = NULL;
 
-	*missing_plugins = mp;
-	*plugin_descriptions = pd;
-	return TRUE;
+	md->priv->jpeg_image_caps = gst_caps_from_string ("image/jpeg, framerate = (fraction) [ 0, 1/1 ]");
 }
 
-gboolean
-rb_metadata_has_audio (RBMetaData *md)
+static void
+rb_metadata_class_init (RBMetaDataClass *klass)
 {
-	return md->priv->has_audio;
-}
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-gboolean
-rb_metadata_has_video (RBMetaData *md)
-{
-	return md->priv->has_video;
+	object_class->finalize = rb_metadata_finalize;
+
+	g_type_class_add_private (klass, sizeof (RBMetaDataPrivate));
+	rb_metadata_gst_register_transforms ();
 }
 
-gboolean
-rb_metadata_has_other_data (RBMetaData *md)
+RBMetaData *
+rb_metadata_new (void)
 {
-	return md->priv->has_non_audio;		/* kinda */
+	return RB_METADATA (g_object_new (RB_TYPE_METADATA, NULL, NULL));
 }
-
diff --git a/metadata/rb-metadata.h b/metadata/rb-metadata.h
index 3b3c84d..8d57cae 100644
--- a/metadata/rb-metadata.h
+++ b/metadata/rb-metadata.h
@@ -126,7 +126,7 @@ const char *	rb_metadata_get_field_name (RBMetaDataField field);
 
 RBMetaData *	rb_metadata_new		(void);
 
-gboolean	rb_metadata_can_save	(RBMetaData *md, const char *mimetype);
+gboolean	rb_metadata_can_save	(RBMetaData *md, const char *media_type);
 char **		rb_metadata_get_saveable_types (RBMetaData *md);
 
 void		rb_metadata_reset	(RBMetaData *md);
@@ -139,7 +139,7 @@ void		rb_metadata_save	(RBMetaData *md,
 					 const char *uri,
 					 GError **error);
 
-const char *	rb_metadata_get_mime	(RBMetaData *md);
+const char *	rb_metadata_get_media_type	(RBMetaData *md);
 
 gboolean	rb_metadata_has_missing_plugins (RBMetaData *md);
 
diff --git a/metadata/test-metadata.c b/metadata/test-metadata.c
index e42a2a3..a055304 100644
--- a/metadata/test-metadata.c
+++ b/metadata/test-metadata.c
@@ -68,12 +68,12 @@ print_metadata_string (RBMetaData *md, RBMetaDataField field, const char *name)
 static gboolean
 check_can_save_cb (gpointer mt)
 {
-	char *mimetype = (char *)mt;
+	char *media_type = (char *)mt;
 
-	if (rb_metadata_can_save (md, mimetype)) {
-		printf ("Can save %s\n", mimetype);
+	if (rb_metadata_can_save (md, media_type)) {
+		printf ("Can save %s\n", media_type);
 	} else {
-		printf ("Unable to save %s\n", mimetype);
+		printf ("Unable to save %s\n", media_type);
 	}
 
 	return FALSE;
@@ -111,7 +111,7 @@ load_metadata_cb (gpointer file)
 		g_clear_error (&error);
 	}
 
-	printf ("type: %s\n", rb_metadata_get_mime (md));
+	printf ("type: %s\n", rb_metadata_get_media_type (md));
 	for (f =(RBMetaDataField)0; f < RB_METADATA_FIELD_LAST; f++)
 		print_metadata_string (md, f, rb_metadata_get_field_name (f));
 
diff --git a/plugins/audiocd/rb-audiocd-source.c b/plugins/audiocd/rb-audiocd-source.c
index 3a96579..54bb763 100644
--- a/plugins/audiocd/rb-audiocd-source.c
+++ b/plugins/audiocd/rb-audiocd-source.c
@@ -620,7 +620,7 @@ rb_audiocd_create_track_entry (RBAudioCdSource *source,
 	entry_set_string_prop (db, entry, RHYTHMDB_PROP_ARTIST, FALSE, NULL);
 	entry_set_string_prop (db, entry, RHYTHMDB_PROP_ALBUM, FALSE, NULL);
 	entry_set_string_prop (db, entry, RHYTHMDB_PROP_GENRE, FALSE, NULL);
-	entry_set_string_prop (db, entry, RHYTHMDB_PROP_MIMETYPE, TRUE, "audio/x-raw-int");
+	entry_set_string_prop (db, entry, RHYTHMDB_PROP_MEDIA_TYPE, TRUE, "audio/x-raw-int");
 
 	extra_data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBAudioCDEntryData);
 	extra_data->extract = TRUE;
diff --git a/plugins/daap/rb-daap-record.c b/plugins/daap/rb-daap-record.c
index 50027c5..5ecb885 100644
--- a/plugins/daap/rb-daap-record.c
+++ b/plugins/daap/rb-daap-record.c
@@ -426,7 +426,7 @@ rb_daap_record_new (RhythmDBEntry *entry)
 						(entry, RHYTHMDB_PROP_GENRE);
 
 		/* FIXME: Support transcoding: */
-		/* FIXME: we should use RHYTHMDB_PROP_MIMETYPE instead */
+		/* FIXME: we should use RHYTHMDB_PROP_MEDIA_TYPE instead */
 		ext = strrchr (record->priv->location, '.');
 		if (ext == NULL) {
 			ext = "mp3";
diff --git a/plugins/dbus-media-server/rb-dbus-media-server-plugin.c b/plugins/dbus-media-server/rb-dbus-media-server-plugin.c
index 85ed16b..b4c863f 100644
--- a/plugins/dbus-media-server/rb-dbus-media-server-plugin.c
+++ b/plugins/dbus-media-server/rb-dbus-media-server-plugin.c
@@ -38,6 +38,7 @@
 
 #include <lib/rb-util.h>
 #include <lib/rb-debug.h>
+#include <lib/rb-gst-media-types.h>
 #include <plugins/rb-plugin-macros.h>
 #include <shell/rb-shell.h>
 #include <shell/rb-shell-player.h>
@@ -223,7 +224,7 @@ entry_property_maps (RhythmDBPropType prop)
 {
 	switch (prop) {
 		case RHYTHMDB_PROP_TITLE:
-		case RHYTHMDB_PROP_MIMETYPE:
+		case RHYTHMDB_PROP_MEDIA_TYPE:
 		case RHYTHMDB_PROP_FILE_SIZE:
 		case RHYTHMDB_PROP_ALBUM:
 		case RHYTHMDB_PROP_ARTIST:
@@ -268,12 +269,8 @@ get_entry_property_value (RhythmDBEntry *entry, const char *property_name)
 
 	} else if (g_strcmp0 (property_name, "MIMEType") == 0) {
 		const char *media_type;
-		/* ugh */
-		media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
-		if (g_strcmp0 (media_type, "application/x-id3") == 0) {
-			media_type = "audio/mpeg";
-		}
-		return g_variant_new_string (media_type);
+		media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
+		return g_variant_new_string (rb_gst_media_type_to_mime_type (media_type));
 	} else if (g_strcmp0 (property_name, "Size") == 0) {
 		return g_variant_new_int64 (rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE));
 	} else if (g_strcmp0 (property_name, "Artist") == 0) {
diff --git a/plugins/generic-player/rb-generic-player-source.c b/plugins/generic-player/rb-generic-player-source.c
index 359bdca..5e18ec2 100644
--- a/plugins/generic-player/rb-generic-player-source.c
+++ b/plugins/generic-player/rb-generic-player-source.c
@@ -50,6 +50,7 @@
 #include "rhythmdb-import-job.h"
 #include "rb-import-errors-source.h"
 #include "rb-builder-helpers.h"
+#include "rb-gst-media-types.h"
 #include "rb-sync-settings.h"
 #include "rb-missing-plugins.h"
 
@@ -74,10 +75,9 @@ static gboolean impl_can_paste (RBSource *source);
 static gboolean impl_can_delete (RBSource *source);
 static void impl_delete (RBSource *source);
 
-static GList* impl_get_mime_types (RBRemovableMediaSource *source);
 static char* impl_build_dest_uri (RBRemovableMediaSource *source,
 				  RhythmDBEntry *entry,
-				  const char *mimetype,
+				  const char *media_type,
 				  const char *extension);
 static guint64 impl_get_capacity (RBMediaPlayerSource *source);
 static guint64 impl_get_free_space (RBMediaPlayerSource *source);
@@ -164,7 +164,6 @@ rb_generic_player_source_class_init (RBGenericPlayerSourceClass *klass)
 	mps_class->impl_remove_playlists = impl_remove_playlists;
 
 	rms_class->impl_build_dest_uri = impl_build_dest_uri;
-	rms_class->impl_get_mime_types = impl_get_mime_types;
 	rms_class->impl_should_paste = rb_removable_media_source_should_paste_no_duplicate;
 
 	klass->impl_get_mount_path = default_get_mount_path;
@@ -218,6 +217,7 @@ impl_constructed (GObject *object)
 	RhythmDBEntryType *entry_type;
 	GMount *mount;
 	char **playlist_formats;
+	char **output_formats;
 	char *mount_name;
 	RBShell *shell;
 	GFile *root;
@@ -269,6 +269,26 @@ impl_constructed (GObject *object)
 	g_strfreev (playlist_formats);
 	g_object_unref (entry_type);
 
+	g_object_get (priv->device_info, "output-formats", &output_formats, NULL);
+	if (output_formats != NULL) {
+		GstEncodingTarget *target;
+		int i;
+
+		target = gst_encoding_target_new ("generic-player", "device", "", NULL);
+		for (i = 0; output_formats[i] != NULL; i++) {
+			const char *media_type = rb_gst_mime_type_to_media_type (output_formats[i]);
+			if (media_type != NULL) {
+				GstEncodingProfile *profile;
+				profile = rb_gst_get_encoding_profile (media_type);
+				if (profile != NULL) {
+					gst_encoding_target_add_profile (target, profile);
+				}
+			}
+		}
+		g_object_set (source, "encoding-target", target, NULL);
+	}
+	g_strfreev (output_formats);
+
         rb_media_player_source_load (RB_MEDIA_PLAYER_SOURCE (source));
 	load_songs (source);
 }
@@ -963,26 +983,10 @@ sanitize_path (const char *str)
 	return res;
 }
 
-static GList *
-impl_get_mime_types (RBRemovableMediaSource *source)
-{
-	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
-	GList *list = NULL;
-	char **output_formats;
-	char **mime;
-
-	g_object_get (priv->device_info, "output-formats", &output_formats, NULL);
-	for (mime = output_formats; mime && *mime != NULL; mime++) {
-		list = g_list_prepend (list, g_strdup (*mime));
-	}
-	g_strfreev (output_formats);
-	return g_list_reverse (list);
-}
-
 static char *
 impl_build_dest_uri (RBRemovableMediaSource *source,
 		     RhythmDBEntry *entry,
-		     const char *mimetype,
+		     const char *media_type,
 		     const char *extension)
 {
 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
diff --git a/plugins/ipod/rb-ipod-source.c b/plugins/ipod/rb-ipod-source.c
index e3ad7a0..17a4fd5 100644
--- a/plugins/ipod/rb-ipod-source.c
+++ b/plugins/ipod/rb-ipod-source.c
@@ -53,6 +53,7 @@
 #include "rb-podcast-manager.h"
 #include "rb-podcast-entry-types.h"
 #include "rb-stock-icons.h"
+#include "rb-gst-media-types.h"
 
 static void rb_ipod_source_constructed (GObject *object);
 static void rb_ipod_source_dispose (GObject *object);
@@ -64,19 +65,18 @@ static gboolean impl_show_popup (RBDisplayPage *page);
 static void impl_delete_thyself (RBDisplayPage *page);
 static GList* impl_get_ui_actions (RBDisplayPage *page);
 
-static GList * impl_get_mime_types (RBRemovableMediaSource *source);
 static gboolean impl_track_added (RBRemovableMediaSource *source,
 				  RhythmDBEntry *entry,
 				  const char *dest,
 				  guint64 filesize,
-				  const char *mimetype);
+				  const char *media_type);
 static char* impl_build_dest_uri (RBRemovableMediaSource *source,
 				  RhythmDBEntry *entry,
-				  const char *mimetype,
+				  const char *media_type,
 				  const char *extension);
 static gchar* ipod_get_filename_for_uri (const gchar *mount_point,
 					 const gchar *uri_str,
-					 const gchar *mimetype,
+					 const gchar *media_type,
 					 const gchar *extension);
 static gchar* ipod_path_from_unix_path (const gchar *mount_point,
 					const gchar *unix_path);
@@ -185,7 +185,6 @@ rb_ipod_source_class_init (RBiPodSourceClass *klass)
 	rms_class->impl_should_paste = rb_removable_media_source_should_paste_no_duplicate;
 	rms_class->impl_track_added = impl_track_added;
 	rms_class->impl_build_dest_uri = impl_build_dest_uri;
-	rms_class->impl_get_mime_types = impl_get_mime_types;
 
 	g_object_class_install_property (object_class,
 					 PROP_DEVICE_INFO,
@@ -282,6 +281,7 @@ rb_ipod_source_constructed (GObject *object)
 	RBiPodSource *source;
 	RBEntryView *songs;
 	RhythmDB *db;
+	GstEncodingTarget *target;
 
 	RB_CHAIN_GOBJECT_METHOD (rb_ipod_source_parent_class, constructed, object);
 	source = RB_IPOD_SOURCE (object);
@@ -301,6 +301,14 @@ rb_ipod_source_constructed (GObject *object)
 
         g_object_unref (db);
 
+	/* is there model-specific data we need to pay attention to here?
+	 * maybe load a target from the device too?
+	 */
+	target = gst_encoding_target_new ("ipod", "device", "ipod", NULL);
+	gst_encoding_target_add_profile (target, rb_gst_get_encoding_profile ("audio/mpeg"));
+	gst_encoding_target_add_profile (target, rb_gst_get_encoding_profile ("audio/x-aac"));
+	g_object_set (source, "encoding-target", target, NULL);
+
         rb_media_player_source_load (RB_MEDIA_PLAYER_SOURCE (source));
 }
 
@@ -700,7 +708,7 @@ load_ipod_playlists (RBiPodSource *source)
 }
 
 static Itdb_Track *
-create_ipod_song_from_entry (RhythmDBEntry *entry, guint64 filesize, const char *mimetype)
+create_ipod_song_from_entry (RhythmDBEntry *entry, guint64 filesize, const char *media_type)
 {
 	Itdb_Track *track;
 
@@ -717,7 +725,7 @@ create_ipod_song_from_entry (RhythmDBEntry *entry, guint64 filesize, const char
 	track->sort_albumartist = rhythmdb_entry_dup_string (entry,
 	                                       	       	     RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME);
 	track->genre = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_GENRE);
-	track->filetype = g_strdup (mimetype);
+	track->filetype = g_strdup (media_type);		/* XXX mapping required? */
 	track->size = filesize;
 	track->tracklen = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
 	track->tracklen *= 1000;
@@ -1375,7 +1383,7 @@ impl_remove_playlists (RBMediaPlayerSource *source)
 static char *
 impl_build_dest_uri (RBRemovableMediaSource *source,
 		     RhythmDBEntry *entry,
-		     const char *mimetype,
+		     const char *media_type,
 		     const char *extension)
 {
 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
@@ -1390,7 +1398,7 @@ impl_build_dest_uri (RBRemovableMediaSource *source,
 	uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
 	mount_path = rb_ipod_db_get_mount_path (priv->ipod_db);
 	dest = ipod_get_filename_for_uri (mount_path,  uri,
-					  mimetype, extension);
+					  media_type, extension);
 	if (dest != NULL) {
 		char *dest_uri;
 
@@ -1589,7 +1597,7 @@ impl_track_added (RBRemovableMediaSource *source,
 		  RhythmDBEntry *entry,
 		  const char *dest,
 		  guint64 filesize,
-		  const char *mimetype)
+		  const char *media_type)
 {
 	RBiPodSource *isource = RB_IPOD_SOURCE (source);
 	RhythmDB *db;
@@ -1597,7 +1605,7 @@ impl_track_added (RBRemovableMediaSource *source,
 
 	db = get_db_for_source (isource);
 
-	song = create_ipod_song_from_entry (entry, filesize, mimetype);
+	song = create_ipod_song_from_entry (entry, filesize, media_type);
 	if (song != NULL) {
 		RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
 		char *filename;
@@ -1816,7 +1824,7 @@ generate_ipod_filename (const gchar *mount_point, const gchar *filename)
 static gchar *
 ipod_get_filename_for_uri (const gchar *mount_point,
 			   const gchar *uri_str,
-			   const gchar *mimetype,
+			   const gchar *media_type,
 			   const gchar *extension)
 {
 	gchar *escaped;
@@ -1929,18 +1937,6 @@ impl_delete_thyself (RBDisplayPage *page)
 	RB_DISPLAY_PAGE_CLASS (rb_ipod_source_parent_class)->delete_thyself (page);
 }
 
-static GList *
-impl_get_mime_types (RBRemovableMediaSource *source)
-{
-	GList *ret = NULL;
-
-	/* FIXME: we should really query MPID for this */
-	ret = g_list_prepend (ret, g_strdup ("audio/aac"));
-	ret = g_list_prepend (ret, g_strdup ("audio/mpeg"));
-
-	return ret;
-}
-
 Itdb_Playlist *
 rb_ipod_source_new_playlist (RBiPodSource *source)
 {
diff --git a/plugins/mpris/rb-mpris-plugin.c b/plugins/mpris/rb-mpris-plugin.c
index 27d10f1..acdc840 100644
--- a/plugins/mpris/rb-mpris-plugin.c
+++ b/plugins/mpris/rb-mpris-plugin.c
@@ -1239,7 +1239,7 @@ entry_changed_cb (RhythmDB *db, RhythmDBEntry *entry, GValueArray *changes, RBMp
 				case RHYTHMDB_PROP_FIRST_SEEN:
 				case RHYTHMDB_PROP_LAST_SEEN:
 				case RHYTHMDB_PROP_LAST_PLAYED:
-				case RHYTHMDB_PROP_MIMETYPE:
+				case RHYTHMDB_PROP_MEDIA_TYPE:
 				case RHYTHMDB_PROP_PLAYBACK_ERROR:
 					break;
 
diff --git a/plugins/mtpdevice/rb-mtp-source.c b/plugins/mtpdevice/rb-mtp-source.c
index 72d4f85..ff35ec4 100644
--- a/plugins/mtpdevice/rb-mtp-source.c
+++ b/plugins/mtpdevice/rb-mtp-source.c
@@ -51,6 +51,7 @@
 #include "rb-player.h"
 #include "rb-encoder.h"
 #include "rb-sync-settings.h"
+#include "rb-gst-media-types.h"
 
 #include "rb-mtp-source.h"
 #include "rb-mtp-thread.h"
@@ -77,19 +78,18 @@ static void impl_delete (RBSource *asource);
 static gboolean impl_show_popup (RBDisplayPage *page);
 static GList* impl_get_ui_actions (RBDisplayPage *page);
 
-static GList * impl_get_mime_types (RBRemovableMediaSource *source);
 static gboolean impl_track_added (RBRemovableMediaSource *source,
 				  RhythmDBEntry *entry,
 				  const char *dest,
 				  guint64 filesize,
-				  const char *mimetype);
+				  const char *media_type);
 static gboolean impl_track_add_error (RBRemovableMediaSource *source,
 				      RhythmDBEntry *entry,
 				      const char *dest,
 				      GError *error);
 static char* impl_build_dest_uri (RBRemovableMediaSource *source,
 				  RhythmDBEntry *entry,
-				  const char *mimetype,
+				  const char *media_type,
 				  const char *extension);
 static void impl_eject (RBRemovableMediaSource *source);
 static gboolean impl_can_eject (RBRemovableMediaSource *source);
@@ -143,7 +143,6 @@ typedef struct
 	char *udi;
 #endif
 	uint16_t supported_types[LIBMTP_FILETYPE_UNKNOWN+1];
-	GList *mediatypes;
 	gboolean album_art_supported;
 
 	/* device information */
@@ -200,7 +199,6 @@ rb_mtp_source_class_init (RBMtpSourceClass *klass)
 	rms_class->impl_track_added = impl_track_added;
 	rms_class->impl_track_add_error = impl_track_add_error;
 	rms_class->impl_build_dest_uri = impl_build_dest_uri;
-	rms_class->impl_get_mime_types = impl_get_mime_types;
 	rms_class->impl_should_paste = rb_removable_media_source_should_paste_no_duplicate;
 	rms_class->impl_can_eject = impl_can_eject;
 	rms_class->impl_eject = impl_eject;
@@ -757,7 +755,8 @@ device_opened_idle (DeviceOpenedData *data)
 {
 	RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (data->source);
 	int i;
-	gboolean has_mp3 = FALSE;
+	GstEncodingTarget *target;
+	GList *profiles = NULL;
 
 	if (data->name != NULL) {
 		g_object_set (data->source, "name", data->name, NULL);
@@ -771,69 +770,71 @@ device_opened_idle (DeviceOpenedData *data)
 
 	for (i = 0; i < data->num_types; i++) {
 		const char *mediatype;
-
+		gboolean prepend;
 		if (i <= LIBMTP_FILETYPE_UNKNOWN) {
 			priv->supported_types[data->types[i]] = 1;
 		}
 
-		/* this has to work with the remapping done in
-		 * rb-removable-media-source.c:impl_paste.
-		 */
+		mediatype = NULL;
+		prepend = FALSE;
 		switch (data->types[i]) {
 		case LIBMTP_FILETYPE_WAV:
-			mediatype = "audio/x-wav";
+			/*mediatype = "audio/x-wav";*/
+			/* don't bother including this? */
 			break;
 		case LIBMTP_FILETYPE_MP3:
-			/* special handling for mp3: always put it at the front of the list
-			 * if it's supported.
-			 */
-			has_mp3 = TRUE;
-			mediatype = NULL;
+			mediatype = "audio/mpeg";
+			prepend = TRUE;		/* always goes first if supported */
 			break;
 		case LIBMTP_FILETYPE_WMA:
-			mediatype = "audio/x-ms-wma";
+			mediatype = "audio/x-wma";
 			break;
 		case LIBMTP_FILETYPE_OGG:
-			mediatype = "application/ogg";
+			mediatype = "audio/x-vorbis";
 			break;
 		case LIBMTP_FILETYPE_MP4:
 		case LIBMTP_FILETYPE_M4A:
 		case LIBMTP_FILETYPE_AAC:
-			mediatype = "audio/aac";
+			mediatype = "audio/x-aac";
 			break;
 		case LIBMTP_FILETYPE_WMV:
-			mediatype = "audio/x-ms-wmv";
+			mediatype = "audio/x-ms-wmv";		/* media type? */
 			break;
 		case LIBMTP_FILETYPE_ASF:
-			mediatype = "video/x-ms-asf";
+			mediatype = "video/x-ms-asf";		/* media type? */
 			break;
 		case LIBMTP_FILETYPE_FLAC:
-			mediatype = "audio/flac";
+			mediatype = "audio/x-flac";
 			break;
 
 		case LIBMTP_FILETYPE_JPEG:
 			rb_debug ("JPEG (album art) supported");
-			mediatype = NULL;
 			priv->album_art_supported = TRUE;
 			break;
 
 		default:
 			rb_debug ("unknown libmtp filetype %s supported", LIBMTP_Get_Filetype_Description (data->types[i]));
-			mediatype = NULL;
 			break;
 		}
 
 		if (mediatype != NULL) {
-			rb_debug ("media type %s supported", mediatype);
-			priv->mediatypes = g_list_prepend (priv->mediatypes,
-							   g_strdup (mediatype));
+			GstEncodingProfile *profile;
+			profile = rb_gst_get_encoding_profile (mediatype);
+			if (profile != NULL) {
+				rb_debug ("media type %s supported", mediatype);
+				if (prepend) {
+					profiles = g_list_prepend (profiles, profile);
+				} else {
+					profiles = g_list_append (profiles, profile);
+				}
+			} else {
+				rb_debug ("no encoding profile for supported media type %s", mediatype);
+			}
 		}
 	}
 
-	if (has_mp3) {
-		rb_debug ("audio/mpeg supported");
-		priv->mediatypes = g_list_prepend (priv->mediatypes, g_strdup ("audio/mpeg"));
-	}
+	target = gst_encoding_target_new ("mtpdevice", "device", "", profiles);
+	g_object_set (data->source, "encoding-target", target, NULL);
 
 	g_object_unref (data->source);
 	free (data->types);
@@ -973,17 +974,17 @@ gdate_to_char (GDate* date)
 }
 
 static LIBMTP_filetype_t
-mimetype_to_filetype (RBMtpSource *source, const char *mimetype)
+media_type_to_filetype (RBMtpSource *source, const char *media_type)
 {
 	RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
 
-	if (!strcmp (mimetype, "audio/mpeg") || !strcmp (mimetype, "application/x-id3")) {
+	if (!strcmp (media_type, "audio/mpeg")) {
 		return LIBMTP_FILETYPE_MP3;
-	}  else if (!strcmp (mimetype, "audio/x-wav")) {
+	}  else if (!strcmp (media_type, "audio/x-wav")) {
 		return  LIBMTP_FILETYPE_WAV;
-	} else if (!strcmp (mimetype, "application/ogg")) {
+	} else if (!strcmp (media_type, "audio/x-vorbis")) {
 		return LIBMTP_FILETYPE_OGG;
-	} else if (!strcmp (mimetype, "audio/x-m4a") || !strcmp (mimetype, "video/quicktime")) {
+	} else if (!strcmp (media_type, "audio/x-aac")) {
 		/* try a few different filetypes that might work */
 		if (priv->supported_types[LIBMTP_FILETYPE_M4A])
 			return LIBMTP_FILETYPE_M4A;
@@ -992,14 +993,14 @@ mimetype_to_filetype (RBMtpSource *source, const char *mimetype)
 		else
 			return LIBMTP_FILETYPE_AAC;
 
-	} else if (!strcmp (mimetype, "audio/x-ms-wma") || !strcmp (mimetype, "audio/x-ms-asf")) {
+	} else if (!strcmp (media_type, "audio/x-wma")) {
 		return LIBMTP_FILETYPE_WMA;
-	} else if (!strcmp (mimetype, "video/x-ms-asf")) {
+	} else if (!strcmp (media_type, "video/x-ms-asf")) {
 		return LIBMTP_FILETYPE_ASF;
-	} else if (!strcmp (mimetype, "audio/x-flac")) {
+	} else if (!strcmp (media_type, "audio/x-flac")) {
 		return LIBMTP_FILETYPE_FLAC;
 	} else {
-		rb_debug ("\"%s\" is not a supported mimetype", mimetype);
+		rb_debug ("\"%s\" is not a supported media_type", media_type);
 		return LIBMTP_FILETYPE_UNKNOWN;
 	}
 }
@@ -1084,19 +1085,12 @@ request_album_art_idle (RequestAlbumArtData *data)
 	return FALSE;
 }
 
-static GList *
-impl_get_mime_types (RBRemovableMediaSource *source)
-{
-	RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
-	return rb_string_list_copy (priv->mediatypes);
-}
-
 static gboolean
 impl_track_added (RBRemovableMediaSource *source,
 		  RhythmDBEntry *entry,
 		  const char *dest,
 		  guint64 filesize,
-		  const char *mimetype)
+		  const char *media_type)
 {
 	LIBMTP_track_t *track = NULL;
 	RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
@@ -1244,21 +1238,21 @@ prepare_encoder_sink_cb (RBEncoderFactory *factory,
 static char *
 impl_build_dest_uri (RBRemovableMediaSource *source,
 		     RhythmDBEntry *entry,
-		     const char *mimetype,
+		     const char *media_type,
 		     const char *extension)
 {
 	gulong id;
 	char *uri;
 	LIBMTP_filetype_t filetype;
 
-	if (mimetype == NULL) {
-		mimetype = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
+	if (media_type == NULL) {
+		media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
 	}
-	filetype = mimetype_to_filetype (RB_MTP_SOURCE (source), mimetype);
+	filetype = media_type_to_filetype (RB_MTP_SOURCE (source), media_type);
 	rb_debug ("using libmtp filetype %d (%s) for source media type %s",
 		  filetype,
 		  LIBMTP_Get_Filetype_Description (filetype),
-		  mimetype);
+		  media_type);
 
 	/* the prepare-sink callback needs the entry ID to set up the
 	 * upload data, and we want to use the supplied extension for
diff --git a/plugins/rbzeitgeist/rbzeitgeist.py b/plugins/rbzeitgeist/rbzeitgeist.py
index 0b60126..d9f0973 100644
--- a/plugins/rbzeitgeist/rbzeitgeist.py
+++ b/plugins/rbzeitgeist/rbzeitgeist.py
@@ -67,7 +67,7 @@ class ZeitgeistPlugin(GObject.Object, Peas.Activatable):
 
     @staticmethod
     def get_song_info(db, entry):
-        # we don't want the PROP_MIMETYPE, as it doesn't contain mimetype
+        # we don't want the PROP_MEDIA_TYPE, as it doesn't contain mimetype
         # of the audio file itself
         song = {
             "album": entry.get_string(RB.RhythmDBPropType.ALBUM),
diff --git a/podcast/rb-feed-podcast-properties-dialog.c b/podcast/rb-feed-podcast-properties-dialog.c
index 1ebd1c2..f40dea6 100644
--- a/podcast/rb-feed-podcast-properties-dialog.c
+++ b/podcast/rb-feed-podcast-properties-dialog.c
@@ -262,13 +262,12 @@ static void
 rb_feed_podcast_properties_dialog_update_language (RBFeedPodcastPropertiesDialog *dialog)
 {
 	const char *language;
-#if GST_CHECK_VERSION(0,10,26)
 	char *separator;
 	char *iso636lang;
 	const char *langname;
-#endif
+
 	language = rhythmdb_entry_get_string (dialog->priv->current_entry, RHYTHMDB_PROP_LANG);
-#if GST_CHECK_VERSION(0,10,26)
+
 	/* language tag is language[-subcode]; we only care about the language bit */
 	iso636lang = g_strdup (language);
 	separator = strchr (iso636lang, '-');
@@ -284,7 +283,7 @@ rb_feed_podcast_properties_dialog_update_language (RBFeedPodcastPropertiesDialog
 		gtk_label_set_text (GTK_LABEL (dialog->priv->language), langname);
 		return;
 	}
-#endif
+
 	gtk_label_set_text (GTK_LABEL (dialog->priv->language), language);
 }
 
diff --git a/podcast/rb-podcast-manager.c b/podcast/rb-podcast-manager.c
index a8b8cfd..9dc64c3 100644
--- a/podcast/rb-podcast-manager.c
+++ b/podcast/rb-podcast-manager.c
@@ -1335,7 +1335,7 @@ rb_podcast_manager_save_metadata (RBPodcastManager *pd, RhythmDBEntry *entry)
 	RBMetaData *md = rb_metadata_new ();
 	GError *error = NULL;
 	GValue val = { 0, };
-	const char *mime;
+	const char *media_type;
 	const char *uri;
 	char **missing_plugins;
 	char **plugin_descriptions;
@@ -1388,11 +1388,11 @@ rb_podcast_manager_save_metadata (RBPodcastManager *pd, RhythmDBEntry *entry)
 		return;
 	}
 
-	mime = rb_metadata_get_mime (md);
-	if (mime) {
+	media_type = rb_metadata_get_media_type (md);
+	if (media_type) {
 		g_value_init (&val, G_TYPE_STRING);
-		g_value_set_string (&val, mime);
-		rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_MIMETYPE, &val);
+		g_value_set_string (&val, media_type);
+		rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_MEDIA_TYPE, &val);
 		g_value_unset (&val);
 	}
 
diff --git a/rhythmdb/rhythmdb-private.h b/rhythmdb/rhythmdb-private.h
index c3168e4..b4d2524 100644
--- a/rhythmdb/rhythmdb-private.h
+++ b/rhythmdb/rhythmdb-private.h
@@ -97,7 +97,7 @@ struct _RhythmDBEntry {
 	RBRefString *location;
 	RBRefString *mountpoint;
 	guint64 file_size;
-	RBRefString *mimetype;
+	RBRefString *media_type;
 	gulong mtime;
 	gulong first_seen;
 	gulong last_seen;
diff --git a/rhythmdb/rhythmdb-song-entry-types.c b/rhythmdb/rhythmdb-song-entry-types.c
index b028c97..f2203ef 100644
--- a/rhythmdb/rhythmdb-song-entry-types.c
+++ b/rhythmdb/rhythmdb-song-entry-types.c
@@ -125,14 +125,14 @@ static gboolean
 song_can_sync_metadata (RhythmDBEntryType *entry_type,
 			RhythmDBEntry *entry)
 {
-	const char *mimetype;
+	const char *media_type;
 	gboolean can_sync;
 	RhythmDB *db;
 
 	g_object_get (entry_type, "db", &db, NULL);
 
-	mimetype = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
-	can_sync = rb_metadata_can_save (db->priv->metadata, mimetype);
+	media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
+	can_sync = rb_metadata_can_save (db->priv->metadata, media_type);
 
 	g_object_unref (db);
 	return can_sync;
diff --git a/rhythmdb/rhythmdb-tree.c b/rhythmdb/rhythmdb-tree.c
index 2014edf..4368550 100644
--- a/rhythmdb/rhythmdb-tree.c
+++ b/rhythmdb/rhythmdb-tree.c
@@ -115,8 +115,8 @@ static void rhythmdb_hash_tree_foreach (RhythmDB *adb,
 					gpointer data);
 
 /* Update both of those! */
-#define RHYTHMDB_TREE_XML_VERSION "1.7"
-#define RHYTHMDB_TREE_XML_VERSION_INT 170
+#define RHYTHMDB_TREE_XML_VERSION "1.8"
+#define RHYTHMDB_TREE_XML_VERSION_INT 180
 
 static void destroy_tree_property (RhythmDBTreeProperty *prop);
 static RhythmDBTreeProperty *get_or_create_album (RhythmDBTree *db, RhythmDBTreeProperty *artist,
@@ -400,6 +400,9 @@ rhythmdb_tree_parser_start_element (struct RhythmDBTreeLoadContext *ctx,
 						rb_debug ("reloading all file metadata to get sortnames, album artist, comments, bpm and updating mountpoints");
 						ctx->reload_all_metadata = TRUE;
 						ctx->update_local_mountpoints = TRUE;
+					case 170:
+						rb_debug ("reloading all file metadata to get new media types");
+						ctx->reload_all_metadata = TRUE;
 					case RHYTHMDB_TREE_XML_VERSION_INT:
 						/* current version */
 						break;
@@ -1060,8 +1063,8 @@ save_entry (RhythmDBTree *db,
 		case RHYTHMDB_PROP_FILE_SIZE:
 			save_entry_uint64(ctx, elt_name, entry->file_size);
 			break;
-		case RHYTHMDB_PROP_MIMETYPE:
-			save_entry_string(ctx, elt_name, rb_refstring_get (entry->mimetype));
+		case RHYTHMDB_PROP_MEDIA_TYPE:
+			save_entry_string(ctx, elt_name, rb_refstring_get (entry->media_type));
 			break;
 		case RHYTHMDB_PROP_MTIME:
 			save_entry_ulong (ctx, elt_name, entry->mtime, FALSE);
@@ -1352,9 +1355,9 @@ rhythmdb_tree_entry_new_internal (RhythmDB *rdb, RhythmDBEntry *entry)
 		g_warning ("Entry %s has missing genre", rb_refstring_get (entry->location));
 		entry->genre = rb_refstring_new (_("Unknown"));
 	}
-	if (entry->mimetype == NULL) {
-		g_warning ("Entry %s has missing mimetype", rb_refstring_get (entry->location));
-		entry->mimetype = rb_refstring_new ("unknown/unknown");
+	if (entry->media_type == NULL) {
+		g_warning ("Entry %s has missing media type", rb_refstring_get (entry->location));
+		entry->media_type = rb_refstring_new ("unknown/unknown");
 	}
 
 	/* Initialize the tree structure. */
diff --git a/rhythmdb/rhythmdb.c b/rhythmdb/rhythmdb.c
index 70ad670..58c25f6 100644
--- a/rhythmdb/rhythmdb.c
+++ b/rhythmdb/rhythmdb.c
@@ -67,6 +67,7 @@
 #include "rb-string-value-map.h"
 #include "rb-async-queue-watch.h"
 #include "rb-podcast-entry-types.h"
+#include "rb-gst-media-types.h"
 
 #define PROP_ENTRY(p,t,n) { RHYTHMDB_PROP_ ## p, "RHYTHMDB_PROP_" #p "", t, n }
 
@@ -102,7 +103,7 @@ static const RhythmDBPropertyDef rhythmdb_properties[] = {
 	PROP_ENTRY(TRACK_PEAK, G_TYPE_DOUBLE, "replaygain-track-peak"),
 	PROP_ENTRY(ALBUM_GAIN, G_TYPE_DOUBLE, "replaygain-album-gain"),
 	PROP_ENTRY(ALBUM_PEAK, G_TYPE_DOUBLE, "replaygain-album-peak"),
-	PROP_ENTRY(MIMETYPE, G_TYPE_STRING, "mimetype"),
+	PROP_ENTRY(MEDIA_TYPE, G_TYPE_STRING, "media-type"),
 	PROP_ENTRY(TITLE_SORT_KEY, G_TYPE_STRING, "title-sort-key"),
 	PROP_ENTRY(GENRE_SORT_KEY, G_TYPE_STRING, "genre-sort-key"),
 	PROP_ENTRY(ARTIST_SORT_KEY, G_TYPE_STRING, "artist-sort-key"),
@@ -1663,7 +1664,7 @@ rhythmdb_entry_allocate (RhythmDB *db,
 	ret->artist_sortname = rb_refstring_ref (db->priv->empty_string);
 	ret->album_sortname = rb_refstring_ref (db->priv->empty_string);
 	ret->album_artist_sortname = rb_refstring_ref (db->priv->empty_string);
-	ret->mimetype = rb_refstring_ref (db->priv->octet_stream_str);
+	ret->media_type = rb_refstring_ref (db->priv->octet_stream_str);
 
 	ret->flags |= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY |
 		      RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY |
@@ -1858,7 +1859,7 @@ rhythmdb_entry_finalize (RhythmDBEntry *entry)
 	rb_refstring_unref (entry->musicbrainz_albumartistid);
 	rb_refstring_unref (entry->artist_sortname);
 	rb_refstring_unref (entry->album_sortname);
-	rb_refstring_unref (entry->mimetype);
+	rb_refstring_unref (entry->media_type);
 
 	g_free (entry);
 }
@@ -1914,15 +1915,15 @@ set_props_from_metadata (RhythmDB *db,
 			 GFileInfo *fileinfo,
 			 RBMetaData *metadata)
 {
-	const char *mime;
+	const char *media_type;
 	GValue val = {0,};
 
 	g_value_init (&val, G_TYPE_STRING);
-	mime = rb_metadata_get_mime (metadata);
-	if (mime) {
-		g_value_set_string (&val, mime);
+	media_type = rb_metadata_get_media_type (metadata);
+	if (media_type) {
+		g_value_set_string (&val, media_type);
 		rhythmdb_entry_set_internal (db, entry, TRUE,
-					     RHYTHMDB_PROP_MIMETYPE, &val);
+					     RHYTHMDB_PROP_MEDIA_TYPE, &val);
 	}
 	g_value_unset (&val);
 
@@ -2348,7 +2349,7 @@ rhythmdb_process_metadata_load (RhythmDB *db, RhythmDBEvent *event)
 		 * that matches one of the media types we don't care about,
 		 * as well as anything that doesn't contain audio.
 		 */
-		const char *media_type = rb_metadata_get_mime (event->metadata);
+		const char *media_type = rb_metadata_get_media_type (event->metadata);
 		if (media_type != NULL && media_type[0] != '\0') {
 			if (rhythmdb_ignore_media_type (media_type) ||
 			    rb_metadata_has_audio (event->metadata) == FALSE) {
@@ -3452,11 +3453,11 @@ rhythmdb_entry_set_internal (RhythmDB *db,
 		case RHYTHMDB_PROP_FILE_SIZE:
 			entry->file_size = g_value_get_uint64 (value);
 			break;
-		case RHYTHMDB_PROP_MIMETYPE:
-			if (entry->mimetype != NULL) {
-				rb_refstring_unref (entry->mimetype);
+		case RHYTHMDB_PROP_MEDIA_TYPE:
+			if (entry->media_type != NULL) {
+				rb_refstring_unref (entry->media_type);
 			}
-			entry->mimetype = rb_refstring_new (g_value_get_string (value));
+			entry->media_type = rb_refstring_new (g_value_get_string (value));
 			break;
 		case RHYTHMDB_PROP_MTIME:
 			entry->mtime = g_value_get_ulong (value);
@@ -4783,8 +4784,8 @@ rhythmdb_entry_get_string (RhythmDBEntry *entry,
 		return rb_refstring_get (entry->album_artist);
 	case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME:
 		return rb_refstring_get (entry->album_artist_sortname);
-	case RHYTHMDB_PROP_MIMETYPE:
-		return rb_refstring_get (entry->mimetype);
+	case RHYTHMDB_PROP_MEDIA_TYPE:
+		return rb_refstring_get (entry->media_type);
 	case RHYTHMDB_PROP_TITLE_SORT_KEY:
 		return rb_refstring_get_sort_key (entry->title);
 	case RHYTHMDB_PROP_ALBUM_SORT_KEY:
@@ -4919,8 +4920,8 @@ rhythmdb_entry_get_refstring (RhythmDBEntry *entry,
 		return rb_refstring_ref (entry->album_sortname);
 	case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME:
 		return rb_refstring_ref (entry->album_artist_sortname);
-	case RHYTHMDB_PROP_MIMETYPE:
-		return rb_refstring_ref (entry->mimetype);
+	case RHYTHMDB_PROP_MEDIA_TYPE:
+		return rb_refstring_ref (entry->media_type);
 	case RHYTHMDB_PROP_MOUNTPOINT:
 		return rb_refstring_ref (entry->mountpoint);
 	case RHYTHMDB_PROP_LAST_PLAYED_STR:
@@ -5357,15 +5358,11 @@ rhythmdb_entry_change_get_type (void)
 gboolean
 rhythmdb_entry_is_lossless (RhythmDBEntry *entry)
 {
-	const char *mime_type;
+	const char *media_type;
 
 	if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE) != 0)
 		return FALSE;
        
-	/* possible performance improvement here, if it proves necessary:
-	 * keep references to the refstrings for all lossless media types here,
-	 * and use pointer comparisons rather than string comparisons to check entries.
-	 */
-	mime_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
-	return (g_str_equal (mime_type, "audio/x-flac"));
+	media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
+	return rb_gst_media_type_is_lossless (media_type);
 }
diff --git a/rhythmdb/rhythmdb.h b/rhythmdb/rhythmdb.h
index 4c68f3b..353e34c 100644
--- a/rhythmdb/rhythmdb.h
+++ b/rhythmdb/rhythmdb.h
@@ -115,7 +115,7 @@ typedef enum
 	RHYTHMDB_PROP_TRACK_PEAK,			/* obsolete */
 	RHYTHMDB_PROP_ALBUM_GAIN,			/* obsolete */
 	RHYTHMDB_PROP_ALBUM_PEAK,			/* obsolete */
-	RHYTHMDB_PROP_MIMETYPE,
+	RHYTHMDB_PROP_MEDIA_TYPE,
 	RHYTHMDB_PROP_TITLE_SORT_KEY,
 	RHYTHMDB_PROP_GENRE_SORT_KEY,
 	RHYTHMDB_PROP_ARTIST_SORT_KEY,
diff --git a/shell/rb-track-transfer-batch.c b/shell/rb-track-transfer-batch.c
index c6cceeb..bb4a05c 100644
--- a/shell/rb-track-transfer-batch.c
+++ b/shell/rb-track-transfer-batch.c
@@ -30,6 +30,8 @@
 
 #include <glib/gi18n.h>
 
+#include <gst/pbutils/install-plugins.h>
+
 #include "rb-source.h"
 #include "rb-track-transfer-batch.h"
 #include "rb-track-transfer-queue.h"
@@ -37,6 +39,7 @@
 #include "rb-marshal.h"
 #include "rb-debug.h"
 #include "rb-util.h"
+#include "rb-gst-media-types.h"
 
 enum
 {
@@ -54,8 +57,7 @@ enum
 enum
 {
 	PROP_0,
-	PROP_MEDIA_TYPES,
-	PROP_MEDIA_TYPES_STRV,
+	PROP_ENCODING_TARGET,
 	PROP_SOURCE,
 	PROP_DESTINATION,
 	PROP_TOTAL_ENTRIES,
@@ -75,7 +77,8 @@ struct _RBTrackTransferBatchPrivate
 {
 	RBTrackTransferQueue *queue;
 
-	GList *media_types;
+	GstEncodingTarget *target;
+	GList *missing_plugin_profiles;
 
 	RBSource *source;
 	RBSource *destination;
@@ -107,13 +110,13 @@ G_DEFINE_TYPE (RBTrackTransferBatch, rb_track_transfer_batch, G_TYPE_OBJECT)
 
 /**
  * rb_track_transfer_batch_new:
- * @media_types: array containing media type strings describing allowable output formats
- * @media_type_list: (element-type utf8): GList containing media type strings.
+ * @target: a #GstEncodingTarget describing allowable encodings (or NULL for defaults)
  * @source: the #RBSource from which the entries are to be transferred
  * @destination: the #RBSource to which the entries are to be transferred
  *
- * Creates a new transfer batch with the specified output types.  Only one of media_types
- * and media_types_list may be specified.
+ * Creates a new transfer batch with the specified encoding target.  If no target
+ * is specified, the default target will be used with the user's preferred
+ * encoding type.
  *
  * One or more entries must be added to the batch (using #rb_track_transfer_batch_add)
  * before the batch can be started (#rb_track_transfer_manager_start_batch).
@@ -121,29 +124,17 @@ G_DEFINE_TYPE (RBTrackTransferBatch, rb_track_transfer_batch, G_TYPE_OBJECT)
  * Return value: new #RBTrackTransferBatch object
  */
 RBTrackTransferBatch *
-rb_track_transfer_batch_new (GList *media_types,
-			     const char * const *media_types_strv,
+rb_track_transfer_batch_new (GstEncodingTarget *target,
 			     GObject *source,
 			     GObject *destination)
 {
 	GObject *obj;
 
-	/* can't specify both, can specify neither */
-	g_assert (media_types == NULL || media_types_strv == NULL);
-
-	if (media_types != NULL) {
-		obj = g_object_new (RB_TYPE_TRACK_TRANSFER_BATCH,
-				    "media-types", media_types,
-				    "source", source,
-				    "destination", destination,
-				    NULL);
-	} else {
-		obj = g_object_new (RB_TYPE_TRACK_TRANSFER_BATCH,
-				    "media-types-strv", &media_types_strv,
-				    "source", source,
-				    "destination", destination,
-				    NULL);
-	}
+	obj = g_object_new (RB_TYPE_TRACK_TRANSFER_BATCH,
+			    "encoding-target", target,
+			    "source", source,
+			    "destination", destination,
+			    NULL);
 	return RB_TRACK_TRANSFER_BATCH (obj);
 }
 
@@ -160,38 +151,131 @@ rb_track_transfer_batch_add (RBTrackTransferBatch *batch, RhythmDBEntry *entry)
 	batch->priv->entries = g_list_append (batch->priv->entries, rhythmdb_entry_ref (entry));
 }
 
+static gboolean
+select_profile_for_entry (RBTrackTransferBatch *batch, RhythmDBEntry *entry, GstEncodingProfile **rprofile, gboolean allow_missing)
+{
+	/* probably want a way to pass in some policy about lossless encoding
+	 * here.  possibilities:
+	 * - convert everything to lossy
+	 * - if transcoding is required, use lossy
+	 * - keep lossless encoded files lossless
+	 * - if transcoding is required, use lossless
+	 * - convert everything to lossless
+	 *
+	 * of course this only applies to targets that include lossless profiles..
+	 */
+
+	const char *media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
+	const GList *p;
+
+	for (p = gst_encoding_target_get_profiles (batch->priv->target); p != NULL; p = p->next) {
+		GstEncodingProfile *profile = GST_ENCODING_PROFILE (p->data);
+		char *profile_media_type;
+		gboolean skip;
+
+		if (g_str_has_prefix (media_type, "audio/x-raw") == FALSE &&
+		    rb_gst_media_type_matches_profile (profile, media_type)) {
+			/* source file is already in a supported encoding, so just copy it */
+			*rprofile = NULL;
+			return TRUE;
+		}
+
+		skip = FALSE;
+		/* ignore lossless encodings for now */
+		profile_media_type = rb_gst_encoding_profile_get_media_type (profile);
+		if (profile_media_type == NULL) {
+			if (g_str_has_prefix (media_type, "audio/x-raw")) {
+				skip = TRUE;
+			}
+		} else if (rb_gst_media_type_is_lossless (profile_media_type)) {
+			skip = TRUE;
+		} else if (allow_missing == FALSE) {
+			if (g_list_find (batch->priv->missing_plugin_profiles, profile)) {
+				skip = TRUE;
+			}
+		}
+
+		if (skip == FALSE && *rprofile == NULL) {
+			*rprofile = profile;
+		}
+		g_free (profile_media_type);
+	}
+
+	return (*rprofile != NULL);
+}
+
 /**
- * rb_track_transfer_batch_check_media_types:
+ * rb_track_transfer_batch_check_profiles:
  * @batch: a #RBTrackTransferBatch
+ * @missing_plugin_profiles: holds a #GList of #GstEncodingProfiles on return
+ * @error_count: holds the number of entries that cannot be transferred on return
  *
  * Checks that all entries in the batch can be transferred in a format
- * supported by the destination.
+ * supported by the destination.  If no encoding profile is available for
+ * some entries, but installing additional plugins could make a profile
+ * available, a list of profiles that require additional plugins is returned.
  *
- * Return value: number of entries that cannot be transferred
+ * Return value: %TRUE if some entries can be transferred without additional plugins
  */
-guint
-rb_track_transfer_batch_check_media_types (RBTrackTransferBatch *batch)
+gboolean
+rb_track_transfer_batch_check_profiles (RBTrackTransferBatch *batch, GList **missing_plugin_profiles, int *error_count)
 {
 	RBEncoder *encoder = rb_encoder_new ();
-	guint count = 0;
-	GList *l;
+	gboolean ret = FALSE;
+	const GList *l;
+
+	rb_debug ("checking profiles");
+
+	/* first, figure out which profiles that we care about would require additional plugins to use */
+	g_list_free (batch->priv->missing_plugin_profiles);
+	batch->priv->missing_plugin_profiles = NULL;
+
+	for (l = gst_encoding_target_get_profiles (batch->priv->target); l != NULL; l = l->next) {
+		GstEncodingProfile *profile = GST_ENCODING_PROFILE (l->data);
+		char *profile_media_type;
+		profile_media_type = rb_gst_encoding_profile_get_media_type (profile);
+		if ((rb_gst_media_type_is_lossless (profile_media_type) == FALSE) &&
+		    rb_encoder_get_missing_plugins (encoder, profile, NULL, NULL)) {
+			batch->priv->missing_plugin_profiles = g_list_append (batch->priv->missing_plugin_profiles, profile);
+		}
+		g_free (profile_media_type);
+	}
+	g_object_unref (encoder);
+
+	rb_debug ("have %d profiles with missing plugins", g_list_length (batch->priv->missing_plugin_profiles));
 
 	for (l = batch->priv->entries; l != NULL; l = l->next) {
 		RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
-		/* check that we can transfer this entry to the device */
-		if (rb_encoder_get_media_type (encoder,
-					       entry,
-					       batch->priv->media_types,
-					       NULL,
-					       NULL) == FALSE) {
+		GstEncodingProfile *profile;
+
+		profile = NULL;
+		if (select_profile_for_entry (batch, entry, &profile, FALSE) == TRUE) {
+			if (profile != NULL) {
+				rb_debug ("found profile %s for %s",
+					  gst_encoding_profile_get_name (profile),
+					  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
+			} else {
+				rb_debug ("copying entry %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
+			}
+			ret = TRUE;
+			continue;
+		}
+
+		(*error_count)++;
+		if (select_profile_for_entry (batch, entry, &profile, TRUE) == FALSE) {
 			rb_debug ("unable to transfer %s (media type %s)",
 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
-				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE));
-			count++;
+				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE));
+		} else {
+			rb_debug ("require additional plugins to transfer %s (media type %s)",
+				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
+				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE));
+			if (*missing_plugin_profiles == NULL) {
+				*missing_plugin_profiles = g_list_copy (batch->priv->missing_plugin_profiles);
+			}
 		}
 	}
-	g_object_unref (encoder);
-	return count;
+	return ret;
 }
 
 /**
@@ -384,11 +468,31 @@ encoder_overwrite_cb (RBEncoder *encoder, GFile *file, RBTrackTransferBatch *bat
 	return overwrite;
 }
 
+static char *
+get_extension_from_location (RhythmDBEntry *entry)
+{
+	char *extension = NULL;
+	const char *ext;
+	GFile *f;
+	char *b;
+
+	f = g_file_new_for_uri (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
+	b = g_file_get_basename (f);
+	g_object_unref (f);
+
+	ext = strrchr (b, '.');
+	if (ext != NULL) {
+		extension = g_strdup (ext+1);
+	}
+	g_free (b);
+
+	return extension;
+}
+
 static gboolean
 start_next (RBTrackTransferBatch *batch)
 {
-	char *media_type = NULL;
-	char *extension = NULL;
+	GstEncodingProfile *profile = NULL;
 
 	if (batch->priv->cancelled == TRUE) {
 		return FALSE;
@@ -402,6 +506,7 @@ start_next (RBTrackTransferBatch *batch)
 
 	batch->priv->current_fraction = 0.0;
 	batch->priv->current_encoder = rb_encoder_new ();
+
 	g_signal_connect_object (batch->priv->current_encoder, "progress",
 				 G_CALLBACK (encoder_progress_cb),
 				 batch, 0);
@@ -420,6 +525,8 @@ start_next (RBTrackTransferBatch *batch)
 		gulong duration;
 		double fraction;
 		GList *n;
+		char *media_type;
+		char *extension;
 
 		n = batch->priv->entries;
 		batch->priv->entries = g_list_remove_link (batch->priv->entries, n);
@@ -443,22 +550,26 @@ start_next (RBTrackTransferBatch *batch)
 			fraction = 1.0 / ((double)count);
 		}
 
-		g_free (media_type);
-		g_free (extension);
-		media_type = NULL;
-		extension = NULL;
-		if (rb_encoder_get_media_type (batch->priv->current_encoder,
-					       entry,
-					       batch->priv->media_types,
-					       &media_type,
-					       &extension) == FALSE) {
-			rb_debug ("skipping entry %s, can't find a destination format",
+		profile = NULL;
+		if (select_profile_for_entry (batch, entry, &profile, FALSE) == FALSE) {
+			rb_debug ("skipping entry %s, can't find an encoding profile",
 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
 			rhythmdb_entry_unref (entry);
 			batch->priv->total_fraction += fraction;
 			continue;
 		}
 
+		if (profile != NULL) {
+			media_type = rb_gst_encoding_profile_get_media_type (profile);
+			extension = g_strdup (rb_gst_media_type_to_extension (media_type));
+		} else {
+			media_type = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
+			extension = g_strdup (rb_gst_media_type_to_extension (media_type));
+			if (extension == NULL) {
+				extension = get_extension_from_location (entry);
+			}
+		}
+
 		g_free (batch->priv->current_dest_uri);
 		batch->priv->current_dest_uri = NULL;
 		g_signal_emit (batch, signals[GET_DEST_URI], 0,
@@ -466,6 +577,9 @@ start_next (RBTrackTransferBatch *batch)
 			       media_type,
 			       extension,
 			       &batch->priv->current_dest_uri);
+		g_free (media_type);
+		g_free (extension);
+
 		if (batch->priv->current_dest_uri == NULL) {
 			rb_debug ("unable to build destination URI for %s, skipping",
 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
@@ -490,10 +604,8 @@ start_next (RBTrackTransferBatch *batch)
 		rb_encoder_encode (batch->priv->current_encoder,
 				   batch->priv->current,
 				   batch->priv->current_dest_uri,
-				   media_type);
+				   profile);
 	}
-	g_free (media_type);
-	g_free (extension);
 
 	return TRUE;
 }
@@ -515,19 +627,9 @@ impl_set_property (GObject *object,
 		   GParamSpec *pspec)
 {
 	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
-	const char * const *strv;
-	int i;
 	switch (prop_id) {
-	case PROP_MEDIA_TYPES:
-		batch->priv->media_types = rb_string_list_copy (g_value_get_pointer (value));
-		break;
-	case PROP_MEDIA_TYPES_STRV:
-		strv = g_value_get_boxed (value);
-		if (strv != NULL) {
-			for (i = 0; strv[i] != NULL; i++) {
-				batch->priv->media_types = g_list_append (batch->priv->media_types, g_strdup (strv[i]));
-			}
-		}
+	case PROP_ENCODING_TARGET:
+		batch->priv->target = GST_ENCODING_TARGET (gst_value_dup_mini_object (value));
 		break;
 	case PROP_SOURCE:
 		batch->priv->source = g_value_dup_object (value);
@@ -549,8 +651,8 @@ impl_get_property (GObject *object,
 {
 	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
 	switch (prop_id) {
-	case PROP_MEDIA_TYPES:
-		g_value_set_pointer (value, batch->priv->media_types);
+	case PROP_ENCODING_TARGET:
+		gst_value_set_mini_object (value, GST_MINI_OBJECT (batch->priv->target));
 		break;
 	case PROP_SOURCE:
 		g_value_set_object (value, batch->priv->source);
@@ -614,6 +716,11 @@ impl_dispose (GObject *object)
 		batch->priv->destination = NULL;
 	}
 
+	if (batch->priv->target != NULL) {
+		gst_encoding_target_unref (batch->priv->target);
+		batch->priv->target = NULL;
+	}
+
 	G_OBJECT_CLASS (rb_track_transfer_batch_parent_class)->dispose (object);
 }
 
@@ -622,10 +729,11 @@ impl_finalize (GObject *object)
 {
 	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
 
-	rb_list_deep_free (batch->priv->media_types);
 	rb_list_destroy_free (batch->priv->entries, (GDestroyNotify) rhythmdb_entry_unref);
 	rb_list_destroy_free (batch->priv->done_entries, (GDestroyNotify) rhythmdb_entry_unref);
-	rhythmdb_entry_unref (batch->priv->current);
+	if (batch->priv->current != NULL) {
+		rhythmdb_entry_unref (batch->priv->current);
+	}
 
 	G_OBJECT_CLASS (rb_track_transfer_batch_parent_class)->finalize (object);
 }
@@ -641,32 +749,18 @@ rb_track_transfer_batch_class_init (RBTrackTransferBatchClass *klass)
 	object_class->dispose = impl_dispose;
 
 	/**
-	 * RBTrackTransferBatch:media-types
-	 *
-	 * Array of media type strings describing the acceptable
-	 * destination formats.  If NULL, no format conversion will
-	 * be done.
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_MEDIA_TYPES_STRV,
-					 g_param_spec_boxed ("media-types-strv",
-							     "media types",
-							     "Set of allowable destination media types",
-							     G_TYPE_STRV,
-							     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-	/**
-	 * RBTrackTransferBatch:media-types
+	 * RBTrackTransferBatch:encoding-target
 	 *
-	 * GList of media type strings describing the acceptable
-	 * destination formats.  If NULL, no format conversion will
-	 * be done.
+	 * A GstEncodingTarget describing allowable target formats.
+	 * If NULL, the default set of profiles will be used.
 	 */
 	g_object_class_install_property (object_class,
-					 PROP_MEDIA_TYPES,
-					 g_param_spec_pointer ("media-types",
-							       "media types",
-							       "Set of allowable destination media types",
-							       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+					 PROP_ENCODING_TARGET,
+					 gst_param_spec_mini_object ("encoding-target",
+								     "encoding target",
+								     "GstEncodingTarget",
+								     GST_TYPE_ENCODING_TARGET,
+								     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 	/**
 	 * RBTrackTransferBatch:source
 	 *
diff --git a/shell/rb-track-transfer-batch.h b/shell/rb-track-transfer-batch.h
index 813da0b..8cc7d75 100644
--- a/shell/rb-track-transfer-batch.h
+++ b/shell/rb-track-transfer-batch.h
@@ -28,6 +28,8 @@
 #ifndef __RB_TRACK_TRANSFER_BATCH_H
 #define __RB_TRACK_TRANSFER_BATCH_H
 
+#include <gst/pbutils/encoding-target.h>
+
 #include <rhythmdb/rhythmdb.h>
 
 G_BEGIN_DECLS
@@ -82,14 +84,15 @@ struct _RBTrackTransferBatchClass
 
 GType			rb_track_transfer_batch_get_type	(void);
 
-RBTrackTransferBatch *	rb_track_transfer_batch_new		(GList *media_type_list,
-								 const char * const *media_types,
+RBTrackTransferBatch *	rb_track_transfer_batch_new		(GstEncodingTarget *target,
 								 GObject *source,
 								 GObject *destination);
 void			rb_track_transfer_batch_add		(RBTrackTransferBatch *batch,
 								 RhythmDBEntry *entry);
 
-guint			rb_track_transfer_batch_check_media_types (RBTrackTransferBatch *batch);
+gboolean		rb_track_transfer_batch_check_profiles  (RBTrackTransferBatch *batch,
+								 GList **missing_plugin_profiles,
+								 int *error_count);
 
 void			rb_track_transfer_batch_cancel		(RBTrackTransferBatch *batch);
 
diff --git a/shell/rb-track-transfer-queue.c b/shell/rb-track-transfer-queue.c
index 297a6fb..4f8700b 100644
--- a/shell/rb-track-transfer-queue.c
+++ b/shell/rb-track-transfer-queue.c
@@ -35,9 +35,14 @@
 #include "rb-debug.h"
 #include "rb-dialog.h"
 #include "rb-alert-dialog.h"
+#include "rb-gst-media-types.h"
+#include "rb-missing-plugins.h"
 
 #include <glib/gi18n.h>
 
+#include <gst/gst.h>
+#include <gst/pbutils/install-plugins.h>
+
 enum
 {
 	PROP_0,
@@ -236,14 +241,6 @@ batch_progress (RBTrackTransferBatch *batch,
 	g_signal_emit (queue, signals[TRANSFER_PROGRESS], 0, done, total, fraction, estimate_time_left (queue, fraction));
 }
 
-#if 0
-static void
-missing_plugins_retry_cb (gpointer inst, gboolean retry, RBTrackTransferQueue *queue)
-{
-	_rb_track_transfer_batch_start (queue->priv->current, G_OBJECT (queue));
-}
-#endif
-
 static void
 actually_start_batch (RBTrackTransferQueue *queue)
 {
@@ -262,29 +259,65 @@ actually_start_batch (RBTrackTransferQueue *queue)
 	_rb_track_transfer_batch_start (queue->priv->current, G_OBJECT (queue));
 }
 
+static GPtrArray *
+get_missing_plugin_strings (GList *profiles, gboolean get_descriptions)
+{
+	RBEncoder *encoder;
+	GPtrArray *strings;
+	GList *l;
+
+	encoder = rb_encoder_new ();
+	strings = g_ptr_array_new_with_free_func (g_free);
+	for (l = profiles; l != NULL; l = l->next) {
+		GstEncodingProfile *profile = GST_ENCODING_PROFILE (l->data);
+		char **details, **descriptions;
+		char **d;
+		int i;
+
+		rb_encoder_get_missing_plugins (encoder, profile, &details, &descriptions);
+		d = get_descriptions ? descriptions : details;
+		for (i = 0; d[i] != NULL; i++) {
+			g_ptr_array_add (strings, g_strdup (d[i]));
+		}
+		g_strfreev (details);
+		g_strfreev (descriptions);
+	}
+	g_ptr_array_add (strings, NULL);
+	g_object_unref (encoder);
+
+	return strings;
+}
+
 static void
-error_response_cb (GtkDialog *dialog, gint response, RBTrackTransferQueue *queue)
+missing_plugins_retry_cb (gpointer inst, gboolean retry, RBTrackTransferQueue *queue)
 {
-	_rb_track_transfer_batch_cancel (queue->priv->current);
-	g_object_unref (queue->priv->current);
+	rb_debug ("plugin install finished (retry %d), checking media types again", retry);
+	g_queue_push_head (queue->priv->batch_queue, queue->priv->current);
 	queue->priv->current = NULL;
-
 	start_next_batch (queue);
-	gtk_widget_destroy (GTK_WIDGET (dialog));
 }
 
 static void
 missing_encoder_response_cb (GtkDialog *dialog, gint response, RBTrackTransferQueue *queue)
 {
+	GClosure *retry;
+	GstEncodingTarget *target;
+	GPtrArray *details;
+	GList *profiles;
+	const GList *l;
+	RBEncoder *encoder;
+
 	switch (response) {
 	case GTK_RESPONSE_YES:
 		/* 'continue' -> start the batch */
+		rb_debug ("starting batch regardless of missing plugins");
 		actually_start_batch (queue);
 		break;
 
 	case GTK_RESPONSE_CANCEL:
 	case GTK_RESPONSE_DELETE_EVENT:
 		/* 'cancel' -> cancel the batch and start the next one */
+		rb_debug ("cancelling batch");
 		_rb_track_transfer_batch_cancel (queue->priv->current);
 		g_object_unref (queue->priv->current);
 		queue->priv->current = NULL;
@@ -292,40 +325,55 @@ missing_encoder_response_cb (GtkDialog *dialog, gint response, RBTrackTransferQu
 		start_next_batch (queue);
 		break;
 
-#if 0
 	case GTK_RESPONSE_ACCEPT:
-		/* 'install an encoder' -> try to install an encoder */
-		/*
-		 * probably need RBEncoder API to get missing plugin installer details
-		 * for a specific pipeline or profile or something.
-		 * since gnome-media profiles use specific element names, installing by
-		 * caps won't necessarily install something that works.  guh.
-		 */
+		/* 'install plugins' -> try to install encoder/muxer */
+
+		/* get profiles that need plugins installed */
+		profiles = NULL;
+		encoder = rb_encoder_new ();
+		g_object_get (queue->priv->current, "encoding-target", &target, NULL);
+		for (l = gst_encoding_target_get_profiles (target); l != NULL; l = l->next) {
+			GstEncodingProfile *profile = GST_ENCODING_PROFILE (l->data);
+			char *profile_media_type;
+			profile_media_type = rb_gst_encoding_profile_get_media_type (profile);
+			if ((rb_gst_media_type_is_lossless (profile_media_type) == FALSE) &&
+			    rb_encoder_get_missing_plugins (encoder, profile, NULL, NULL)) {
+				profiles = g_list_append (profiles, profile);
+			}
+			g_free (profile_media_type);
+		}
+		g_object_unref (encoder);
+		g_object_unref (target);
 
+		if (profiles == NULL) {
+			rb_debug ("apparently we don't need any plugins any more");
+			actually_start_batch (queue);
+			break;
+		}
+
+		rb_debug ("attempting plugin installation");
+		details = get_missing_plugin_strings (profiles, FALSE);
 		retry = g_cclosure_new ((GCallback) missing_plugins_retry_cb,
 					g_object_ref (queue),
 					(GClosureNotify) g_object_unref);
 		g_closure_set_marshal (retry, g_cclosure_marshal_VOID__BOOLEAN);
-		g_signal_emit (queue,
-			       signals[MISSING_PLUGINS], 0,
-			       details, descriptions, retry,
-			       &processing);
-		if (processing) {
+		if (rb_missing_plugins_install ((const char **)details->pdata, FALSE, retry)) {
 			rb_debug ("attempting to install missing plugins for transcoding");
 		} else {
 			rb_debug ("proceeding without the missing plugins for transcoding");
+			actually_start_batch (queue);
 		}
 
 		g_closure_sink (retry);
+		g_ptr_array_free (details, TRUE);
+		g_list_free (profiles);
 		break;
-#endif
 
 	default:
 		g_assert_not_reached ();
 	}
 
 	gtk_widget_destroy (GTK_WIDGET (dialog));
-	g_object_unref (dialog);
 }
 
 static void
@@ -333,6 +381,11 @@ start_next_batch (RBTrackTransferQueue *queue)
 {
 	int count;
 	int total;
+	gboolean can_continue;
+	GtkWidget *dialog;
+	GtkWindow *window;
+	GList *profiles = NULL;
+	char *message;
 
 	if (queue->priv->current != NULL) {
 		return;
@@ -350,66 +403,86 @@ start_next_batch (RBTrackTransferQueue *queue)
 	queue->priv->overwrite_decision = OVERWRITE_PROMPT;
 	g_object_get (queue->priv->current, "total-entries", &total, NULL);
 
-	count = rb_track_transfer_batch_check_media_types (queue->priv->current);
-	rb_debug ("%d tracks in the batch, %d of which cannot be transferred", total, count);
-	if (total == 0) {
-		rb_debug ("what is this batch doing here anyway");
-	} else if (count == total) {
-		GtkWindow *window;
-		GtkWidget *dialog;
-		g_object_get (queue->priv->shell, "window", &window, NULL);
-		/* once we do encoder installation this should turn into a
-		 * normal confirmation dialog with no 'continue' option.
-		 */
-		dialog = rb_alert_dialog_new (window,
-					      0,
-					      GTK_MESSAGE_ERROR,
-					      GTK_BUTTONS_CANCEL,
-					      _("Unable to transfer tracks"),
-					      _("None of the tracks to be transferred "
-					        "are in a format supported by the target "
-						"device, and no encoders are available "
-						"for the supported formats."));
-		rb_alert_dialog_set_details_label (RB_ALERT_DIALOG (dialog), NULL);
-		g_object_unref (window);
-		g_signal_connect_object (dialog, "response", G_CALLBACK (error_response_cb), queue, 0);
-		gtk_widget_show (dialog);
+	count = 0;
+	can_continue = rb_track_transfer_batch_check_profiles (queue->priv->current,
+							       &profiles,
+							       &count);
+
+	if (can_continue && count == 0 && profiles == NULL) {
+		/* no problems, go ahead */
+		actually_start_batch (queue);
 		return;
+	}
 
-	} else if (count > 0) {
-		GtkWindow *window;
-		GtkWidget *dialog;
-		char *text;
+	if (profiles == NULL) {
+		if (total == 1) {
+			message = g_strdup (_("This file cannot be transferred as it is not in a "
+					      "format supported by the target device and no suitable "
+					      "encoding profiles are available."));
+		} else {
+			message = g_strdup_printf (_("%d of the %d files cannot be transferred as "
+						     "they must be converted into a format supported "
+						     "by the target device but no suitable encoding "
+						     "profiles are available."), count, total);
+		}
+	} else {
+		GPtrArray *descriptions;
+		GstEncodingTarget *target;
+		char *plugins;
+		gboolean is_library;
+
+		descriptions = get_missing_plugin_strings (profiles, TRUE);
+		plugins = g_strjoinv ("\n", (char **)descriptions->pdata);
+
+		/* this is a tiny bit hackish */
+		g_object_get (queue->priv->current, "encoding-target", &target, NULL);
+		is_library = (g_strcmp0 (gst_encoding_target_get_name (target), "rhythmbox-library") == 0);
+		gst_encoding_target_unref (target);
+
+		if (is_library) {
+			/* XXX should provide the option of picking a different format? */
+			message = g_strdup_printf (_("Additional software is required to encode media "
+						     "in your preferred format:\n%s"), plugins);
+		} else if (total == 1) {
+			message = g_strdup_printf (_("Additional software is required to convert this "
+						     "file into a format supported by the target "
+						     "device:\n%s"), plugins);
+		} else {
+			message = g_strdup_printf (_("Additional software is required to convert %d "
+						     "of the %d files to be transferred into a format "
+						     "supported by the target device:\n%s"),
+						     count, total, plugins);
+		}
 
+		g_free (plugins);
+		g_ptr_array_free (descriptions, TRUE);
+	}
 
-		rb_debug ("can't find a supported media type for %d/%d files, prompting", count, total);
-		text = g_strdup_printf (_("%d of the %d files to be transferred are not in a format supported"
-					  " by the target device, and no encoders are available for the"
-					  " supported formats."),
-					count, total);
-		g_object_get (queue->priv->shell, "window", &window, NULL);
-		dialog = rb_alert_dialog_new (window,
-					      0,
-					      GTK_MESSAGE_WARNING,
-					      GTK_BUTTONS_NONE,
-					      _("Unable to transfer all tracks. Do you want to continue?"),
-					      text);
-		g_object_unref (window);
-		g_free (text);
+	g_object_get (queue->priv->shell, "window", &window, NULL);
+	dialog = rb_alert_dialog_new (window,
+				      0,
+				      GTK_MESSAGE_ERROR,
+				      GTK_BUTTONS_NONE,
+				      _("Unable to transfer tracks"),
+				      message);
+	g_object_unref (window);
+	g_free (message);
+
+	gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel the transfer"), GTK_RESPONSE_CANCEL);
+	if (can_continue) {
+		gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Skip these files"), GTK_RESPONSE_YES);
+	}
+	if (profiles != NULL && gst_install_plugins_supported ()) {
+		gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Install"), GTK_RESPONSE_ACCEPT);
+	}
 
-		rb_alert_dialog_set_details_label (RB_ALERT_DIALOG (dialog), NULL);
-		gtk_dialog_add_buttons (GTK_DIALOG (dialog),
-					_("_Cancel"), GTK_RESPONSE_CANCEL,
-					_("C_ontinue"), GTK_RESPONSE_YES,
-					/*_("_Install an encoder"), GTK_RESPONSE_ACCEPT,*/
-					NULL);
+	rb_alert_dialog_set_details_label (RB_ALERT_DIALOG (dialog), NULL);
+	g_signal_connect_object (dialog, "response", G_CALLBACK (missing_encoder_response_cb), queue, 0);
+	gtk_widget_show (dialog);
 
-		g_signal_connect_object (dialog, "response", G_CALLBACK (missing_encoder_response_cb), queue, 0);
-		gtk_widget_show (dialog);
-		return;
+	if (profiles != NULL) {
+		g_list_free (profiles);
 	}
-
-	actually_start_batch (queue);
 }
 
 /**
diff --git a/sources/rb-import-errors-source.c b/sources/rb-import-errors-source.c
index e48fbc9..830b72c 100644
--- a/sources/rb-import-errors-source.c
+++ b/sources/rb-import-errors-source.c
@@ -241,13 +241,14 @@ rb_import_errors_source_constructed (GObject *object)
 	rhythmdb_query_free (query);
 
 	/* set up info bar for triggering codec installation */
-	source->priv->infobar = gtk_info_bar_new_with_buttons (_("Install Plugins"), GTK_RESPONSE_OK, NULL);
+	source->priv->infobar = gtk_info_bar_new_with_buttons (_("Install Additional Software"), GTK_RESPONSE_OK, NULL);
 	g_signal_connect_object (source->priv->infobar,
 				 "response",
 				 G_CALLBACK (infobar_response_cb),
 				 source, 0);
 
-	label = gtk_label_new (_("Additional GStreamer plugins are required to play some of these files."));
+	label = gtk_label_new (_("Additional software is required to play some of these files."));
+	gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
 	gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (source->priv->infobar))),
 			   label);
 
diff --git a/sources/rb-library-source.c b/sources/rb-library-source.c
index 5e2d64e..074d22a 100644
--- a/sources/rb-library-source.c
+++ b/sources/rb-library-source.c
@@ -51,11 +51,10 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 #include <glib-object.h>
+#include <gst/pbutils/install-plugins.h>
 
 #include "rb-track-transfer-batch.h"
 #include "rb-track-transfer-queue.h"
-#include <libgnome-media-profiles/gnome-media-profiles.h>
-#include <libgnome-media-profiles/audio-profile-choose.h>
 
 #include "rhythmdb.h"
 #include "rb-debug.h"
@@ -67,6 +66,7 @@
 #include "rb-auto-playlist-source.h"
 #include "rb-encoder.h"
 #include "rb-missing-plugins.h"
+#include "rb-gst-media-types.h"
 
 static void rb_library_source_class_init (RBLibrarySourceClass *klass);
 static void rb_library_source_init (RBLibrarySource *source);
@@ -92,8 +92,6 @@ static void impl_add_uri (RBSource *source,
 
 static void library_settings_changed_cb (GSettings *settings, const char *key, RBLibrarySource *source);
 static void db_settings_changed_cb (GSettings *settings, const char *key, RBLibrarySource *source);
-static void rb_library_source_edit_profile_clicked_cb (GtkButton *button,
-						       RBLibrarySource *source);
 static gboolean rb_library_source_library_location_cb (GtkEntry *entry,
 						       GdkEventFocus *event,
 						       RBLibrarySource *source);
@@ -104,7 +102,9 @@ static void rb_library_source_filename_changed_cb (GtkComboBox *box,
 						   RBLibrarySource *source);
 static void rb_library_source_format_changed_cb (GtkWidget *widget,
 						 RBLibrarySource *source);
-static void layout_example_label_update (RBLibrarySource *source);
+static void rb_library_source_install_plugins_cb (GtkWidget *widget,
+						  RBLibrarySource *source);
+static void update_layout_example_label (RBLibrarySource *source);
 static RhythmDBImportJob *maybe_create_import_job (RBLibrarySource *source);
 
 typedef struct {
@@ -148,6 +148,8 @@ struct RBLibrarySourcePrivate
 	GtkWidget *layout_filename_menu;
 	GtkWidget *preferred_format_menu;
 	GtkWidget *layout_example_label;
+	GtkWidget *install_plugins_button;
+	GtkTreeModel *profile_model;
 
 	GList *import_jobs;
 	guint start_import_job_id;
@@ -185,8 +187,6 @@ rb_library_source_class_init (RBLibrarySourceClass *klass)
 	browser_source_class->impl_has_drop_support = (RBBrowserSourceFeatureFunc) rb_true_function;
 
 	g_type_class_add_private (klass, sizeof (RBLibrarySourcePrivate));
-
-	gnome_media_profiles_init (gconf_client_get_default ());
 }
 
 static void
@@ -354,14 +354,9 @@ rb_library_source_new (RBShell *shell)
 }
 
 static void
-rb_library_source_edit_profile_clicked_cb (GtkButton *button, RBLibrarySource *source)
+rb_library_source_profile_settings_clicked_cb (GtkButton *button, RBLibrarySource *source)
 {
-	GtkWidget *dialog;
-
-	dialog = gm_audio_profiles_edit_new (gconf_client_get_default (),
-					     GTK_WINDOW (source->priv->shell_prefs));
-	gtk_widget_show_all (dialog);
-	gtk_dialog_run (GTK_DIALOG (dialog));
+	/* do something.  not much idea what yet. */
 }
 
 static void
@@ -455,7 +450,7 @@ update_layout_path (RBLibrarySource *source)
 		gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->layout_path_menu), active);
 	}
 
-	layout_example_label_update (source);
+	update_layout_example_label (source);
 }
 
 static void
@@ -478,17 +473,38 @@ update_layout_filename (RBLibrarySource *source)
 
 	gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->layout_filename_menu), active);
 
-	layout_example_label_update (source);
+	update_layout_example_label (source);
 }
 
 static void
-update_preferred_format (RBLibrarySource *source)
+update_preferred_media_type (RBLibrarySource *source)
 {
-	char *str = g_settings_get_string (source->priv->settings, "preferred-format");
-	if (str) {
-		gm_audio_profile_choose_set_active (source->priv->preferred_format_menu, str);
-		g_free (str);
+	GtkTreeIter iter;
+	gboolean done;
+	char *str;
+
+	done = FALSE;
+	str = g_settings_get_string (source->priv->settings, "preferred-media-type");
+	if (gtk_tree_model_get_iter_first (source->priv->profile_model, &iter)) {
+		do {
+			char *media_type;
+
+			gtk_tree_model_get (source->priv->profile_model, &iter,
+					    0, &media_type,
+					    -1);
+			if (g_strcmp0 (media_type, str) == 0) {
+				gtk_combo_box_set_active_iter (GTK_COMBO_BOX (source->priv->preferred_format_menu), &iter);
+				done = TRUE;
+			}
+			g_free (media_type);
+		} while (done == FALSE && gtk_tree_model_iter_next (source->priv->profile_model, &iter));
+	}
+
+	if (done == FALSE) {
+		gtk_combo_box_set_active_iter (GTK_COMBO_BOX (source->priv->preferred_format_menu), NULL);
 	}
+
+	g_free (str);
 }
 
 static void
@@ -509,9 +525,9 @@ library_settings_changed_cb (GSettings *settings, const char *key, RBLibrarySour
 	} else if (g_strcmp0 (key, "layout-filename") == 0) {
 		rb_debug ("layout filename changed");
 		update_layout_filename (source);
-	} else if (g_strcmp0 (key, "preferred-format") == 0) {
-		rb_debug ("preferred format changed");
-		update_preferred_format (source);
+	} else if (g_strcmp0 (key, "preferred-media-type") == 0) {
+		rb_debug ("preferred media type changed");
+		update_preferred_media_type (source);
 	}
 }
 
@@ -519,9 +535,12 @@ static GtkWidget *
 impl_get_config_widget (RBDisplayPage *asource, RBShellPreferences *prefs)
 {
 	RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
+	GtkCellRenderer *renderer;
+	GstEncodingTarget *target;
 	GtkBuilder *builder;
 	GObject *tmp;
 	GObject *label;
+	const GList *p;
 	int i;
 
 	if (source->priv->config_widget)
@@ -582,17 +601,42 @@ impl_get_config_widget (RBDisplayPage *asource, RBShellPreferences *prefs)
 						_(library_layout_filenames[i].title));
 	}
 
-	tmp = gtk_builder_get_object (builder, "edit_profile_button");
+	/* not implemented yet */
+	tmp = gtk_builder_get_object (builder, "profile_settings_button");
 	g_signal_connect (tmp,
 			  "clicked",
-			  G_CALLBACK (rb_library_source_edit_profile_clicked_cb),
+			  G_CALLBACK (rb_library_source_profile_settings_clicked_cb),
 			  asource);
+	gtk_widget_set_sensitive (GTK_WIDGET (tmp), FALSE);
+
+	target = rb_gst_get_default_encoding_target ();
+	source->priv->profile_model = GTK_TREE_MODEL (gtk_tree_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER));
+	for (p = gst_encoding_target_get_profiles (target); p != NULL; p = p->next) {
+		GstEncodingProfile *profile = GST_ENCODING_PROFILE (p->data);
+		char *media_type;
+
+		media_type = rb_gst_encoding_profile_get_media_type (profile);
+		if (media_type == NULL) {
+			continue;
+		}
+		gtk_tree_store_insert_with_values (GTK_TREE_STORE (source->priv->profile_model),
+						   NULL,
+						   NULL,
+						   -1,
+						   0, media_type,
+						   1, gst_encoding_profile_get_description (profile),
+						   2, profile,
+						   -1);
+		g_free (media_type);
+	}
+
+	source->priv->preferred_format_menu = GTK_WIDGET (gtk_builder_get_object (builder, "format_select_combo"));
+	gtk_combo_box_set_model (GTK_COMBO_BOX (source->priv->preferred_format_menu), source->priv->profile_model);
+	gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (source->priv->preferred_format_menu), 1);
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (source->priv->preferred_format_menu), renderer, TRUE);
+	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (source->priv->preferred_format_menu), renderer, "text", 1, NULL);
 
-	tmp = gtk_builder_get_object (builder, "preferred_format_menu_box");
-	label = gtk_builder_get_object (builder, "preferred_format_menu_label");
-	source->priv->preferred_format_menu = gm_audio_profile_choose_new ();
-	gtk_box_pack_start (GTK_BOX (tmp), source->priv->preferred_format_menu, TRUE, TRUE, 0);
-	gtk_label_set_mnemonic_widget (GTK_LABEL (label), source->priv->preferred_format_menu);
 	g_signal_connect (G_OBJECT (source->priv->preferred_format_menu),
 			  "changed",
 			  G_CALLBACK (rb_library_source_format_changed_cb),
@@ -600,8 +644,12 @@ impl_get_config_widget (RBDisplayPage *asource, RBShellPreferences *prefs)
 
 	source->priv->layout_example_label = GTK_WIDGET (gtk_builder_get_object (builder, "layout_example_label"));
 
+	source->priv->install_plugins_button = GTK_WIDGET (gtk_builder_get_object (builder, "install_plugins_button"));
+	gtk_widget_set_no_show_all (source->priv->install_plugins_button, TRUE);
+	g_signal_connect (G_OBJECT (source->priv->install_plugins_button), "clicked", G_CALLBACK (rb_library_source_install_plugins_cb), source);
+
 	update_library_locations (source);
-	update_preferred_format (source);
+	update_preferred_media_type (source);
 
 	update_layout_path (source);
 	update_layout_filename (source);
@@ -715,12 +763,85 @@ rb_library_source_filename_changed_cb (GtkComboBox *box, RBLibrarySource *source
 static void
 rb_library_source_format_changed_cb (GtkWidget *widget, RBLibrarySource *source)
 {
-	GMAudioProfile *profile;
+	GtkTreeIter iter;
+	char *media_type = NULL;
+	GstEncodingProfile *profile;
+	RBEncoder *encoder;
+
+	/* get selected media type */
+	if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget), &iter) == FALSE)
+		return;
+	gtk_tree_model_get (GTK_TREE_MODEL (source->priv->profile_model),
+			    &iter,
+			    0, &media_type,
+			    2, &profile,
+			    -1);
+
+	g_settings_set_string (source->priv->settings, "preferred-media-type", media_type);
+
+	update_layout_example_label (source);
+
+	/* indicate whether additional plugins are required to encode in this format */
+	encoder = rb_encoder_new ();
+	if (rb_encoder_get_missing_plugins (encoder, profile, NULL, NULL)) {
+		rb_debug ("additional plugins are required to encode %s", media_type);
+		gtk_widget_set_visible (source->priv->install_plugins_button, TRUE);
+		/* not a great way to handle this situation; probably should describe
+		 * the plugins that are missing when automatic install isn't available.
+		 */
+		gtk_widget_set_sensitive (source->priv->install_plugins_button,
+					gst_install_plugins_supported ());
+	} else {
+		rb_debug ("can encode %s", media_type);
+		gtk_widget_set_visible (source->priv->install_plugins_button, FALSE);
+	}
+	g_free (media_type);
+}
+
+static void
+plugin_install_done_cb (gpointer inst, gboolean retry, RBLibrarySource *source)
+{
+	rb_library_source_format_changed_cb (source->priv->preferred_format_menu, source);
+}
 
-	profile = gm_audio_profile_choose_get_active (widget);
-	g_settings_set_string (source->priv->settings, "preferred-format", gm_audio_profile_get_id (profile));
-	
-	layout_example_label_update (source);
+static void
+rb_library_source_install_plugins_cb (GtkWidget *widget, RBLibrarySource *source)
+{
+	char *media_type;
+	GstEncodingProfile *profile;
+	RBEncoder *encoder;
+	char **details;
+	GClosure *closure;
+
+	/* get profile */
+	media_type = g_settings_get_string (source->priv->settings, "preferred-media-type");
+	profile = rb_gst_get_encoding_profile (media_type);
+	if (profile == NULL) {
+		g_warning ("no encoding profile available for %s, so how can we install plugins?",
+			   media_type);
+		g_free (media_type);
+		return;
+	}
+	g_free (media_type);
+
+	/* get plugin details */
+	encoder = rb_encoder_new ();
+	if (rb_encoder_get_missing_plugins (encoder, profile, &details, NULL) == FALSE) {
+		/* what? */
+		g_object_unref (encoder);
+		return;
+	}
+
+	/* attempt installation */
+	closure = g_cclosure_new ((GCallback) plugin_install_done_cb,
+				  g_object_ref (source),
+				  (GClosureNotify) g_object_unref);
+	g_closure_set_marshal (closure, g_cclosure_marshal_VOID__BOOLEAN);
+
+	rb_missing_plugins_install ((const char **)details, TRUE, closure);
+
+	g_closure_sink (closure);
+	g_strfreev (details);
 }
 
 /*
@@ -918,7 +1039,7 @@ filepath_parse_pattern (RBLibrarySource *source,
 				break;
 			default:
 				string = g_strdup_printf ("%%t%c", *p);
- 			}
+			}
 
 			break;
 
@@ -940,7 +1061,7 @@ filepath_parse_pattern (RBLibrarySource *source,
 }
 
 static void
-layout_example_label_update (RBLibrarySource *source)
+update_layout_example_label (RBLibrarySource *source)
 {
 	char *file_pattern;
 	char *path_pattern;
@@ -950,11 +1071,11 @@ layout_example_label_update (RBLibrarySource *source)
 	char *format;
 	char *tmp;
 	gboolean strip_chars;
-	GMAudioProfile *profile;
+	char *media_type;
 	RhythmDBEntryType *entry_type;
 	RhythmDBEntry *sample_entry;
 
-  	profile = gm_audio_profile_choose_get_active (source->priv->preferred_format_menu);
+	media_type = g_settings_get_string (source->priv->settings, "preferred-media-type");
 
 	file_pattern = g_settings_get_string (source->priv->settings, "layout-filename");
 	if (file_pattern == NULL) {
@@ -989,9 +1110,10 @@ layout_example_label_update (RBLibrarySource *source)
 			      "</b> ",
 			      example,
 			      ".",
-			      profile ? gm_audio_profile_get_extension (profile) : "ogg",
+			      media_type ? rb_gst_media_type_to_extension (media_type) : "ogg",
 			      "</i></small>", NULL);
 	g_free (example);
+	g_free (media_type);
 
 	gtk_label_set_markup (GTK_LABEL (source->priv->layout_example_label), format);
 	g_free (format);
@@ -1086,7 +1208,7 @@ impl_can_paste (RBSource *asource)
 	can_paste &= (str != NULL);
 	g_free (str);
 
-	str = g_settings_get_string (source->priv->settings, "preferred-format");
+	str = g_settings_get_string (source->priv->settings, "preferred-media-type");
 	can_paste &= (str != NULL);
 	g_free (str);
 
@@ -1162,6 +1284,9 @@ impl_paste (RBSource *asource, GList *entries)
 	RhythmDBEntryType *source_entry_type;
 	RBTrackTransferBatch *batch;
 	gboolean start_batch = FALSE;
+	GstEncodingTarget *target;
+	GstEncodingProfile *profile;
+	char *preferred_media_type;
 
 	if (impl_can_paste (asource) == FALSE) {
 		g_warning ("RBLibrarySource impl_paste called when layout settings unset");
@@ -1175,7 +1300,25 @@ impl_paste (RBSource *asource, GList *entries)
 	g_object_get (shell, "track-transfer-queue", &xferq, NULL);
 	g_object_unref (shell);
 
-	batch = rb_track_transfer_batch_new (NULL, NULL, NULL, G_OBJECT (source));
+	target = gst_encoding_target_new ("rhythmbox-library", "device", "", NULL);
+
+	/* set up profile for user's preferred format */
+	preferred_media_type = g_settings_get_string (source->priv->settings, "preferred-media-type");
+	profile = rb_gst_get_encoding_profile (preferred_media_type);
+	g_free (preferred_media_type);
+	/* have a preset as part of the user settings too?  would that work for containerful streams,
+	 * where the interesting settings are on the stream inside the container?
+	 */
+	if (profile != NULL) {
+		gst_encoding_target_add_profile (target, profile);
+	}
+
+	/* set up profile for copying, which accepts any format */
+	profile = GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (gst_caps_new_any (), NULL, NULL, 1));
+	gst_encoding_profile_set_name (profile, "copy");
+	gst_encoding_target_add_profile (target, profile);
+
+	batch = rb_track_transfer_batch_new (target, NULL, G_OBJECT (source));
 	g_signal_connect_object (batch, "get-dest-uri", G_CALLBACK (get_dest_uri_cb), source, 0);
 	g_signal_connect_object (batch, "track-done", G_CALLBACK (track_done_cb), source, 0);
 
diff --git a/sources/rb-removable-media-source.c b/sources/rb-removable-media-source.c
index 75fd0c2..04d489e 100644
--- a/sources/rb-removable-media-source.c
+++ b/sources/rb-removable-media-source.c
@@ -43,6 +43,7 @@
 
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
+#include <gst/pbutils/encoding-target.h>
 
 #include "rhythmdb.h"
 #include "rb-removable-media-source.h"
@@ -56,18 +57,6 @@
 #include "rb-track-transfer-batch.h"
 #include "rb-track-transfer-queue.h"
 
-#if !GLIB_CHECK_VERSION(2,22,0)
-#define g_mount_unmount_with_operation_finish g_mount_unmount_finish
-#define g_mount_unmount_with_operation(m,f,mo,ca,cb,ud) g_mount_unmount(m,f,ca,cb,ud)
-
-#define g_mount_eject_with_operation_finish g_mount_eject_finish
-#define g_mount_eject_with_operation(m,f,mo,ca,cb,ud) g_mount_eject(m,f,ca,cb,ud)
-
-#define g_volume_eject_with_operation_finish g_volume_eject_finish
-#define g_volume_eject_with_operation(v,f,mo,ca,cb,ud) g_volume_eject(v,f,ca,cb,ud)
-#endif
-
-
 /* arbitrary length limit for file extensions */
 #define EXTENSION_LENGTH_LIMIT	8
 
@@ -98,6 +87,7 @@ typedef struct
 {
 	GVolume *volume;
 	GMount *mount;
+	GstEncodingTarget *encoding_target;
 } RBRemovableMediaSourcePrivate;
 
 G_DEFINE_TYPE (RBRemovableMediaSource, rb_removable_media_source, RB_TYPE_BROWSER_SOURCE)
@@ -108,6 +98,7 @@ enum
 	PROP_0,
 	PROP_VOLUME,
 	PROP_MOUNT,
+	PROP_ENCODING_TARGET
 };
 
 static void
@@ -167,6 +158,18 @@ rb_removable_media_source_class_init (RBRemovableMediaSourceClass *klass)
 							      "GIO Mount",
 							      G_TYPE_MOUNT,
 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBRemovableMediaSource:encoding-target
+	 *
+	 * The #GstEncodingTarget for this device
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_ENCODING_TARGET,
+					 gst_param_spec_mini_object ("encoding-target",
+								     "encoding target",
+								     "GstEncodingTarget",
+								     GST_TYPE_ENCODING_TARGET,
+								     G_PARAM_READWRITE));
 
 	g_type_class_add_private (klass, sizeof (RBRemovableMediaSourcePrivate));
 }
@@ -292,6 +295,12 @@ rb_removable_media_source_set_property (GObject *object,
 			g_object_ref (priv->mount);
 		}
 		break;
+	case PROP_ENCODING_TARGET:
+		if (priv->encoding_target) {
+			g_object_unref (priv->encoding_target);
+		}
+		priv->encoding_target = GST_ENCODING_TARGET (gst_value_dup_mini_object (value));
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -313,6 +322,9 @@ rb_removable_media_source_get_property (GObject *object,
 	case PROP_MOUNT:
 		g_value_set_object (value, priv->mount);
 		break;
+	case PROP_ENCODING_TARGET:
+		g_value_set_object (value, priv->encoding_target);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -387,9 +399,9 @@ static RBTrackTransferBatch *
 impl_paste (RBSource *bsource, GList *entries)
 {
 	RBRemovableMediaSource *source = RB_REMOVABLE_MEDIA_SOURCE (bsource);
+	RBRemovableMediaSourcePrivate *priv = REMOVABLE_MEDIA_SOURCE_GET_PRIVATE (bsource);
 	RBTrackTransferQueue *xferq;
 	RBShell *shell;
-	GList *mime_types;
 	GList *l;
 	RhythmDBEntryType *our_entry_type;
 	RBTrackTransferBatch *batch;
@@ -402,9 +414,7 @@ impl_paste (RBSource *bsource, GList *entries)
 	g_object_get (shell, "track-transfer-queue", &xferq, NULL);
 	g_object_unref (shell);
 
-	mime_types = rb_removable_media_source_get_mime_types (source);
-	batch = rb_track_transfer_batch_new (mime_types, NULL, NULL, G_OBJECT (source));
-	rb_list_deep_free (mime_types);
+	batch = rb_track_transfer_batch_new (priv->encoding_target, NULL, G_OBJECT (source));
 
 	g_signal_connect_object (batch, "get-dest-uri", G_CALLBACK (get_dest_uri_cb), source, 0);
 	g_signal_connect_object (batch, "track-done", G_CALLBACK (track_done_cb), source, 0);
@@ -607,7 +617,7 @@ impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
  * rb_removable_media_source_build_dest_uri:
  * @source: an #RBRemovableMediaSource
  * @entry: the #RhythmDBEntry to build a URI for
- * @mimetype: destination media type
+ * @media_type: destination media type
  * @extension: extension associated with destination media type
  *
  * Constructs a URI to use as the destination for a transfer or transcoding
@@ -624,7 +634,7 @@ impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
 char *
 rb_removable_media_source_build_dest_uri (RBRemovableMediaSource *source,
 					  RhythmDBEntry *entry,
-					  const char *mimetype,
+					  const char *media_type,
 					  const char *extension)
 {
 	RBRemovableMediaSourceClass *klass = RB_REMOVABLE_MEDIA_SOURCE_GET_CLASS (source);
@@ -632,7 +642,7 @@ rb_removable_media_source_build_dest_uri (RBRemovableMediaSource *source,
 	char *sane_uri = NULL;
 
 	if (klass->impl_build_dest_uri) {
-		uri = klass->impl_build_dest_uri (source, entry, mimetype, extension);
+		uri = klass->impl_build_dest_uri (source, entry, media_type, extension);
 	} else {
 		uri = NULL;
 	}
@@ -642,8 +652,8 @@ rb_removable_media_source_build_dest_uri (RBRemovableMediaSource *source,
 	g_free(uri);
 	uri = sane_uri;
 
-	rb_debug ("Built dest URI for mime='%s', extension='%s': '%s'",
-		  mimetype,
+	rb_debug ("Built dest URI for media type='%s', extension='%s': '%s'",
+		  media_type,
 		  extension,
 		  uri);
 
@@ -651,32 +661,6 @@ rb_removable_media_source_build_dest_uri (RBRemovableMediaSource *source,
 }
 
 /**
- * rb_removable_media_source_get_mime_types:
- * @source: an #RBRemovableMediaSource
- *
- * Returns a #GList of allocated media type strings describing the
- * formats supported by the device.  If possible, these should be
- * sorted in order of preference, as the first entry in the list
- * for which an encoder is available will be used.
- *
- * Common media types include "audio/mpeg" for MP3, "application/ogg"
- * for Ogg Vorbis, "audio/x-flac" for FLAC, and "audio/x-aac" for
- * MP4/AAC.
- *
- * Return value: (element-type utf8) (transfer full): list of media types
- */
-GList *
-rb_removable_media_source_get_mime_types (RBRemovableMediaSource *source)
-{
-	RBRemovableMediaSourceClass *klass = RB_REMOVABLE_MEDIA_SOURCE_GET_CLASS (source);
-
-	if (klass->impl_get_mime_types)
-		return klass->impl_get_mime_types (source);
-	else
-		return NULL;
-}
-
-/**
  * rb_removable_media_source_get_format_descriptions:
  * @source: a #RBRemovableMediaSource
  *
@@ -689,27 +673,15 @@ rb_removable_media_source_get_mime_types (RBRemovableMediaSource *source)
 GList *
 rb_removable_media_source_get_format_descriptions (RBRemovableMediaSource *source)
 {
-	GList *mime;
+	GstEncodingTarget *target;
+	const GList *l;
 	GList *desc = NULL;
-	GList *t;
-
-	mime = rb_removable_media_source_get_mime_types (source);
-	for (t = mime; t != NULL; t = t->next) {
-		const char *mimetype;
-		char *content_type;
-
-		mimetype = t->data;
-		content_type = g_content_type_from_mime_type (mimetype);
-		if (content_type != NULL) {
-			char *description;
-			description = g_content_type_get_description (content_type);
-			desc = g_list_append (desc, description);
-		} else {
-			desc = g_list_append (desc, g_strdup (mimetype));
-		}
+	g_object_get (source, "encoding-target", &target, NULL);
+	for (l = gst_encoding_target_get_profiles (target); l != NULL; l = l->next) {
+		GstEncodingProfile *profile = l->data;
+		desc = g_list_append (desc, g_strdup (gst_encoding_profile_get_description (profile)));
 	}
-
-	rb_list_deep_free (mime);
+	g_object_unref (target);
 	return desc;
 }
 
@@ -815,7 +787,7 @@ rb_removable_media_source_should_paste (RBRemovableMediaSource *source,
  * @entry: the source #RhythmDBEntry for the transfer
  * @uri: the destination URI
  * @filesize: size of the destination file
- * @mimetype: media type of the destination file
+ * @media_type: media type of the destination file
  *
  * This is called when a transfer to the device has completed.
  * If the source's impl_track_added method returns %TRUE, the destination
@@ -830,13 +802,13 @@ rb_removable_media_source_track_added (RBRemovableMediaSource *source,
 				       RhythmDBEntry *entry,
 				       const char *uri,
 				       guint64 filesize,
-				       const char *mimetype)
+				       const char *media_type)
 {
 	RBRemovableMediaSourceClass *klass = RB_REMOVABLE_MEDIA_SOURCE_GET_CLASS (source);
 	gboolean add_to_db = TRUE;
 
 	if (klass->impl_track_added)
-		add_to_db = klass->impl_track_added (source, entry, uri, filesize, mimetype);
+		add_to_db = klass->impl_track_added (source, entry, uri, filesize, media_type);
 
 	if (add_to_db) {
 		RhythmDBEntryType *entry_type;
@@ -1048,15 +1020,8 @@ rb_removable_media_source_eject (RBRemovableMediaSource *source)
  * impl_build_dest_uri:
  * @source: the source
  * @entry: entry to build URI for
- * @mimetype: destination media type
+ * @media_type: destination media type
  * @extension: extension for destination media type
  *
  * Return value: (transfer full): destination URI for the entry
  */
-
-/**
- * impl_get_mime_types:
- * @source: the source
- *
- * Return value: (element-type utf8) (transfer full): list of media types
- */
diff --git a/sources/rb-removable-media-source.h b/sources/rb-removable-media-source.h
index eed9b17..da04b1a 100644
--- a/sources/rb-removable-media-source.h
+++ b/sources/rb-removable-media-source.h
@@ -55,14 +55,13 @@ struct _RBRemovableMediaSourceClass
 
 	char*		(*impl_build_dest_uri)	(RBRemovableMediaSource *source,
 						 RhythmDBEntry *entry,
-						 const char *mimetype,
+						 const char *media_type,
 						 const char *extension);
-	GList*		(*impl_get_mime_types)	(RBRemovableMediaSource *source);
 	gboolean	(*impl_track_added)	(RBRemovableMediaSource *source,
 						 RhythmDBEntry *entry,
 						 const char *uri,
 						 guint64 dest_size,
-						 const char *mimetype);
+						 const char *media_type);
 	gboolean	(*impl_track_add_error) (RBRemovableMediaSource *source,
 						 RhythmDBEntry *entry,
 						 const char *uri,
@@ -80,18 +79,17 @@ GType			rb_removable_media_source_get_type	(void);
 
 char*		rb_removable_media_source_build_dest_uri 	(RBRemovableMediaSource *source,
 								 RhythmDBEntry *entry,
-								 const char *mimetype,
+								 const char *media_type,
 								 const char *extension);
 void		rb_removable_media_source_track_added		(RBRemovableMediaSource *source,
 								 RhythmDBEntry *entry,
 								 const char *uri,
 								 guint64 filesize,
-								 const char *mimetype);
+								 const char *media_type);
 void		rb_removable_media_source_track_add_error	(RBRemovableMediaSource *source,
 								 RhythmDBEntry *entry,
 								 const char *uri,
 								 GError *error);
-GList *		rb_removable_media_source_get_mime_types	(RBRemovableMediaSource *source);
 GList *		rb_removable_media_source_get_format_descriptions (RBRemovableMediaSource *source);
 gboolean	rb_removable_media_source_should_paste		(RBRemovableMediaSource *source,
 								 RhythmDBEntry *entry);



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