tracker-extract-gstreamer.c 38.7 KB
Newer Older
1
/*
2 3
 * Copyright (C) 2006, Laurent Aguerreche <laurent.aguerreche@free.fr>
 * Copyright (C) 2007, Jamie McCracken <jamiemcc@gnome.org>
4
 * Copyright (C) 2008, Nokia <ivan.frade@nokia.com>
5
 * Copyright (C) 2016, Sam Thursfield <sam@afuera.me.uk>
6
 *
7 8
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13 14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17 18
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
19 20 21 22 23 24
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

#include "config.h"

25
/* Ensure we have a valid backend enabled */
26
#if !defined(GSTREAMER_BACKEND_DISCOVERER) && \
27 28 29 30
    !defined(GSTREAMER_BACKEND_GUPNP_DLNA)
#error Not a valid GStreamer backend defined
#endif

31
#include <string.h>
32 33
#include <stdio.h>
#include <stdlib.h>
34
#include <math.h>
35 36

#include <glib.h>
37 38
#include <glib/gstdio.h>

39
#if defined(GSTREAMER_BACKEND_DISCOVERER) || \
40
    defined(GSTREAMER_BACKEND_GUPNP_DLNA)
41 42 43 44
#define GST_USE_UNSTABLE_API
#include <gst/pbutils/pbutils.h>
#endif

45
#if defined(GSTREAMER_BACKEND_GUPNP_DLNA)
46 47
#include <libgupnp-dlna/gupnp-dlna.h>
#include <libgupnp-dlna/gupnp-dlna-gst-utils.h>
48 49
#endif

50
#include <gst/gst.h>
51
#include <gst/tag/tag.h>
52

53
#include <libtracker-miners-common/tracker-common.h>
54 55
#include <libtracker-extract/tracker-extract.h>

56
#include "tracker-cue-sheet.h"
57

58 59 60
/* We wait this long (seconds) for NULL state before freeing */
#define TRACKER_EXTRACT_GUARD_TIMEOUT 3

61
/* An additional tag in gstreamer for the content source. Remove when in upstream */
Mikael Ottela's avatar
Mikael Ottela committed
62 63 64 65
#ifndef GST_TAG_CLASSIFICATION
#define GST_TAG_CLASSIFICATION "classification"
#endif

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
/* Some additional tagreadbin tags (FIXME until they are defined upstream)*/
#ifndef GST_TAG_CHANNEL
#define GST_TAG_CHANNEL "channels"
#endif

#ifndef GST_TAG_RATE
#define GST_TAG_RATE "rate"
#endif

#ifndef GST_TAG_WIDTH
#define GST_TAG_WIDTH "width"
#endif

#ifndef GST_TAG_HEIGHT
#define GST_TAG_HEIGHT "height"
#endif

#ifndef GST_TAG_PIXEL_RATIO
#define GST_TAG_PIXEL_RATIO "pixel-aspect-ratio"
#endif

#ifndef GST_TAG_FRAMERATE
#define GST_TAG_FRAMERATE "framerate"
#endif

91 92 93
typedef enum {
	EXTRACT_MIME_AUDIO,
	EXTRACT_MIME_VIDEO,
94
	EXTRACT_MIME_IMAGE,
95
	EXTRACT_MIME_GUESS
96 97 98
} ExtractMime;

typedef struct {
Martyn Russell's avatar
Martyn Russell committed
99
	ExtractMime     mime;
100
	GstTagList     *tagcache;
101
	GstToc         *gst_toc;
102
	TrackerToc     *toc;
103
	gboolean        is_content_encrypted;
104 105 106

	GSList         *artist_list;

107 108 109
	GstSample      *sample;
	GstMapInfo      info;

110 111
#if defined(GSTREAMER_BACKEND_DISCOVERER) || \
    defined(GSTREAMER_BACKEND_GUPNP_DLNA)
112 113 114 115
	gboolean        has_image;
	gboolean        has_audio;
	gboolean        has_video;
	GList          *streams;
116
#endif
117

118 119
#if defined(GSTREAMER_BACKEND_DISCOVERER) || \
    defined(GSTREAMER_BACKEND_GUPNP_DLNA)
120 121 122 123
	GstDiscoverer  *discoverer;
#endif

#if defined(GSTREAMER_BACKEND_GUPNP_DLNA)
124
	const gchar          *dlna_profile;
125
	const gchar          *dlna_mime;
126 127
#endif

128 129
#if defined(GSTREAMER_BACKEND_DISCOVERER) || \
    defined(GSTREAMER_BACKEND_GUPNP_DLNA)
130
	gint64          duration;
Martyn Russell's avatar
Martyn Russell committed
131 132
	gint            audio_channels;
	gint            audio_samplerate;
133 134 135 136 137
	gint            height;
	gint            width;
	gfloat          aspect_ratio;
	gfloat          video_fps;
#endif
138 139
} MetadataExtractor;

