[rhythmbox] process embedded images received during playback



commit 95d5ed9fb1fe752440c6a60ee950e0fd8e3628a7
Author: Jonathan Matthew <jonathan d14n org>
Date:   Sat Apr 18 02:03:59 2009 +1000

    process embedded images received during playback
    
    - add a new signal on the RBPlayer interface to make embedded images
      available as GdkPixbufs
    - move tag processing code to rb-player-gst-helper.c, add code to handle
      converting images from GstBuffers to GdkPixbufs
    - process GST_TAG_IMAGE and GST_TAG_PREVIEW_IMAGE tags, emitting the new
      'image' signal
    - emit rb:coverArt extra metadata notification when an image is received
      from the player, causing the art display plugin to use the embedded
      image.
    
      Fixes half of #345975.
---
 backends/gstreamer/rb-player-gst-helper.c |  134 +++++++++++++++++++++++++++++
 backends/gstreamer/rb-player-gst-helper.h |   21 ++++-
 backends/gstreamer/rb-player-gst-xfade.c  |   72 ++++-----------
 backends/gstreamer/rb-player-gst.c        |   71 ++++-----------
 backends/rb-player.c                      |   36 ++++++++
 backends/rb-player.h                      |    5 +
 lib/rb-marshal.list                       |    1 +
 shell/rb-shell-player.c                   |   29 ++++++
 8 files changed, 262 insertions(+), 107 deletions(-)

diff --git a/backends/gstreamer/rb-player-gst-helper.c b/backends/gstreamer/rb-player-gst-helper.c
index f36d441..abdd0ea 100644
--- a/backends/gstreamer/rb-player-gst-helper.c
+++ b/backends/gstreamer/rb-player-gst-helper.c
@@ -125,3 +125,137 @@ rb_player_gst_find_element_with_property (GstElement *element, const char *prope
 	gst_iterator_free (iter);
 	return result;
 }
