[rhythmbox] encoder: rethink most of the encoder interface



commit 8b528003d9d3e170c3d15907efa62485507522f0
Author: Jonathan Matthew <jonathan d14n org>
Date:   Fri May 21 22:49:40 2010 +1000

    encoder: rethink most of the encoder interface
    
    rb_encoder_get_preferred_mimetype has been renamed to
    rb_encoder_get_media_type, and it also takes the entry being encoded
    to allow more intelligent media type selection.
    
    rb_encoder_encode now takes a single destination media type, so the
    caller must first call rb_encoder_get_media_type to select one.
    
    Rather than prompting the user to overwrite a destination file, the
    encoder emits the 'overwrite' signal.
    
    The 'error' and 'completed' signals have been combined, so now the caller
    will only get one signal to notify it that the encoder has finished.  The
    'completed' signal now includes the destination media type.
    
    Two new error conditions have been added: one indicating the destination
    filesystem is full, and one indicating that the destination filesystem
    is mounted read-only.
    
    rb_encoder_get_missing_plugins provides GStreamer plugin installer detail
    strings for any plugins required to transcode to the specified media
    type.

 backends/gstreamer/rb-encoder-gst.c |  427 ++++++++++++++++++++++-------------
 backends/gstreamer/rb-encoder-gst.h |    2 +-
 backends/rb-encoder.c               |  135 ++++++++----
 backends/rb-encoder.h               |   41 ++--
 lib/rb-marshal.list                 |    2 +
 shell/rb-removable-media-manager.c  |    4 +-
 sources/rb-removable-media-source.c |    2 +-
 7 files changed, 399 insertions(+), 214 deletions(-)
---
diff --git a/backends/gstreamer/rb-encoder-gst.c b/backends/gstreamer/rb-encoder-gst.c
index 4168d51..aa3e967 100644
--- a/backends/gstreamer/rb-encoder-gst.c
+++ b/backends/gstreamer/rb-encoder-gst.c
@@ -38,6 +38,7 @@
 #include <profiles/gnome-media-profiles.h>
 #include <gtk/gtk.h>
 #include <gio/gio.h>
+#include <gst/pbutils/missing-plugins.h>
 
 #include "rhythmdb.h"
 #include "eel-gconf-extensions.h"
@@ -60,15 +61,17 @@ struct _RBEncoderGstPrivate {
 	gboolean transcoding;
 	gint decoded_pads;
 
-	gboolean error_emitted;
 	gboolean completion_emitted;
 
 	GstFormat position_format;
 	gint64 total_length;
 	guint progress_id;
 	char *dest_uri;
+	const char *dest_mediatype;
 
 	GOutputStream *outstream;
+
+	GError *error;
 };
 
 G_DEFINE_TYPE_WITH_CODE(RBEncoderGst, rb_encoder_gst, G_TYPE_OBJECT,
@@ -79,15 +82,43 @@ G_DEFINE_TYPE_WITH_CODE(RBEncoderGst, rb_encoder_gst, G_TYPE_OBJECT,
 static gboolean rb_encoder_gst_encode (RBEncoder *encoder,
 				       RhythmDBEntry *entry,
 				       const char *dest,
-				       GList *mime_types);
+				       const char *dest_media_type);
 static void rb_encoder_gst_cancel (RBEncoder *encoder);
-static gboolean rb_encoder_gst_get_preferred_mimetype (RBEncoder *encoder,
-						       GList *mime_types,
-						       char **mime,
-						       char **extension);
+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)
 {
@@ -98,35 +129,36 @@ rb_encoder_gst_class_init (RBEncoderGstClass *klass)
 
         g_type_class_add_private (klass, sizeof (RBEncoderGstPrivate));
 
-	/* create the mimetype -> GstCaps lookup table
+	/* 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->mime_caps_table = g_hash_table_new_full (g_str_hash, g_str_equal,
-							NULL, (GDestroyNotify)gst_caps_unref);
+	klass->media_caps_table = g_hash_table_new_full (g_str_hash, g_str_equal,
+							 NULL,
+							 (GDestroyNotify)gst_caps_unref);
 
 	/* AAC */
 	caps = gst_caps_new_simple ("audio/mpeg",
 				    "mpegversion", G_TYPE_INT, 4,
 				    NULL);
-	g_hash_table_insert (klass->mime_caps_table, "audio/aac", caps);
+	g_hash_table_insert (klass->media_caps_table, "audio/aac", 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->mime_caps_table, "audio/mpeg", caps);
+	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->mime_caps_table, "application/ogg", caps);
+	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->mime_caps_table, "audio/flac", caps);
+	g_hash_table_insert (klass->media_caps_table, "audio/flac", caps);
 }
 
 static void