140 141
static void common_extract_stream_metadata (MetadataExtractor    *extractor,
                                            const gchar          *uri,
142
                                            TrackerResource      *resource);
143

144 145 146
static TrackerResource *
intern_artist (MetadataExtractor     *extractor,
               const gchar           *artist_name)
147
{
148 149 150
	GSList *node;
	TrackerResource *artist;
	gchar *artist_uri;
151

152 153
	if (artist_name == NULL)
		return NULL;
154

155
	artist_uri = tracker_sparql_escape_uri_printf ("urn:artist:%s", artist_name);
156

157 158 159
	node = g_slist_find_custom (extractor->artist_list, artist_uri,
	                            (GCompareFunc) tracker_resource_identifier_compare_func);
	if (node) {
160
		g_free (artist_uri);
161
		return node->data;
162 163
	}

164
	artist = tracker_extract_new_artist (artist_name);
165
	g_free (artist_uri);
166

167
	extractor->artist_list = g_slist_prepend (extractor->artist_list, artist);
168

169
	return artist;
170 171
}

172
static void
173 174 175 176
set_property_from_gst_tag (TrackerResource *resource,
                           const gchar     *property_uri,
                           GstTagList      *tag_list,
                           const gchar     *tag)
177
{
178
	GValue value = G_VALUE_INIT;
179

180 181 182
	if (gst_tag_list_copy_value (&value, tag_list, tag)) {
		tracker_resource_set_gvalue (resource, property_uri, &value);
		g_value_unset (&value);
183 184 185
	}
}

186 187 188 189 190 191 192 193 194 195 196
static inline gboolean
get_gst_date_time_to_buf (GstDateTime *date_time,
                          gchar       *buf,
                          size_t       size)
{
	const gchar *offset_str;
	gint year, month, day, hour, minute, second;
	gfloat offset;
	gboolean complete;

	offset_str = "+";
197 198
	year = hour = minute = second = 0;
	month = day = 1;
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
	offset = 0.0;
	complete = TRUE;

	if (gst_date_time_has_year (date_time)) {
		year = gst_date_time_get_year (date_time);
	} else {
		complete = FALSE;
	}

	if (gst_date_time_has_month (date_time)) {
		month = gst_date_time_get_month (date_time);
	} else {
		complete = FALSE;
	}

	if (gst_date_time_has_day (date_time)) {
		day = gst_date_time_get_day (date_time);
	} else {
		complete = FALSE;
	}

	/* Hour and Minute data is retrieved by first checking the
	 * _has_time() API.
	 */

	if (gst_date_time_has_second (date_time)) {
		second = gst_date_time_get_second (date_time);
	} else {
		complete = FALSE;
	}

	if (gst_date_time_has_time (date_time)) {
		hour = gst_date_time_get_hour (date_time);
		minute = gst_date_time_get_minute (date_time);
233
		offset_str = gst_date_time_get_time_zone_offset (date_time) >= 0 ? "+" : "-";
234 235 236 237 238 239
		offset = gst_date_time_get_time_zone_offset (date_time);
	} else {
		offset_str = "+";
		complete = FALSE;
	}

240
	snprintf (buf, size, "%04d-%02d-%02dT%02d:%02d:%02d%s%02d:00",
241 242 243 244 245 246 247
	          year,
	          month,
	          day,
	          hour,
	          minute,
	          second,
	          offset_str,
248
	          (gint) ABS (offset));
249 250 251 252

	return complete;
}

253
static void
254 255 256 257 258 259
add_date_time_gst_tag_with_mtime_fallback (TrackerResource *resource,
                                           const gchar     *uri,
                                           const gchar     *key,
                                           GstTagList      *tag_list,
                                           const gchar     *tag_date_time,
                                           const gchar     *tag_date)