+
+/**
+ * rb_gst_process_embedded_image:
+ * @taglist:	a #GstTagList containing an image
+ * @tag:	the tag name
+ *
+ * Converts embedded image data extracted from a tag list into
+ * a #GdkPixbuf.  The returned #GdkPixbuf is owned by the caller.
+ *
+ * Returns: a #GdkPixbuf, or NULL.
+ */
+GdkPixbuf *
+rb_gst_process_embedded_image (const GstTagList *taglist, const char *tag)
+{
+	GstBuffer *buf;
+	GdkPixbufLoader *loader;
+	GdkPixbuf *pixbuf;
+	GError *error = NULL;
+	const GValue *val;
+
+	val = gst_tag_list_get_value_index (taglist, tag, 0);
+	if (val == NULL) {
+		rb_debug ("no value for tag %s in the tag list" , tag);
+		return NULL;
+	}
+
+	buf = gst_value_get_buffer (val);
+	if (buf == NULL) {
+		rb_debug ("apparently couldn't get image buffer");
+		return NULL;
+	}
+
+	/* probably should check media type?  text/uri-list won't work too well in a pixbuf loader */
+
+	loader = gdk_pixbuf_loader_new ();
+	rb_debug ("sending %d bytes to pixbuf loader", buf->size);
+	if (gdk_pixbuf_loader_write (loader, buf->data, buf->size, &error) == FALSE) {
+		rb_debug ("pixbuf loader doesn't like the data: %s", error->message);
+		g_error_free (error);
+		g_object_unref (loader);
+		return NULL;
+	}
+
+	pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+	if (pixbuf != NULL) {
+		g_object_ref (pixbuf);
+	}
+	
+	gdk_pixbuf_loader_close (loader, NULL);
+	g_object_unref (loader);
+
+	if (pixbuf == NULL) {
+		rb_debug ("pixbuf loader didn't give us a pixbuf");
+		return NULL;
+	}
+
+	rb_debug ("returning embedded image: %d x %d / %d",
+		  gdk_pixbuf_get_width (pixbuf),
+		  gdk_pixbuf_get_height (pixbuf),
+		  gdk_pixbuf_get_bits_per_sample (pixbuf));
+	return pixbuf;
+}
+
+/**
+ * rb_gst_process_tag_string:
+ * @taglist:	a #GstTagList containing a string tag
+ * @tag:	tag name
+ * @field:	returns the #RBMetaDataField corresponding to the tag
+ * @value:	returns the tag value
+ *
+ * Processes a tag string, determining the metadata field identifier
+ * corresponding to the tag name, and converting the tag data into the
+ * appropriate value type.
+ *
+ * Return value: %TRUE if the tag was successfully converted.
+ */
+gboolean
+rb_gst_process_tag_string (const GstTagList *taglist,
+			   const char *tag,
+			   RBMetaDataField *field,
+			   GValue *value)
+{
+	const GValue *tagval;
+
+	if (gst_tag_list_get_tag_size (taglist, tag) < 0) {
+		rb_debug ("no values in taglist for tag %s", tag);
+		return FALSE;
+	}
+
+	/* only handle a few fields here */
+	if (!strcmp (tag, GST_TAG_TITLE))
+		*field = RB_METADATA_FIELD_TITLE;
+	else if (!strcmp (tag, GST_TAG_GENRE))
+		*field = RB_METADATA_FIELD_GENRE;
+	else if (!strcmp (tag, GST_TAG_COMMENT))
+		*field = RB_METADATA_FIELD_COMMENT;
+	else if (!strcmp (tag, GST_TAG_BITRATE))
+		*field = RB_METADATA_FIELD_BITRATE;
+#ifdef GST_TAG_MUSICBRAINZ_TRACKID
+	else if (!strcmp (tag, GST_TAG_MUSICBRAINZ_TRACKID))
+		*field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
+#endif
+	else {
+		rb_debug ("tag %s doesn't correspond to a metadata field we're interested in", tag);
+		return FALSE;
+	}
+
+	/* most of the fields we care about are strings */
+	switch (*field) {
+	case RB_METADATA_FIELD_BITRATE:
+		g_value_init (value, G_TYPE_ULONG);
+		break;
+
+	case RB_METADATA_FIELD_TITLE:
+	case RB_METADATA_FIELD_GENRE:
+	case RB_METADATA_FIELD_COMMENT:
+	case RB_METADATA_FIELD_MUSICBRAINZ_TRACKID:
+	default:
+		g_value_init (value, G_TYPE_STRING);
+		break;
+	}
+
+	tagval = gst_tag_list_get_value_index (taglist, tag, 0);
+	if (!g_value_transform (tagval, value)) {
+		rb_debug ("Could not transform tag value type %s into %s",
+			  g_type_name (G_VALUE_TYPE (tagval)),
+			  g_type_name (G_VALUE_TYPE (value)));
+		g_value_unset (value);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
diff --git a/backends/gstreamer/rb-player-gst-helper.h b/backends/gstreamer/rb-player-gst-helper.h
index 85cb20e..539fa05 100644
--- a/backends/gstreamer/rb-player-gst-helper.h
+++ b/backends/gstreamer/rb-player-gst-helper.h
@@ -29,8 +29,25 @@
 #ifndef __RB_PLAYER_GST_HELPER_H
 #define __RB_PLAYER_GST_HELPER_H
 
-GstElement *rb_player_gst_try_audio_sink (const char *plugin_name, const char *name);
+#include <gst/gst.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
 
-GstElement *rb_player_gst_find_element_with_property (GstElement *element, const char *property);
+#include "rb-metadata.h"
+
+G_BEGIN_DECLS
+
+GstElement *	rb_player_gst_try_audio_sink (const char *plugin_name, const char *name);
+
+GstElement *	rb_player_gst_find_element_with_property (GstElement *element, const char *property);
+
+GdkPixbuf *	rb_gst_process_embedded_image 	(const GstTagList *taglist,
+						 const char *tag);
+
+gboolean	rb_gst_process_tag_string	(const GstTagList *taglist,
+						 const char *tag,
+						 RBMetaDataField *field,
+						 GValue *value);
+
+G_END_DECLS
 
 #endif /* __RB_PLAYER_GST_HELPER_H */
diff --git a/backends/gstreamer/rb-player-gst-xfade.c b/backends/gstreamer/rb-player-gst-xfade.c
index e0e8806..e6a884f 100644
--- a/backends/gstreamer/rb-player-gst-xfade.c
+++ b/backends/gstreamer/rb-player-gst-xfade.c
@@ -1442,61 +1442,27 @@ schedule_stream_reap (RBPlayerGstXFade *player)
 static void
 process_tag (const GstTagList *list, const gchar *tag, RBXFadeStream *stream)
 {
-	int count;
 	RBMetaDataField field;
-	const GValue *val;
-	GValue newval = {0,};
-
-	count = gst_tag_list_get_tag_size (list, tag);
-	if (count < 1)
-		return;
-
-	rb_debug ("got tag %s for stream %s", tag, stream->uri);
-	/* only handle the subset of fields we use for iradio */
-	if (!strcmp (tag, GST_TAG_TITLE))
-		field = RB_METADATA_FIELD_TITLE;
-	else if (!strcmp (tag, GST_TAG_GENRE))
-		field = RB_METADATA_FIELD_GENRE;
-	else if (!strcmp (tag, GST_TAG_COMMENT))
-		field = RB_METADATA_FIELD_COMMENT;
-	else if (!strcmp (tag, GST_TAG_BITRATE))
-		field = RB_METADATA_FIELD_BITRATE;
-#ifdef GST_TAG_MUSICBRAINZ_TRACKID
-	else if (!strcmp (tag, GST_TAG_MUSICBRAINZ_TRACKID))
-		field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
-#endif
-	else
-		return;
-
-	/* of those, all except bitrate are strings */
-	switch (field) {
-	case RB_METADATA_FIELD_BITRATE:
-		g_value_init (&newval, G_TYPE_ULONG);
-		break;
-
-	case RB_METADATA_FIELD_TITLE:
-	case RB_METADATA_FIELD_GENRE:
-	case RB_METADATA_FIELD_COMMENT:
-	case RB_METADATA_FIELD_MUSICBRAINZ_TRACKID:
-	default:
-		g_value_init (&newval, G_TYPE_STRING);
-		break;
+	GValue value = {0,};
+
+	/* process embedded images */
+	if (!strcmp (tag, GST_TAG_IMAGE) || !strcmp (tag, GST_TAG_PREVIEW_IMAGE)) {
+		GdkPixbuf *pixbuf;
+		pixbuf = rb_gst_process_embedded_image (list, tag);
+		if (pixbuf != NULL) {
+			_rb_player_emit_image (RB_PLAYER (stream->player),
+					       stream->stream_data,
+					       pixbuf);
+			g_object_unref (pixbuf);
+		}
+	} else if (rb_gst_process_tag_string (list, tag, &field, &value)) {
+		rb_debug ("emitting info field %d", field);
+		_rb_player_emit_info (RB_PLAYER (stream->player),
+				      stream->stream_data,
+				      field,
+				      &value);
+		g_value_unset (&value);
 	}
-	val = gst_tag_list_get_value_index (list, tag, 0);
-	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)));
-		return;
-	}
-
-	rb_debug ("emitting info field %d for uri %s", field, stream->uri);
-	_rb_player_emit_info (RB_PLAYER (stream->player),
-			      stream->stream_data,
-			      field,
-			      &newval);
-
-	g_value_unset (&newval);
 }
 
 
