rb-metadata-gst.c 33.7 KB
Newer Older
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
Colin Walters's avatar
Colin Walters committed
2
 *
3
 *  Copyright (C) 2003,2004 Colin Walters <walters@verbum.org>
Colin Walters's avatar
Colin Walters committed
4 5 6 7 8 9
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
10
 *  The Rhythmbox authors hereby grant permission for non-GPL compatible
James Livingston's avatar
James Livingston committed
11 12 13 14 15 16 17
 *  GStreamer plugins to be used and distributed together with GStreamer
 *  and Rhythmbox. This permission is above and beyond the permissions granted
 *  by the GPL license by which Rhythmbox is covered. If you modify this code
 *  you may extend this exception to your version of the code, but you are not
 *  obligated to do so. If you do not wish to do so, delete this exception
 *  statement from your version.
 *
Colin Walters's avatar
Colin Walters committed
18 19 20 21 22 23 24
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
Ryan P. Skadberg's avatar
Ryan P. Skadberg committed
25
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
Colin Walters's avatar
Colin Walters committed
26 27 28 29
 *
 */

#include <config.h>
30 31 32 33

#include <string.h>

#include <glib/gi18n.h>
34
#include <gst/gsttagsetter.h>
35
#include <gst/tag/tag.h>
36
#include <gst/gsturi.h>
37
#include <gio/gio.h>
38
#include <gst/pbutils/pbutils.h>
James Livingston's avatar
James Livingston committed
39

Colin Walters's avatar
Colin Walters committed
40
#include "rb-metadata.h"
41
#include "rb-metadata-gst-common.h"
42
#include "rb-debug.h"
43
#include "rb-util.h"
44
#include "rb-file-helpers.h"
Colin Walters's avatar
Colin Walters committed
45

46 47
G_DEFINE_TYPE(RBMetaData, rb_metadata, G_TYPE_OBJECT)

48
typedef GstElement *(*RBAddTaggerElem) (GstElement *pipeline, GstElement *source, GstTagList *tags);
49

50
static void rb_metadata_finalize (GObject *object);
51

Colin Walters's avatar
Colin Walters committed
52 53 54 55 56
struct RBMetaDataPrivate
{
	GHashTable *metadata;

	GstElement *pipeline;
57
	GstElement *sink;
58
	gulong typefind_cb_id;
59
	GstTagList *tags;
60

61
	GHashTable *taggers;
62

63
	char *type;
64
	gboolean eos;
65 66
	gboolean has_audio;
	gboolean has_non_audio;
67
	gboolean has_video;
68
	GSList *missing_plugins;
69
	GError *error;
Colin Walters's avatar
Colin Walters committed
70 71
};

72 73
#define RB_METADATA_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_METADATA, RBMetaDataPrivate))

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
void
rb_metadata_reset (RBMetaData *md)
{
	if (md->priv->metadata)
		g_hash_table_destroy (md->priv->metadata);
	md->priv->metadata = g_hash_table_new_full (g_direct_hash, g_direct_equal,
						    NULL, (GDestroyNotify) rb_value_free);

	if (md->priv->pipeline) {
		gst_object_unref (md->priv->pipeline);
		md->priv->pipeline = NULL;
	}
	if (md->priv->sink) {
		md->priv->sink = NULL;
	}
	md->priv->typefind_cb_id = 0;
	if (md->priv->tags) {
		gst_tag_list_free (md->priv->tags);
		md->priv->tags = NULL;
	}

	g_free (md->priv->type);
	md->priv->type = NULL;
	md->priv->eos = FALSE;
	md->priv->has_audio = FALSE;
	md->priv->has_non_audio = FALSE;
	md->priv->has_video = FALSE;
	if (md->priv->missing_plugins != NULL) {
102 103 104 105
		GSList *t;
		for (t = md->priv->missing_plugins; t != NULL; t = t->next) {
			gst_message_unref (GST_MESSAGE (t->data));
		}
106 107 108 109 110 111 112
		g_slist_free (md->priv->missing_plugins);
		md->priv->missing_plugins = NULL;
	}

	g_clear_error (&md->priv->error);
}

Colin Walters's avatar
Colin Walters committed
113

114
static GstElement *
115
flac_tagger (GstElement *pipeline, GstElement *link_to, GstTagList *tags)
116 117 118
{
	GstElement *tagger = NULL;

119 120
	tagger = gst_element_factory_make ("flactag", NULL);
	if (tagger == NULL)
121 122
		return NULL;

123 124
	gst_bin_add (GST_BIN (pipeline), tagger);
	gst_element_link_many (link_to, tagger, NULL);
125
	gst_element_set_state (tagger, GST_STATE_PAUSED);
126

127
	gst_tag_setter_merge_tags (GST_TAG_SETTER (tagger), tags, GST_TAG_MERGE_REPLACE_ALL);
128 129 130
	return tagger;
}

131 132 133 134 135 136 137
static void
id3_pad_added_cb (GstElement *demux, GstPad *pad, GstElement *mux)
{
	GstPad *mux_pad;

	mux_pad = gst_element_get_compatible_pad (mux, pad, NULL);
	if (gst_pad_link (pad, mux_pad) != GST_PAD_LINK_OK)
138
		rb_debug ("unable to link pad from id3demux to id3v2mux");
139
	else
140
		rb_debug ("linked pad from id3demux to id3v2mux");
141 142
}