260
{
261
	GstDateTime *date_time;
262
	GDate *date;
263
	gchar buf[26];
264

265
	date_time = NULL;
266
	date = NULL;
267 268
	buf[0] = '\0';

269
	if (gst_tag_list_get_date_time (tag_list, tag_date_time, &date_time)) {
270
		gboolean complete;
271

272
		complete = get_gst_date_time_to_buf (date_time, buf, sizeof (buf));
273
		gst_date_time_unref (date_time);
274 275

		if (!complete) {
276
			g_debug ("GstDateTime was not complete, parts of the date/time were missing (e.g. hours, minutes, seconds)");
277
		}
278
	} else if (gst_tag_list_get_date (tag_list, tag_date, &date)) {
279
		gboolean ret = FALSE;
280

281
		if (date && g_date_valid (date)) {
282
			if (date->julian)
283 284 285
				ret = g_date_valid_julian (date->julian_days);
			if (date->dmy)
				ret = g_date_valid_dmy (date->day, date->month, date->year);
286
		}
287

288
		if (ret) {
289 290
			/* GDate does not carry time zone information, assume UTC */
			g_date_strftime (buf, sizeof (buf), "%Y-%m-%dT%H:%M:%SZ", date);
291 292 293 294 295 296
		}
	}

	if (date) {
		g_date_free (date);
	}
297

298
	tracker_guarantee_resource_date_from_file_mtime (resource, key, buf, uri);
299 300
}

301
static void
302 303
set_keywords_from_gst_tag (TrackerResource *resource,
                           GstTagList      *tag_list)
304 305 306 307 308 309 310 311 312 313 314 315 316
{
	gboolean ret;
	gchar *str;

	ret = gst_tag_list_get_string (tag_list, GST_TAG_KEYWORDS, &str);

	if (ret) {
		GStrv keywords;
		gint i = 0;

		keywords = g_strsplit_set (str, " ,", -1);

		while (keywords[i]) {
317
			tracker_resource_add_string (resource, "nie:keyword", g_strstrip (keywords[i]));
318 319 320 321 322 323 324 325
			i++;
		}

		g_strfreev (keywords);
		g_free (str);
	}
}

326 327
static gchar *
get_embedded_cue_sheet_data (GstTagList *tag_list)
328 329
{
	gint i, count;
330
	gchar *buffer = NULL;
331 332 333 334 335 336

	count = gst_tag_list_get_tag_size (tag_list, GST_TAG_EXTENDED_COMMENT);
	for (i = 0; i < count; i++) {
		gst_tag_list_get_string_index (tag_list, GST_TAG_EXTENDED_COMMENT, i, &buffer);

		if (g_ascii_strncasecmp (buffer, "cuesheet=", 9) == 0) {
337 338 339 340 341 342 343 344 345
			/* Use same functionality as g_strchug() here
			 * for cuesheet, to avoid allocating new
			 * memory but also to return the string and
			 * not have to jump past cuesheet= on the
			 * returned value.
			 */
			g_memmove (buffer, buffer + 9, strlen ((gchar *) buffer + 9) + 1);

			return buffer;
346 347 348 349
		}

		g_free (buffer);
	}
350 351

	return NULL;
352
}
353

354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
static TrackerToc *
translate_discoverer_toc (GstToc *gst_toc)
{
	const GList *entries, *l;
	TrackerToc *toc;
	gint i = 0;

	entries = gst_toc_get_entries (gst_toc);
	if (!entries)
		return NULL;

	toc = tracker_toc_new ();

	for (l = entries; l; l = l->next) {
		GstTocEntry *entry = l->data;
		GstTagList *tags, *copy = NULL;
		gint64 start, stop;

		tags = gst_toc_entry_get_tags (entry);

		if (tags) {
			copy = gst_tag_list_copy (tags);

			if (gst_tag_list_get_tag_size (copy, GST_TAG_TRACK_NUMBER) == 0) {
				gst_tag_list_add (copy, GST_TAG_MERGE_REPLACE,
				                  GST_TAG_TRACK_NUMBER, i + 1,
				                  NULL);
			}
		}

		gst_toc_entry_get_start_stop_times (entry, &start, &stop);
		tracker_toc_add_entry (toc, copy, (gdouble) start / GST_SECOND,
		                       (gdouble) (stop - start) / GST_SECOND);
		gst_tag_list_unref (copy);
		i++;
	}

	return toc;
}

394 395 396
static TrackerResource *
extractor_get_geolocation (MetadataExtractor     *extractor,
                           GstTagList            *tag_list)