@@ -140,7 +172,8 @@ rb_encoder_init (RBEncoderIface *iface)
 {
 	iface->encode = rb_encoder_gst_encode;
 	iface->cancel = rb_encoder_gst_cancel;
-	iface->get_preferred_mimetype = rb_encoder_gst_get_preferred_mimetype;
+	iface->get_media_type = rb_encoder_gst_get_media_type;
+	iface->get_missing_plugins = rb_encoder_gst_get_missing_plugins;
 }
 
 static void
@@ -175,10 +208,27 @@ rb_encoder_gst_new (void)
 }
 
 static void
-rb_encoder_gst_emit_error (RBEncoderGst *encoder, GError *error)
+set_error (RBEncoderGst *encoder, GError *error)
 {
-	encoder->priv->error_emitted = TRUE;
-	_rb_encoder_emit_error (RB_ENCODER (encoder), error);
+	if (encoder->priv->error != NULL) {
+		g_warning ("got encoding error %s, but already have one: %s",
+			   error->message,
+			   encoder->priv->error->message);
+		return;
+	}
+
+	/* translate some GStreamer errors into generic ones */
+	if (g_error_matches (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_NO_SPACE_LEFT)) {
+		GError *old = error;
+		error = g_error_new (RB_ENCODER_ERROR, RB_ENCODER_ERROR_OUT_OF_SPACE, "%s", old->message);
+		g_error_free (old);
+	} else if (g_error_matches (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_OPEN_WRITE)) {
+		GError *old = error;
+		error = g_error_new (RB_ENCODER_ERROR, RB_ENCODER_ERROR_DEST_READ_ONLY, "%s", old->message);
+		g_error_free (old);
+	}
+
+	g_propagate_error (&encoder->priv->error, error);
 }
 
 static void
@@ -196,16 +246,16 @@ rb_encoder_gst_emit_completed (RBEncoderGst *encoder)
 
 	/* emit an error if no audio pad has been found and it wasn't due to an
 	 * error */
-	if (encoder->priv->error_emitted == FALSE &&
-			encoder->priv->transcoding &&
-			encoder->priv->decoded_pads == 0) {
+	if (encoder->priv->error == NULL &&
+	    encoder->priv->transcoding &&
+	    encoder->priv->decoded_pads == 0) {
 		rb_debug ("received EOS and no decoded pad");
 		g_set_error (&error,
 				RB_ENCODER_ERROR,
 				RB_ENCODER_ERROR_FORMAT_UNSUPPORTED,
 				"no decodable audio pad found");
 
-		rb_encoder_gst_emit_error (encoder, error);
+		set_error (encoder, error);
 		g_error_free (error);
 	}
 
@@ -226,7 +276,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);
+	_rb_encoder_emit_completed (RB_ENCODER (encoder), dest_size, encoder->priv->dest_mediatype, encoder->priv->error);
 }
 
 static void