diff --git a/backends/gstreamer/rb-player-gst.c b/backends/gstreamer/rb-player-gst.c
index 3e96f05..d21b5a3 100644
--- a/backends/gstreamer/rb-player-gst.c
+++ b/backends/gstreamer/rb-player-gst.c
@@ -262,60 +262,27 @@ rb_player_gst_gst_free_playbin (RBPlayerGst *player)
 static void
 process_tag (const GstTagList *list, const gchar *tag, RBPlayerGst *player)
 {
-	int count;
 	RBMetaDataField field;
-	const GValue *val;
-	GValue *newval;
-
-	count = gst_tag_list_get_tag_size (list, tag);
-	if (count < 1)
-		return;
-
-	/* only handle the subset of fields we use for iradio */
-	if (!strcmp (tag, GST_TAG_TITLE))
-		field = RB_METADATA_FIELD_TITLE;
-	else if (!strcmp (tag, GST_TAG_GENRE))
-		field = RB_METADATA_FIELD_GENRE;
-	else if (!strcmp (tag, GST_TAG_COMMENT))
-		field = RB_METADATA_FIELD_COMMENT;
-	else if (!strcmp (tag, GST_TAG_BITRATE))
-		field = RB_METADATA_FIELD_BITRATE;
-#ifdef GST_TAG_MUSICBRAINZ_TRACKID
-	else if (!strcmp (tag, GST_TAG_MUSICBRAINZ_TRACKID))
-		field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
-#endif
-	else
-		return;
-
-	/* of those, all except bitrate are strings */
-	newval = g_new0 (GValue, 1);
-	switch (field) {
-	case RB_METADATA_FIELD_BITRATE:
-		g_value_init (newval, G_TYPE_ULONG);
-		break;
-
-	case RB_METADATA_FIELD_TITLE:
-	case RB_METADATA_FIELD_GENRE:
-	case RB_METADATA_FIELD_COMMENT:
-	case RB_METADATA_FIELD_MUSICBRAINZ_TRACKID:
-	default:
-		g_value_init (newval, G_TYPE_STRING);
-		break;
-	}
-	val = gst_tag_list_get_value_index (list, tag, 0);
-	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_free (newval);
-		return;
+	GValue value = {0,};
+
+	/* process embedded images */
+	if (!strcmp (tag, GST_TAG_IMAGE) || !strcmp (tag, GST_TAG_PREVIEW_IMAGE)) {
+		GdkPixbuf *pixbuf;
+		pixbuf = rb_gst_process_embedded_image (list, tag);
+		if (pixbuf != NULL) {
+			_rb_player_emit_image (RB_PLAYER (player),
+					       player->priv->stream_data,
+					       pixbuf);
+			g_object_unref (pixbuf);
+		}
+	} else if (rb_gst_process_tag_string (list, tag, &field, &value)) {
+		rb_debug ("emitting info field %d", field);
+		_rb_player_emit_info (RB_PLAYER (player),
+				      player->priv->stream_data,
+				      field,
+				      &value);
+		g_value_unset (&value);
 	}
-
-	_rb_player_emit_info (RB_PLAYER (player),
-			      player->priv->stream_data,
-			      field,
-			      newval);
-	g_free (newval);
 }
 
 static void