397
{
398
	TrackerResource *location = NULL;
399 400 401 402 403 404 405 406 407
	gdouble lat, lon, alt;
	gboolean has_coords;

	g_debug ("Retrieving geolocation metadata...");

	has_coords = (gst_tag_list_get_double (tag_list, GST_TAG_GEO_LOCATION_LATITUDE, &lat) &&
	              gst_tag_list_get_double (tag_list, GST_TAG_GEO_LOCATION_LONGITUDE, &lon) &&
	              gst_tag_list_get_double (tag_list, GST_TAG_GEO_LOCATION_ELEVATION, &alt));

408 409 410
	if (has_coords) {
		location = tracker_resource_new (NULL);
		tracker_resource_set_uri (location, "rdf:type", "slo:GeoLocation");
411

412 413 414 415
		tracker_resource_set_double (location, "slo:latitude", lat);
		tracker_resource_set_double (location, "slo:longitude", lon);
		tracker_resource_set_double (location, "slo:altitude", alt);
	}
416

417 418
	return location;
}
419

420 421 422 423 424 425
static TrackerResource *
extractor_get_address (MetadataExtractor     *extractor,
                       GstTagList            *tag_list)
{
	TrackerResource *address = NULL;
	gchar *country = NULL, *city = NULL, *sublocation = NULL;
426

427
	g_debug ("Retrieving address metadata...");
428

429 430 431
	gst_tag_list_get_string (tag_list, GST_TAG_GEO_LOCATION_CITY, &city);
	gst_tag_list_get_string (tag_list, GST_TAG_GEO_LOCATION_COUNTRY, &country);
	gst_tag_list_get_string (tag_list, GST_TAG_GEO_LOCATION_SUBLOCATION, &sublocation);
432

433 434
	if (city || country || sublocation) {
		gchar *address_uri = NULL;
435

436 437
		address_uri = tracker_sparql_get_uuid_urn ();
		address = tracker_resource_new (address_uri);
438

439
		tracker_resource_set_uri (address, "rdf:type", "nco:PostalAddress");
440

441 442
		if (sublocation) {
			tracker_resource_set_string (address, "nco:region", sublocation);
443 444
		}

445 446
		if (city) {
			tracker_resource_set_string (address, "nco:locality", city);
447 448
		}

449 450 451
		if (country) {
			tracker_resource_set_string (address, "nco:country", country);
		}
452 453
	}

454
	return address;
455 456
}

457
static void
458
extractor_guess_content_type (MetadataExtractor *extractor)
459
{
460 461 462 463 464 465 466 467 468 469 470
	if (extractor->has_video) {
		extractor->mime = EXTRACT_MIME_VIDEO;
	} else if (extractor->has_audio) {
		extractor->mime = EXTRACT_MIME_AUDIO;
	} else if (extractor->has_image) {
		extractor->mime = EXTRACT_MIME_IMAGE;
	} else {
		/* default to video */
		extractor->mime = EXTRACT_MIME_VIDEO;
	}
}
471

472 473
static void
extractor_apply_general_metadata (MetadataExtractor     *extractor,
474
                                  GstTagList            *tag_list,
475
                                  const gchar           *file_url,
476 477 478
                                  TrackerResource       *resource,
                                  TrackerResource      **p_performer,
                                  TrackerResource      **p_composer)