@@ -261,7 +311,7 @@ bus_watch_cb (GstBus *bus, GstMessage *message, gpointer data)
 	switch (GST_MESSAGE_TYPE (message)) {
 	case GST_MESSAGE_ERROR:
 		gst_message_parse_error (message, &error, &string);
-		rb_encoder_gst_emit_error (encoder, error);
+		set_error (encoder, error);
 		rb_debug ("received error %s", string);
 		g_error_free (error);
 		g_free (string);
@@ -578,45 +628,6 @@ add_decoding_pipeline (RBEncoderGst *encoder,
 }
 
 static gboolean
-prompt_for_overwrite (GFile *file)
-{
-	GtkWidget *dialog;
-	GFileInfo *info;
-	gint response;
-	char *free_name;
-	const char *display_name;
-
-	free_name = NULL;
-	display_name = NULL;
-	info = g_file_query_info (file,
-				  G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
-				  G_FILE_QUERY_INFO_NONE,
-				  NULL,
-				  NULL);
-	if (info != NULL) {
-		display_name = g_file_info_get_display_name (info);
-	}
-
-	if (display_name == NULL) {
-		free_name = g_file_get_uri (file);
-		display_name = free_name;
-	}
-
-	dialog = gtk_message_dialog_new (NULL, 0,
-					 GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
-					 _("Do you want to overwrite the file \"%s\"?"),
-					 display_name);
-	response = gtk_dialog_run (GTK_DIALOG (dialog));
-	gtk_widget_destroy (dialog);
-	g_free (free_name);
-	if (info != NULL) {
-		g_object_unref (info);
-	}
-
-	return (response == GTK_RESPONSE_YES);
-}
-
-static gboolean
 attach_output_pipeline (RBEncoderGst *encoder,
 			GstElement *end,
 			const char *dest,
@@ -648,7 +659,7 @@ attach_output_pipeline (RBEncoderGst *encoder,
 			} else if (g_error_matches (local_error,
 						    G_IO_ERROR,
 						    G_IO_ERROR_EXISTS)) {
-				if (prompt_for_overwrite (file)) {
+				if (_rb_encoder_emit_overwrite (RB_ENCODER (encoder), file)) {
 					g_error_free (local_error);
 					stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error);
 					if (stream == NULL) {
@@ -692,7 +703,7 @@ attach_output_pipeline (RBEncoderGst *encoder,
 }
 
 static gboolean
-encoder_match_mime (RBEncoderGst *rbencoder, GstElement *encoder, const gchar *mime_type)
+encoder_match_media_type (RBEncoderGst *rbencoder, GstElement *encoder, const gchar *media_type)
 {
 	GstPad *srcpad;
 	GstCaps *element_caps = NULL;
@@ -709,15 +720,15 @@ encoder_match_mime (RBEncoderGst *rbencoder, GstElement *encoder, const gchar *m
 		goto end;
 	}
 
-	desired_caps = g_hash_table_lookup (RB_ENCODER_GST_GET_CLASS (rbencoder)->mime_caps_table, mime_type);
+	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 (mime_type, NULL);
+		desired_caps = gst_caps_new_simple (media_type, NULL);
 	}
 
 	if (desired_caps == NULL) {
-		g_warning ("couldn't create any desired caps for mimetype: %s", mime_type);
+		g_warning ("couldn't create any desired caps for media type: %s", media_type);
 		goto end;
 	}
 
@@ -791,8 +802,52 @@ profile_bin_find_encoder (GstBin *profile_bin)
 	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_mime_type (RBEncoderGst *rbencoder, const char *mime_type)
+get_profile_from_media_type (RBEncoderGst *rbencoder, const char *media_type)
 {
 	GList *profiles, *walk;
 	gchar *pipeline_description;
@@ -802,7 +857,7 @@ get_profile_from_mime_type (RBEncoderGst *rbencoder, const char *mime_type)
 	GMAudioProfile *matching_profile = NULL;
 	GError *error = NULL;
 
-	rb_debug ("Looking up profile for mimetype '%s'", mime_type);
+	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)) {
@@ -824,7 +879,7 @@ get_profile_from_mime_type (RBEncoderGst *rbencoder, const char *mime_type)
 			continue;
 		}
 
-		if (encoder_match_mime (rbencoder, encoder, mime_type)) {
+		if (encoder_match_media_type (rbencoder, encoder, media_type)) {
 			matching_profile = profile;
 			gst_object_unref (GST_OBJECT (encoder));
 			gst_object_unref (GST_OBJECT (pipeline));
@@ -842,28 +897,6 @@ get_profile_from_mime_type (RBEncoderGst *rbencoder, const char *mime_type)
 	return matching_profile;
 }
 
-static GMAudioProfile*
-get_profile_from_mime_types (RBEncoderGst *rbencoder, GList *mime_types)
-{
-	GMAudioProfile *profile = NULL;
-	GList *l;
-
-	if (mime_types == NULL) {
-		const char *profile_name;
-
-		profile_name = eel_gconf_get_string (CONF_LIBRARY_PREFERRED_FORMAT);
-		profile = gm_audio_profile_lookup (profile_name);
-	} else {
-		for (l = mime_types; l != NULL; l = g_list_next (l)) {
-			profile = get_profile_from_mime_type (rbencoder, (const char *)l->data);
-			if (profile != NULL)
-				break;
-		}
-	}
-
-	return profile;
-}
-
 static GstElement *
 create_pipeline_and_source (RBEncoderGst *encoder,
 			    RhythmDBEntry *entry,
@@ -926,7 +959,6 @@ static gboolean
 transcode_track (RBEncoderGst *encoder,
 	 	 RhythmDBEntry *entry,
 		 const char *dest,
-		 GList *mime_types,
 		 GError **error)
 {
 	/* src ! decodebin ! queue ! encoding_profile ! queue ! sink */
@@ -934,19 +966,21 @@ transcode_track (RBEncoderGst *encoder,
 	GstElement *src, *decoder, *end;
 
 	g_assert (encoder->priv->pipeline == NULL);
+	g_assert (encoder->priv->dest_mediatype != NULL);
 
-	profile = get_profile_from_mime_types (encoder, mime_types);
+	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 mime-type "
-			     /*"'%s'", mime_type*/);
+			     "Unable to locate encoding profile for media-type %s",
+			     encoder->priv->dest_mediatype);
 		goto error;
-	} else {
-		rb_debug ("selected profile %s", gm_audio_profile_get_name (profile));
 	}
 
+	rb_debug ("selected profile %s", gm_audio_profile_get_name (profile));
+
 	src = create_pipeline_and_source (encoder, entry, error);
 	if (src == NULL)
 		goto error;
@@ -1009,80 +1043,52 @@ static gboolean
 rb_encoder_gst_encode (RBEncoder *encoder,
 		       RhythmDBEntry *entry,
 		       const char *dest,
-		       GList *mime_types)
+		       const char *dest_media_type)
 {
 	RBEncoderGstPrivate *priv = RB_ENCODER_GST (encoder)->priv;
-	const char *entry_mime_type;
-	gboolean copy;
-	gboolean was_raw;
+	const char *entry_media_type;
 	gboolean result;
 	GError *error = NULL;
 
 	g_return_val_if_fail (priv->pipeline == NULL, FALSE);
+	g_return_val_if_fail (dest_media_type != NULL, FALSE);
 
-	entry_mime_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
-	was_raw = g_str_has_prefix (entry_mime_type, "audio/x-raw");
-
-	/* hackish mapping of gstreamer media types to mime types; this
-	 * should be easier when we do proper (deep) typefinding.
-	 */
-	if (rb_safe_strcmp (entry_mime_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_mime_type = "audio/mpeg";
-	} else if (rb_safe_strcmp (entry_mime_type, "application/x-id3") == 0) {
-		entry_mime_type = "audio/mpeg";
-	} else if (rb_safe_strcmp (entry_mime_type, "audio/x-flac") == 0) {
-		entry_mime_type = "audio/flac";
-	}
+	entry_media_type = get_entry_media_type (entry);
 
 	if (rb_uri_create_parent_dirs (dest, &error) == FALSE) {
+		RBEncoderGst *gencoder = RB_ENCODER_GST (encoder);
 		error = g_error_new_literal (RB_ENCODER_ERROR,
 					     RB_ENCODER_ERROR_FILE_ACCESS,
 					     error->message);		/* I guess */
 
-		_rb_encoder_emit_error (encoder, error);
-		_rb_encoder_emit_completed (encoder, 0);
+		set_error (gencoder, error);
+		_rb_encoder_emit_completed (encoder, 0, NULL, gencoder->priv->error);
 		g_error_free (error);
 		return FALSE;
 	}
 
-	if (mime_types == NULL) {
-		/* don't copy raw audio */
-		copy = !was_raw;
-	} else {
-		GList *l;
-
-		/* see if it's already in any of the destination formats */
-		copy = FALSE;
-		for (l = mime_types; l != NULL; l = g_list_next (l)) {
-			rb_debug ("Comparing mimetypes '%s' '%s'", entry_mime_type, (char *)l->data);
-			if (rb_safe_strcmp (entry_mime_type, l->data) == 0) {
-				rb_debug ("Matched mimetypes '%s' '%s'", entry_mime_type, (char *)l->data);
-
-				copy = TRUE;
-				break;
-			}
-		}
-	}
-
 	priv->dest_uri = g_strdup (dest);
-	if (copy) {
+
+	/* 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);
 		priv->total_length = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
 		priv->position_format = GST_FORMAT_BYTES;
 
 		result = copy_track (RB_ENCODER_GST (encoder), entry, dest, &error);
+		priv->dest_mediatype = entry_media_type;
 	} else {
 		priv->total_length = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
 		priv->position_format = GST_FORMAT_TIME;
+		priv->dest_mediatype = dest_media_type;		/* hmm, need to strdup it? */
 
-		result = transcode_track (RB_ENCODER_GST (encoder), entry, dest, mime_types, &error);
+		result = transcode_track (RB_ENCODER_GST (encoder), entry, dest, &error);
 	}
 
 	if (error) {
 		RBEncoderGst *enc = RB_ENCODER_GST (encoder);
 
-		rb_encoder_gst_emit_error (enc, error);
+		set_error (enc, error);
 		g_error_free (error);
 		if (enc->priv->pipeline == NULL) {
 			rb_encoder_gst_emit_completed (enc);
@@ -1096,26 +1102,80 @@ rb_encoder_gst_encode (RBEncoder *encoder,
 }
 
 static gboolean
-rb_encoder_gst_get_preferred_mimetype (RBEncoder *encoder,
-				       GList *mime_types,
-				       char **mime,
-				       char **extension)
+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;
 
-	g_return_val_if_fail (mime_types != NULL, FALSE);
-	g_return_val_if_fail (mime != NULL, FALSE);
-	g_return_val_if_fail (extension != NULL, FALSE);
+	src_media_type = get_entry_media_type (entry);
+	g_return_val_if_fail (src_media_type != NULL, FALSE);
 
-	for (l = mime_types; l != NULL; l = g_list_next (l)) {
+	if (media_type != NULL)
+		*media_type = NULL;
+	if (extension != NULL)
+		*extension = NULL;
+
+
+	/* 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")) {
+			const char *profile_name = 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 (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);
+		}
+		return TRUE;
+	}
+
+	/* 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);
+		profile = get_profile_from_media_type (RB_ENCODER_GST (encoder), src_media_type);
+		if (profile) {
+			if (extension != NULL)
+				*extension = g_strdup (gm_audio_profile_get_extension (profile));
+			g_object_unref (profile);
+		}
+		return TRUE;
+	}
+
+	/* 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 *mimetype;
+		const char *mt;
 
-		mimetype = (const char *)l->data;
-		profile = get_profile_from_mime_type (RB_ENCODER_GST (encoder), mimetype);
+		mt = (const char *)l->data;
+		profile = get_profile_from_media_type (RB_ENCODER_GST (encoder), mt);
 		if (profile) {
-			*extension = g_strdup (gm_audio_profile_get_extension (profile));
-			*mime = g_strdup (mimetype);
+			rb_debug ("selected destination media type %s", mt);
+			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;
 		}
@@ -1123,3 +1183,62 @@ rb_encoder_gst_get_preferred_mimetype (RBEncoder *encoder,
 
 	return FALSE;
 }
+
+static int
+add_element_if_missing (char ***details, int index, const char *element_name)
+{
+	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;
+}
+
+static gboolean
+rb_encoder_gst_get_missing_plugins (RBEncoder *encoder,
+				    const char *media_type,
+				    char ***details)
+{
+	/*
+	 * 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)
+	 */
+
+	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;
+}
diff --git a/backends/gstreamer/rb-encoder-gst.h b/backends/gstreamer/rb-encoder-gst.h
index bd3d46a..985b730 100644
--- a/backends/gstreamer/rb-encoder-gst.h
+++ b/backends/gstreamer/rb-encoder-gst.h
@@ -48,7 +48,7 @@ typedef struct
 {
 	GObjectClass obj_class;
 
-	GHashTable *mime_caps_table;
+	GHashTable *media_caps_table;
 } RBEncoderGstClass;
 
 typedef struct
diff --git a/backends/rb-encoder.c b/backends/rb-encoder.c
index a533a62..bc1f60a 100644
--- a/backends/rb-encoder.c
+++ b/backends/rb-encoder.c
@@ -37,7 +37,9 @@
  * @short_description: audio transcoder interface
  *
  * The RBEncoder interface provides transcoding between audio formats based on
- * MIME types.
+ * 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
@@ -53,9 +55,9 @@ static void rb_encoder_factory_init       (RBEncoderFactory *encoder);
 enum {
 	PROGRESS,
 	COMPLETED,
-	ERROR,
 	PREPARE_SOURCE,		/* this is on RBEncoderFactory */
 	PREPARE_SINK,		/* this is on RBEncoderFactory */
+	OVERWRITE,
 	LAST_SIGNAL
 };
 
@@ -140,9 +142,13 @@ rb_encoder_interface_init (RBEncoderIface *iface)
 	/**
 	 * RBEncoder::completed:
 	 * @encoder: the #RBEncoder instance
+	 * @dest_size: size of the output file
+	 * @mediatype: output media type
+	 * @error: encoding error, or NULL if successful
 	 * 
-	 * Emitted when the encoding process is complete.  The destination file
-	 * will be closed and flushed to disk when this occurs.
+	 * Emitted when the encoding process is complete, or when a fatal error
+	 * has occurred.  The destination file, if one exists,  will be closed
+	 * and flushed to disk before this signal is emitted.
 	 */
 	signals[COMPLETED] =
 		g_signal_new ("completed",
@@ -150,26 +156,27 @@ rb_encoder_interface_init (RBEncoderIface *iface)
 			      G_SIGNAL_RUN_LAST,
 			      G_STRUCT_OFFSET (RBEncoderIface, completed),
 			      NULL, NULL,
-			      rb_marshal_VOID__UINT64,
+			      rb_marshal_VOID__UINT64_STRING_POINTER,
 			      G_TYPE_NONE,
-			      1, G_TYPE_UINT64);
+			      3, G_TYPE_UINT64, G_TYPE_STRING, G_TYPE_POINTER);
 	/**
-	 * RBEncoder::error:
+	 * RBEncoder::overwrite:
 	 * @encoder: the #RBEncoder instance
-	 * @error: a #GError describing the error
+	 * @file: the #GFile that may be overwritten
 	 *
-	 * Emitted when an error occurs during encoding.
+	 * Emitted when a destination file already exists.  If the
+	 * return value if %TRUE, the file will be overwritten, otherwise
+	 * the transfer will be aborted.
 	 */
-	signals[ERROR] =
-		g_signal_new ("error",
+	signals[OVERWRITE] =
+		g_signal_new ("overwrite",
 			      G_TYPE_FROM_INTERFACE (iface),
 			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (RBEncoderIface, error),
-			      NULL, NULL,
-			      g_cclosure_marshal_VOID__POINTER,
-			      G_TYPE_NONE,
-			      1, G_TYPE_POINTER);
-
+			      G_STRUCT_OFFSET (RBEncoderIface, overwrite),
+			      NULL, NULL,		/* need an accumulator here? */
+			      rb_marshal_BOOLEAN__OBJECT,
+			      G_TYPE_BOOLEAN,
+			      1, G_TYPE_OBJECT);
 }
 
 GType
@@ -219,15 +226,14 @@ rb_encoder_factory_get ()
  * @encoder: the #RBEncoder
  * @entry: the #RhythmDBEntry to transcode
  * @dest: destination file URI
- * @mime_types: a #GList of target MIME types in order of preference
+ * @dest_media_type: destination media type, or NULL to just copy it
  *
- * Initiates encoding.  A target MIME type will be selected from the list
- * given.  If the source format is in the list, that will be chosen regardless
- * of order.  Otherwise, the first type in the list that the encoder can produce
- * will be selected.
+ * 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.
  *
  * Encoding takes places asynchronously.  If the return value is TRUE, the caller
- * should wait for a 'completed' or 'error' signal to indicate that it has finished.
+ * should wait for a 'completed' signal to indicate that it has finished.
  *
  * Return value: TRUE if encoding has started
  */
@@ -235,11 +241,11 @@ gboolean
 rb_encoder_encode (RBEncoder *encoder,
 		   RhythmDBEntry *entry,
 		   const char *dest,
-		   GList *mime_types)
+		   const char *dest_media_type)
 {
 	RBEncoderIface *iface = RB_ENCODER_GET_IFACE (encoder);
 
-	return iface->encode (encoder, entry, dest, mime_types);
+	return iface->encode (encoder, entry, dest, dest_media_type);
 }
 
 /**
@@ -247,7 +253,8 @@ rb_encoder_encode (RBEncoder *encoder,
  * @encoder: a #RBEncoder
  *
  * Attempts to cancel any in progress encoding.  The encoder should
- * delete the destination file, if it created one.
+ * delete the destination file, if it created one, and emit the
+ * 'completed' signal.
  */
 void
 rb_encoder_cancel (RBEncoder *encoder)
@@ -258,27 +265,49 @@ rb_encoder_cancel (RBEncoder *encoder)
 }
 
 /**
- * rb_encoder_get_preferred_mimetype:
+ * rb_encoder_get_media_type:
  * @encoder: a #RBEncoder
- * @mime_types: a #GList of MIME type strings in order of preference
- * @mime: returns the selected MIME type, if any
- * @extension: returns the file extension associated with the selected MIME type, if any
+ * @entry: the source #RhythmDBEntry
+ * @dest_media_types: a #GList of media type strings in order of preference
+ * @media_type: returns the selected media type, if any
+ * @extension: returns the file extension associated with the selected media type, if any
  *
- * Identifies the first MIME type in the list that the encoder can actually encode to.
+ * 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_preferred_mimetype (RBEncoder *encoder,
-				   GList *mime_types,
-				   char **mime,
-				   char **extension)
+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_preferred_mimetype (encoder, mime_types, mime, extension);
+	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
+ * @details: returns plugin installer detail strings
+ *
+ * Retrieves the plugin installer detail strings for any missing plugins
+ * required to encode the specified media type.
+ *
+ * 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)
+{
+	RBEncoderIface *iface = RB_ENCODER_GET_IFACE (encoder);
+	return iface->get_missing_plugins (encoder, media_type, details);
 }
 
 /**
@@ -301,15 +330,17 @@ _rb_encoder_emit_progress (RBEncoder *encoder, double fraction)
 }
 
 void
-_rb_encoder_emit_completed (RBEncoder *encoder, guint64 dest_size)
+_rb_encoder_emit_completed (RBEncoder *encoder, guint64 dest_size, const char *mediatype, GError *error)
 {
-	g_signal_emit (encoder, signals[COMPLETED], 0, dest_size);
+	g_signal_emit (encoder, signals[COMPLETED], 0, dest_size, mediatype, error);
 }
 
-void
-_rb_encoder_emit_error (RBEncoder *encoder, GError *error)
+gboolean
+_rb_encoder_emit_overwrite (RBEncoder *encoder, GFile *file)
 {
-	g_signal_emit (encoder, signals[ERROR], 0, error);
+	gboolean ret = FALSE;
+	g_signal_emit (encoder, signals[OVERWRITE], 0, file, &ret);
+	return ret;
 }
 
 void
@@ -334,3 +365,25 @@ rb_encoder_error_quark (void)
 	return quark;
 }
 
+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
+
+GType
+rb_encoder_error_get_type (void)
+{
+	static GType etype = 0;
+
+	if (etype == 0)	{
+		static const GEnumValue values[] = {
+			ENUM_ENTRY (RB_ENCODER_ERROR_FORMAT_UNSUPPORTED, "Unable to find a supported destination format"),
+			ENUM_ENTRY (RB_ENCODER_ERROR_INTERNAL, "Internal encoder error"),
+			ENUM_ENTRY (RB_ENCODER_ERROR_FILE_ACCESS, "Unable to write to destination file"),
+			ENUM_ENTRY (RB_ENCODER_ERROR_OUT_OF_SPACE, "Not enough space to write destination file"),
+			ENUM_ENTRY (RB_ENCODER_ERROR_DEST_READ_ONLY, "Destination is read-only"),
+			{ 0, 0, 0 }
+		};
+
+		etype = g_enum_register_static ("RBPlayerError", values);
+	}
+
+	return etype;
+}
diff --git a/backends/rb-encoder.h b/backends/rb-encoder.h
index 52f697f..e2f9310 100644
--- a/backends/rb-encoder.h
+++ b/backends/rb-encoder.h
@@ -52,10 +52,13 @@ enum
 	RB_ENCODER_ERROR_FORMAT_UNSUPPORTED,
 	RB_ENCODER_ERROR_INTERNAL,
 	RB_ENCODER_ERROR_FILE_ACCESS,
+	RB_ENCODER_ERROR_OUT_OF_SPACE,
+	RB_ENCODER_ERROR_DEST_READ_ONLY
 };
 
+GType rb_encoder_error_get_type (void);
+#define RB_TYPE_ENCODER_ERROR	(rb_encoer_error_get_type())
 #define RB_ENCODER_ERROR rb_encoder_error_quark ()
-
 GQuark rb_encoder_error_quark (void);
 
 typedef struct _RBEncoder RBEncoder;
@@ -71,17 +74,21 @@ struct _RBEncoderIface
 	gboolean	(*encode)	(RBEncoder *encoder,
 					 RhythmDBEntry *entry,
 					 const char *dest,
-					 GList *mime_types);
+					 const char *dest_media_type);
 	void		(*cancel)	(RBEncoder *encoder);
-	gboolean	(*get_preferred_mimetype)	(RBEncoder *encoder,
-							 GList *mime_types,
-							 char **mime,
-							 char **extension);
+	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);
 
 	/* signals */
 	void (*progress) (RBEncoder *encoder,  double fraction);
-	void (*completed) (RBEncoder *encoder, guint64 dest_size);
-	void (*error) (RBEncoder *encoder, GError *error);
+	gboolean (*overwrite) (RBEncoder *encoder, GFile *file);
+	void (*completed) (RBEncoder *encoder, guint64 dest_size, const char *mediatype, GError *error);
 };
 
 struct _RBEncoderFactoryClass
@@ -108,18 +115,22 @@ GType 		rb_encoder_get_type 	(void);
 gboolean	rb_encoder_encode	(RBEncoder *encoder,
 					 RhythmDBEntry *entry,
 					 const char *dest,
-					 GList *mime_types);
+					 const char *dest_media_type);
 void		rb_encoder_cancel	(RBEncoder *encoder);
 
-gboolean	rb_encoder_get_preferred_mimetype (RBEncoder *encoder,
-						   GList *mime_types,
-						   char **mime,
-						   char **extension);
+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);
 
 /* only to be used by subclasses */
 void	_rb_encoder_emit_progress (RBEncoder *encoder, double fraction);
-void	_rb_encoder_emit_completed (RBEncoder *encoder, guint64 dest_size);
-void	_rb_encoder_emit_error (RBEncoder *encoder, GError *error);
+void	_rb_encoder_emit_completed (RBEncoder *encoder, guint64 dest_size, const char *mediatype, GError *error);
+gboolean _rb_encoder_emit_overwrite (RBEncoder *encoder, GFile *file);
 
 void	_rb_encoder_emit_prepare_source (RBEncoder *encoder, const char *uri, GObject *source);
 void	_rb_encoder_emit_prepare_sink (RBEncoder *encoder, const char *uri, GObject *sink);
diff --git a/lib/rb-marshal.list b/lib/rb-marshal.list
index 7234923..0c103f0 100644
--- a/lib/rb-marshal.list
+++ b/lib/rb-marshal.list
@@ -1,6 +1,7 @@
 BOOLEAN:BOOLEAN,BOOLEAN
 BOOLEAN:BOOLEAN,BOOLEAN,BOOLEAN
 BOOLEAN:BOXED
+BOOLEAN:OBJECT
 BOOLEAN:POINTER
 BOOLEAN:POINTER,POINTER,POINTER
 BOOLEAN:STRING,BOOLEAN
@@ -47,6 +48,7 @@ VOID:STRING,STRING,OBJECT
 VOID:STRING,STRING,STRING
 VOID:STRING,STRING,STRING,UINT,BOOLEAN
 VOID:UINT,STRING,STRING,OBJECT,BOOLEAN
+VOID:UINT64,STRING,POINTER
 VOID:ULONG,FLOAT
 VOID:OBJECT,BOOLEAN
 VOID:STRING,STRING,POINTER,POINTER
diff --git a/shell/rb-removable-media-manager.c b/shell/rb-removable-media-manager.c
index 14ae616..c410f76 100644
--- a/shell/rb-removable-media-manager.c
+++ b/shell/rb-removable-media-manager.c
@@ -990,7 +990,7 @@ progress_cb (RBEncoder *encoder, double fraction, TransferData *data)
 }
 
 static void
