diff -rN -u old-meta/metadata/rb-metadata-dbus.c new-meta/metadata/rb-metadata-dbus.c
--- old-meta/metadata/rb-metadata-dbus.c	2007-02-10 22:32:39.000000000 +1000
+++ new-meta/metadata/rb-metadata-dbus.c	2007-02-10 22:32:39.000000000 +1000
@@ -1,5 +1,5 @@
 /*
- *  Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.hn.org>
+ *  Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.wh9.net>
  *
  *  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
@@ -72,19 +72,22 @@
 		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
 			DBUS_TYPE_UINT32_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
 		DBUS_DICT_ENTRY_END_CHAR_AS_STRING;
-	rb_debug ("opening container type %s", etype);
 	if (!dbus_message_iter_open_container (iter, DBUS_TYPE_ARRAY, etype, &a_iter)) {
 		return FALSE;
 	}
 
 	for (field = RB_METADATA_FIELD_TITLE; field < RB_METADATA_FIELD_LAST; field++) {
 		GType vtype = rb_metadata_get_field_type (field);
-		GValue v = {0,};
 		DBusMessageIter d_iter;
 		DBusMessageIter v_iter;
-		const char *v_sig = NULL;
+		DBusMessageIter e_iter;
+		const char *v_sig;
+		char *ae_sig;
+		GValueArray *va;
+		int vi;
 
-		if (!rb_metadata_get (md, field, &v))
+		va = rb_metadata_get_array (md, field);
+		if (va == NULL)
 			continue;
 
 		if (!dbus_message_iter_open_container (&a_iter, DBUS_TYPE_DICT_ENTRY, NULL, &d_iter)) {
@@ -105,48 +108,68 @@
 		case G_TYPE_STRING:
 			v_sig = DBUS_TYPE_STRING_AS_STRING;
 			break;
+		default:
+			g_assert_not_reached ();
 		}
-		if (!dbus_message_iter_open_container (&d_iter, DBUS_TYPE_VARIANT, v_sig, &v_iter)) {
+
+		ae_sig = g_strdup_printf ("%c%s", DBUS_TYPE_ARRAY, v_sig);
+
+		if (!dbus_message_iter_open_container (&d_iter, DBUS_TYPE_VARIANT, ae_sig, &v_iter)) {
+			g_free (ae_sig);
 			return FALSE;
 		}
+		g_free (ae_sig);
 
-		/* not exactly stolen from dbus-gvalue.c */
-		switch (vtype) {
-		case G_TYPE_ULONG:
+		if (!dbus_message_iter_open_container (&v_iter, DBUS_TYPE_ARRAY, v_sig, &e_iter)) {
+			return FALSE;
+		}
+
+		for (vi = 0; vi < va->n_values; vi++) {
+
+			GValue *v;
+			v = g_value_array_get_nth (va, vi);
+
+			/* not exactly stolen from dbus-gvalue.c */
+			switch (vtype) {
+			case G_TYPE_ULONG:
 			{
-				dbus_uint32_t n = g_value_get_ulong (&v);
-				if (!dbus_message_iter_append_basic (&v_iter, DBUS_TYPE_UINT32, &n)) {
+				dbus_uint32_t n = g_value_get_ulong (v);
+				if (!dbus_message_iter_append_basic (&e_iter, DBUS_TYPE_UINT32, &n)) {
 					return FALSE;
 				}
 				break;
 			}
 
-		case G_TYPE_DOUBLE:
+			case G_TYPE_DOUBLE:
 			{
-				double n = g_value_get_double (&v);
-				if (!dbus_message_iter_append_basic (&v_iter, DBUS_TYPE_DOUBLE, &n)) {
+				double n = g_value_get_double (v);
+				if (!dbus_message_iter_append_basic (&e_iter, DBUS_TYPE_DOUBLE, &n)) {
 					return FALSE;
 				}
 				break;
 			}
 
-		case G_TYPE_STRING:
+			case G_TYPE_STRING:
 			{
-				const char *s = g_value_get_string (&v);
+				const char *s = g_value_get_string (v);
 				if (!s)
 					s = "";
-				if (!dbus_message_iter_append_basic (&v_iter, DBUS_TYPE_STRING, &s)) {
+				if (!dbus_message_iter_append_basic (&e_iter, DBUS_TYPE_STRING, &s)) {
 					return FALSE;
 				}
 				break;
 			}
 
-		default:
-			g_assert_not_reached ();
-			break;
+			default:
+				g_assert_not_reached ();
+				break;
+			}
+		}
+
+		if (!dbus_message_iter_close_container (&v_iter, &e_iter)) {
+			return FALSE;
 		}
 
-		g_value_unset (&v);
 		if (!dbus_message_iter_close_container (&d_iter, &v_iter)) {
 			return FALSE;
 		}
@@ -184,59 +207,70 @@
 	}
 
 	while (current_type != DBUS_TYPE_INVALID) {
-		DBusMessageIter e_iter;
+		DBusMessageIter d_iter;
 		DBusMessageIter v_iter;
+		DBusMessageIter e_iter;
 		RBMetaDataField field;
-		GValue *val;
+		GValueArray *valarray;
 
-		dbus_message_iter_recurse (&a_iter, &e_iter);
+		dbus_message_iter_recurse (&a_iter, &d_iter);
 
-		if (!rb_metadata_dbus_get_uint32 (&e_iter, &field)) {
+		if (!rb_metadata_dbus_get_uint32 (&d_iter, &field)) {
 			return FALSE;
 		}
 
-		if (dbus_message_iter_get_arg_type (&e_iter) != DBUS_TYPE_VARIANT) {
+		if (dbus_message_iter_get_arg_type (&d_iter) != DBUS_TYPE_VARIANT) {
 			rb_debug ("Expected D-BUS variant type for value; got type '%c'",
-				  dbus_message_iter_get_arg_type (&e_iter));
+				  dbus_message_iter_get_arg_type (&d_iter));
 			return FALSE;
 		}
 
-		dbus_message_iter_recurse (&e_iter, &v_iter);
-		val = g_new0 (GValue, 1);
-		switch (dbus_message_iter_get_arg_type (&v_iter)) {
-		case DBUS_TYPE_UINT32:
+		valarray = g_value_array_new (0);
+		dbus_message_iter_recurse (&d_iter, &v_iter);
+		dbus_message_iter_recurse (&v_iter, &e_iter);
+		while (dbus_message_iter_get_arg_type (&e_iter) != DBUS_TYPE_INVALID) {
+			GValue val = {0,};
+
+			switch (dbus_message_iter_get_arg_type (&e_iter)) {
+			case DBUS_TYPE_UINT32:
 			{
 				dbus_uint32_t n;
-				dbus_message_iter_get_basic (&v_iter, &n);
-				g_value_init (val, G_TYPE_ULONG);
-				g_value_set_ulong (val, n);
+				dbus_message_iter_get_basic (&e_iter, &n);
+				g_value_init (&val, G_TYPE_ULONG);
+				g_value_set_ulong (&val, n);
 				break;
 			}
 
-		case DBUS_TYPE_DOUBLE:
+			case DBUS_TYPE_DOUBLE:
 			{
 				double n;
-				dbus_message_iter_get_basic (&v_iter, &n);
-				g_value_init (val, G_TYPE_DOUBLE);
-				g_value_set_double (val, n);
+				dbus_message_iter_get_basic (&e_iter, &n);
+				g_value_init (&val, G_TYPE_DOUBLE);
+				g_value_set_double (&val, n);
 				break;
 			}
 
-		case DBUS_TYPE_STRING:
+			case DBUS_TYPE_STRING:
 			{
 				const gchar *n;
-				dbus_message_iter_get_basic (&v_iter, &n);
-				g_value_init (val, G_TYPE_STRING);
-				g_value_set_string (val, n);
+				dbus_message_iter_get_basic (&e_iter, &n);
+				g_value_init (&val, G_TYPE_STRING);
+				g_value_set_string (&val, n);
 				break;
 			}
 
-		default:
-			g_assert_not_reached ();
-			break;
+			default:
+				g_assert_not_reached ();
+				break;
+			}
+
+			g_value_array_append (valarray, &val);
+			g_value_unset (&val);
+
+			dbus_message_iter_next (&e_iter);
 		}
 
-		g_hash_table_insert (metadata, GINT_TO_POINTER (field), val);
+		g_hash_table_insert (metadata, GINT_TO_POINTER (field), valarray);
 
 		dbus_message_iter_next (&a_iter);
 		current_type = dbus_message_iter_get_arg_type (&a_iter);
diff -rN -u old-meta/metadata/rb-metadata-dbus-client.c new-meta/metadata/rb-metadata-dbus-client.c
--- old-meta/metadata/rb-metadata-dbus-client.c	2007-02-10 22:32:39.000000000 +1000
+++ new-meta/metadata/rb-metadata-dbus-client.c	2007-02-10 22:32:39.000000000 +1000
@@ -1,5 +1,5 @@
 /*
- *  Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.hn.org>
+ *  Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.wh9.net>
  *
  *  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
@@ -61,16 +61,29 @@
 static void rb_metadata_init (RBMetaData *md);
 static void rb_metadata_finalize (GObject *object);
 
+struct MediaType {
+	char *container_type;
+	char *audio_type;
+	char *video_type;
+};
+
+static guint media_type_hash (struct MediaType *mt);
+static gboolean media_type_equal (struct MediaType *a, struct MediaType *b);
+static void media_type_free (struct MediaType *mt);
+
 static gboolean tried_env_address = FALSE;
 static DBusConnection *dbus_connection = NULL;
 static GPid metadata_child = 0;
 static GMainContext *main_context = NULL;
 static GStaticMutex conn_mutex = G_STATIC_MUTEX_INIT;
+static GHashTable *can_save_types = NULL;
 
 struct RBMetaDataPrivate
 {
 	char       *uri;
-	char       *mimetype;
+	char       *audio_type;
+	char       *video_type;
+	char       *container_type;
 	GHashTable *metadata;
 };
 
@@ -85,7 +98,12 @@
 
 	g_type_class_add_private (object_class, sizeof (RBMetaDataPrivate));
 
-	main_context = g_main_context_new ();	/* maybe not needed? */
+	main_context = g_main_context_new ();
+
+	can_save_types = g_hash_table_new_full ((GHashFunc) media_type_hash,
+						(GEqualFunc) media_type_equal,
+						(GDestroyNotify) media_type_free,
+						NULL);
 }
 
 static void
@@ -102,7 +120,9 @@
 	md = RB_METADATA (object);
 
 	g_free (md->priv->uri);
-	g_free (md->priv->mimetype);
+	g_free (md->priv->audio_type);
+	g_free (md->priv->video_type);
+	g_free (md->priv->container_type);
 	if (md->priv->metadata)
 		g_hash_table_destroy (md->priv->metadata);
 
@@ -339,8 +359,12 @@
 	if (error == NULL)
 		error = &fake_error;
 
-	g_free (md->priv->mimetype);
-	md->priv->mimetype = NULL;
+	g_free (md->priv->container_type);
+	g_free (md->priv->audio_type);
+	g_free (md->priv->video_type);
+	md->priv->container_type = NULL;
+	md->priv->audio_type = NULL;
+	md->priv->video_type = NULL;
 
 	g_free (md->priv->uri);
 	md->priv->uri = g_strdup (uri);
@@ -349,7 +373,7 @@
 
 	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);
+	md->priv->metadata = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_value_array_free);
 
 	g_static_mutex_lock (&conn_mutex);
 
@@ -402,15 +426,33 @@
 				     _("D-BUS communication error"));
 			rb_debug ("couldn't get success flag from response message");
 		} else if (ok) {
-			/* get mime type */
-			if (!rb_metadata_dbus_get_string (&iter, &md->priv->mimetype)) {
+			if (!rb_metadata_dbus_get_string (&iter, &md->priv->container_type) ||
+			    !rb_metadata_dbus_get_string (&iter, &md->priv->audio_type) ||
+			    !rb_metadata_dbus_get_string (&iter, &md->priv->video_type)) {
 				g_set_error (error,
 					     RB_METADATA_ERROR,
 					     RB_METADATA_ERROR_INTERNAL,
 					     _("D-BUS communication error"));
 			} else {
+				/* convert empty strings to nulls */
+				if (strlen (md->priv->container_type) == 0) {
+					g_free (md->priv->container_type);
+					md->priv->container_type = NULL;
+				}
+				if (strlen (md->priv->audio_type) == 0) {
+					g_free (md->priv->audio_type);
+					md->priv->audio_type = NULL;
+				}
+				if (strlen (md->priv->video_type) == 0) {
+					g_free (md->priv->video_type);
+					md->priv->video_type = NULL;
+				}
+
 				/* get metadata */
-				rb_debug ("got mimetype: %s", md->priv->mimetype);
+				rb_debug ("got container type: %s, audio type: %s; video type %s",
+					  md->priv->container_type,
+					  md->priv->audio_type,
+					  md->priv->video_type);
 				rb_metadata_dbus_read_from_message (md, md->priv->metadata, &iter);
 			}
 		} else {
@@ -429,49 +471,133 @@
 }
 
 const char *
-rb_metadata_get_mime (RBMetaData *md)
+rb_metadata_get_audio_type (RBMetaData *md)
+{
+	return md->priv->audio_type;
+}
+
+const char *
+rb_metadata_get_video_type (RBMetaData *md)
 {
-	return md->priv->mimetype;
+	return md->priv->video_type;
+}
+
+const char *
+rb_metadata_get_container_type (RBMetaData *md)
+{
+	return md->priv->container_type;
 }
 
 gboolean
 rb_metadata_get (RBMetaData *md, RBMetaDataField field,
 		 GValue *ret)
 {
+	GValueArray *valarray;
 	GValue *val;
-	if (!md->priv->metadata)
+
+	valarray = g_hash_table_lookup (md->priv->metadata, GINT_TO_POINTER (field));
+	if (valarray == NULL)
 		return FALSE;
 
-	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);
-		return TRUE;
-	}
-	return FALSE;
+	val = g_value_array_get_nth (valarray, 0);
+	g_value_init (ret, G_VALUE_TYPE (val));
+	g_value_copy (val, ret);
+	return TRUE;
 }
 