479
{
480
	const gchar *performer_name = NULL;
481 482
	gchar *performer_temp = NULL;
	gchar *artist_temp = NULL;
483
	gchar *composer_name = NULL;
484 485
	gchar *genre = NULL;
	gchar *title = NULL;
486
	gchar *title_guaranteed = NULL;
487

488 489 490
	*p_composer = NULL;
	*p_performer = NULL;

491 492
	gst_tag_list_get_string (tag_list, GST_TAG_PERFORMER, &performer_temp);
	gst_tag_list_get_string (tag_list, GST_TAG_ARTIST, &artist_temp);
493
	gst_tag_list_get_string (tag_list, GST_TAG_COMPOSER, &composer_name);
494

495
	performer_name = tracker_coalesce_strip (2, performer_temp, artist_temp);
496

497 498
	if (performer_name != NULL) {
		*p_performer = intern_artist (extractor, performer_name);
499
	}
500

501 502
	if (composer_name != NULL) {
		*p_composer = intern_artist (extractor, composer_name);
503
	}
504

505 506
	gst_tag_list_get_string (tag_list, GST_TAG_GENRE, &genre);
	gst_tag_list_get_string (tag_list, GST_TAG_TITLE, &title);
507

508
	if (genre && g_strcmp0 (genre, "Unknown") != 0) {
509
		tracker_resource_add_string (resource, "nfo:genre", genre);
510
	}
511

512 513 514 515 516
	tracker_guarantee_resource_title_from_file (resource,
	                                           "nie:title",
	                                            title,
	                                            file_url,
	                                            &title_guaranteed);
517

518
	add_date_time_gst_tag_with_mtime_fallback (resource,
519 520 521 522 523
	                                           file_url,
	                                           "nie:contentCreated",
	                                           tag_list,
	                                           GST_TAG_DATE_TIME,
	                                           GST_TAG_DATE);
524

525 526 527 528
	set_property_from_gst_tag (resource, "nie:copyright", tag_list, GST_TAG_COPYRIGHT);
	set_property_from_gst_tag (resource, "nie:license", tag_list, GST_TAG_LICENSE);
	set_property_from_gst_tag (resource, "dc:coverage", tag_list, GST_TAG_LOCATION);
	set_property_from_gst_tag (resource, "nie:comment", tag_list, GST_TAG_COMMENT);
529

530
	g_free (title_guaranteed);
531 532
	g_free (performer_temp);
	g_free (artist_temp);
533
	g_free (composer_name);
534
	g_free (genre);
535
	g_free (title);
536 537
}

538 539 540
static TrackerResource *
extractor_maybe_get_album_disc (MetadataExtractor *extractor,
                                GstTagList        *tag_list)
541
{
542
	GstDateTime *datetime_temp = NULL;
543
	TrackerResource *album = NULL, *album_artist = NULL, *album_disc = NULL;
544
	gchar *album_artist_name = NULL;
545
	gchar *album_datetime = NULL;
546
	gchar *album_title = NULL;
547
	gchar *track_artist_temp = NULL;
548
	gboolean has_it;
549
	guint volume_number;
550

551
	gst_tag_list_get_string (tag_list, GST_TAG_ALBUM, &album_title);
Jürg Billeter's avatar
Jürg Billeter committed
552

553
	if (!album_title)
554
		return NULL;
555

556
	gst_tag_list_get_string (tag_list, GST_TAG_ALBUM_ARTIST, &album_artist_name);
557
	gst_tag_list_get_string (tag_list, GST_TAG_ARTIST, &track_artist_temp);
558
	gst_tag_list_get_date_time (tag_list, GST_TAG_DATE_TIME, &datetime_temp);
559

560 561
	if (datetime_temp)
		album_datetime = gst_date_time_to_iso8601_string (datetime_temp);
562
	album_artist = intern_artist (extractor, album_artist_name);
563
	has_it = gst_tag_list_get_uint (tag_list, GST_TAG_ALBUM_VOLUME_NUMBER, &volume_number);
564

565 566 567 568
	album_disc = tracker_extract_new_music_album_disc (album_title,
	                                                   album_artist,
	                                                   has_it ? volume_number : 1,
	                                                   album_datetime);
569

570 571
	album = tracker_resource_get_first_relation (album_disc, "nmm:albumDiscAlbum");
	set_property_from_gst_tag (album, "nmm:albumTrackCount", tag_list, GST_TAG_TRACK_COUNT);
572 573
	set_property_from_gst_tag (album, "nmm:albumGain", extractor->tagcache, GST_TAG_ALBUM_GAIN);
	set_property_from_gst_tag (album, "nmm:albumPeakGain", extractor->tagcache, GST_TAG_ALBUM_PEAK);
574

575
	g_clear_pointer (&datetime_temp, (GDestroyNotify) gst_date_time_unref);
576
	g_free (album_artist_name);
577
	g_free (album_datetime);
578
	g_free (track_artist_temp);
579 580

	return album_disc;
581
}
582

583 584 585
static TrackerResource *
extractor_get_equipment (MetadataExtractor    *extractor,
                         GstTagList           *tag_list)