diff --git a/backends/rb-player.c b/backends/rb-player.c
index 2d65f22..960fa11 100644
--- a/backends/rb-player.c
+++ b/backends/rb-player.c
@@ -43,6 +43,7 @@ enum {
 	EVENT,
 	PLAYING_STREAM,
 	VOLUME_CHANGED,
+	IMAGE,
 	LAST_SIGNAL
 };
 
@@ -249,6 +250,26 @@ rb_player_interface_init (RBPlayerIface *iface)
 			      G_TYPE_NONE,
 			      1,
 			      G_TYPE_FLOAT);
+	
+	/**
+	 * RBPlayer::image:
+	 * @player: the #RBPlayer
+	 * @stream_data: data associated with the stream
+	 * @image: the image extracted from the stream
+	 *
+	 * The 'image' signal is emitted to provide access to images extracted
+	 * from the stream.
+	 */
+	signals[IMAGE] =
+		g_signal_new ("image",
+			      G_TYPE_FROM_INTERFACE (iface),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBPlayerIface, image),
+			      NULL, NULL,
+			      rb_marshal_VOID__POINTER_OBJECT,
+			      G_TYPE_NONE,
+			      2,
+			      G_TYPE_POINTER, GDK_TYPE_PIXBUF);
 }
 
 GType
@@ -658,6 +679,21 @@ _rb_player_emit_volume_changed (RBPlayer *player, float volume)
 	g_signal_emit (player, signals[VOLUME_CHANGED], 0, volume);
 }
 
+/**
+ * _rb_player_emit_image:
+ * @player: a #RBPlayer implementation
+ * @stream_data: data associated with the stream
+ * @image: an image extracted from the stream
+ *
+ * Emits the 'image' signal to notify listeners of an image that
+ * has been extracted from the stream.  To be used by implementations only.
+ */
+void
+_rb_player_emit_image (RBPlayer *player, gpointer stream_data, GdkPixbuf *image)
+{
+	g_signal_emit (player, signals[IMAGE], 0, stream_data, image);
+}
+
 GQuark
 rb_player_error_quark (void)
 {
diff --git a/backends/rb-player.h b/backends/rb-player.h
index 634ddab..e9aa61a 100644
--- a/backends/rb-player.h
+++ b/backends/rb-player.h
@@ -32,6 +32,7 @@
 #define __RB_PLAYER_H
 
 #include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
 #include "rb-metadata.h"
 
 G_BEGIN_DECLS
@@ -121,6 +122,9 @@ struct _RBPlayerIface
 						 gpointer data);
 	void		(*volume_changed)	(RBPlayer *player,
 						 float volume);