+GValueArray *
+rb_metadata_get_array (RBMetaData *md, RBMetaDataField field)
+{
+	return g_hash_table_lookup (md->priv->metadata, GINT_TO_POINTER (field));
+}
+
+
 gboolean
 rb_metadata_set (RBMetaData *md, RBMetaDataField field,
 		 const GValue *val)
 {
-	GValue *newval;
+	GValueArray *valarray;
 	GType type;
 
 	type = rb_metadata_get_field_type (field);
 	g_return_val_if_fail (type == G_VALUE_TYPE (val), FALSE);
 
-	newval = g_new0 (GValue, 1);
-	g_value_init (newval, type);
-	g_value_copy (val, newval);
+	valarray = g_value_array_new (1);
+
+	g_value_array_append (valarray, val);
+
+	g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (field), valarray);
+	return TRUE;
+}
+
+gboolean
+rb_metadata_set_array (RBMetaData *md, RBMetaDataField field, GValueArray *array)
+{
+	GType type;
+	int i;
+	GValueArray *v;
+
+	/* check all value types */
+	type = rb_metadata_get_field_type (field);
+	for (i = 0; i < array->n_values; i++) {
+		const GValue *cv;
 
-	g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (field),
-			     newval);
+		cv = g_value_array_get_nth (array, i);
+		g_return_val_if_fail (type == G_VALUE_TYPE (cv), FALSE);
+	}
+
+	v = g_value_array_copy (array);
+	g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (field), v);
 	return TRUE;
 }
 
+static guint
+media_type_hash (struct MediaType *mt)
+{
+	guint h = 0;
+	if (mt->container_type != NULL)
+		h += g_str_hash (mt->container_type);
+	if (mt->audio_type != NULL)
+		h += g_str_hash (mt->audio_type);
+	if (mt->video_type != NULL)
+		h += g_str_hash (mt->video_type);
+	return h;
+}
+
+static gboolean
+careful_str_equal (const char *a, const char *b)
+{
+	if (a == NULL && b == NULL) {
+		return TRUE;
+	} else if (a == NULL || b == NULL) {
+		return FALSE;
+	} else {
+		return g_str_equal (a, b);
+	}
+}
+
+static gboolean
+media_type_equal (struct MediaType *a, struct MediaType *b)
+{
+	return careful_str_equal (a->container_type, b->container_type) &&
+	       careful_str_equal (a->audio_type, b->audio_type) &&
+	       careful_str_equal (a->video_type, b->video_type);
+}
+
+static void
+media_type_free (struct MediaType *mt)
+{
+	g_free (mt->container_type);
+	g_free (mt->audio_type);
+	g_free (mt->video_type);
+	g_free (mt);
+}
+
 gboolean
-rb_metadata_can_save (RBMetaData *md, const char *mimetype)
+rb_metadata_can_save (RBMetaData *md,
+		      const char *container_type,
+		      const char *audio_type,
+		      const char *video_type)
 {
 	GError *error = NULL;
 	DBusMessage *message = NULL;
@@ -480,9 +606,41 @@
 	DBusError dbus_error = {0,};
 	DBusMessageIter iter;
 	gboolean ok = TRUE;
+	struct MediaType lookup = {0,};
+	struct MediaType *cache;
+	enum {
+		UNKNOWN = 0,
+		CAN_SAVE,
+		CANNOT_SAVE
+	} result;
 
 	g_static_mutex_lock (&conn_mutex);
 
+	if (container_type == NULL)
+		container_type = "";
+	if (audio_type == NULL)
+		audio_type = "";
+	if (video_type == NULL)
+		video_type = "";
+
+	/* check for a cached result */
+	lookup.container_type = (char *)container_type;
+	lookup.audio_type = (char *)audio_type;
+	lookup.video_type = (char *)video_type;
+
+	result = GPOINTER_TO_INT (g_hash_table_lookup (can_save_types, &lookup));
+	switch (result) {
+	case UNKNOWN:
+		break;
+	case CAN_SAVE:
+	case CANNOT_SAVE:
+		rb_debug ("cache says we %s save [%s, %s, %s] media type",
+			  result == CAN_SAVE ? "can" : "can't",
+			  container_type, audio_type, video_type);
+		g_static_mutex_unlock (&conn_mutex);
+		return (result == CAN_SAVE);
+	}
+
 	if (start_metadata_service (&error) == FALSE) {
 		g_error_free (error);
 		ok = FALSE;
@@ -495,7 +653,11 @@
 							"canSave");
 		if (!message) {
 			ok = FALSE;
-		} else if (!dbus_message_append_args (message, DBUS_TYPE_STRING, &mimetype, DBUS_TYPE_INVALID)) {
+		} else if (!dbus_message_append_args (message,
+						      DBUS_TYPE_STRING, &container_type,
+						      DBUS_TYPE_STRING, &audio_type,
+						      DBUS_TYPE_STRING, &video_type,
+						      DBUS_TYPE_INVALID)) {
 			ok = FALSE;
 		}
 	}
@@ -510,6 +672,18 @@
 			ok = FALSE;
 		} else if (dbus_message_iter_init (response, &iter)) {
 			rb_metadata_dbus_get_boolean (&iter, &can_save);
+
+			/* cache the result */
+			result = can_save ? CAN_SAVE : CANNOT_SAVE;
+			rb_debug ("caching can_save result:  we %s save [%s, %s, %s] media type",
+				  result == CAN_SAVE ? "can" : "can't",
+				  container_type, audio_type, video_type);
+
+			cache = g_new0 (struct MediaType, 1);
+			cache->container_type = g_strdup (container_type);
+			cache->audio_type = g_strdup (audio_type);
+			cache->video_type = g_strdup (video_type);
+			g_hash_table_insert (can_save_types, cache, GINT_TO_POINTER (result));
 		}
 	}
 
@@ -517,6 +691,7 @@
 		dbus_message_unref (message);
 	if (response)
 		dbus_message_unref (response);
+
 	g_static_mutex_unlock (&conn_mutex);
 
 	return can_save;