586
{
587
	TrackerResource *equipment;
588
	gchar *model = NULL, *manuf = NULL;
589

590 591
	gst_tag_list_get_string (tag_list, GST_TAG_DEVICE_MODEL, &model);
	gst_tag_list_get_string (tag_list, GST_TAG_DEVICE_MANUFACTURER, &manuf);
592

593
	if (model == NULL && manuf == NULL)
594
		return NULL;
595

596
	equipment = tracker_extract_new_equipment (manuf, model);
597

598 599
	g_free (model);
	g_free (manuf);
600 601

	return equipment;
602
}
603

604 605
static void
extractor_apply_audio_metadata (MetadataExtractor     *extractor,
606
                                GstTagList            *tag_list,
607 608 609 610
                                TrackerResource       *audio,
                                TrackerResource       *performer,
                                TrackerResource       *composer,
                                TrackerResource       *album_disc)
611
{
612 613 614 615
	set_property_from_gst_tag (audio, "nmm:trackNumber", tag_list, GST_TAG_TRACK_NUMBER);
	set_property_from_gst_tag (audio, "nfo:codec", tag_list, GST_TAG_AUDIO_CODEC);
	set_property_from_gst_tag (audio, "nfo:gain", tag_list, GST_TAG_TRACK_GAIN);
	set_property_from_gst_tag (audio, "nfo:peakGain", tag_list, GST_TAG_TRACK_PEAK);
616

617 618
	if (performer) {
		tracker_resource_set_relation (audio, "nmm:performer", performer);
619
	}
620

621 622
	if (composer) {
		tracker_resource_set_relation (audio, "nmm:composer", composer);
623
	}
624

625 626 627 628 629 630
	if (album_disc) {
		TrackerResource *album;
		album = tracker_resource_get_first_relation (album_disc, "nmm:albumDiscAlbum");

		tracker_resource_set_relation (audio, "nmm:musicAlbumDisc", album_disc);
		tracker_resource_set_relation (audio, "nmm:musicAlbum", album);
631 632
	}
}
633

634
static void
635 636 637 638 639
extractor_apply_video_metadata (MetadataExtractor *extractor,
                                GstTagList        *tag_list,
                                TrackerResource   *video,
                                TrackerResource   *performer,
                                TrackerResource   *composer)
640
{
641
	set_property_from_gst_tag (video, "dc:source", tag_list, GST_TAG_CLASSIFICATION);
642

643 644
	if (performer) {
		tracker_resource_set_relation (video, "nmm:leadActor", performer);
645
	}
646

647 648
	if (composer) {
		tracker_resource_set_relation (video, "nmm:director", composer);
649
	}
650

651
	set_keywords_from_gst_tag (video, tag_list);
652
}
653

654 655 656 657 658
static TrackerResource *
extract_track (MetadataExtractor    *extractor,
               TrackerTocEntry      *toc_entry,
               const gchar          *file_url,
               TrackerResource      *album_disc)
659
{
660 661
	TrackerResource *track;
	TrackerResource *track_performer = NULL, *track_composer = NULL;
662 663 664
	gchar *track_uri;

	track_uri = tracker_sparql_get_uuid_urn ();
665
	track = tracker_resource_new (track_uri);
666

667 668
	tracker_resource_add_uri (track, "rdf:type", "nmm:MusicPiece");
	tracker_resource_add_uri (track, "rdf:type", "nfo:Audio");
669 670 671 672

	extractor_apply_general_metadata (extractor,
	                                  toc_entry->tag_list,
	                                  file_url,
673 674 675
	                                  track,
	                                  &track_performer,
	                                  &track_composer);
676 677 678

	extractor_apply_audio_metadata (extractor,
	                                toc_entry->tag_list,
679 680 681 682
	                                track,
	                                track_performer,
	                                track_composer,
	                                album_disc);
683 684

	if (toc_entry->duration > 0) {
685
		tracker_resource_set_int64 (track, "nfo:duration", (gint64)toc_entry->duration);
686 687 688 689 690 691 692
	} else if (extractor->toc->entry_list &&
	           toc_entry == g_list_last (extractor->toc->entry_list)->data) {
		/* The last element may not have a duration, because it depends
		 * on the duration of the media file rather than info from the
		 * cue sheet. In this case figure the data out from the total
		 * duration.
		 */
693
		tracker_resource_set_int64 (track, "nfo:duration", (gint64)extractor->duration - toc_entry->start);
694 695
	}

696
	tracker_resource_set_double (track, "nfo:audioOffset", toc_entry->start);
697 698

	g_free (track_uri);
699

700
	return track;
701 702
}

703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
#define CHUNK_N_BYTES (2 << 15)