143
static GstElement *
144
id3_tagger (GstElement *pipeline, GstElement *link_to, GstTagList *tags)
145
{
146 147
	GstElement *demux = NULL;
	GstElement *mux = NULL;
148 149
	GstPad *link_src = NULL;
	GstStructure *link_type;
150

151
	/* TODO use new id3tag element here once it's in gst-plugins-good */
152
	mux = gst_element_factory_make ("id3v2mux", NULL);
153 154 155
	if (mux == NULL) {
		return NULL;
	}
156

157 158 159 160 161 162
	/* if the input stream doesn't contain id3 tags already, we can just link the
	 * tag writer to it and we're done.
	 */
	link_src = gst_element_get_static_pad (link_to, "src");
	if (link_src == NULL) {
		rb_debug ("couldn't get src pad to link to?");
163
		goto error;
164
	}
165

166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
	link_type = gst_caps_get_structure (GST_PAD_CAPS (link_src), 0);
	if (gst_structure_has_name (link_type, "application/x-id3") == FALSE) {
		rb_debug ("input file has no tags, plugging id3 muxer directly");
		gst_bin_add (GST_BIN (pipeline), mux);
		if (!gst_element_link (link_to, mux)) {
			gst_bin_remove (GST_BIN (pipeline), mux);
			goto error;
		}
	} else {
		rb_debug ("input file already has tags, using id3demux first");
		demux = gst_element_factory_make ("id3demux", NULL);
		if (demux == NULL)
			goto error;

		gst_bin_add_many (GST_BIN (pipeline), demux, mux, NULL);
		if (!gst_element_link (link_to, demux)) {
			gst_bin_remove_many (GST_BIN (pipeline), demux, mux, NULL);
			goto error;
		}
185

186 187 188 189 190
		g_signal_connect (demux, "pad-added", (GCallback)id3_pad_added_cb, mux);
		gst_element_set_state (demux, GST_STATE_PAUSED);
	}

	gst_element_set_state (mux, GST_STATE_PAUSED);
191
	gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), tags, GST_TAG_MERGE_REPLACE_ALL);
192 193 194
	return mux;

error:
195 196 197 198 199 200
	if (link_src != NULL) {
		gst_object_unref (link_src);
	}
	if (demux != NULL) {
		g_object_unref (demux);
	}
201 202 203
	g_object_unref (mux);
	return NULL;
}
204 205

static void
206
ogg_pad_added_cb (GstElement *demux, GstPad *pad, GstTagList *tags)
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
{
	GstCaps *caps;
	GstStructure *structure;
	const gchar *mimetype;
	GstPad *conn_pad = NULL;
	GstElement *mux;

	caps = gst_pad_get_caps (pad);
	structure = gst_caps_get_structure (caps, 0);
	mimetype = gst_structure_get_name (structure);

	mux = g_object_get_data (G_OBJECT (demux), "mux");

	if (strcmp (mimetype, "audio/x-vorbis") == 0) {
		GstElement *tagger, *parser;
		GstBin *bin;
		GstState state;

		rb_debug ("found vorbis stream in ogg container, using vorbistag");

		parser = gst_element_factory_make ("vorbisparse", NULL);
228 229 230 231
		if (parser == NULL) {
			rb_debug ("could not create vorbisparse element");
			goto end;
		}
232

233
		tagger = gst_element_factory_make ("vorbistag", NULL);
234 235 236 237 238
		if (tagger == NULL) {
			rb_debug ("could not create vorbistag element");
			gst_object_unref (parser);
			goto end;
		}
239 240 241 242 243 244 245 246 247 248

		bin = GST_BIN (gst_element_get_parent (mux));
		gst_bin_add_many (bin, tagger, parser, NULL);
		gst_object_unref (GST_OBJECT (bin));

		/* connect and bring them up to the same state */
		gst_element_link_many (tagger, parser, mux, NULL);
		gst_element_get_state (mux, &state, NULL, 0);
		gst_element_set_state (parser, state);
		gst_element_set_state (tagger, state);
249

250 251 252
		conn_pad = gst_element_get_compatible_pad (tagger, pad, NULL);
		gst_pad_link (pad, conn_pad);

253
		gst_tag_setter_merge_tags (GST_TAG_SETTER (tagger), tags, GST_TAG_MERGE_REPLACE_ALL);
254 255 256 257 258 259
	} else {
		conn_pad = gst_element_get_compatible_pad (mux, pad, NULL);
		gst_pad_link (pad, conn_pad);
		rb_debug ("found stream in ogg container with no known tagging element");
	}

260
end:
261 262 263 264
	gst_caps_unref (caps);
}

