1 /*
2  * Copyright (C) 2006, Jamie McCracken <jamiemcc@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA  02110-1301, USA.
18  */
19 
20 #include "config-miners.h"
21 
22 #include <locale.h>
23 
24 #include <libtracker-miners-common/tracker-utils.h>
25 
26 #include "tracker-resource-helpers.h"
27 #include "tracker-xmp.h"
28 #include "tracker-utils.h"
29 
30 #ifdef HAVE_EXEMPI
31 
32 #define NS_XMP_REGIONS "http://www.metadataworkinggroup.com/schemas/regions/"
33 #define NS_ST_DIM "http://ns.adobe.com/xap/1.0/sType/Dimensions#"
34 #define NS_ST_AREA "http://ns.adobe.com/xmp/sType/Area#"
35 
36 #define REGION_LIST_REGEX "^mwg-rs:Regions/mwg-rs:RegionList\\[(\\d+)\\]"
37 
38 #include <exempi/xmp.h>
39 #include <exempi/xmpconsts.h>
40 
41 /**
42  * SECTION:tracker-xmp
43  * @title: XMP
44  * @short_description: Extensible Metadata Platform (XMP)
45  * @stability: Stable
46  * @include: libtracker-extract/tracker-extract.h
47  *
48  * The Adobe Extensible Metadata Platform (XMP) is a standard, created
49  * by Adobe Systems Inc., for processing and storing standardized and
50  * proprietary information relating to the contents of a file.
51  *
52  * XMP standardizes the definition, creation, and processing of
53  * extensible metadata. Serialized XMP can be embedded into a
54  * significant number of popular file formats, without breaking their
55  * readability by non-XMP-aware applications. Embedding metadata ("the
56  * truth is in the file") avoids many problems that occur when
57  * metadata is stored separately. XMP is used in PDF, photography and
58  * photo editing applications.
59  *
60  * This API is provided to remove code duplication between extractors
61  * using these standards.
62  **/
63 
64 static void iterate        (XmpPtr                xmp,
65                             XmpIteratorPtr        iter,
66                             const gchar          *uri,
67                             TrackerXmpData       *data,
68                             gboolean              append);
69 static void iterate_simple (const gchar          *uri,
70                             TrackerXmpData       *data,
71                             const gchar          *schema,
72                             const gchar          *path,
73                             const gchar          *value,
74                             gboolean              append);
75 
76 static const gchar *
fix_metering_mode(const gchar * mode)77 fix_metering_mode (const gchar *mode)
78 {
79 	gint value;
80 	value = atoi(mode);
81 
82 	switch (value) {
83 	case 0:
84 		return "nmm:metering-mode-other";
85 	case 1:
86 		return "nmm:metering-mode-average";
87 	case 2:
88 		return "nmm:metering-mode-center-weighted-average";
89 	case 3:
90 		return "nmm:metering-mode-spot";
91 	case 4:
92 		return "nmm:metering-mode-multispot";
93 	case 5:
94 		return "nmm:metering-mode-pattern";
95 	case 6:
96 		return "nmm:metering-mode-partial";
97 	}
98 
99 	return "nmm:metering-mode-other";
100 }
101 
102 static const gchar *
fix_flash(const gchar * flash)103 fix_flash (const gchar *flash)
104 {
105 	static const gint fired_mask = 0x1;
106 	gint value;
107 
108 	value = atoi (flash);
109 
110 	if (value & fired_mask) {
111 		return "nmm:flash-on";
112 	} else {
113 		return "nmm:flash-off";
114 	}
115 }
116 
117 static const gchar *
fix_white_balance(const gchar * wb)118 fix_white_balance (const gchar *wb)
119 {
120 	if (g_strcmp0 (wb, "1") == 0) {
121 		return "nmm:white-balance-manual";
122 	} else {
123 		return "nmm:white-balance-auto";
124 	}
125 }
126 
127 static gchar *
gps_coordinate_dup(const gchar * coordinates)128 gps_coordinate_dup (const gchar *coordinates)
129 {
130 	static GRegex *reg = NULL;
131 	GMatchInfo *info = NULL;
132 
133 	if (!reg) {
134 		reg = g_regex_new ("([0-9]+),([0-9]+.[0-9]+)([A-Z])", 0, 0, NULL);
135 	}
136 
137 	if (g_regex_match (reg, coordinates, 0, &info)) {
138 		gchar *deg,*min,*ref;
139 		gdouble r,d,m;
140 
141 		deg = g_match_info_fetch (info, 1);
142 		min = g_match_info_fetch (info, 2);
143 		ref = g_match_info_fetch (info, 3);
144 
145 		d = atof (deg);
146 		m = atof (min);
147 
148 		r = d + m/60;
149 
150 		if ( (ref[0] == 'S') || (ref[0] == 'W')) {
151 			r = r * -1;
152 		}
153 
154 		g_free (deg);
155 		g_free (min);
156 		g_free (ref);
157                 g_match_info_free (info);
158 
159 		return g_strdup_printf ("%f", r);
160 	} else {
161                 g_match_info_free (info);
162 		return NULL;
163 	}
164 }
165 
166 /* We have an array, now recursively iterate over it's children.  Set
167  * 'append' to true so that all values of the array are added under
168  * one entry.
169  */
170 static void
iterate_array(XmpPtr xmp,const gchar * uri,TrackerXmpData * data,const gchar * schema,const gchar * path)171 iterate_array (XmpPtr          xmp,
172                const gchar    *uri,
173                TrackerXmpData *data,
174                const gchar    *schema,
175                const gchar    *path)
176 {
177 	XmpIteratorPtr iter;
178 
179 	iter = xmp_iterator_new (xmp, schema, path, XMP_ITER_JUSTCHILDREN);
180 	iterate (xmp, iter, uri, data, TRUE);
181 	xmp_iterator_free (iter);
182 }
183 
184 /* We have an array, now recursively iterate over it's children.  Set
185  * 'append' to false so that only one item is used.
186  */
187 static void
iterate_alt_text(XmpPtr xmp,const gchar * uri,TrackerXmpData * data,const gchar * schema,const gchar * path)188 iterate_alt_text (XmpPtr          xmp,
189                   const gchar    *uri,
190                   TrackerXmpData *data,
191                   const gchar    *schema,
192                   const gchar    *path)
193 {
194 	XmpIteratorPtr iter;
195 
196 	iter = xmp_iterator_new (xmp, schema, path, XMP_ITER_JUSTCHILDREN);
197 	iterate (xmp, iter, uri, data, FALSE);
198 	xmp_iterator_free (iter);
199 }
200 
201 static gchar *
div_str_dup(const gchar * value)202 div_str_dup (const gchar *value)
203 {
204 	gchar *ret;
205 	gchar *ptr = strchr (value, '/');
206 
207 	if (ptr) {
208 		gchar *cpy = g_strdup (value);
209 		gint a, b;
210 
211 		cpy [ptr - value] = '\0';
212 		a = atoi (cpy);
213 		b = atoi (cpy + (ptr - value) + 1);
214 
215 		if (b != 0) {
216 			ret = g_strdup_printf ("%G", ((gdouble)((gdouble) a / (gdouble) b)));
217 		} else {
218 			ret = NULL;
219 		}
220 
221 		g_free (cpy);
222 	} else {
223 		ret = g_strdup (value);
224 	}
225 
226 	return ret;
227 }
228 
229 /* We have a simple element, but need to iterate over the qualifiers */
230 static void
iterate_simple_qual(XmpPtr xmp,const gchar * uri,TrackerXmpData * data,const gchar * schema,const gchar * path,const gchar * value,gboolean append)231 iterate_simple_qual (XmpPtr          xmp,
232                      const gchar    *uri,
233                      TrackerXmpData *data,
234                      const gchar    *schema,
235                      const gchar    *path,
236                      const gchar    *value,
237                      gboolean        append)
238 {
239 	XmpIteratorPtr iter;
240 	XmpStringPtr the_path;
241 	XmpStringPtr the_prop;
242 	static gchar *locale = NULL;
243 	gboolean ignore_element = FALSE;
244 
245 	iter = xmp_iterator_new (xmp, schema, path, XMP_ITER_JUSTCHILDREN | XMP_ITER_JUSTLEAFNAME);
246 
247 	the_path = xmp_string_new ();
248 	the_prop = xmp_string_new ();
249 
250 	if (G_UNLIKELY (!locale)) {
251 		locale = g_strdup (setlocale (LC_ALL, NULL));
252 
253 		if (!locale) {
254 			locale = g_strdup ("C");
255 		} else {
256 			gchar *sep;
257 
258 			sep = strchr (locale, '.');
259 
260 			if (sep) {
261 				locale[sep - locale] = '\0';
262 			}
263 
264 			sep = strchr (locale, '_');
265 
266 			if (sep) {
267 				locale[sep - locale] = '-';
268 			}
269 		}
270 	}
271 
272 	while (xmp_iterator_next (iter, NULL, the_path, the_prop, NULL)) {
273 		const gchar *qual_path = xmp_string_cstr (the_path);
274 		const gchar *qual_value = xmp_string_cstr (the_prop);
275 
276 		if (g_ascii_strcasecmp (qual_path, "xml:lang") == 0) {
277 			/* Is this a language we should ignore? */
278 			if (g_ascii_strcasecmp (qual_value, "x-default") != 0 &&
279 			    g_ascii_strcasecmp (qual_value, "x-repair") != 0 &&
280 			    g_ascii_strcasecmp (qual_value, locale) != 0) {
281 				ignore_element = TRUE;
282 				break;
283 			}
284 		}
285 	}
286 
287 	if (!ignore_element) {
288 		iterate_simple (uri, data, schema, path, value, append);
289 	}
290 
291 	xmp_string_free (the_prop);
292 	xmp_string_free (the_path);
293 
294 	xmp_iterator_free (iter);
295 }
296 
297 static const gchar *
fix_orientation(const gchar * orientation)298 fix_orientation (const gchar *orientation)
299 {
300 	if (orientation && g_ascii_strcasecmp (orientation, "1") == 0) {
301 		return "nfo:orientation-top";
302 	} else if (orientation && g_ascii_strcasecmp (orientation, "2") == 0) {
303 		return  "nfo:orientation-top-mirror";
304 	} else if (orientation && g_ascii_strcasecmp (orientation, "3") == 0) {
305 		return "nfo:orientation-bottom";
306 	} else if (orientation && g_ascii_strcasecmp (orientation, "4") == 0) {
307 		return  "nfo:orientation-bottom-mirror";
308 	} else if (orientation && g_ascii_strcasecmp (orientation, "5") == 0) {
309 		return  "nfo:orientation-left-mirror";
310 	} else if (orientation && g_ascii_strcasecmp (orientation, "6") == 0) {
311 		return  "nfo:orientation-right";
312 	} else if (orientation && g_ascii_strcasecmp (orientation, "7") == 0) {
313 		return "nfo:orientation-right-mirror";
314 	} else if (orientation && g_ascii_strcasecmp (orientation, "8") == 0) {
315 		return  "nfo:orientation-left";
316 	}
317 
318 	return  "nfo:orientation-top";
319 }
320 
321 /*
322  * In a path like: mwg-rs:Regions/mwg-rs:RegionList[2]/mwg-rs:Area/stArea:x
323  * this function returns the "2" from RegionsList[2]
324  *  Note: The first element from a list is 1
325  */
326 static gint
get_region_counter(const gchar * path)327 get_region_counter (const gchar *path)
328 {
329         static GRegex *regex = NULL;
330         GMatchInfo    *match_info = NULL;
331         gchar         *match;
332         gint           result;
333 
334         if (!regex) {
335                 regex = g_regex_new (REGION_LIST_REGEX, 0, 0, NULL);
336         }
337 
338         if (!g_regex_match (regex, path, 0, &match_info)) {
339                 g_match_info_free (match_info);
340                 return -1;
341         }
342 
343         match = g_match_info_fetch (match_info, 1);
344         result =  g_strtod (match, NULL);
345 
346         g_free (match);
347         g_match_info_free (match_info);
348 
349         return result;
350 }
351 
352 
353 
354 /* We have a simple element. Add any data we know about to the
355  * hash table.
356  */
357 static void
iterate_simple(const gchar * uri,TrackerXmpData * data,const gchar * schema,const gchar * path,const gchar * value,gboolean append)358 iterate_simple (const gchar    *uri,
359                 TrackerXmpData *data,
360                 const gchar    *schema,
361                 const gchar    *path,
362                 const gchar    *value,
363                 gboolean        append)
364 {
365 	gchar *name;
366 	const gchar *p;
367 	gchar *propname;
368 
369 	p = strchr (path, ':');
370 	if (!p) {
371 		return;
372 	}
373 
374 	name = g_strdup (p + 1);
375 
376 	/* For 'dc:subject[1]' the name will be 'subject'.
377 	 * This rule doesn't work for RegionLists
378 	 */
379 	p = strrchr (name, '[');
380 	if (p) {
381 		name[p - name] = '\0';
382 	}
383 
384 	/* Exif basic scheme */
385 	if (g_ascii_strcasecmp (schema, NS_EXIF) == 0) {
386 		if (!data->title2 && g_ascii_strcasecmp (name, "Title") == 0) {
387 			data->title2 = g_strdup (value);
388 		} else if (g_ascii_strcasecmp (name, "DateTimeOriginal") == 0 && !data->time_original) {
389 			data->time_original = tracker_date_guess (value);
390 		} else if (!data->artist && g_ascii_strcasecmp (name, "Artist") == 0) {
391 			data->artist = g_strdup (value);
392 		/* } else if (g_ascii_strcasecmp (name, "Software") == 0) {
393 			   tracker_statement_list_insert (metadata, uri,
394 			   "Image:Software", value);*/
395 		} else if (!data->make && g_ascii_strcasecmp (name, "Make") == 0) {
396 			data->make = g_strdup (value);
397 		} else if (!data->model && g_ascii_strcasecmp (name, "Model") == 0) {
398 			data->model = g_strdup (value);
399 		} else if (!data->flash && g_ascii_strcasecmp (name, "Flash") == 0) {
400 			data->flash = g_strdup (fix_flash (value));
401 		} else if (!data->metering_mode && g_ascii_strcasecmp (name, "MeteringMode") == 0) {
402 			data->metering_mode = g_strdup (fix_metering_mode (value));
403 		/* } else if (g_ascii_strcasecmp (name, "ExposureProgram") == 0) {
404 			   tracker_statement_list_insert (metadata, uri,
405 			   "Image:ExposureProgram", value);*/
406 		} else if (!data->exposure_time && g_ascii_strcasecmp (name, "ExposureTime") == 0) {
407 			data->exposure_time = div_str_dup (value);
408 		} else if (!data->fnumber && g_ascii_strcasecmp (name, "FNumber") == 0) {
409 			data->fnumber = div_str_dup (value);
410 		} else if (!data->focal_length && g_ascii_strcasecmp (name, "FocalLength") == 0) {
411 			data->focal_length = div_str_dup (value);
412 		} else if (!data->iso_speed_ratings && g_ascii_strcasecmp (name, "ISOSpeedRatings") == 0) {
413 			data->iso_speed_ratings = div_str_dup (value);
414 		} else if (!data->white_balance && g_ascii_strcasecmp (name, "WhiteBalance") == 0) {
415 			data->white_balance = g_strdup (fix_white_balance (value));
416 		} else if (!data->copyright && g_ascii_strcasecmp (name, "Copyright") == 0) {
417 			data->copyright = g_strdup (value);
418 		} else if (!data->gps_altitude && g_ascii_strcasecmp (name, "GPSAltitude") == 0) {
419 			data->gps_altitude = div_str_dup (value);
420 		} else if (!data->gps_altitude_ref && g_ascii_strcasecmp (name, "GPSAltitudeRef") == 0) {
421 			data->gps_altitude_ref = g_strdup (value);
422 		} else if (!data->gps_latitude && g_ascii_strcasecmp (name, "GPSLatitude") == 0) {
423 			data->gps_latitude = gps_coordinate_dup (value);
424 		} else if (!data->gps_longitude && g_ascii_strcasecmp (name, "GPSLongitude") == 0) {
425 			data->gps_longitude = gps_coordinate_dup (value);
426 		} else if (!data->gps_direction && g_ascii_strcasecmp (name, "GPSImgDirection") == 0) {
427 			data->gps_direction = div_str_dup (value);
428 		}
429 		/* TIFF */
430 	} else if (g_ascii_strcasecmp (schema, NS_TIFF) == 0) {
431 		if (!data->orientation && g_ascii_strcasecmp (name, "Orientation") == 0) {
432 			data->orientation = g_strdup (fix_orientation (value));
433 		}
434 		/* PDF*/
435 	} else if (g_ascii_strcasecmp (schema, NS_PDF) == 0) {
436 		if (g_ascii_strcasecmp (name, "keywords") == 0) {
437 			if (data->pdf_keywords) {
438 				gchar *temp = g_strdup_printf ("%s, %s", value, data->pdf_keywords);
439 				g_free (data->pdf_keywords);
440 				data->pdf_keywords = temp;
441 			} else {
442 				data->pdf_keywords = g_strdup (value);
443 			}
444 		} else
445 			if (!data->pdf_title && g_ascii_strcasecmp (name, "title") == 0) {
446 				data->pdf_title = g_strdup (value);
447 			}
448 		/* Dublin Core */
449 	} else if (g_ascii_strcasecmp (schema, NS_DC) == 0) {
450 		if (!data->title && g_ascii_strcasecmp (name, "title") == 0) {
451 			data->title = g_strdup (value);
452 		} else if (!data->rights && g_ascii_strcasecmp (name, "rights") == 0) {
453 			data->rights = g_strdup (value);
454 		} else if (!data->creator && g_ascii_strcasecmp (name, "creator") == 0) {
455 			data->creator = g_strdup (value);
456 		} else if (!data->description && g_ascii_strcasecmp (name, "description") == 0) {
457 			data->description = g_strdup (value);
458 		} else if (!data->date && g_ascii_strcasecmp (name, "date") == 0) {
459 			data->date = tracker_date_guess (value);
460 		} else if (g_ascii_strcasecmp (name, "keywords") == 0) {
461 			if (data->keywords) {
462 				gchar *temp = g_strdup_printf ("%s, %s", value, data->keywords);
463 				g_free (data->keywords);
464 				data->keywords = temp;
465 			} else {
466 				data->keywords = g_strdup (value);
467 			}
468 		} else if (g_ascii_strcasecmp (name, "subject") == 0) {
469 			if (data->subject) {
470 				gchar *temp = g_strdup_printf ("%s, %s", value, data->subject);
471 				g_free (data->subject);
472 				data->subject = temp;
473 			} else {
474 				data->subject = g_strdup (value);
475 			}
476 		} else if (!data->publisher && g_ascii_strcasecmp (name, "publisher") == 0) {
477 			data->publisher = g_strdup (value);
478 		} else if (!data->contributor && g_ascii_strcasecmp (name, "contributor") == 0) {
479 			data->contributor = g_strdup (value);
480 		} else if (!data->type && g_ascii_strcasecmp (name, "type") == 0) {
481 			data->type = g_strdup (value);
482 		} else if (!data->format && g_ascii_strcasecmp (name, "format") == 0) {
483 			data->format = g_strdup (value);
484 		} else if (!data->identifier && g_ascii_strcasecmp (name, "identifier") == 0) {
485 			data->identifier = g_strdup (value);
486 		} else if (!data->source && g_ascii_strcasecmp (name, "source") == 0) {
487 			data->source = g_strdup (value);
488 		} else if (!data->language && g_ascii_strcasecmp (name, "language") == 0) {
489 			data->language = g_strdup (value);
490 		} else if (!data->relation && g_ascii_strcasecmp (name, "relation") == 0) {
491 			data->relation = g_strdup (value);
492 		} else if (!data->coverage && g_ascii_strcasecmp (name, "coverage") == 0) {
493 			data->coverage = g_strdup (value);
494 		}
495 		/* Creative Commons */
496 	} else if (g_ascii_strcasecmp (schema, NS_CC) == 0) {
497 		if (!data->license && g_ascii_strcasecmp (name, "license") == 0) {
498 			data->license = g_strdup (value);
499 		}
500 		/* TODO: A lot of these location fields are pretty vague and ambigious.
501 		 * We should go through them one by one and ensure that all of them are
502 		 * used sanely */
503 
504 		/* Photoshop TODO: is this needed anyway? */
505 	} else if (g_ascii_strcasecmp (schema, NS_PHOTOSHOP) == 0) {
506 		if (!data->city && g_ascii_strcasecmp (name, "City") == 0) {
507 			data->city = g_strdup (value);
508 		} else if (!data->country && g_ascii_strcasecmp (name, "Country") == 0) {
509 			data->country = g_strdup (value);
510 		} else if (!data->state && g_ascii_strcasecmp (name, "State") == 0) {
511 			data->state = g_strdup (value);
512 		} else if (!data->address && g_ascii_strcasecmp (name, "Location") == 0) {
513 			data->address = g_strdup (value);
514 		}
515 		/* IPTC4XMP scheme - GeoClue / location stuff, TODO */
516 	} else if (g_ascii_strcasecmp (schema, NS_IPTC4XMP) == 0) {
517 		if (!data->city && g_ascii_strcasecmp (name, "City") == 0) {
518 			data->city = g_strdup (value);
519 		} else if (!data->country && g_ascii_strcasecmp (name, "Country") == 0) {
520 			data->country = g_strdup (value);
521 		} else if (!data->country && g_ascii_strcasecmp (name, "CountryName") == 0) {
522 			data->country = g_strdup (value);
523 		} else if (!data->country && g_ascii_strcasecmp (name, "PrimaryLocationName") == 0) {
524 			data->country = g_strdup (value);
525 		} else if (!data->state && g_ascii_strcasecmp (name, "State") == 0) {
526 			data->state = g_strdup (value);
527 		} else if (!data->state && g_ascii_strcasecmp (name, "Province") == 0) {
528 			data->state = g_strdup (value);
529 		} else if (!data->address && g_ascii_strcasecmp (name, "Sublocation") == 0) {
530 			data->address = g_strdup (value);
531 		}
532 	} else if (g_ascii_strcasecmp (schema, NS_XAP) == 0) {
533 		if (!data->rating && g_ascii_strcasecmp (name, "Rating") == 0) {
534 			data->rating = g_strdup (value);
535 		}
536 	} else if (g_ascii_strcasecmp (schema, NS_XMP_REGIONS) == 0) {
537                 if (g_str_has_prefix (path, "mwg-rs:Regions/mwg-rs:RegionList")) {
538 	                TrackerXmpRegion *current_region;
539                         gint              position = get_region_counter (path);
540 
541                         if (position == -1) {
542                                 g_free (name);
543                                 return;
544                         }
545 
546                         /* First time a property appear for a region, we create the region */
547                         current_region = g_slist_nth_data (data->regions, position-1);
548                         if (current_region == NULL) {
549                                 current_region = g_slice_new0 (TrackerXmpRegion);
550                                 data->regions = g_slist_append (data->regions, current_region);
551                         }
552 
553                         propname = g_strdup (strrchr (path, '/') + 1);
554 
555                         if (!current_region->title && g_ascii_strcasecmp (propname, "mwg-rs:Name") == 0) {
556                                 current_region->title = g_strdup (value);
557                         } else if (!current_region->description && g_ascii_strcasecmp (propname, "mwg-rs:Description") == 0) {
558                                 current_region->description = g_strdup (value);
559                         } else if (!current_region->x && g_ascii_strcasecmp (propname, "stArea:x") == 0) {
560                                 current_region->x = g_strdup (value);
561                         } else if (!current_region->y && g_ascii_strcasecmp (propname, "stArea:y") == 0) {
562                                 current_region->y = g_strdup (value);
563                         } else if (!current_region->width && g_ascii_strcasecmp (propname, "stArea:w") == 0) {
564                                 current_region->width = g_strdup (value);
565                         } else if (!current_region->height && g_ascii_strcasecmp (propname, "stArea:h") == 0) {
566                                 current_region->height = g_strdup (value);
567 
568                                 /* Spec not clear about units
569                                  *  } else if (!current_region->unit
570                                  *             && g_ascii_strcasecmp (propname, "stArea:unit") == 0) {
571                                  *      current_region->unit = g_strdup (value);
572                                  *
573                                  *  we consider it always comes normalized
574                                 */
575                         } else if (!current_region->type && g_ascii_strcasecmp (propname, "mwg-rs:Type") == 0) {
576                                 current_region->type = g_strdup (value);
577                         }
578                         else if (!current_region->link_class && !current_region->link_uri &&
579                                    strrchr (path, ']')  != NULL && strrchr (path, ']') + 2 < strrchr (path, '\0') &&
580                                    g_str_has_prefix (strrchr (path, ']') + 2, "mwg-rs:Extensions")) {
581                                 current_region->link_class = g_strdup (propname);
582                                 current_region->link_uri = g_strdup (value);
583                         }
584 
585                         g_free (propname);
586                 }
587         }
588 
589 	g_free (name);
590 }
591 
592 
593 /* Iterate over the XMP, dispatching to the appropriate element type
594  * (simple, simple w/qualifiers, or an array) handler.
595  */
596 static void
iterate(XmpPtr xmp,XmpIteratorPtr iter,const gchar * uri,TrackerXmpData * data,gboolean append)597 iterate (XmpPtr          xmp,
598          XmpIteratorPtr  iter,
599          const gchar    *uri,
600          TrackerXmpData *data,
601          gboolean        append)
602 {
603 	XmpStringPtr the_schema = xmp_string_new ();
604 	XmpStringPtr the_path = xmp_string_new ();
605 	XmpStringPtr the_prop = xmp_string_new ();
606 
607 	uint32_t opt;
608 
609 	while (xmp_iterator_next (iter, the_schema, the_path, the_prop, &opt)) {
610 		const gchar *schema = xmp_string_cstr (the_schema);
611 		const gchar *path = xmp_string_cstr (the_path);
612 		const gchar *value = xmp_string_cstr (the_prop);
613 
614 		if (XMP_IS_PROP_SIMPLE (opt)) {
615 			if (!tracker_is_empty_string (path)) {
616 				if (XMP_HAS_PROP_QUALIFIERS (opt)) {
617 					iterate_simple_qual (xmp, uri, data, schema, path, value, append);
618 				} else {
619 					iterate_simple (uri, data, schema, path, value, append);
620 				}
621 			}
622 		} else if (XMP_IS_PROP_ARRAY (opt)) {
623 			if (XMP_IS_ARRAY_ALTTEXT (opt)) {
624 				iterate_alt_text (xmp, uri, data, schema, path);
625 				xmp_iterator_skip (iter, XMP_ITER_SKIPSUBTREE);
626 			} else {
627 				iterate_array (xmp, uri, data, schema, path);
628 
629                                 /* Some dc: elements are handled as arrays by exempi.
630                                  * In those cases, to avoid duplicated values, is easier
631                                  * to skip the subtree.
632                                  */
633                                 if (g_ascii_strcasecmp (schema, NS_DC) == 0) {
634                                         xmp_iterator_skip (iter, XMP_ITER_SKIPSUBTREE);
635                                 }
636 			}
637 		}
638 	}
639 
640 	xmp_string_free (the_prop);
641 	xmp_string_free (the_path);
642 	xmp_string_free (the_schema);
643 }
644 
645 static void
register_namespace(const gchar * ns_uri,const gchar * suggested_prefix)646 register_namespace (const gchar *ns_uri,
647                     const gchar *suggested_prefix)
648 {
649         if (!xmp_namespace_prefix (ns_uri, NULL)) {
650                 xmp_register_namespace (ns_uri, suggested_prefix, NULL);
651         }
652 }
653 
654 #endif /* HAVE_EXEMPI */
655 
656 static gboolean
parse_xmp(const gchar * buffer,size_t len,const gchar * uri,TrackerXmpData * data)657 parse_xmp (const gchar    *buffer,
658            size_t          len,
659            const gchar    *uri,
660            TrackerXmpData *data)
661 {
662 #ifdef HAVE_EXEMPI
663 	XmpPtr xmp;
664 #endif /* HAVE_EXEMPI */
665 
666 	memset (data, 0, sizeof (TrackerXmpData));
667 
668 #ifdef HAVE_EXEMPI
669 
670 	xmp_init ();
671 
672         register_namespace (NS_XMP_REGIONS, "mwg-rs");
673         register_namespace (NS_ST_DIM, "stDim");
674         register_namespace (NS_ST_AREA, "stArea");
675 
676 	xmp = xmp_new_empty ();
677 	xmp_parse (xmp, buffer, len);
678 
679 	if (xmp != NULL) {
680 		XmpIteratorPtr iter;
681 
682 		iter = xmp_iterator_new (xmp, NULL, NULL, XMP_ITER_PROPERTIES);
683 		iterate (xmp, iter, uri, data, FALSE);
684 		xmp_iterator_free (iter);
685 		xmp_free (xmp);
686 	}
687 
688 	xmp_terminate ();
689 #endif /* HAVE_EXEMPI */
690 
691 	return TRUE;
692 }
693 
694 #ifndef TRACKER_DISABLE_DEPRECATED
695 
696 // LCOV_EXCL_START
697 
698 /**
699  * tracker_xmp_read:
700  * @buffer: a chunk of data with xmp data in it.
701  * @len: the size of @buffer.
702  * @uri: the URI this is related to.
703  * @data: a pointer to a TrackerXmpData structure to populate.
704  *
705  * This function takes @len bytes of @buffer and runs it through the
706  * XMP library. The result is that @data is populated with the XMP
707  * data found in @uri.
708  *
709  * Returns: %TRUE if the @data was populated successfully, otherwise
710  * %FALSE is returned.
711  *
712  * Since: 0.8
713  *
714  * Deprecated: 0.9. Use tracker_xmp_new() instead.
715  **/
716 gboolean
tracker_xmp_read(const gchar * buffer,size_t len,const gchar * uri,TrackerXmpData * data)717 tracker_xmp_read (const gchar    *buffer,
718                   size_t          len,
719                   const gchar    *uri,
720                   TrackerXmpData *data)
721 {
722 	g_return_val_if_fail (buffer != NULL, FALSE);
723 	g_return_val_if_fail (len > 0, FALSE);
724 	g_return_val_if_fail (uri != NULL, FALSE);
725 	g_return_val_if_fail (data != NULL, FALSE);
726 
727 	return parse_xmp (buffer, len, uri, data);
728 }
729 
730 // LCOV_EXCL_STOP
731 
732 #endif /* TRACKER_DISABLE_DEPRECATED */
733 
734 static void
xmp_region_free(gpointer data)735 xmp_region_free (gpointer data)
736 {
737         TrackerXmpRegion *region = (TrackerXmpRegion *) data;
738 
739         g_free (region->title);
740         g_free (region->description);
741         g_free (region->type);
742         g_free (region->x);
743         g_free (region->y);
744         g_free (region->width);
745         g_free (region->height);
746         g_free (region->link_class);
747         g_free (region->link_uri);
748 
749         g_slice_free (TrackerXmpRegion, region);
750 }
751 
752 
753 /**
754  * tracker_xmp_new:
755  * @buffer: a chunk of data with xmp data in it.
756  * @len: the size of @buffer.
757  * @uri: the URI this is related to.
758  *
759  * This function takes @len bytes of @buffer and runs it through the
760  * XMP library.
761  *
762  * Returns: a newly allocated #TrackerXmpData struct if XMP data was
763  * found, %NULL otherwise. Free the returned struct with tracker_xmp_free().
764  *
765  * Since: 0.10
766  **/
767 TrackerXmpData *
tracker_xmp_new(const gchar * buffer,gsize len,const gchar * uri)768 tracker_xmp_new (const gchar *buffer,
769                  gsize        len,
770                  const gchar *uri)
771 {
772 	TrackerXmpData *data;
773 
774 	g_return_val_if_fail (buffer != NULL, NULL);
775 	g_return_val_if_fail (len > 0, NULL);
776 	g_return_val_if_fail (uri != NULL, NULL);
777 
778 	data = g_new0 (TrackerXmpData, 1);
779 
780 	if (!parse_xmp (buffer, len, uri, data)) {
781 		tracker_xmp_free (data);
782 		return NULL;
783 	}
784 
785 	return data;
786 }
787 
788 /**
789  * tracker_xmp_free:
790  * @data: a #TrackerXmpData struct
791  *
792  * Frees @data and all #TrackerXmpData members. %NULL will produce a
793  * a warning.
794  *
795  * Since: 0.10
796  **/
797 void
tracker_xmp_free(TrackerXmpData * data)798 tracker_xmp_free (TrackerXmpData *data)
799 {
800 	g_return_if_fail (data != NULL);
801 
802 	g_free (data->title);
803 	g_free (data->rights);
804 	g_free (data->creator);
805 	g_free (data->description);
806 	g_free (data->date);
807 	g_free (data->keywords);
808 	g_free (data->subject);
809 	g_free (data->publisher);
810 	g_free (data->contributor);
811 	g_free (data->type);
812 	g_free (data->format);
813 	g_free (data->identifier);
814 	g_free (data->source);
815 	g_free (data->language);
816 	g_free (data->relation);
817 	g_free (data->coverage);
818 	g_free (data->license);
819 	g_free (data->pdf_title);
820 	g_free (data->pdf_keywords);
821 	g_free (data->title2);
822 	g_free (data->time_original);
823 	g_free (data->artist);
824 	g_free (data->make);
825 	g_free (data->model);
826 	g_free (data->orientation);
827 	g_free (data->flash);
828 	g_free (data->metering_mode);
829 	g_free (data->exposure_time);
830 	g_free (data->fnumber);
831 	g_free (data->focal_length);
832 	g_free (data->iso_speed_ratings);
833 	g_free (data->white_balance);
834 	g_free (data->copyright);
835 	g_free (data->rating);
836 	g_free (data->address);
837 	g_free (data->country);
838 	g_free (data->state);
839 	g_free (data->city);
840 	g_free (data->gps_altitude);
841 	g_free (data->gps_altitude_ref);
842 	g_free (data->gps_latitude);
843 	g_free (data->gps_longitude);
844 	g_free (data->gps_direction);
845 
846         g_slist_free_full (data->regions, xmp_region_free);
847 	g_free (data);
848 }
849 
850 
851 static const gchar *
fix_region_type(const gchar * region_type)852 fix_region_type (const gchar *region_type)
853 {
854         if (region_type == NULL) {
855                 return "nfo:region-content-undefined";
856         }
857 
858         if (g_ascii_strncasecmp (region_type, "Face", 4) == 0) {
859                 return "nfo:roi-content-face";
860         } else if (g_ascii_strncasecmp (region_type, "Pet", 3) == 0) {
861                 return "nfo:roi-content-pet";
862         } else if (g_ascii_strncasecmp (region_type, "Focus", 5) == 0) {
863                 return "nfo:roi-content-focus";
864         } else if (g_ascii_strncasecmp (region_type, "BarCode", 7) == 0) {
865                 return "nfo:roi-content-barcode";
866         }
867 
868         return "nfo:roi-content-undefined";
869 }
870 
871 
872 /**
873  * tracker_xmp_apply_to_resource:
874  * @resource: the #TrackerResource to apply XMP data to.
875  * @data: the data to push into @resource.
876  *
877  * This function applies all data in @data to @resource.
878  *
879  * This function also calls tracker_xmp_apply_regions_to_resource(), so there
880  * is no need to call both functions.
881  *
882  * Returns: %TRUE if the @data was applied to @resource successfully,
883  * otherwise %FALSE is returned.
884  *
885  * Since: 1.10
886  **/
887 gboolean
tracker_xmp_apply_to_resource(TrackerResource * resource,TrackerXmpData * data)888 tracker_xmp_apply_to_resource (TrackerResource *resource,
889                                TrackerXmpData  *data)
890 {
891 	GPtrArray *keywords;
892 	guint i;
893 
894 	g_return_val_if_fail (TRACKER_IS_RESOURCE (resource), FALSE);
895 	g_return_val_if_fail (data != NULL, FALSE);
896 
897 	keywords = g_ptr_array_new ();
898 
899 	if (data->keywords) {
900 		tracker_keywords_parse (keywords, data->keywords);
901 	}
902 
903 	if (data->subject) {
904 		tracker_keywords_parse (keywords, data->subject);
905 	}
906 
907 	if (data->pdf_keywords) {
908 		tracker_keywords_parse (keywords, data->pdf_keywords);
909 	}
910 
911 	for (i = 0; i < keywords->len; i++) {
912 		TrackerResource *label;
913 		gchar *p;
914 
915 		p = g_ptr_array_index (keywords, i);
916 		label = tracker_extract_new_tag (p);
917 
918 		tracker_resource_set_relation (resource, "nao:hasTag", label);
919 
920 		g_free (p);
921 		g_object_unref (label);
922 	}
923 	g_ptr_array_free (keywords, TRUE);
924 
925 	if (data->publisher) {
926 		TrackerResource *publisher;
927 
928 		publisher = tracker_extract_new_contact (data->publisher);
929 		tracker_resource_set_relation (resource, "nco:publisher", publisher);
930 		g_object_unref (publisher);
931 	}
932 
933 	if (data->type) {
934 		tracker_resource_set_string (resource, "dc:type", data->type);
935 	}
936 
937 	if (data->format) {
938 		tracker_resource_set_string (resource, "dc:format", data->format);
939 	}
940 
941 	if (data->identifier) {
942 		tracker_resource_set_string (resource, "dc:identifier", data->identifier);
943 	}
944 
945 	if (data->source) {
946 		tracker_resource_set_string (resource, "dc:source", data->source);
947 	}
948 
949 	if (data->language) {
950 		tracker_resource_set_string (resource, "dc:language", data->language);
951 	}
952 
953 	if (data->relation) {
954 		tracker_resource_set_string (resource, "dc:relation", data->relation);
955 	}
956 
957 	if (data->coverage) {
958 		tracker_resource_set_string (resource, "dc:coverage", data->coverage);
959 	}
960 
961 	if (data->license) {
962 		tracker_resource_set_string (resource, "dc:license", data->license);
963 	}
964 
965 	if (data->make || data->model) {
966 		TrackerResource *equipment;
967 
968 		equipment = tracker_extract_new_equipment (data->make, data->model);
969 		tracker_resource_set_relation (resource, "nfo:equipment", equipment);
970 		g_object_unref (equipment);
971 	}
972 
973 	if (data->title || data->title2 || data->pdf_title) {
974 		const gchar *final_title = tracker_coalesce_strip (3, data->title,
975 		                                                   data->title2,
976 		                                                   data->pdf_title);
977 
978 		tracker_resource_set_string (resource, "nie:title", final_title);
979 	}
980 
981 	if (data->orientation) {
982 		TrackerResource *orientation;
983 
984 		orientation = tracker_resource_new (data->orientation);
985 		tracker_resource_set_relation (resource, "nfo:orientation", orientation);
986 		g_object_unref (orientation);
987 	}
988 
989 	if (data->rights || data->copyright) {
990 		const gchar *final_rights = tracker_coalesce_strip (2, data->copyright, data->rights);
991 
992 		tracker_resource_set_string (resource, "nie:copyright", final_rights);
993 	}
994 
995 	if (data->white_balance) {
996 		TrackerResource *white_balance;
997 
998 		white_balance = tracker_resource_new (data->white_balance);
999 		tracker_resource_set_relation (resource, "nmm:whiteBalance", white_balance);
1000 		g_object_unref (white_balance);
1001 	}
1002 
1003 	if (data->fnumber) {
1004 		tracker_resource_set_string (resource, "nmm:fnumber", data->fnumber);
1005 	}
1006 
1007 	if (data->flash) {
1008 		TrackerResource *flash;
1009 
1010 		flash = tracker_resource_new (data->flash);
1011 		tracker_resource_set_relation (resource, "nmm:flash", flash);
1012 		g_object_unref (flash);
1013 	}
1014 
1015 	if (data->focal_length) {
1016 		tracker_resource_set_string (resource, "nmm:focalLength", data->focal_length);
1017 	}
1018 
1019 	if (data->artist || data->contributor) {
1020 		TrackerResource *contributor;
1021 		const gchar *final_artist = tracker_coalesce_strip (2, data->artist, data->contributor);
1022 
1023 		contributor = tracker_extract_new_contact (final_artist);
1024 		tracker_resource_set_relation (resource, "nco:contributor", contributor);
1025 		g_object_unref (contributor);
1026 	}
1027 
1028 	if (data->exposure_time) {
1029 		tracker_resource_set_string (resource, "nmm:exposureTime", data->exposure_time);
1030 	}
1031 
1032 	if (data->iso_speed_ratings) {
1033 		tracker_resource_set_string (resource, "nmm:isoSpeed", data->iso_speed_ratings);
1034 	}
1035 
1036 	if (data->date || data->time_original) {
1037 		const gchar *final_date = tracker_coalesce_strip (2, data->date,
1038 		                                                  data->time_original);
1039 
1040 		tracker_resource_set_string (resource, "nie:contentCreated", final_date);
1041 	}
1042 
1043 	if (data->description) {
1044 		tracker_resource_set_string (resource, "nie:description", data->description);
1045 	}
1046 
1047 	if (data->metering_mode) {
1048 		TrackerResource *metering;
1049 
1050 		metering = tracker_resource_new (data->metering_mode);
1051 		tracker_resource_set_relation (resource, "nmm:meteringMode", metering);
1052 		g_object_unref (metering);
1053 	}
1054 
1055 	if (data->creator) {
1056 		TrackerResource *creator;
1057 
1058 		creator = tracker_extract_new_contact (data->creator);
1059 		tracker_resource_set_relation (resource, "nco:creator", creator);
1060 		g_object_unref (creator);
1061 	}
1062 
1063 	if (data->address || data->state || data->country || data->city ||
1064 	    data->gps_altitude || data->gps_latitude || data->gps_longitude) {
1065 		TrackerResource *geopoint;
1066 
1067 		geopoint = tracker_extract_new_location (data->address, data->state, data->city,
1068 		        data->country, data->gps_altitude, data->gps_latitude, data->gps_longitude);
1069 		tracker_resource_set_relation (resource, "slo:location", geopoint);
1070 		g_object_unref (geopoint);
1071 	}
1072 
1073 	if (data->gps_direction) {
1074 		tracker_resource_set_string (resource, "nfo:heading", data->gps_direction);
1075 	}
1076 
1077 	if (data->regions) {
1078 		tracker_xmp_apply_regions_to_resource (resource, data);
1079 	}
1080 
1081 	return TRUE;
1082 }
1083 
1084 /**
1085  * tracker_xmp_apply_regions_to_resource:
1086  * @resource: the #TrackerResource object to apply XMP data to.
1087  * @data: the data to push into @resource
1088  *
1089  * This function applies all regional @data to @resource. Regional data exists
1090  * for image formats like JPEG, PNG, etc. where parts of the image refer to
1091  * areas of interest. This can be people's faces, places to focus, barcodes,
1092  * etc. The regional data describes the title, height, width, X, Y and can
1093  * occur multiple times in a given file.
1094  *
1095  * This data usually is standardized between image formats and that's
1096  * what makes this function different to tracker_xmp_apply_to_resource() which
1097  * is useful for XMP files only.
1098  *
1099  * Returns: %TRUE if the @data was applied to @resource successfully, otherwise
1100  * %FALSE is returned.
1101  *
1102  * Since: 1.10
1103  **/
1104 gboolean
tracker_xmp_apply_regions_to_resource(TrackerResource * resource,TrackerXmpData * data)1105 tracker_xmp_apply_regions_to_resource (TrackerResource *resource,
1106                                        TrackerXmpData  *data)
1107 {
1108 	GSList *iter;
1109 
1110 	g_return_val_if_fail (TRACKER_IS_RESOURCE (resource), FALSE);
1111 	g_return_val_if_fail (data != NULL, FALSE);
1112 
1113 	if (!data->regions) {
1114 		return TRUE;
1115 	}
1116 
1117 	for (iter = data->regions; iter != NULL; iter = iter->next) {
1118 		TrackerResource *region_resource;
1119 		TrackerXmpRegion *region;
1120 		gchar *uuid;
1121 
1122 		region = (TrackerXmpRegion *) iter->data;
1123 		uuid = tracker_sparql_get_uuid_urn ();
1124 
1125 		region_resource = tracker_resource_new (uuid);
1126 		tracker_resource_set_uri (region_resource, "rdf:type", "nfo:RegionOfInterest");
1127 
1128 		g_free (uuid);
1129 
1130 		if (region->title) {
1131 			tracker_resource_set_string (region_resource, "nie:title", region->title);
1132 		}
1133 
1134 		if (region->description) {
1135 			tracker_resource_set_string (region_resource, "nie:description", region->description);
1136 		}
1137 
1138 		if (region->type) {
1139 			tracker_resource_set_string (region_resource, "nfo:regionOfInterestType", fix_region_type (region->type));
1140 		}
1141 
1142 		if (region->x) {
1143 			tracker_resource_set_string (region_resource, "nfo:regionOfInterestX", region->x);
1144 		}
1145 
1146 		if (region->y) {
1147 			tracker_resource_set_string (region_resource, "nfo:regionOfInterestY", region->y);
1148 		}
1149 
1150 		if (region->width) {
1151 			tracker_resource_set_string (region_resource, "nfo:regionOfInterestWidth", region->width);
1152 		}
1153 
1154 		if (region->height) {
1155 			tracker_resource_set_string (region_resource, "nfo:regionOfInterestHeight", region->height);
1156 		}
1157 
1158 		if (region->link_uri && region->link_class) {
1159 			tracker_resource_set_string (region_resource, "nfo:roiRefersTo", region->link_uri);
1160 		}
1161 
1162 		tracker_resource_add_relation (resource, "nfo:hasRegionOfInterest", region_resource);
1163 		g_object_unref (region_resource);
1164 	}
1165 
1166 	return TRUE;
1167 }
1168