static guint64
extract_gibest_hash (GFile *file)
{
	guint64 buffer[2][CHUNK_N_BYTES/8];
	GInputStream *stream = NULL;
	gssize n_bytes, file_size;
	GError *error = NULL;
	guint64 hash = 0;
	gint i;

	stream = G_INPUT_STREAM (g_file_read (file, NULL, &error));
	if (stream == NULL)
		goto fail;

	/* Extract start/end chunks of the file */
	n_bytes = g_input_stream_read (stream, buffer[0], CHUNK_N_BYTES, NULL, &error);
	if (n_bytes == -1)
		goto fail;

	if (!g_seekable_seek (G_SEEKABLE (stream), -CHUNK_N_BYTES, G_SEEK_END, NULL, &error))
		goto fail;

	n_bytes = g_input_stream_read (stream, buffer[1], CHUNK_N_BYTES, NULL, &error);
	if (n_bytes == -1)
		goto fail;

	for (i = 0; i < G_N_ELEMENTS (buffer[0]); i++)
		hash += buffer[0][i] + buffer[1][i];

	file_size = g_seekable_tell (G_SEEKABLE (stream));

	if (file_size < CHUNK_N_BYTES)
		goto end;

	/* Include file size */
	hash += file_size;
	g_object_unref (stream);

	return hash;

fail:
	g_warning ("Could not get file hash: %s\n", error ? error->message : "Unknown error");
	g_clear_error (&error);

end:
	g_clear_object (&stream);
	return 0;
}

754
static TrackerResource *
755
extract_metadata (MetadataExtractor      *extractor,
756
                  const gchar            *file_url)