static GstElement *
265
vorbis_tagger (GstElement *pipeline, GstElement *link_to, GstTagList *tags)
266 267 268 269 270 271 272 273 274 275
{
	GstElement *demux = NULL;
	GstElement *mux = NULL;

	demux = gst_element_factory_make ("oggdemux", NULL);
	mux =  gst_element_factory_make ("oggmux", NULL);

	if (demux == NULL || mux == NULL)
		goto error;

276 277
	gst_bin_add_many (GST_BIN (pipeline), demux, mux, NULL);
	if (!gst_element_link (link_to, demux))
278 279
		goto error;

280
	gst_element_set_state (demux, GST_STATE_PAUSED);
281
	g_object_set_data (G_OBJECT (demux), "mux", mux);
282
	g_signal_connect (demux, "pad-added", (GCallback)ogg_pad_added_cb, tags);
283 284 285 286 287 288 289 290
	return mux;

error:
	g_object_unref (demux);
	g_object_unref (mux);
	return NULL;
}

291
static void
292
mp4_pad_added_cb (GstElement *demux, GstPad *demuxpad, GstPad *muxpad)
293 294
{
	if (gst_pad_link (demuxpad, muxpad) != GST_PAD_LINK_OK)
295
		rb_debug ("unable to link pad from qtdemux to mp4mux");
296
	else
297
		rb_debug ("linked pad from qtdemux to mp4mux");
298 299 300 301
}


static GstElement *
302
mp4_tagger (GstElement *pipeline, GstElement *link_to, GstTagList *tags)
303 304 305 306 307 308
{
	GstElement *demux;
	GstElement *mux;
	GstPad *muxpad;

	demux = gst_element_factory_make ("qtdemux", NULL);
309
	mux = gst_element_factory_make ("mp4mux", NULL);
310 311 312
	if (demux == NULL || mux == NULL)
		goto error;

313 314
	gst_bin_add_many (GST_BIN (pipeline), demux, mux, NULL);
	if (!gst_element_link (link_to, demux))
315 316 317
		goto error;

	muxpad = gst_element_get_request_pad (mux, "audio_%d");
318
	g_signal_connect (demux, "pad-added", G_CALLBACK (mp4_pad_added_cb), muxpad);
319 320
	gst_element_set_state (demux, GST_STATE_PAUSED);
	gst_element_set_state (mux, GST_STATE_PAUSED);
321 322

	gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), tags, GST_TAG_MERGE_REPLACE_ALL);
323 324 325 326 327 328 329 330 331 332 333

	return mux;

error:
	if (demux != NULL)
		g_object_unref (demux);
	if (mux != NULL)
		g_object_unref (mux);
	return NULL;
}

334 335


336
static void
337
rb_metadata_class_init (RBMetaDataClass *klass)
338
{
339 340 341 342 343 344
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->finalize = rb_metadata_finalize;

	g_type_class_add_private (klass, sizeof (RBMetaDataPrivate));
	rb_metadata_gst_register_transforms ();
345 346
}

Colin Walters's avatar
Colin Walters committed
347 348 349
static void
rb_metadata_init (RBMetaData *md)
{
350
	md->priv = RB_METADATA_GET_PRIVATE (md);
351

352
	md->priv->taggers = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
353

354 355 356 357 358 359 360 361 362 363 364
	if (gst_element_factory_find ("giostreamsink") == FALSE) {
		rb_debug ("giostreamsink not found, can't tag anything");
	} else {
		if (gst_element_factory_find ("vorbistag") &&
		    gst_element_factory_find ("vorbisparse") &&
		    gst_element_factory_find ("oggdemux") &&
		    gst_element_factory_find ("oggmux")) {
			rb_debug ("ogg vorbis tagging available");
			g_hash_table_insert (md->priv->taggers, "application/ogg", vorbis_tagger);
			g_hash_table_insert (md->priv->taggers, "audio/x-vorbis", vorbis_tagger);
		}
James Livingston's avatar
James Livingston committed
365

366 367 368 369
		if (gst_element_factory_find ("flactag")) {
			rb_debug ("flac tagging available");
			g_hash_table_insert (md->priv->taggers, "audio/x-flac", flac_tagger);
		}
James Livingston's avatar
James Livingston committed
370

371 372 373 374 375 376
		/* TODO check for new id3 tag element too */
		if (gst_element_factory_find ("id3v2mux") && gst_element_factory_find ("id3demux")) {
			rb_debug ("id3 tagging available");
			g_hash_table_insert (md->priv->taggers, "application/x-id3", id3_tagger);
			g_hash_table_insert (md->priv->taggers, "audio/mpeg", id3_tagger);
		}
377

378 379 380 381 382 383
		if (gst_element_factory_find ("qtdemux") && gst_element_factory_find ("mp4mux")) {
			rb_debug ("mp4 tagging available");
			g_hash_table_insert (md->priv->taggers, "audio/x-m4a", mp4_tagger);
			g_hash_table_insert (md->priv->taggers, "video/quicktime", mp4_tagger);
		}
	}
Colin Walters's avatar
Colin Walters committed
384 385 386 387 388 389 390 391 392
}

static void
rb_metadata_finalize (GObject *object)
{
	RBMetaData *md;

	md = RB_METADATA (object);

393
	rb_metadata_reset (md);
394 395 396
	if (md->priv->taggers)
		g_hash_table_destroy (md->priv->taggers);

Colin Walters's avatar
Colin Walters committed
397

398
	G_OBJECT_CLASS (rb_metadata_parent_class)->finalize (object);
Colin Walters's avatar
Colin Walters committed
399 400 401 402 403
}