diff -rN -u old-meta/metadata/rb-metadata-dbus.h new-meta/metadata/rb-metadata-dbus.h
--- old-meta/metadata/rb-metadata-dbus.h	2007-02-10 22:32:39.000000000 +1000
+++ new-meta/metadata/rb-metadata-dbus.h	2007-02-10 22:32:39.000000000 +1000
@@ -1,5 +1,5 @@
 /*
- *  Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.hn.org>
+ *  Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.wh9.net>
  *
  *  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
diff -rN -u old-meta/metadata/rb-metadata-dbus-service.c new-meta/metadata/rb-metadata-dbus-service.c
--- old-meta/metadata/rb-metadata-dbus-service.c	2007-02-10 22:32:39.000000000 +1000
+++ new-meta/metadata/rb-metadata-dbus-service.c	2007-02-10 22:32:39.000000000 +1000
@@ -1,5 +1,5 @@
 /*
- *  Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.hn.org>
+ *  Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.wh9.net>
  *
  *  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
@@ -99,7 +99,9 @@
 	DBusMessage *reply;
 	GError *error = NULL;
 	gboolean ok = TRUE;
-	const char *mimetype = NULL;
+	const char *audio_type = NULL;
+	const char *video_type = NULL;
+	const char *container_type = NULL;
 
 	if (!dbus_message_iter_init (message, &iter)) {
 		return DBUS_HANDLER_RESULT_NEED_MEMORY;
@@ -124,7 +126,6 @@
 		g_clear_error (&error);
 		return r;
 	}
-	rb_debug ("metadata load finished; mimetype = %s", rb_metadata_get_mime (svc->metadata));
 
 	/* construct reply */
 	reply = dbus_message_new_method_return (message);
@@ -133,11 +134,25 @@
 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 	}
 
-	mimetype = rb_metadata_get_mime (svc->metadata);
-	dbus_message_iter_init_append (reply, &iter);
+	audio_type = rb_metadata_get_audio_type (svc->metadata);
+	video_type = rb_metadata_get_video_type (svc->metadata);
+	container_type = rb_metadata_get_container_type (svc->metadata);
+
+	if (audio_type == NULL)
+		audio_type = "";
+	if (video_type == NULL)
+		video_type = "";
+	if (container_type == NULL)
+		container_type = "";
+
+	rb_debug ("metadata load finished; container type = %s; audio type = %s, video type = %s",
+		  container_type, audio_type, video_type);
 
+	dbus_message_iter_init_append (reply, &iter);
 	if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_BOOLEAN, &ok) ||
-	    !dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &mimetype)) {
+	    !dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &container_type) ||
+	    !dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &audio_type) ||
+	    !dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &video_type)) {
 		rb_debug ("out of memory adding data to return message");
 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 	}
@@ -159,12 +174,23 @@
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
 
+static void
+null_if_empty (char **str)
+{
+	if (strlen (*str) == 0) {
+		g_free (*str);
+		*str = NULL;
+	}
+}
+
 static DBusHandlerResult
 rb_metadata_dbus_can_save (DBusConnection *connection,
 			   DBusMessage *message,
 			   ServiceData *svc)
 {
-	char *mimetype;
+	char *container_type;
+	char *audio_type;
+	char *video_type;
 	DBusMessageIter iter;
 	DBusMessage *reply;
 	gboolean can_save;
@@ -173,15 +199,28 @@
 		return DBUS_HANDLER_RESULT_NEED_MEMORY;
 	}
 
-	if (!rb_metadata_dbus_get_string (&iter, &mimetype)) {
+	if (!rb_metadata_dbus_get_string (&iter, &container_type) ||
+	    !rb_metadata_dbus_get_string (&iter, &audio_type) ||
+	    !rb_metadata_dbus_get_string (&iter, &video_type)) {
 		/* make translatable? */
 		return _send_error (connection, message, TRUE,
 				    RB_METADATA_ERROR_INTERNAL,
 				    "Unable to read MIME type from request");
 	}
 
-	can_save = rb_metadata_can_save (svc->metadata, mimetype);
-	g_free (mimetype);
+	null_if_empty (&container_type);
+	null_if_empty (&audio_type);
+	null_if_empty (&video_type);
+
+	rb_debug ("checking if we can save [%s,%s,%s]", container_type, audio_type, video_type);
+
+	can_save = rb_metadata_can_save (svc->metadata,
+					 container_type,
+					 audio_type,
+					 video_type);
+	g_free (container_type);
+	g_free (audio_type);
+	g_free (video_type);
 
 	/* construct reply */
 	reply = dbus_message_new_method_return (message);
@@ -207,10 +246,10 @@
 }
 
 static gboolean
-_set_metadata (gpointer key, GValue *data, RBMetaData *metadata)
+_set_metadata (gpointer key, GValueArray *data, RBMetaData *metadata)
 {
 	RBMetaDataField field = GPOINTER_TO_INT (key);
-	rb_metadata_set (metadata, field, data);
+	rb_metadata_set_array (metadata, field, data);
 	return TRUE;
 }
 
@@ -228,7 +267,7 @@
 	if (!dbus_message_iter_init (message, &iter)) {
 		return DBUS_HANDLER_RESULT_NEED_MEMORY;
 	}
-	data = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)rb_value_free);
+	data = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_value_array_free);
 	if (!rb_metadata_dbus_read_from_message (svc->metadata,
 						 data,
 						 &iter)) {
@@ -367,14 +406,16 @@
 }
 
 static int
-test_can_save (const char *mimetype)
+test_can_save (const char *container_type,
+	       const char *audio_type,
+	       const char *video_type)
 {
 	RBMetaData *md;
 	gboolean can_save;
 
 	md = rb_metadata_new ();
-	can_save = rb_metadata_can_save (md, mimetype);
-	g_print ("%s save %s\n", can_save ? "Can" : "Can't", mimetype);
+	can_save = rb_metadata_can_save (md, container_type, audio_type, video_type);
+	g_print ("%s save %s/%s/%s\n", can_save ? "Can" : "Can't", container_type, audio_type, video_type);
 	g_object_unref (G_OBJECT (md));
 	return 0;
 }
