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