RBMetaData *
rb_metadata_new (void)
{
404
	return RB_METADATA (g_object_new (RB_TYPE_METADATA, NULL, NULL));
Colin Walters's avatar
Colin Walters committed
405 406 407
}

static void
408
rb_metadata_gst_load_tag (const GstTagList *list, const gchar *tag, RBMetaData *md)
Colin Walters's avatar
Colin Walters committed
409
{
410
	int count, tem, type;
411 412 413 414 415 416 417 418 419
	RBMetaDataField field;
	GValue *newval;
	const GValue *val;

	count = gst_tag_list_get_tag_size (list, tag);
	if (count < 1)
		return;

	tem = rb_metadata_gst_tag_to_field (tag);
420 421
	if (tem < 0) {
		rb_debug ("no metadata field for tag \"%s\"", tag);
422
		return;
423
	}
424 425
	field = (RBMetaDataField) tem;

426
	type = rb_metadata_get_field_type (field);
427
	val = gst_tag_list_get_value_index (list, tag, 0);
428
	newval = g_slice_new0 (GValue);
429
	g_value_init (newval, type);
430
	if (!g_value_transform (val, newval)) {
431

432
		rb_debug ("Could not transform tag value type %s into %s",
433
			  g_type_name (G_VALUE_TYPE (val)),
434
			  g_type_name (G_VALUE_TYPE (newval)));
James Livingston's avatar
James Livingston committed
435
		g_value_unset (newval);
436
		g_slice_free (GValue, newval);
437 438 439
		return;
	}

440 441
	switch (type) {
	case G_TYPE_STRING: {
442 443
		/* Reject invalid utf-8 strings, shorter duplicated tags
		 * and then remove leading and trailing whitespace.
444
		 */
445
		char *str;
446

447
		str = g_value_dup_string (newval);
448 449 450 451 452

		if (!g_utf8_validate (str, -1, NULL)) {
			rb_debug ("Got invalid UTF-8 tag data");
			g_free (str);
			g_value_unset (newval);
453
			g_slice_free (GValue, newval);
454 455
			return;
		}
456
		str = g_strstrip (str);
457 458 459

		/* Check whether we have a shorter duplicate tag,
		 * Doesn't work with non-normalised UTF-8 strings */
460 461 462 463 464 465 466 467 468 469 470
		val = g_hash_table_lookup (md->priv->metadata,
					   GINT_TO_POINTER (field));
		if (val != NULL) {
			const char *old_str;
			old_str = g_value_get_string (val);
			if (old_str != NULL
			    && g_utf8_strlen (old_str, -1) > g_utf8_strlen (str, -1)) {
				if (g_str_has_prefix (old_str, str) != FALSE) {
					rb_debug ("Got shorter duplicate tag");
					g_free (str);
					g_value_unset (newval);
471
					g_slice_free (GValue, newval);
472 473
					return;
				}
474 475
			}
		}
476 477 478

		rb_debug ("processed string tag \"%s\": \"%s\"", tag, str);

479 480 481 482 483 484 485
		g_value_take_string (newval, str);
		break;
	}
	default:
		break;
	}

486 487 488
	switch (field) {
	case RB_METADATA_FIELD_BITRATE: {
		/* GStreamer sends us bitrate in bps, but we need it in kbps*/
Colin Walters's avatar
Colin Walters committed
489 490
		gulong bitrate;
		bitrate = g_value_get_ulong (newval);
491
		g_value_set_ulong (newval, bitrate/1000);
492
		rb_debug ("processed bitrate value: %lu", g_value_get_ulong (newval));
493 494 495 496
		break;
	}

	case RB_METADATA_FIELD_DURATION: {
497
		/* GStreamer sends us duration in ns,
498 499 500 501
		 * but we need it in seconds
		 */
		guint64 duration;
		duration = g_value_get_uint64 (val);
Colin Walters's avatar
Colin Walters committed
502
		g_value_set_ulong (newval, duration/(1000*1000*1000));
503
		rb_debug ("processed duration value: %lu", g_value_get_ulong (newval));
504 505 506 507 508 509 510
		break;
	}

	default:
		break;
	}

511
	g_hash_table_insert (md->priv->metadata,
512 513
			     GINT_TO_POINTER (field),
			     newval);
Colin Walters's avatar
Colin Walters committed
514 515
}

James Livingston's avatar
James Livingston committed
516 517 518
static void
rb_metadata_gst_typefind_cb (GstElement *typefind, guint probability, GstCaps *caps, RBMetaData *md)
{
519
	if (!(gst_caps_is_empty (caps) || gst_caps_is_any (caps))) {
520
		g_free (md->priv->type);
James Livingston's avatar
James Livingston committed
521 522 523
		md->priv->type = g_strdup (gst_structure_get_name (gst_caps_get_structure (caps, 0)));
		rb_debug ("found type %s", md->priv->type);
	}
524 525

	g_signal_handler_disconnect (typefind, md->priv->typefind_cb_id);
526
	md->priv->typefind_cb_id = 0;
James Livingston's avatar
James Livingston committed
527 528
}