@@ -389,10 +430,10 @@
 	md = rb_metadata_new ();
 	rb_metadata_load (md, uri, &error);
 	if (error) {
-		if (error->code == RB_METADATA_ERROR_NOT_AUDIO_IGNORE) {
-			g_print ("%s is not an audio stream (ignored)\n", uri);
-		} else if (error->code == RB_METADATA_ERROR_NOT_AUDIO) {
-			g_print ("%s is not an audio stream\n", uri);
+		if (error->code == RB_METADATA_ERROR_IGNORE) {
+			g_print ("%s is not an audio/video stream (ignored)\n", uri);
+		} else if (error->code == RB_METADATA_ERROR_NOT_AUDIO_OR_VIDEO) {
+			g_print ("%s is not an audio/video stream\n", uri);
 		} else {
 			g_print ("Error loading metadata from %s: %s\n", uri, error->message);
 		}
@@ -400,18 +441,24 @@
 		rv = -1;
 	} else {
 		int i;
-		g_print ("mimetype: %s\n", rb_metadata_get_mime (md));
+		g_print ("container type: %s\n", rb_metadata_get_container_type (md));
+		g_print ("audio type: %s\n", rb_metadata_get_audio_type (md));
+		g_print ("video type: %s\n", rb_metadata_get_video_type (md));
 		for (i=0; i<RB_METADATA_FIELD_LAST; i++) {
-			GValue v = {0,};
-			GValue sv = {0,};
-			if (rb_metadata_get (md, i, &v)) {
-				g_value_init (&sv, G_TYPE_STRING);
-				g_value_transform (&v, &sv);
+			GValueArray *va;
+			int j;
+
+			va = rb_metadata_get_array (md, i);
+			if (va != NULL) {
+				for (j=0; j < va->n_values; j++) {
+					GValue sv = {0,};
+					g_value_init (&sv, G_TYPE_STRING);
+					g_value_transform (g_value_array_get_nth (va, j), &sv);
 
-				g_print ("%s: %s\n", rb_metadata_get_field_name (i), g_value_get_string (&sv));
+					g_print ("%s: %s\n", rb_metadata_get_field_name (i), g_value_get_string (&sv));
 
-				g_value_unset (&v);
-				g_value_unset (&sv);
+					g_value_unset (&sv);
+				}
 			}
 		}
 	}
@@ -455,7 +502,9 @@
 		return test_load (argv[2]);
 	}
 	if (argv[1] != NULL && strcmp(argv[1], "--can-save") == 0) {
-		return test_can_save (argv[2]);
+		return test_can_save (argv[2][0] ? argv[2] : NULL,
+				      argv[2] && argv[3] ? argv[3][0] ? argv[3] : NULL : NULL,
+				      argv[2] && argv[3] && argv[4] ? argv[4][0] ? argv[4] : NULL : NULL);
 	}
 
 	if (argv[1] != NULL && strcmp (argv[1], "--external") == 0) {
diff -rN -u old-meta/metadata/rb-metadata-gst.c new-meta/metadata/rb-metadata-gst.c
--- old-meta/metadata/rb-metadata-gst.c	2007-02-10 22:32:39.000000000 +1000
+++ new-meta/metadata/rb-metadata-gst.c	2007-02-10 22:32:39.000000000 +1000
@@ -48,10 +48,14 @@
 
 static void rb_metadata_finalize (GObject *object);
 
+typedef gboolean (*RBCanTagFunc) (RBMetaData *,
+				  const char *container_type,
+				  const char *audio_type,
+				  const char *video_type);
 typedef GstElement *(*RBAddTaggerElem) (RBMetaData *, GstElement *);
 
 /*
- * The list of mine type prefixes for files that shouldn't display errors about being non-audio.
+ * The list of media type prefixes for files that shouldn't display errors about being non-audio.
  * Useful for people who have cover art, et cetera, in their music directories
  */
 const char * ignore_mime_types[] = {
@@ -70,9 +74,8 @@
 
 struct RBMetadataGstType
 {
-	char *mimetype;
+	RBCanTagFunc can_tag_func;
 	RBAddTaggerElem tag_func;
-	char *human_name;
 };
 
 struct RBMetaDataPrivate
@@ -83,17 +86,18 @@
 
 	GstElement *pipeline;
 	GstElement *sink;
-	gulong typefind_cb_id;
 	GstTagList *tags;
 
 	/* Array of RBMetadataGstType */
 	GPtrArray *supported_types;
 
-	char *type;
+	char *container_type;
+	char *audio_type;
+	char *video_type;
+	gboolean tag_precedence;
+
 	gboolean handoff;
 	gboolean eos;
-	gboolean non_audio;
-	gboolean has_video;
 	GError *error;
 };
 
@@ -134,6 +138,19 @@
 #endif
 }
 
+static gboolean
+flac_can_tag (RBMetaData *md, const char *container_type, const char *audio_type, const char *video_type)
+{
+	if (video_type != NULL)
+		return FALSE;
+
+	/* what possible container types are there for flac? */
+	if (container_type != NULL)
+		return FALSE;
+
+	return (audio_type != NULL && strcmp (audio_type, "audio/x-flac") == 0);
+}
+
 static GstElement *
 rb_add_flac_tagger (RBMetaData *md, GstElement *element)
 {
@@ -150,17 +167,76 @@
 	return tagger;
 }
 
+static gboolean
+id3_can_tag (RBMetaData *md, const char *container_type, const char *audio_type, const char *video_type)
+{
+	/* make sure we don't do anything silly to video files */
+	if (video_type != NULL) {
+		rb_debug ("video type %s; can't save", video_type);
+		return FALSE;
+	}
+
+	/* check the container type.  we can't tag quicktime files, for instance */
+	if (container_type != NULL) {
+		if (strcmp (container_type, "application/x-id3") != 0 &&
+		    strcmp (container_type, "application/x-apetag") != 0) {
+			rb_debug ("container type %s; can't save", container_type);
+			return FALSE;
+		}
+	}
+
+	return (audio_type != NULL && strcmp (audio_type, "audio/mpeg") == 0);
+}
+
 #ifdef HAVE_GSTREAMER_0_10
+
 static void
-id3_pad_added_cb (GstElement *demux, GstPad *pad, GstElement *mux)
+id3_pad_added_cb (GstElement *demux, GstPad *pad, RBMetaData *md)
 {
-	GstPad *mux_pad;
+	GstPad *link_pad = NULL;
+	GstCaps *caps;
+	GstStructure *structure;
+	const char *mediatype;
+	GstBin *bin;
 
-	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 id3mux");
-	else
-		rb_debug ("linked pad from id3de to id3mux");
+	bin = GST_BIN (GST_ELEMENT_PARENT (demux));
+	caps = gst_pad_get_caps (pad);
+	structure = gst_caps_get_structure (caps, 0);
+	mediatype = gst_structure_get_name (structure);
+	rb_debug ("id3demux src pad has media type %s", mediatype);
+	if (strcmp (mediatype, "application/x-apetag") == 0) {
+		GstElement *apedemux;
+
+		apedemux = gst_bin_get_by_name (bin, "apedemux");
+		/* avoid recursive tags, just in case */
+		if (apedemux != NULL && apedemux != demux) {
+			link_pad = gst_element_get_pad (apedemux, "sink");
+		}
+
+		if (apedemux != NULL)
+			gst_object_unref (apedemux);
+	}
+	gst_caps_unref (caps);
+
+	if (link_pad == NULL) {
+		GstElement *id3mux;
+
+		id3mux = gst_bin_get_by_name (bin, "id3mux");
+		link_pad = gst_element_get_compatible_pad (id3mux, pad, NULL);
+		gst_object_unref (id3mux);
+	}
+
+	if (gst_pad_link (pad, link_pad) != GST_PAD_LINK_OK) {
+		rb_debug ("unable to link pad from %s to %s",
+			  gst_element_get_name (GST_PAD_PARENT (pad)),
+			  gst_element_get_name (GST_PAD_PARENT (link_pad)));
+	} else {
+		rb_debug ("linked pad from %s to %s",
+			  gst_element_get_name (GST_PAD_PARENT (pad)),
+			  gst_element_get_name (GST_PAD_PARENT (link_pad)));
+	}
+
+	gst_object_unref (link_pad);
 }
 
 static gboolean
@@ -193,10 +269,12 @@
 {
 	GstElement *demux = NULL;
 	GstElement *mux = NULL;
+	GstElement *apedemux = NULL;
 
 	demux = gst_element_factory_make ("id3demux", NULL);
+	apedemux = gst_element_factory_make ("apedemux", "apedemux");
 
-	mux = gst_element_factory_make ("id3v2mux", NULL);
+	mux = gst_element_factory_make ("id3v2mux", "id3mux");
 	if (mux != NULL) {
 		/* check for backwards id3v2mux merge-mode */
 		if (!rb_gst_plugin_greater ("taglib", "id3v2mux", 0, 10, 3)) {
@@ -206,7 +284,7 @@
 			rb_debug ("using id3v2mux");
 		}
 	} else {
-		mux =  gst_element_factory_make ("id3mux", NULL);
+		mux =  gst_element_factory_make ("id3mux", "id3mux");
 
 		/* check for backwards id3mux merge-mode */
 		if (mux && !rb_gst_plugin_greater ("mad", "id3mux", 0, 10, 3)) {
@@ -217,6 +295,9 @@
 		}
 	}
 
+	/* don't complain about apedemux == NULL,
+	 * as it probably doesn't matter anyway.
+	 */
 	if (demux == NULL || mux == NULL)
 		goto error;
 
@@ -224,18 +305,49 @@
 	if (!gst_element_link (element, demux))
 		goto error;
 
-	g_signal_connect (demux, "pad-added", (GCallback)id3_pad_added_cb, mux);
+	g_signal_connect (demux, "pad-added", (GCallback)id3_pad_added_cb, md);
+
+	if (apedemux != NULL) {
+		md->priv->tag_precedence = FALSE;
+		gst_bin_add (GST_BIN (md->priv->pipeline), apedemux);
+		g_signal_connect (apedemux, "pad-added", (GCallback)id3_pad_added_cb, md);
+	}
 
 	gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), md->priv->tags, GST_TAG_MERGE_REPLACE_ALL);
 
 	return mux;
 
 error:
-	g_object_unref (demux);
-	g_object_unref (mux);
+	if (apedemux != NULL)
+		g_object_unref (apedemux);
+	if (demux != NULL)
+		g_object_unref (demux);
+	if (mux != NULL)
+		g_object_unref (mux);
 	return NULL;
 }
 
+static gboolean
+ogg_can_tag (RBMetaData *md, const char *container_type, const char *audio_type, const char *video_type)
+{
+	/* don't do anything to video files */
+	if (video_type != NULL)
+		return FALSE;
+
+	/* temporary hack: pretend we have an application/ogg container
+	 * if no container type is specified.  this should mostly hold us
+	 * over until I figure out what to do with container types in rhythmdb.
+	 */
+	if (container_type == NULL)
+		container_type = "application/ogg";
+
+	/* require application/ogg container with audio/x-vorbis contents */
+	return (container_type != NULL && audio_type != NULL &&
+		strcmp (container_type, "application/ogg") == 0 &&
+		strcmp (audio_type, "audio/x-vorbis") == 0);
+}
+
+
 static void
 ogg_pad_added_cb (GstElement *demux, GstPad *pad, RBMetaData *md)
 {
@@ -359,14 +471,12 @@
 
 static void
 add_supported_type (RBMetaData *md,
-		    const char *mime,
-		    RBAddTaggerElem add_tagger_func,
-		    const char *human_name)
+		    RBCanTagFunc can_tag_func,
+		    RBAddTaggerElem add_tagger_func)
 {
 	struct RBMetadataGstType *type = g_new0 (struct RBMetadataGstType, 1);
-	type->mimetype = g_strdup (mime);
+	type->can_tag_func = can_tag_func;
 	type->tag_func = add_tagger_func;
-	type->human_name = g_strdup (human_name);
 	g_ptr_array_add (md->priv->supported_types, type);
 }
 
@@ -381,15 +491,6 @@
 
 	md->priv->supported_types = g_ptr_array_new ();
 
- 	/* the list of supported types serves two purposes:
- 	 * - it knows how to construct elements for tag writing
- 	 * - it knows human-readable names for MIME types so we can say
- 	 *     "There is no plugin available to play WMV files"
- 	 *     rather than " .. play video/x-ms-asf files".
- 	 *
- 	 * only registering types we have plugins for defeats the second
-	 * purpose.
- 	 */
 	has_gnomevfssink = (gst_element_factory_find ("gnomevfssrc") != NULL &&
 			    gst_element_factory_find ("gnomevfssink") != NULL);
 #ifdef HAVE_GSTREAMER_0_10
@@ -401,8 +502,7 @@
 #endif
 
 	tagger = (has_gnomevfssink && has_id3) ?  rb_add_id3_tagger : NULL;
-	add_supported_type (md, "application/x-id3", tagger, "MP3");
-	add_supported_type (md, "audio/mpeg", tagger, "MP3");
+	add_supported_type (md, id3_can_tag, tagger);
 
 #ifdef HAVE_GSTREAMER_0_10
 	{
@@ -417,15 +517,10 @@
 #else
 	tagger = NULL;
 #endif
-	add_supported_type (md, "application/ogg", tagger, "Ogg Vorbis");
-	add_supported_type (md, "audio/x-vorbis", tagger, "Ogg Vorbis");
-
-	add_supported_type (md, "audio/x-mod", NULL, "MOD");
-	add_supported_type (md, "audio/x-wav", NULL, "WAV");
-	add_supported_type (md, "video/x-ms-asf", NULL, "ASF");
+	add_supported_type (md, ogg_can_tag, tagger);
 
 	tagger = (has_gnomevfssink && gst_element_factory_find ("flactag")) ?  rb_add_flac_tagger : NULL;
-	add_supported_type (md, "audio/x-flac", tagger, "FLAC");
+	add_supported_type (md, flac_can_tag, tagger);
 
 }
 
@@ -439,8 +534,6 @@
 
 	for (i = 0; i < md->priv->supported_types->len; i++) {
 		struct RBMetadataGstType *type = g_ptr_array_index (md->priv->supported_types, i);
-		g_free (type->mimetype);
-		g_free (type->human_name);
 		g_free (type);
 	}
 	g_ptr_array_free (md->priv->supported_types, TRUE);
@@ -451,7 +544,10 @@
 	if (md->priv->pipeline)
 		gst_object_unref (GST_OBJECT (md->priv->pipeline));
 
-	g_free (md->priv->type);
+	g_free (md->priv->container_type);
+	g_free (md->priv->audio_type);
+	g_free (md->priv->video_type);
+
 	g_free (md->priv->uri);
 	g_clear_error (&md->priv->error);
 
