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