529 530 531 532 533 534
static void
rb_metadata_gst_new_decoded_pad_cb (GstElement *decodebin, GstPad *pad, gboolean last, RBMetaData *md)
{
	GstCaps *caps;
	GstStructure *structure;
	const gchar *mimetype;
535
	gboolean cancel = FALSE;
536 537 538 539 540 541

	caps = gst_pad_get_caps (pad);

	/* we get "ANY" caps for text/plain files etc. */
	if (gst_caps_is_empty (caps) || gst_caps_is_any (caps)) {
		rb_debug ("decoded pad with no caps or any caps.  this file is boring.");
542
		md->priv->has_non_audio = TRUE;
543
		cancel = TRUE;
544
	} else {
545 546
		GstPad *sink_pad;

547
		sink_pad = gst_element_get_static_pad (md->priv->sink, "sink");
548
		gst_pad_link (pad, sink_pad);
549
		gst_object_unref (sink_pad);
550

551 552 553
		/* is this pad audio? */
		structure = gst_caps_get_structure (caps, 0);
		mimetype = gst_structure_get_name (structure);
554

555
		if (g_str_has_prefix (mimetype, "audio/x-raw")) {
556
			rb_debug ("got decoded audio pad of type %s", mimetype);
557
			md->priv->has_audio = TRUE;
558 559 560
		} else if (g_str_has_prefix (mimetype, "video/")) {
			rb_debug ("got decoded video pad of type %s", mimetype);
			md->priv->has_video = TRUE;
561 562
		} else {
			rb_debug ("got decoded pad of non-audio type %s", mimetype);
563
			md->priv->has_non_audio = TRUE;
564 565 566
		}
	}

James Livingston's avatar
James Livingston committed
567
	gst_caps_unref (caps);
568

569
	/* If this is non-audio, cancel the operation.
570
	 * This seems to cause some deadlocks with video files, so only do it
571
	 * when we get no/any caps.
572
	 */
573
	if (cancel)
574
		gst_element_set_state (md->priv->pipeline, GST_STATE_NULL);
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
}

static GstElement *make_pipeline_element (GstElement *pipeline, const char *element, GError **error)
{
	GstElement *elem = gst_element_factory_make (element, element);
	if (elem == NULL) {
		g_set_error (error,
			     RB_METADATA_ERROR,
			     RB_METADATA_ERROR_MISSING_PLUGIN,
			     _("Failed to create %s element; check your installation"),
			     element);
		return NULL;
	}

	gst_bin_add (GST_BIN (pipeline), elem);
	return elem;
}

593 594 595
static void
rb_metadata_handle_missing_plugin_message (RBMetaData *md, GstMessage *message)
{
596 597 598
	char *detail;

	detail = gst_missing_plugin_message_get_installer_detail (message);
599 600
	rb_debug ("got missing-plugin message from %s: %s",
		  GST_OBJECT_NAME (GST_MESSAGE_SRC (message)),
601 602 603
		  detail);
	g_free (detail);

604
	md->priv->missing_plugins = g_slist_prepend (md->priv->missing_plugins, gst_message_ref (message));
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628

	/* update our information on what's in the stream based on
	 * what we're missing.
	 */
	switch (rb_metadata_gst_get_missing_plugin_type (message)) {
	case MEDIA_TYPE_NONE:
		break;
	case MEDIA_TYPE_CONTAINER:
		/* hm, maybe we need a way to say 'we don't even know what's in here'.
		 * but for now, the things we actually identify as containers are mostly
		 * used for audio, so pretending they actually are is good enough.
		 */
	case MEDIA_TYPE_AUDIO:
		md->priv->has_audio = TRUE;
		break;
	case MEDIA_TYPE_VIDEO:
		md->priv->has_video = TRUE;
		break;
	case MEDIA_TYPE_OTHER:
		md->priv->has_non_audio = TRUE;
		break;
	default:
		g_assert_not_reached ();
	}
629 630
}