@@ -464,13 +560,6 @@
 	return RB_METADATA (g_object_new (RB_TYPE_METADATA, NULL, NULL));
 }
 
-static void
-free_gvalue (GValue *val)
-{
-	g_value_unset (val);
-	g_free (val);
-}
-
 static int
 rb_metadata_gst_tag_to_field (const char *tag)
 {
@@ -604,45 +693,36 @@
 	}
 }
 
-static const char *
-rb_metadata_gst_type_to_name (RBMetaData *md, const char *mimetype)
-{
-	int i;
-	for (i = 0; i < md->priv->supported_types->len; i++) {
-		struct RBMetadataGstType *type = g_ptr_array_index (md->priv->supported_types, i);
-		if (!strcmp (type->mimetype, mimetype))
-			return type->human_name;
-	}
-	return NULL;
-}
-
 static RBAddTaggerElem
-rb_metadata_gst_type_to_tag_function (RBMetaData *md, const char *mimetype)
+rb_metadata_gst_type_to_tag_function (RBMetaData *md,
+				      const char *container_type,
+				      const char *audio_type,
+				      const char *video_type)
 {
 	int i;
 	for (i = 0; i < md->priv->supported_types->len; i++) {
 		struct RBMetadataGstType *type = g_ptr_array_index (md->priv->supported_types, i);
-		if (!strcmp (type->mimetype, mimetype))
+		if (type->tag_func &&
+		    type->can_tag_func &&
+		    type->can_tag_func (md, container_type, audio_type, video_type)) {
 			return type->tag_func;
+		}
 	}
 	return NULL;
 }
 
 static char *
-make_undecodable_error (RBMetaData *md)
+make_undecodable_error (RBMetaData *md, const char *type)
 {
 	const char *human_name;
 
-	human_name= rb_metadata_gst_type_to_name (md, md->priv->type);
-	if (human_name == NULL)
-		human_name = rb_mime_get_friendly_name (md->priv->type);
-
+	human_name = rb_mime_get_friendly_name (type);
 	if (human_name) {
 		return g_strdup_printf (_("The GStreamer plugins to decode \"%s\" files cannot be found"),
 					human_name);
 	} else {
 		return g_strdup_printf (_("The file contains a stream of type %s, which is not decodable"),
-					md->priv->type);
+					type);
 	}
 }
 
@@ -651,83 +731,101 @@
 {
 	int count, tem, type;
 	RBMetaDataField field;
-	GValue *newval;
-	const GValue *val;
-
-	rb_debug ("uri: %s tag: %s ", md->priv->uri, tag);
+	GValueArray *valarray;
+	int i;
 
 	count = gst_tag_list_get_tag_size (list, tag);
 	if (count < 1)
 		return;
 
+	rb_debug ("uri: %s tag: %s, count %d", md->priv->uri, tag, count);
+
 	tem = rb_metadata_gst_tag_to_field (tag);
 	if (tem < 0)
 		return;
 	field = (RBMetaDataField) tem;
 
-	type = rb_metadata_get_field_type (field);
-	val = gst_tag_list_get_value_index (list, tag, 0);
-	newval = g_new0 (GValue, 1);
-	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_free (newval);
+	if (md->priv->tag_precedence == FALSE &&
+	    g_hash_table_lookup (md->priv->metadata,
+		    		 GINT_TO_POINTER (field))) {
+		rb_debug ("we already have a value for tag %s; dropping it", tag);
 		return;
 	}
 
-	switch (type) {
-	case G_TYPE_STRING: {
-		/* Reject invalid utf-8 strings, and then
-		 * remove leading and trailing whitespace.
-		 */
-		char *str;
-		str = g_value_dup_string (newval);
+	type = rb_metadata_get_field_type (field);
 
-		if (!g_utf8_validate (str, -1, NULL)) {
-			rb_debug ("Got invalid UTF-8 tag data");
-			g_free (str);
-			g_value_unset (newval);
-			g_free (newval);
-			return;
+	valarray = g_value_array_new (count);
+	for (i = 0; i < count; i++) {
+		GValue newval = {0,};
+		const GValue *val;
+		gboolean add_value;
+
+		add_value = TRUE;
+		val = gst_tag_list_get_value_index (list, tag, i);
+		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);
+			continue;
 		}
-		str = g_strstrip (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);
-		break;
-	}
+		switch (type) {
+		case G_TYPE_STRING: {
+			/* Reject invalid utf-8 strings, and then
+			 * remove leading and trailing whitespace.
+			 */
+			char *str;
+			str = g_value_dup_string (&newval);
 
-	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));
-		break;
-	}
+			if (g_utf8_validate (str, -1, NULL)) {
+				str = g_strstrip (str);
+				g_value_take_string (&newval, str);
+			} else {
+				rb_debug ("Got invalid UTF-8 tag data");
+				g_free (str);
+				g_value_unset (&newval);
+				add_value = FALSE;
+			}
+			break;
+		}
+		default:
+			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);
+			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));
+			break;
+		}
+
+		default:
+			break;
+		}
+
+		if (add_value)
+			g_value_array_append (valarray, &newval);
+		g_value_unset (&newval);
 	}
 
 	g_hash_table_insert (md->priv->metadata,
 			     GINT_TO_POINTER (field),
-			     newval);
+			     valarray);
 }
 
 #ifdef HAVE_GSTREAMER_0_8
@@ -787,23 +885,9 @@
 #endif
 
 static void
-rb_metadata_gst_typefind_cb (GstElement *typefind, guint probability, GstCaps *caps, RBMetaData *md)
-{
-	if (!(gst_caps_is_empty (caps) || gst_caps_is_any (caps))) {
-		g_free (md->priv->type);
-		md->priv->type = g_strdup (gst_structure_get_name (gst_caps_get_structure (caps, 0)));
-		rb_debug ("found type %s", md->priv->type);
-	}
-
-	g_signal_handler_disconnect (typefind, md->priv->typefind_cb_id);
-}
-
-static void
 rb_metadata_gst_new_decoded_pad_cb (GstElement *decodebin, GstPad *pad, gboolean last, RBMetaData *md)
 {
 	GstCaps *caps;
-	GstStructure *structure;
-	const gchar *mimetype;
 	gboolean cancel = FALSE;
 
 	caps = gst_pad_get_caps (pad);
@@ -811,7 +895,6 @@
 	/* 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->non_audio = TRUE;
 		cancel = TRUE;
 	} else {
 		GstPad *sink_pad;
@@ -819,30 +902,14 @@
 		sink_pad = gst_element_get_pad (md->priv->sink, "sink");
 		gst_pad_link (pad, sink_pad);
 
-		/* is this pad audio? */
-		structure = gst_caps_get_structure (caps, 0);
-		mimetype = gst_structure_get_name (structure);
-
-		if (g_str_has_prefix (mimetype, "audio/x-raw")) {
-			rb_debug ("got decoded audio pad of type %s", mimetype);
-		} else if (g_str_has_prefix (mimetype, "video/")) {
-			rb_debug ("got decoded video pad of type %s", mimetype);
-			md->priv->non_audio = TRUE;
-			md->priv->has_video = TRUE;
-		} else {
-			/* assume anything we can get a video or text stream out of is
-			 * something that should be fed to totem rather than rhythmbox.
-			 */
-			rb_debug ("got decoded pad of non-audio type %s", mimetype);
-			md->priv->non_audio = TRUE;
-		}
+		gst_object_unref (sink_pad);
 	}
 
 	gst_caps_unref (caps);
 
 #ifdef HAVE_GSTREAMER_0_10
 	/* if this is non-audio, cancel the operation
-	 * under 0.8 this causes assertion faliures when a pad with no caps in found
+	 * under 0.8 this causes assertion failures when a pad with no caps in found
 	 * it isn't /needed/ under 0.8, so we don't do it.
 	 *
 	 * This seems to cause some deadlocks with video files, so only do it
@@ -857,7 +924,7 @@
 rb_metadata_gst_unknown_type_cb (GstElement *decodebin, GstPad *pad, GstCaps *caps, RBMetaData *md)
 {
 	/* try to shortcut it a bit */
-	md->priv->non_audio = TRUE;
+	md->priv->eos = TRUE;
 
 	if (!gst_caps_is_empty (caps) && !gst_caps_is_any (caps)) {
 		GstStructure *structure;
@@ -866,8 +933,12 @@
 		structure = gst_caps_get_structure (caps, 0);
 		mimetype = gst_structure_get_name (structure);
 
-		g_free (md->priv->type);
-		md->priv->type = g_strdup (mimetype);
+		g_free (md->priv->container_type);
+		g_free (md->priv->video_type);
+		g_free (md->priv->audio_type);
+		md->priv->container_type = g_strdup (mimetype);
+		md->priv->audio_type = NULL;
+		md->priv->video_type = NULL;
 
 		rb_debug ("decodebin emitted unknown type signal for %s", mimetype);
 	} else {
@@ -878,14 +949,15 @@
 	{
 		char *msg;
 
-		msg = make_undecodable_error (md);
+		msg = make_undecodable_error (md, md->priv->container_type);
 		GST_ELEMENT_ERROR (md->priv->pipeline, STREAM, CODEC_NOT_FOUND, ("%s", msg), (NULL));
 		g_free (msg);
 	}
 #endif
 }
 