-completed_cb (RBEncoder *encoder, guint64 dest_size, TransferData *data)
+completed_cb (RBEncoder *encoder, guint64 dest_size, const char *media_type, GError *error, TransferData *data)
 {
 	RBRemovableMediaManagerPrivate *priv = GET_PRIVATE (data->manager);
 
@@ -1050,7 +1050,7 @@ do_transfer (RBRemovableMediaManager *manager)
 	rb_debug ("starting transfer of %s to %s",
 		  rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_LOCATION),
 		  data->dest);
-	if (rb_encoder_encode (encoder, data->entry, data->dest, data->mime_types) == FALSE) {
+	if (rb_encoder_encode (encoder, data->entry, data->dest, "application/ogg") == FALSE) {
 		rb_debug ("unable to start transfer");
 	}
 }
diff --git a/sources/rb-removable-media-source.c b/sources/rb-removable-media-source.c
index 7b32334..d56f8cf 100644
--- a/sources/rb-removable-media-source.c
+++ b/sources/rb-removable-media-source.c
@@ -417,7 +417,7 @@ impl_paste (RBSource *source, GList *entries)
 
 		mime_types = rb_removable_media_source_get_mime_types (RB_REMOVABLE_MEDIA_SOURCE (source));
 		if (mime_types != NULL && !rb_string_list_contains (mime_types, entry_mime)) {
-			if (!rb_encoder_get_preferred_mimetype (encoder, mime_types, &mimetype, &extension)) {
+			if (!rb_encoder_get_media_type (encoder, entry, mime_types, &mimetype, &extension)) {
 				rb_debug ("failed to find acceptable mime type for %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
 				goto impl_paste_end;
 			}



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