James Livingston's avatar
James Livingston committed
631 632 633 634 635 636 637
static gboolean
rb_metadata_bus_handler (GstBus *bus, GstMessage *message, RBMetaData *md)
{
	switch (GST_MESSAGE_TYPE (message)) {
	case GST_MESSAGE_EOS:
		rb_debug ("EOS reached");
		md->priv->eos = TRUE;
638 639
		return TRUE;

James Livingston's avatar
James Livingston committed
640 641 642 643
	case GST_MESSAGE_ERROR:
	{
		GError *gerror;
		gchar *debug;
644 645 646 647 648
		char *src;

		src = gst_element_get_name (GST_MESSAGE_SRC (message));
		rb_debug ("got error message from %s", src);
		g_free (src);
James Livingston's avatar
James Livingston committed
649 650

		gst_message_parse_error (message, &gerror, &debug);
651 652 653
		if (gerror->domain == GST_STREAM_ERROR &&
		    gerror->code == GST_STREAM_ERROR_TYPE_NOT_FOUND) {
			rb_debug ("caught type not found error");
654 655 656 657 658
		} else if (gerror->domain == GST_STREAM_ERROR &&
			   gerror->code == GST_STREAM_ERROR_WRONG_TYPE &&
			   md->priv->type != NULL &&
			   strcmp (md->priv->type, "text/plain") == 0) {
			rb_debug ("got WRONG_TYPE error for text/plain: setting non-audio flag");
659
			md->priv->has_non_audio = TRUE;
660 661 662 663
		} else if (md->priv->error) {
			rb_debug ("caught error: %s, but we've already got one", gerror->message);
		} else {
			rb_debug ("caught error: %s ", gerror->message);
664

665 666 667 668 669
			g_clear_error (&md->priv->error);
			md->priv->error = g_error_new_literal (RB_METADATA_ERROR,
							       RB_METADATA_ERROR_GENERAL,
							       gerror->message);
		}
James Livingston's avatar
James Livingston committed
670

671 672 673
		/* treat this as equivalent to EOS */
		md->priv->eos = TRUE;

James Livingston's avatar
James Livingston committed
674 675
		g_error_free (gerror);
		g_free (debug);
676
		return TRUE;
James Livingston's avatar
James Livingston committed
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
	}
	case GST_MESSAGE_TAG:
	{
		GstTagList *tags;

		gst_message_parse_tag (message, &tags);
		if (tags) {
			gst_tag_list_foreach (tags, (GstTagForeachFunc) rb_metadata_gst_load_tag, md);
			gst_tag_list_free (tags);
		} else {
			const gchar *errmsg = "Could not retrieve tag list";

			rb_debug ("caught error: %s ", errmsg);
			md->priv->error = g_error_new_literal (RB_METADATA_ERROR,
							       RB_METADATA_ERROR_GENERAL,
							       errmsg);
		}
		break;
	}
696 697 698 699 700 701 702
	case GST_MESSAGE_ELEMENT:
	{
		if (gst_is_missing_plugin_message (message)) {
			rb_metadata_handle_missing_plugin_message (md, message);
		}
		break;
	}
James Livingston's avatar
James Livingston committed
703
	default:
704 705 706 707 708 709 710
	{
		char *src;

		src = gst_element_get_name (GST_MESSAGE_SRC (message));
		rb_debug ("message of type %s from %s",
			  GST_MESSAGE_TYPE_NAME (message), src);
		g_free (src);
James Livingston's avatar
James Livingston committed
711 712
		break;
	}
713
	}
James Livingston's avatar
James Livingston committed
714 715 716 717

	return FALSE;
}

718
static void
719
rb_metadata_event_loop (RBMetaData *md, GstElement *element, gboolean block)
James Livingston's avatar
James Livingston committed
720 721 722 723 724 725 726 727 728 729
{
	GstBus *bus;
	gboolean done = FALSE;

	bus = gst_element_get_bus (element);
	g_return_if_fail (bus != NULL);

	while (!done && !md->priv->eos) {
		GstMessage *message;

730
		if (block)
731
			message = gst_bus_timed_pop (bus, GST_CLOCK_TIME_NONE);
732 733 734
		else
			message = gst_bus_pop (bus);

James Livingston's avatar
James Livingston committed
735 736 737 738 739 740 741 742 743 744 745
		if (message == NULL) {
			gst_object_unref (bus);
			return;
		}

		done = rb_metadata_bus_handler (bus, message, md);
		gst_message_unref (message);
	}
	gst_object_unref (bus);
}