-static GstElement *make_pipeline_element (GstElement *pipeline, const char *element, GError **error)
+static GstElement *
+make_pipeline_element (GstElement *pipeline, const char *element, GError **error)
 {
 	GstElement *elem = gst_element_factory_make (element, element);
 	if (elem == NULL) {
@@ -902,6 +974,7 @@
 }
 
 #ifdef HAVE_GSTREAMER_0_10
+
 static gboolean
 rb_metadata_bus_handler (GstBus *bus, GstMessage *message, RBMetaData *md)
 {
@@ -937,6 +1010,19 @@
 	case GST_MESSAGE_TAG:
 	{
 		GstTagList *tags;
+		GstElement *emitter;
+		GstElementFactory *factory;
+		const char *emitter_type;
+
+		/* set tag precedence based on the tag type.
+		 * at the moment, we just consider ape tags less useful than
+		 * any other type.
+		 */
+		emitter = GST_ELEMENT (GST_MESSAGE_SRC (message));
+		factory = gst_element_get_factory (emitter);
+		emitter_type = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory));
+		md->priv->tag_precedence = (strcmp (emitter_type, "apedemux") != 0);
+		rb_debug ("got tags from %s", emitter_type);
 
 		gst_message_parse_tag (message, &tags);
 		if (tags) {
@@ -997,7 +1083,6 @@
 	GstElement *pipeline = NULL;
 	GstElement *urisrc = NULL;
 	GstElement *decodebin = NULL;
-	GstElement *typefind = NULL;
 	gint64 file_size = -1;
 	GstFormat file_size_format = GST_FORMAT_BYTES;
 #ifdef HAVE_GSTREAMER_0_10
@@ -1007,13 +1092,17 @@
 
 	g_free (md->priv->uri);
 	md->priv->uri = NULL;
-	g_free (md->priv->type);
-	md->priv->type = NULL;
+
+	g_free (md->priv->container_type);
+	g_free (md->priv->audio_type);
+	g_free (md->priv->video_type);
+	md->priv->container_type = NULL;
+	md->priv->audio_type = NULL;
+	md->priv->video_type = NULL;
+
 	md->priv->error = NULL;
 	md->priv->eos = FALSE;
 	md->priv->handoff = FALSE;
-	md->priv->non_audio = FALSE;
-	md->priv->has_video = FALSE;
 
 	if (md->priv->pipeline) {
 		gst_object_unref (GST_OBJECT (md->priv->pipeline));
@@ -1029,7 +1118,7 @@
 	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) free_gvalue);
+						    NULL, (GDestroyNotify) g_value_array_free);
 
 	/* The main tagfinding pipeline looks like this:
  	 * gnomevfssrc ! decodebin ! fakesink
@@ -1053,7 +1142,14 @@
 	}
 	gst_bin_add (GST_BIN (pipeline), urisrc);
 
- 	decodebin = make_pipeline_element (pipeline, "decodebin", error);
+	/* optionally try decodebin2 first */
+	if (g_getenv ("USE_DECODEBIN2") != NULL) {
+		decodebin = make_pipeline_element (pipeline, "decodebin2", error);
+		g_printerr ("created decodebin2 %p\n", decodebin);
+	}
+
+	if (decodebin == NULL)
+		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");
@@ -1072,20 +1168,6 @@
  	g_signal_connect_object (decodebin, "new-decoded-pad", G_CALLBACK (rb_metadata_gst_new_decoded_pad_cb), md, 0);
  	g_signal_connect_object (decodebin, "unknown-type", G_CALLBACK (rb_metadata_gst_unknown_type_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;
@@ -1105,7 +1187,7 @@
 	change_timeout = 5;
         while (state_ret == GST_STATE_CHANGE_ASYNC &&
 	       !md->priv->eos &&
-	       !md->priv->non_audio &&
+	       !md->priv->error &&
 	       change_timeout > 0) {
 	    GstState state;
 	    rb_debug ("element state changing asynchronously: %d, %d", state_ret, state);
@@ -1116,13 +1198,109 @@
 	if (state_ret != GST_STATE_CHANGE_SUCCESS) {
 		rb_debug ("failed to go to PAUSED for %s", uri);
 		rb_metadata_event_loop (md, GST_ELEMENT (pipeline), FALSE);
-		if (!md->priv->non_audio && md->priv->error == NULL)
+		if ((md->priv->audio_type || md->priv->video_type) && md->priv->error == NULL) {
 			g_set_error (error,
 				     RB_METADATA_ERROR,
 				     RB_METADATA_ERROR_INTERNAL,
 				     _("GStreamer error: failed to change state"));
-	} else
+		}
+	} else {
 		rb_debug ("gone to PAUSED for %s", uri);
+	}
+
+	/* now look through the pipeline for container, audio, and video types */
+	{
+		gpointer elemptr;
+		GstElement *element;
+		GstIterator *elem_iter;
+
+		elem_iter = gst_bin_iterate_elements (GST_BIN (decodebin));
+		while (gst_iterator_next (elem_iter, &elemptr) == GST_ITERATOR_OK) {
+			GstIterator *pad_iter;
+			gpointer padptr;
+			GstPad *pad;
+			gboolean is_audio_decoder;
+			gboolean is_video_decoder;
+			const char *sink_mediatype = NULL;
+			const char *src_mediatype = NULL;
+
+			element = (GstElement *)elemptr;
+			is_audio_decoder = TRUE;
+			is_video_decoder = TRUE;
+			pad_iter = gst_element_iterate_pads (element);
+			while (gst_iterator_next (pad_iter, &padptr) == GST_ITERATOR_OK) {
+				const GstCaps *caps;
+				GstStructure *structure;
+				const char *mediatype;
+				gboolean is_raw_audio;
+				gboolean is_raw_video;
+
+				pad = (GstPad *)padptr;
+				if (gst_pad_is_linked (pad) == FALSE) {
+					is_audio_decoder = FALSE;
+					is_video_decoder = FALSE;
+					gst_object_unref (pad);
+					continue;
+				}
+
+				caps = GST_PAD_CAPS (pad);
+				if (caps == NULL) {
+					caps = gst_pad_get_pad_template_caps (pad);
+				}
+
+				if (gst_caps_is_any (caps) || gst_caps_is_empty (caps)) {
+					gst_object_unref (pad);
+					continue;
+				}
+
+				structure = gst_caps_get_structure (caps, 0);
+				mediatype = gst_structure_get_name (structure);
+				is_raw_audio = g_str_has_prefix (mediatype, "audio/x-raw");
+				is_raw_video = g_str_has_prefix (mediatype, "video/x-raw");
+
+				switch (GST_PAD_DIRECTION (pad)) {
+				case GST_PAD_SRC:
+					/* if it outputs raw data, consider it a decoder */
+					is_audio_decoder &= is_raw_audio;
+					is_video_decoder &= is_raw_video;
+					src_mediatype = mediatype;
+					break;
+				case GST_PAD_SINK:
+					/* .. unless it also takes in raw data */
+					sink_mediatype = mediatype;
+					if (is_raw_audio)
+						is_audio_decoder = FALSE;
+					if (is_raw_video)
+						is_video_decoder = FALSE;
+					break;
+				default:
+					break;
+				}
+
+				gst_object_unref (pad);
+			}
+			gst_iterator_free (pad_iter);
+
+			if (sink_mediatype != NULL && (src_mediatype == NULL || strcmp (src_mediatype, sink_mediatype) != 0)) {
+				if (is_audio_decoder) {
+					g_free (md->priv->audio_type);
+					md->priv->audio_type = g_strdup (sink_mediatype);
+				} else if (is_video_decoder) {
+					g_free (md->priv->video_type);
+					md->priv->video_type = g_strdup (sink_mediatype);
+				} else if (md->priv->container_type == NULL) {
+					/* only take the first thing that looks like a container type.
+					 * maybe we should actually take the last?  or multiple types?
+					 */
+					md->priv->container_type = g_strdup (sink_mediatype);
+				}
+			}
+
+			gst_object_unref (element);
+		}
+		gst_iterator_free (elem_iter);
+	}
+
 
 	if (state_ret == GST_STATE_CHANGE_SUCCESS) {
 		/* Post application specific message so we'll know when to stop
@@ -1145,28 +1323,33 @@
 		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;
 
 #ifdef HAVE_GSTREAMER_0_8
 			if (gst_element_query (md->priv->sink, GST_QUERY_TOTAL, &format, &length)) {
 #endif
 #ifdef HAVE_GSTREAMER_0_10
 			if (gst_element_query_duration (md->priv->sink, &format, &length)) {
+				GValueArray *valarray;
+				GValue v = {0,};
+
 				g_assert (format == GST_FORMAT_TIME);
 #endif
-				newval = g_new0 (GValue, 1);
+				rb_debug ("duration query succeeded: %" G_GINT64_FORMAT, length);
+				g_value_init (&v, G_TYPE_ULONG);
+				valarray = g_value_array_new (1);
+
+				/* FIXME - use gint64 for duration? */
+				g_value_set_ulong (&v, (long) (length / GST_SECOND));
+				g_value_array_append (valarray, &v);
+				g_value_unset (&v);
 
-				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);
+						     valarray);
 			} else {
 				rb_debug ("duration query failed!");
 			}
 		}