757
{
758 759 760 761 762
	TrackerResource *resource;

	g_return_val_if_fail (extractor != NULL, NULL);

	resource = tracker_resource_new (NULL);
763

764
	if (extractor->toc) {
765 766
		gst_tag_list_insert (extractor->tagcache,
		                     extractor->toc->tag_list,
767
		                     GST_TAG_MERGE_KEEP);
768

769 770 771 772 773 774 775 776 777
		if (g_list_length (extractor->toc->entry_list) == 1) {
			/* If we only got one track, stick all the info together and
			 * forget about the table of contents
			 */
			TrackerTocEntry *toc_entry;

			toc_entry = extractor->toc->entry_list->data;
			gst_tag_list_insert (extractor->tagcache,
			                     toc_entry->tag_list,
778
			                     GST_TAG_MERGE_KEEP);
779 780 781 782 783 784

			tracker_toc_free (extractor->toc);
			extractor->toc = NULL;
		}
	}

785
	if (extractor->mime == EXTRACT_MIME_GUESS && !gst_tag_list_is_empty (extractor->tagcache)) {
786
		extractor_guess_content_type (extractor);
787 788 789 790 791 792 793 794 795 796 797 798 799 800
	} else {
		/* Rely on the information from the discoverer rather than the
		 * mimetype, this is a safety net for those formats that fool
		 * mimetype sniffing (eg. .ogg suffixed OGG videos being detected
		 * as audio/ogg.
		 */
		if (extractor->mime == EXTRACT_MIME_AUDIO && extractor->has_video) {
			g_debug ("mimetype says its audio, but has video frames. Falling back to video extraction.");
			extractor->mime = EXTRACT_MIME_VIDEO;
		} else if (extractor->mime == EXTRACT_MIME_VIDEO &&
			   !extractor->has_video && extractor->has_audio) {
			g_debug ("mimetype says its video, but has only audio. Falling back to audio extraction.");
			extractor->mime = EXTRACT_MIME_AUDIO;
		}
801 802
	}

803 804 805
	if (extractor->mime == EXTRACT_MIME_GUESS) {
		g_warning ("Cannot guess real stream type if no tags were read! "
		           "Defaulting to Video.");
806
		tracker_resource_add_uri (resource, "rdf:type", "nmm:Video");
807
	} else {
808
		if (extractor->mime == EXTRACT_MIME_AUDIO) {
809 810 811 812
			/* Audio: don't make an nmm:MusicPiece for the file resource if it's
			 * actually a container for an entire album - we will make a
			 * nmm:MusicPiece for each of the tracks inside instead.
			 */
813
			tracker_resource_add_uri (resource, "rdf:type", "nfo:Audio");
814 815

			if (extractor->toc == NULL || extractor->toc->entry_list == NULL)
816
				tracker_resource_add_uri (resource, "rdf:type", "nmm:MusicPiece");
817
		} else if (extractor->mime == EXTRACT_MIME_VIDEO) {
818
			tracker_resource_add_uri (resource, "rdf:type", "nmm:Video");
819
		} else {
820 821
			tracker_resource_add_uri (resource, "rdf:type", "nfo:Image");
			tracker_resource_add_uri (resource, "rdf:type", "nmm:Photo");
822
		}
823 824 825
	}

	if (!gst_tag_list_is_empty (extractor->tagcache)) {
826
		GList *node;
827 828 829 830
		TrackerResource *equipment;
		TrackerResource *geolocation, *address;
		TrackerResource *performer = NULL, *composer = NULL;
		TrackerResource *album_disc;
831

832
		extractor_apply_general_metadata (extractor,
833
		                                  extractor->tagcache,
834
		                                  file_url,
835 836 837 838 839 840 841 842 843 844 845 846
		                                  resource,
		                                  &performer,
		                                  &composer);

		equipment = extractor_get_equipment (extractor, extractor->tagcache);
		if (equipment) {
			tracker_resource_set_relation (resource, "nfo:equipment", equipment);
			g_object_unref (equipment);
		}

		geolocation = extractor_get_geolocation (extractor, extractor->tagcache);
		if (geolocation) {
847 848 849 850 851 852
			address = extractor_get_address (extractor, extractor->tagcache);
			if (address) {
				tracker_resource_set_relation (geolocation, "slo:postalAddress", address);
				g_object_unref (address);
			}

853 854 855 856
			tracker_resource_set_relation (resource, "slo:location", geolocation);
			g_object_unref (geolocation);
		}

857
		if (extractor->mime == EXTRACT_MIME_VIDEO) {
858
			extractor_apply_video_metadata (extractor,
859
			                                extractor->tagcache,
860 861 862
			                                resource,
			                                performer,
			                                composer);
863
		}
864 865

		if (extractor->mime == EXTRACT_MIME_AUDIO) {
866
			album_disc = extractor_maybe_get_album_disc (extractor, extractor->tagcache);
867 868

			extractor_apply_audio_metadata (extractor,
869
			                                extractor->tagcache,
870 871 872 873
			                                resource,
			                                performer,
			                                composer,
			                                album_disc);
874

875 876 877 878
			/* If the audio file contains multiple tracks, we create the tracks
			 * as abstract information element types and relate them to the
			 * concrete nfo:FileDataObject using nie:isStoredAs.
			 */
879
			if (extractor->toc && g_list_length (extractor->toc->entry_list) > 1) {
880 881
				for (node = extractor->toc->entry_list; node; node = node->next) {
					TrackerResource *track;
882

883 884 885
					track = extract_track (extractor, node->data, file_url, album_disc);
					tracker_resource_set_relation (track, "nie:isStoredAs", resource);
					g_object_unref (track);
886 887
				}

888
				tracker_resource_set_string (resource, "nie:url", file_url);
889
			}
890

891 892 893
			if (album_disc)
				g_object_unref (album_disc);
		}
894 895
	}

896 897 898 899 900 901 902 903 904 905
	/* OpenSubtitles compatible hash */
	if (extractor->mime == EXTRACT_MIME_VIDEO) {
		guint64 hash;
		GFile *file;

		file = g_file_new_for_uri (file_url);
		hash = extract_gibest_hash (file);
		g_object_unref (file);

		if (hash) {
906
			TrackerResource *hash_resource;
907 908
			char *hash_str;

909 910
			hash_resource = tracker_resource_new (NULL);
			tracker_resource_set_uri (hash_resource, "rdf:type", "nfo:FileHash");
911

912
			hash_str = g_strdup_printf ("%" G_GINT64_MODIFIER "x", hash);
913
			tracker_resource_set_string (hash_resource, "nfo:hashValue", hash_str);
914 915
			g_free (hash_str);

916
			tracker_resource_set_string (hash_resource, "nfo:hashAlgorithm", "gibest");
917

918 919 920
			tracker_resource_set_relation (resource, "nfo:hasHash", hash_resource);

			g_object_unref (hash_resource);
921 922 923
		}
	}

924
	/* If content was encrypted, set it. */
925
/* #warning TODO: handle encrypted content with the Discoverer/GUPnP-DLNA backends */
926