Colin Walters's avatar
Colin Walters committed
746 747 748 749 750 751
void
rb_metadata_load (RBMetaData *md,
		  const char *uri,
		  GError **error)
{
	GstElement *pipeline = NULL;
752
	GstElement *urisrc = NULL;
753
	GstElement *decodebin = NULL;
Colin Walters's avatar
Colin Walters committed
754
	GstElement *typefind = NULL;
755 756
	gint64 file_size = -1;
	GstFormat file_size_format = GST_FORMAT_BYTES;
James Livingston's avatar
James Livingston committed
757
	GstStateChangeReturn state_ret;
James Livingston's avatar
James Livingston committed
758
	int change_timeout;
759
	GstBus *bus;
760

761
	rb_metadata_reset (md);
762 763 764
	if (uri == NULL)
		return;

765
	rb_debug ("loading metadata for uri: %s", uri);
Colin Walters's avatar
Colin Walters committed
766 767

	/* The main tagfinding pipeline looks like this:
768
 	 * <src> ! decodebin ! fakesink
769 770 771 772 773
 	 *
 	 * but we can only link the fakesink in when the decodebin
 	 * creates an audio source pad.  we do this in the 'new-decoded-pad'
 	 * signal handler.
 	 */
774
	pipeline = gst_pipeline_new ("pipeline");
Colin Walters's avatar
Colin Walters committed
775

776 777 778 779 780 781 782 783 784 785 786
	urisrc = gst_element_make_from_uri (GST_URI_SRC, uri, "urisrc");
	if (urisrc == NULL) {
		g_set_error (error,
			     RB_METADATA_ERROR,
			     RB_METADATA_ERROR_MISSING_PLUGIN,
			     _("Failed to create a source element; check your installation"));
		rb_debug ("missing an element to load the uri, sadly");
		goto out;
	}
	gst_bin_add (GST_BIN (pipeline), urisrc);

787 788
 	decodebin = make_pipeline_element (pipeline, "decodebin", error);
 	md->priv->sink = make_pipeline_element (pipeline, "fakesink", error);
789
 	if (!(urisrc && decodebin && md->priv->sink)) {
790 791 792 793 794
 		rb_debug ("missing an element, sadly");
 		goto out;
 	}

 	g_signal_connect_object (decodebin, "new-decoded-pad", G_CALLBACK (rb_metadata_gst_new_decoded_pad_cb), md, 0);
795

796
 	/* locate the decodebin's typefind, so we can get the have_type signal too.
797
 	 * this is kind of nasty, since it relies on an essentially arbitrary string
798 799 800 801 802
 	 * 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);
803 804 805 806
	md->priv->typefind_cb_id = g_signal_connect_object (typefind,
							    "have_type",
							    G_CALLBACK (rb_metadata_gst_typefind_cb),
							    md,
807
							    0);
808
	gst_object_unref (GST_OBJECT (typefind));
809

810
 	gst_element_link (urisrc, decodebin);
Colin Walters's avatar
Colin Walters committed
811 812

	md->priv->pipeline = pipeline;
James Livingston's avatar
James Livingston committed
813 814
	rb_debug ("going to PAUSED for metadata, uri: %s", uri);
	state_ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
815 816 817
	bus = gst_element_get_bus (GST_ELEMENT (pipeline));
	change_timeout = 0;
	while (state_ret == GST_STATE_CHANGE_ASYNC &&
James Livingston's avatar
James Livingston committed
818
	       !md->priv->eos &&
819 820 821
	       change_timeout < 5) {
		GstMessage *msg;

822
		msg = gst_bus_timed_pop (bus, 1 * GST_SECOND);
823 824 825 826 827 828 829 830 831
		if (msg) {
			rb_metadata_bus_handler (bus, msg, md);
			gst_message_unref (msg);
			change_timeout = 0;
		} else {
			change_timeout++;
		}

		state_ret = gst_element_get_state (pipeline, NULL, NULL, 0);
James Livingston's avatar
James Livingston committed
832
	}
833 834
	gst_object_unref (GST_OBJECT (bus));

835
	rb_metadata_event_loop (md, GST_ELEMENT (pipeline), FALSE);
836

837
	if (state_ret != GST_STATE_CHANGE_SUCCESS) {
James Livingston's avatar
James Livingston committed
838
		rb_debug ("failed to go to PAUSED for %s", uri);
839
		if (!md->priv->has_non_audio && md->priv->error == NULL)
James Livingston's avatar
James Livingston committed
840 841 842 843 844 845 846
			g_set_error (error,
				     RB_METADATA_ERROR,
				     RB_METADATA_ERROR_INTERNAL,
				     _("GStreamer error: failed to change state"));
	} else
		rb_debug ("gone to PAUSED for %s", uri);

847
	if (state_ret == GST_STATE_CHANGE_SUCCESS) {
848

849
		/* The pipeline went to PAUSED,
James Livingston's avatar
James Livingston committed
850
		 * which means the decoder should have read all
851 852
		 * of the metadata, and should know the length now.
		 */
853 854 855
		if (g_hash_table_lookup (md->priv->metadata, GINT_TO_POINTER (RB_METADATA_FIELD_DURATION)) == NULL) {
			GstFormat format = GST_FORMAT_TIME;
			gint64 length;
James Livingston's avatar
James Livingston committed
856
			GValue *newval;
857

James Livingston's avatar
James Livingston committed
858 859
			if (gst_element_query_duration (md->priv->sink, &format, &length)) {
				g_assert (format == GST_FORMAT_TIME);
860
				newval = g_slice_new0 (GValue);
861

862
				rb_debug ("duration query succeeded");
863

Colin Walters's avatar
Colin Walters committed
864
				g_value_init (newval, G_TYPE_ULONG);
865
				/* FIXME - use guint64 for duration? */
James Livingston's avatar
James Livingston committed
866
				g_value_set_ulong (newval, (long) (length / GST_SECOND));
867 868 869 870 871
				g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (RB_METADATA_FIELD_DURATION),
						     newval);
			} else {
				rb_debug ("duration query failed!");
			}
872
		}
James Livingston's avatar
James Livingston committed
873 874
	}

875 876 877 878 879
	/* Get the file size, since it might be interesting, and return
	 * the pipeline to NULL state.
	 */
	if (gst_element_query_duration (urisrc, &file_size_format, &file_size))
		g_assert (file_size_format == GST_FORMAT_BYTES);
880

James Livingston's avatar
James Livingston committed
881 882
	state_ret = gst_element_set_state (pipeline, GST_STATE_NULL);
	if (state_ret == GST_STATE_CHANGE_ASYNC) {
883
		g_warning ("Failed to return metadata reader to NULL state");
James Livingston's avatar
James Livingston committed
884
	}
885

886
	if (md->priv->error != NULL) {
887
		g_propagate_error (error, md->priv->error);
888
		md->priv->error = NULL;
889 890 891 892
	} else if (!md->priv->type) {
		g_clear_error (error);
		g_set_error (error,
			     RB_METADATA_ERROR,
893
			     RB_METADATA_ERROR_UNRECOGNIZED,
894
			     _("The MIME type of the file could not be identified"));
895 896 897
	} else {
		/* yay, it worked */
		rb_debug ("successfully read metadata for %s", uri);
898
	}