+
 	}
 #ifdef HAVE_GSTREAMER_0_8
 	else if (md->priv->eos) {
@@ -1196,66 +1379,55 @@
 	 * these don't include the URI as the import errors source
 	 * already displays it.
 	 */
-	if (md->priv->non_audio || !md->priv->handoff) {
-		gboolean ignore = FALSE;
-		int i;
+	if (md->priv->handoff && (md->priv->audio_type != NULL || md->priv->video_type != NULL) && md->priv->error == NULL) {
+		/* yay, it worked */
+		rb_debug ("successfully read metadata for %s", uri);
+	} else if (md->priv->audio_type == NULL && md->priv->video_type == NULL) {
+		if (md->priv->container_type == NULL) {
+			/* ignore really small files that can't be identified */
+			gint error_code = RB_METADATA_ERROR_UNRECOGNIZED;
+			if (file_size > 0 && file_size < REALLY_SMALL_FILE_SIZE) {
+				rb_debug ("ignoring %s because it's too small to care about", md->priv->uri);
+				error_code = RB_METADATA_ERROR_IGNORE;
+			}
 
-		if (md->priv->has_video) {
-			ignore = TRUE;
+			g_clear_error (error);
+			g_set_error (error,
+				     RB_METADATA_ERROR,
+				     error_code,
+				     _("The MIME type of the file could not be identified"));
 		} else {
+			gboolean ignore = FALSE;
+			int i;
+
 			for (i = 0; i < G_N_ELEMENTS (ignore_mime_types); i++) {
-				if (g_str_has_prefix (md->priv->type, ignore_mime_types[i])) {
+				if (g_str_has_prefix (md->priv->container_type, ignore_mime_types[i])) {
 					ignore = TRUE;
 					break;
 				}
 			}
-		}
 
-		if (!ignore) {
-			char *msg = make_undecodable_error (md);
-			g_set_error (error,
-				     RB_METADATA_ERROR,
-				     RB_METADATA_ERROR_NOT_AUDIO,
-				     "%s", msg);
-			g_free (msg);
-		} else {
-			/* we don't need an error message here (it'll never be
-			 * displayed).  using NULL causes crashes with some C
-			 * libraries, and gcc doesn't like zero-length format
-			 * strings, so we use a single space instead.
-			 */
-			g_set_error (error,
-				     RB_METADATA_ERROR,
-				     RB_METADATA_ERROR_NOT_AUDIO_IGNORE,
-				     " ");
+			if (!ignore) {
+				char *msg = make_undecodable_error (md, md->priv->container_type);
+				g_set_error (error,
+					     RB_METADATA_ERROR,
+					     RB_METADATA_ERROR_UNSUPPORTED,
+					     "%s", msg);
+				g_free (msg);
+			} else {
+				/* we don't need an error message here (it'll never be
+				 * displayed).  using NULL causes crashes with some C
+				 * libraries, and gcc doesn't like zero-length format
+				 * strings, so we use a single space instead.
+				 */
+				g_set_error (error,
+					     RB_METADATA_ERROR,
+					     RB_METADATA_ERROR_IGNORE,
+					     " ");
+			}
 		}
 	} else if (md->priv->error) {
 		g_propagate_error (error, md->priv->error);
-	} else if (!md->priv->type) {
-		/* ignore really small files that can't be identified */
-		gint error_code = RB_METADATA_ERROR_UNRECOGNIZED;
-		if (file_size > 0 && file_size < REALLY_SMALL_FILE_SIZE) {
-			rb_debug ("ignoring %s because it's too small to care about", md->priv->uri);
-			error_code = RB_METADATA_ERROR_NOT_AUDIO_IGNORE;
-		}
-
-		g_clear_error (error);
-		g_set_error (error,
-			     RB_METADATA_ERROR,
-			     error_code,
-			     _("The MIME type of the file could not be identified"));
-	} else {
-		/* yay, it worked */
-		rb_debug ("successfully read metadata for %s", uri);
-
-		/* it doesn't matter if we don't recognise the format,
-		 * as long as gstreamer can play it, we can put it in
-		 * the library.
-		 */
-		if (!rb_metadata_gst_type_to_name (md, md->priv->type)) {
-			rb_debug ("we don't know what type %s (from file %s) is, but we'll play it anyway",
-				  md->priv->type, uri);
-		}
 	}
 
  out:
@@ -1265,44 +1437,53 @@
 }
 
 gboolean
-rb_metadata_can_save (RBMetaData *md, const char *mimetype)
+rb_metadata_can_save (RBMetaData *md,
+		      const char *container_type,
+		      const char *audio_type,
+		      const char *video_type)
 {
 #ifdef ENABLE_TAG_WRITING
-	return rb_metadata_gst_type_to_tag_function (md, mimetype) != NULL;
+	return rb_metadata_gst_type_to_tag_function (md, container_type, audio_type, video_type) != NULL;
 #else
 	return FALSE;
 #endif
 }
 
 static void
-rb_metadata_gst_add_tag_data (gpointer key, const GValue *val, RBMetaData *md)
+rb_metadata_gst_add_tag_data (gpointer key, const GValueArray *values, RBMetaData *md)
 {
 	RBMetaDataField field = GPOINTER_TO_INT (key);
 	const char *tag = rb_metadata_gst_field_to_gst_tag (field);
+	int i;
 
 	/* don't write this out */
 	if (field == RB_METADATA_FIELD_DURATION)
 		return;
 
-	if (tag) {
-		if (field == RB_METADATA_FIELD_DATE && g_value_get_ulong (val) == 0) {
+	if (tag == NULL)
+		return;
+
+	for (i = 0; i < values->n_values; i++) {
+		GValue newval = {0,};
+
+		if (field == RB_METADATA_FIELD_DATE &&
+		    g_value_get_ulong (&values->values[i]) == 0) {
 			/* we should ask gstreamer to remove the tag,
-			 * but there is no easy way of doing so
+			 * but there is no easy way of doing so.
 			 */
-		} else {
-			GValue newval = {0,};
+			return;
+		}
 
-			g_value_init (&newval, gst_tag_get_type (tag));
-			if (g_value_transform (val, &newval)) {
-				rb_debug("Setting %s",tag);
-
-				gst_tag_list_add_values (md->priv->tags,
-							 GST_TAG_MERGE_APPEND,
-							 tag, &newval,
-							 NULL);
-			}
-			g_value_unset (&newval);
+		g_value_init (&newval, gst_tag_get_type (tag));
+		if (g_value_transform (&values->values[i], &newval)) {
+			rb_debug ("Setting %s",tag);
+
+			gst_tag_list_add_values (md->priv->tags,
+						 GST_TAG_MERGE_APPEND,
+						 tag, &newval,
+						 NULL);
 		}
+		g_value_unset (&newval);
 	}
 }
 
@@ -1329,7 +1510,7 @@
 {
 	GstElement *pipeline = NULL;
 	GstElement *gnomevfssrc = NULL;
-        GstElement *retag_end = NULL; /* the last elemet after retagging subpipeline */
+	GstElement *retag_end = NULL; /* the last element after retagging subpipeline */
 	const char *plugin_name = NULL;
 	char *tmpname = NULL;
 	GnomeVFSHandle *handle = NULL;
@@ -1337,7 +1518,7 @@
 	RBAddTaggerElem add_tagger_func;
 
 	g_return_if_fail (md->priv->uri != NULL);
-	g_return_if_fail (md->priv->type != NULL);
+	g_return_if_fail (md->priv->audio_type != NULL || md->priv->video_type != NULL);
 
 	rb_debug ("saving metadata for uri: %s", md->priv->uri);
 
@@ -1374,13 +1555,16 @@
 			      md);
 
 	/* Tagger element(s) */
-	add_tagger_func = rb_metadata_gst_type_to_tag_function (md, md->priv->type);
+	add_tagger_func = rb_metadata_gst_type_to_tag_function (md,
+								md->priv->container_type,
+								md->priv->audio_type,
+								md->priv->video_type);
 
 	if (!add_tagger_func) {
 		g_set_error (error,
 			     RB_METADATA_ERROR,
 			     RB_METADATA_ERROR_UNSUPPORTED,
-			     _("Unsupported file type: %s"), md->priv->type);
+			     _("Unsupported file type: %s"), md->priv->container_type ? md->priv->container_type : md->priv->audio_type);
 		goto out_error;
 	}
 
@@ -1474,27 +1658,49 @@
 rb_metadata_get (RBMetaData *md, RBMetaDataField field,
 		 GValue *ret)
 {
+	GValueArray *valarray;
 	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);
-		return TRUE;
-	}
-	return FALSE;
+
+	valarray = g_hash_table_lookup (md->priv->metadata, GINT_TO_POINTER (field));
+	if (valarray == NULL)
+		return FALSE;
+
+	val = g_value_array_get_nth (valarray, 0);
+	g_value_init (ret, G_VALUE_TYPE (val));
+	g_value_copy (val, ret);
+	return TRUE;
+}
+
+GValueArray *
+rb_metadata_get_array (RBMetaData *md, RBMetaDataField field)
+{
+	return g_hash_table_lookup (md->priv->metadata, GINT_TO_POINTER (field));
+}
+
+const char *
+rb_metadata_get_audio_type (RBMetaData *md)
+{
+	return md->priv->audio_type;
+}
+
+const char *
+rb_metadata_get_video_type (RBMetaData *md)
+{
+	return md->priv->video_type;
 }
 
 const char *
-rb_metadata_get_mime (RBMetaData *md)
+rb_metadata_get_container_type (RBMetaData *md)
 {
-	return md->priv->type;
+	return md->priv->container_type;
 }
 
 gboolean
-rb_metadata_set (RBMetaData *md, RBMetaDataField field,
+rb_metadata_set (RBMetaData *md,
+		 RBMetaDataField field,
 		 const GValue *val)
 {
-	GValue *newval;
+	GValueArray *valarray;
 	GType type;
 
 	if (rb_metadata_gst_field_to_gst_tag (field) == NULL) {
@@ -1504,11 +1710,36 @@
 	type = rb_metadata_get_field_type (field);
 	g_return_val_if_fail (type == G_VALUE_TYPE (val), FALSE);
 
-	newval = g_new0 (GValue, 1);
-	g_value_init (newval, type);
-	g_value_copy (val, newval);
+	valarray = g_value_array_new (1);
 
-	g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (field),
-			     newval);
+	g_value_array_append (valarray, val);
+
+	g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (field), valarray);
 	return TRUE;
 }
+
+gboolean
+rb_metadata_set_array (RBMetaData *md, RBMetaDataField field, GValueArray *array)
+{
+	GType type;
+	int i;
+	GValueArray *v;
+
+	if (rb_metadata_gst_field_to_gst_tag (field) == NULL) {
+		return FALSE;
+	}
+
+	/* check all value types */
+	type = rb_metadata_get_field_type (field);
+	for (i = 0; i < array->n_values; i++) {
+		const GValue *cv;
+
+		cv = g_value_array_get_nth (array, i);
+		g_return_val_if_fail (type == G_VALUE_TYPE (cv), FALSE);
+	}
+
+	v = g_value_array_copy (array);
+	g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (field), v);
+	return TRUE;
+}
+
diff -rN -u old-meta/metadata/rb-metadata.h new-meta/metadata/rb-metadata.h
--- old-meta/metadata/rb-metadata.h	2007-02-10 22:32:39.000000000 +1000
+++ new-meta/metadata/rb-metadata.h	2007-02-10 22:32:39.000000000 +1000
@@ -70,8 +70,8 @@
 	RB_METADATA_ERROR_UNSUPPORTED,
 	RB_METADATA_ERROR_GENERAL,
 	RB_METADATA_ERROR_INTERNAL,
-	RB_METADATA_ERROR_NOT_AUDIO,
-	RB_METADATA_ERROR_NOT_AUDIO_IGNORE,
+	RB_METADATA_ERROR_IGNORE,
+	RB_METADATA_ERROR_NOT_AUDIO_OR_VIDEO,
 } RBMetaDataError;
 
 #define RB_METADATA_ERROR rb_metadata_error_quark ()
@@ -107,7 +107,10 @@
 
 RBMetaData *	rb_metadata_new		(void);
 