+	void		(*image)		(RBPlayer *player,
+						 gpointer stream_data,
+						 GdkPixbuf *image);
 };
 
 GType		rb_player_get_type   (void);
@@ -163,6 +167,7 @@ void	_rb_player_emit_tick (RBPlayer *player, gpointer stream_data, long elapsed,
 void	_rb_player_emit_event (RBPlayer *player, gpointer stream_data, const char *name, gpointer data);
 void	_rb_player_emit_playing_stream (RBPlayer *player, gpointer stream_data);
 void	_rb_player_emit_volume_changed (RBPlayer *player, float volume);
+void	_rb_player_emit_image (RBPlayer *player, gpointer stream_data, GdkPixbuf *image);
 
 G_END_DECLS
 
diff --git a/lib/rb-marshal.list b/lib/rb-marshal.list
index 5a18afb..c2edac6 100644
--- a/lib/rb-marshal.list
+++ b/lib/rb-marshal.list
@@ -27,6 +27,7 @@ VOID:OBJECT,INT,POINTER
 VOID:POINTER,INT
 VOID:POINTER,INT,POINTER
 VOID:POINTER,LONG,LONG
+VOID:POINTER,OBJECT
 VOID:POINTER,POINTER
 VOID:POINTER,POINTER,POINTER
 VOID:POINTER,UINT
diff --git a/shell/rb-shell-player.c b/shell/rb-shell-player.c
index b5609ee..8ed04f2 100644
--- a/shell/rb-shell-player.c
+++ b/shell/rb-shell-player.c
@@ -157,6 +157,7 @@ static void tick_cb (RBPlayer *player, RhythmDBEntry *entry, long elapsed, long
 static void error_cb (RBPlayer *player, RhythmDBEntry *entry, const GError *err, gpointer data);
 static void missing_plugins_cb (RBPlayer *player, RhythmDBEntry *entry, const char **details, const char **descriptions, RBShellPlayer *sp);
 static void playing_stream_cb (RBPlayer *player, RhythmDBEntry *entry, RBShellPlayer *shell_player);
+static void player_image_cb (RBPlayer *player, RhythmDBEntry *entry, GdkPixbuf *image, RBShellPlayer *shell_player);
 static void rb_shell_player_error (RBShellPlayer *player, gboolean async, const GError *err);
 
 static void rb_shell_player_set_play_order (RBShellPlayer *player,
@@ -1005,6 +1006,11 @@ rb_shell_player_init (RBShellPlayer *player)
 				 G_CALLBACK (rb_shell_player_volume_changed_cb),
 				 player, 0);
 
+	g_signal_connect_object (player->priv->mmplayer,
+				 "image",
+				 G_CALLBACK (player_image_cb),
+				 player, 0);
+
 	{
 		GVolumeMonitor *monitor = g_volume_monitor_get ();
 		g_signal_connect (G_OBJECT (monitor),
@@ -3676,6 +3682,29 @@ missing_plugins_cb (RBPlayer *player,
 	g_closure_sink (retry);
 }
 
+static void
+player_image_cb (RBPlayer *player,
+		 RhythmDBEntry *entry,
+		 GdkPixbuf *image,
+		 RBShellPlayer *shell_player)
+{
+	GValue v = {0,};
+
+	if (image == NULL) {
+		return;
+	}
+
+	g_value_init (&v, GDK_TYPE_PIXBUF);
+	g_value_set_object (&v, image);
+
+	rhythmdb_emit_entry_extra_metadata_notify (shell_player->priv->db,
+						   entry,
+						   "rb:coverArt",
+						   &v);
+	g_value_unset (&v);
+
+}
+
 /**
  * rb_shell_player_get_playing_path:
  * @player: the #RBShellPlayer



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