899

900 901 902 903
 out:
	if (pipeline != NULL)
		gst_object_unref (GST_OBJECT (pipeline));
	md->priv->pipeline = NULL;
Colin Walters's avatar
Colin Walters committed
904 905 906
}

gboolean
907
rb_metadata_can_save (RBMetaData *md, const char *mimetype)
Colin Walters's avatar
Colin Walters committed
908
{
909
	return g_hash_table_lookup (md->priv->taggers, mimetype) != NULL;
910 911
}

912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930
char **
rb_metadata_get_saveable_types (RBMetaData *md)
{
	GHashTableIter iter;
	gpointer key;
	gpointer value;
	char **types;
	int i;

	types = g_new0 (char *, g_hash_table_size (md->priv->taggers) + 1);
	i = 0;
	g_hash_table_iter_init (&iter, md->priv->taggers);
	while (g_hash_table_iter_next (&iter, &key, &value)) {
		types[i++] = g_strdup ((const char *) key);
	}

	return types;
}

931
static void
932
rb_metadata_gst_add_tag_data (gpointer key, const GValue *val, RBMetaData *md)
933 934 935
{
	RBMetaDataField field = GPOINTER_TO_INT (key);
	const char *tag = rb_metadata_gst_field_to_gst_tag (field);
936

James Livingston's avatar
James Livingston committed
937 938 939
	/* don't write this out */
	if (field == RB_METADATA_FIELD_DURATION)
		return;
James Livingston's avatar
James Livingston committed
940

941
	if (tag) {
942 943 944 945 946 947
		if (field == RB_METADATA_FIELD_DATE && g_value_get_ulong (val) == 0) {
			/* we should ask gstreamer to remove the tag,
			 * but there is no easy way of doing so
			 */
		} else {
			GValue newval = {0,};
948

949 950 951
			g_value_init (&newval, gst_tag_get_type (tag));
			if (g_value_transform (val, &newval)) {
				rb_debug("Setting %s",tag);
James Livingston's avatar
James Livingston committed
952

953 954 955 956
				gst_tag_list_add_values (md->priv->tags,
							 GST_TAG_MERGE_APPEND,
							 tag, &newval,
							 NULL);
James Livingston's avatar
James Livingston committed
957
			}
958
			g_value_unset (&newval);
959 960
		}
	}
Colin Walters's avatar
Colin Walters committed
961 962
}

963
static gboolean
964
rb_metadata_file_valid (const char *original, const char *newfile)
965 966 967 968 969 970 971
{
	RBMetaData *md = rb_metadata_new ();
	GError *error = NULL;
	gboolean ret;

	rb_metadata_load (md, newfile, &error);
	ret = (error == NULL);
972

973 974
	/* TODO: check that the tags are correct? */

975
	if (error != NULL)
976 977 978 979
		g_error_free (error);
	g_object_unref (G_OBJECT (md));
	return ret;
}
980

981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
static void
metadata_save_type_found (GstElement *typefind, guint probability, GstCaps *caps, RBMetaData *md)
{
	RBAddTaggerElem add_tagger_func;
        GstElement *retag_end;
	GstMessage *message;
	const char *type;
	GError *error = NULL;

	g_signal_handler_disconnect (typefind, md->priv->typefind_cb_id);
	md->priv->typefind_cb_id = 0;

	if (gst_caps_is_empty (caps) || gst_caps_is_any (caps)) {
		rb_debug ("got empty/no caps from typefind");
		g_set_error (&error,
			     RB_METADATA_ERROR,
			     RB_METADATA_ERROR_UNSUPPORTED,
			     _("Unable to identify file type"));
		goto out_error;
	}

	type = gst_structure_get_name (gst_caps_get_structure (caps, 0));

	/* Tagger element(s) */
	add_tagger_func = g_hash_table_lookup (md->priv->taggers, type);
	if (!add_tagger_func) {
		rb_debug ("found unsupported type %s", type);
		g_set_error (&error,
			     RB_METADATA_ERROR,
			     RB_METADATA_ERROR_UNSUPPORTED,
			     _("Unsupported file type: %s"), type);
		goto out_error;
	}

	rb_debug ("adding tagger for type %s", type);
	retag_end = add_tagger_func (md->priv->pipeline, typefind, md->priv->tags);
	if (!retag_end) {
		g_set_error (&error,
			     RB_METADATA_ERROR,
			     RB_METADATA_ERROR_UNSUPPORTED,
			     _("Unable to create tag-writing elements"));
		goto out_error;
	}

	gst_element_link_many (retag_end, md->priv->sink, NULL);
	return;

out_error:
	message = gst_message_new_error (GST_OBJECT (typefind), error, NULL);
	gst_element_post_message (typefind, message);
	g_clear_error (&error);
}

Colin Walters's avatar
Colin Walters committed
1034
void
1035
rb_metadata_save (RBMetaData *md, const char *uri, GError **error)
Colin Walters's avatar
Colin Walters committed
1036
{
1037
	GstElement *pipeline = NULL;
1038
	GstElement *source = NULL;