-gboolean	rb_metadata_can_save	(RBMetaData *md, const char *mimetype);
+gboolean	rb_metadata_can_save	(RBMetaData *md,
+					 const char *mimetype,
+					 const char *audio_type,
+					 const char *video_type);
 
 void		rb_metadata_load	(RBMetaData *md,
 					 const char *uri,
@@ -116,14 +119,23 @@
 void		rb_metadata_save	(RBMetaData *md,
 					 GError **error);
 
-const char *	rb_metadata_get_mime	(RBMetaData *md);
+const char *	rb_metadata_get_container_type (RBMetaData *md);
+
+const char *	rb_metadata_get_audio_type (RBMetaData *md);
+
+const char *	rb_metadata_get_video_type (RBMetaData *md);
 
 gboolean	rb_metadata_get		(RBMetaData *md, RBMetaDataField field,
 					 GValue *val);
 
+GValueArray *	rb_metadata_get_array	(RBMetaData *md, RBMetaDataField field);
+
 gboolean	rb_metadata_set		(RBMetaData *md, RBMetaDataField field,
 					 const GValue *val);
 
+gboolean	rb_metadata_set_array	(RBMetaData *md, RBMetaDataField field,
+					 GValueArray *array);
+
 G_END_DECLS
 
 #endif /* __RB_METADATA_H */
diff -rN -u old-meta/metadata/test-metadata.c new-meta/metadata/test-metadata.c
--- old-meta/metadata/test-metadata.c	2007-02-10 22:32:39.000000000 +1000
+++ new-meta/metadata/test-metadata.c	2007-02-10 22:32:39.000000000 +1000
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- *  Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.hn.org>
+ *  Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.wh9.net>
  *
  *  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
@@ -46,26 +46,37 @@
 static void
 print_metadata_string (RBMetaData *md, RBMetaDataField field, const char *name)
 {
-	GValue v = {0,};
-	if (rb_metadata_get (md, field, &v)) {
+	GValueArray *valarray;
+	int i;
+
+	valarray = rb_metadata_get_array (md, field);
+	if (valarray == NULL)
+		return;
+
+	for (i = 0; i < valarray->n_values; i++) {
 		char *s;
 
-		s = g_strdup_value_contents (&v);
+		s = g_strdup_value_contents (g_value_array_get_nth (valarray, i));
 		printf ("%s: %s\n", name, s);
 		g_free (s);
-		g_value_unset (&v);
 	}
 }
 
 static gboolean
 check_can_save_cb (gpointer mt)
 {
-	char *mimetype = (char *)mt;
-
-	if (rb_metadata_can_save (md, mimetype)) {
-		printf ("Can save %s\n", mimetype);
+	char **args = (char **)mt;
+	char *container;
+	char *audio;
+	char *video;
+
+	container = args[0];
+	audio = args[0] ? args[1] : NULL;
+	video = args[0] && args[1] ? args[2] : NULL;
+	if (rb_metadata_can_save (md, container, audio, video)) {
+		printf ("Can save %s/%s/%s\n", container, audio, video);
 	} else {
-		printf ("Unable to save %s\n", mimetype);
+		printf ("Unable to save %s/%s/%s\n", container, audio, video);
 	}
 
 	return FALSE;
@@ -92,7 +103,7 @@
 
 	if (error) {
 		switch (error->code) {
-		case RB_METADATA_ERROR_NOT_AUDIO_IGNORE:
+		case RB_METADATA_ERROR_IGNORE:
 			printf ("file ignored: %s\n", error->message);
 			break;
 		default:
@@ -103,7 +114,9 @@
 	} else {
 		RBMetaDataField f;
 
-		printf ("type: %s\n", rb_metadata_get_mime (md));
+		printf ("container type: %s\n", rb_metadata_get_container_type (md));
+		printf ("audio type: %s\n", rb_metadata_get_audio_type (md));
+		printf ("video type: %s\n", rb_metadata_get_video_type (md));
 		for (f =(RBMetaDataField)0; f < RB_METADATA_FIELD_LAST; f++)
 			print_metadata_string (md, f, rb_metadata_get_field_name (f));
 	}
@@ -145,7 +158,7 @@
 	}
 
 	if (can_save) {
-		g_idle_add (check_can_save_cb, argv[2]);
+		g_idle_add (check_can_save_cb, argv + 2);
 	}
 
 	loop = g_main_loop_new (NULL, FALSE);

diff -rN -u old-meta/podcast/rb-podcast-manager.c new-meta/podcast/rb-podcast-manager.c
--- old-meta/podcast/rb-podcast-manager.c	2007-02-10 22:32:39.000000000 +1000
+++ new-meta/podcast/rb-podcast-manager.c	2007-02-10 22:32:39.000000000 +1000
@@ -1013,7 +1013,7 @@
 	RBMetaData *md = rb_metadata_new ();
 	GError *error = NULL;
 	GValue val = { 0, };
-	const char *mime;
+	const char *audio_type;
 
 	rb_debug ("Loading podcast metadata from %s", uri);
         rb_metadata_load (md, uri, &error);
@@ -1038,10 +1038,10 @@
 		return FALSE;
 	}
 
-	mime = rb_metadata_get_mime (md);
-	if (mime) {
+	audio_type = rb_metadata_get_audio_type (md);
+	if (audio_type) {
 		g_value_init (&val, G_TYPE_STRING);
-		g_value_set_string (&val, mime);
+		g_value_set_string (&val, audio_type);
 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MIMETYPE, &val);
 		g_value_unset (&val);
 	}

diff -rN -u old-meta/rhythmdb/rhythmdb.c new-meta/rhythmdb/rhythmdb.c
--- old-meta/rhythmdb/rhythmdb.c	2007-02-10 22:32:39.000000000 +1000
+++ new-meta/rhythmdb/rhythmdb.c	2007-02-10 22:32:39.000000000 +1000
@@ -1385,8 +1385,12 @@
 	const char *mime;
 	GValue val = {0,};
 
+	/* only use the audio type for now.  we should also store the container
+	 * type, but I don't want to waste one pointer for each RhythmDBEntry
+	 * for this.
+	 */
 	g_value_init (&val, G_TYPE_STRING);
-	mime = rb_metadata_get_mime (metadata);
+	mime = rb_metadata_get_audio_type (metadata);
 	if (mime) {
 		g_value_set_string (&val, mime);
 		rhythmdb_entry_set_internal (db, entry, TRUE,
@@ -1661,19 +1665,23 @@
 
 static void
 rhythmdb_add_import_error_entry (RhythmDB *db,
-				 RhythmDBEvent *event)
+				 RhythmDBEvent *event,
+				 RhythmDBEntryType error_entry_type)
 {
 	RhythmDBEntry *entry;
 	GValue value = {0,};
-	RhythmDBEntryType error_entry_type = RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR;
 
-	if (g_error_matches (event->error, RB_METADATA_ERROR, RB_METADATA_ERROR_NOT_AUDIO_IGNORE)) {
-		/* only add an ignore entry for the main library */
-		if (event->entry_type != RHYTHMDB_ENTRY_TYPE_SONG &&
-		    event->entry_type != RHYTHMDB_ENTRY_TYPE_INVALID)
-			return;
+	if (error_entry_type == RHYTHMDB_ENTRY_TYPE_INVALID) {
+		if (g_error_matches (event->error, RB_METADATA_ERROR, RB_METADATA_ERROR_IGNORE)) {
+			/* only add an ignore entry for the main library */
+			if (event->entry_type != RHYTHMDB_ENTRY_TYPE_SONG &&
+			    event->entry_type != RHYTHMDB_ENTRY_TYPE_INVALID)
+				return;
 
-		error_entry_type = RHYTHMDB_ENTRY_TYPE_IGNORE;
+			error_entry_type = RHYTHMDB_ENTRY_TYPE_IGNORE;
+		} else {
+			error_entry_type = RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR;
+		}
 	}
 
 	entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri);
@@ -1704,7 +1712,7 @@
 			/* mtime */
 			g_value_init (&value, G_TYPE_ULONG);
 			g_value_set_ulong (&value, event->vfsinfo->mtime);
-			rhythmdb_entry_set(db, entry, RHYTHMDB_PROP_MTIME, &value);
+			rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MTIME, &value);
 			g_value_unset (&value);
 		}
 
@@ -1750,7 +1758,6 @@
 {
 	RhythmDBEntry *entry;
 	GValue value = {0,};
-	const char *mime;
 	GTimeVal time;
 
 	if (rhythmdb_get_readonly (db)) {
@@ -1759,15 +1766,18 @@
 		return FALSE;
 	}
 
-	if (event->error) {
-		rhythmdb_add_import_error_entry (db, event);
+	/* ignore anything that doesn't have audio or does have video.
+	 * we might want to make this more flexible in future, to support
+	 * video podcasts etc.
+	 */
+	if ((rb_metadata_get_audio_type (event->metadata) == NULL) ||
+	    (rb_metadata_get_video_type (event->metadata) != NULL)) {
+		rhythmdb_add_import_error_entry (db, event, RHYTHMDB_ENTRY_TYPE_IGNORE);
 		return TRUE;
 	}
 
-	/* do we really need to do this? */
-	mime = rb_metadata_get_mime (event->metadata);
-	if (!mime) {
-		rb_debug ("unsupported file");
+	if (event->error) {
+		rhythmdb_add_import_error_entry (db, event, RHYTHMDB_ENTRY_TYPE_INVALID);
 		return TRUE;
 	}
 
@@ -2424,7 +2434,7 @@
 		event->db = db;
 		event->real_uri = rb_refstring_new (canon_uri);
 		event->error = make_access_failed_error (canon_uri, GNOME_VFS_ERROR_LOOP);
-		rhythmdb_add_import_error_entry (db, event);
+		rhythmdb_add_import_error_entry (db, event, RHYTHMDB_ENTRY_TYPE_INVALID);
 		g_free (event);
 
 	} else if (rb_uri_is_directory (realuri)) {
@@ -4063,8 +4073,12 @@
 {
 	const char *mimetype;
 
+	/* for now, the mime type stored in the entry is the audio media type.
+	 * things will mostly work if we just pretend container types don't
+	 * exist for now.
+	 */
 	mimetype = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
-	return rb_metadata_can_save (db->priv->metadata, mimetype);
+	return rb_metadata_can_save (db->priv->metadata, NULL, mimetype, NULL);
 }
 
 static char *

