tracker-0.16.2/src/tracker-writeback/tracker-writeback-xmp.c

No issues found

  1 /*
  2  * Copyright (C) 2009, Nokia <ivan.frade@nokia.com>
  3  *
  4  * This library is free software; you can redistribute it and/or
  5  * modify it under the terms of the GNU General Public
  6  * License as published by the Free Software Foundation; either
  7  * version 2 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  * General Public License for more details.
 13  *
 14  * You should have received a copy of the GNU 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  * Authors: Philip Van Hoof <philip@codeminded.be>
 20  */
 21 
 22 #include "config.h"
 23 
 24 #include <locale.h>
 25 #include <string.h>
 26 #include <math.h>
 27 
 28 #include <exempi/xmp.h>
 29 #include <exempi/xmpconsts.h>
 30 
 31 #include <glib-object.h>
 32 #include <gio/gio.h>
 33 
 34 #include <libtracker-common/tracker-ontologies.h>
 35 #include <libtracker-common/tracker-utils.h>
 36 
 37 #include "tracker-writeback-file.h"
 38 
 39 #define TRACKER_TYPE_WRITEBACK_XMP (tracker_writeback_xmp_get_type ())
 40 
 41 typedef struct TrackerWritebackXMP TrackerWritebackXMP;
 42 typedef struct TrackerWritebackXMPClass TrackerWritebackXMPClass;
 43 
 44 struct TrackerWritebackXMP {
 45 	TrackerWritebackFile parent_instance;
 46 };
 47 
 48 struct TrackerWritebackXMPClass {
 49 	TrackerWritebackFileClass parent_class;
 50 };
 51 
 52 static GType                tracker_writeback_xmp_get_type     (void) G_GNUC_CONST;
 53 static gboolean             writeback_xmp_update_file_metadata (TrackerWritebackFile     *writeback_file,
 54                                                                 GFile                    *file,
 55                                                                 GPtrArray                *values,
 56                                                                 TrackerSparqlConnection  *connection,
 57                                                                 GCancellable             *cancellable,
 58                                                                 GError                  **error);
 59 static const gchar * const *writeback_xmp_content_types        (TrackerWritebackFile     *writeback_file);
 60 
 61 G_DEFINE_DYNAMIC_TYPE (TrackerWritebackXMP, tracker_writeback_xmp, TRACKER_TYPE_WRITEBACK_FILE);
 62 
 63 static void
 64 tracker_writeback_xmp_class_init (TrackerWritebackXMPClass *klass)
 65 {
 66 	TrackerWritebackFileClass *writeback_file_class = TRACKER_WRITEBACK_FILE_CLASS (klass);
 67 
 68 	xmp_init ();
 69 
 70 	writeback_file_class->update_file_metadata = writeback_xmp_update_file_metadata;
 71 	writeback_file_class->content_types = writeback_xmp_content_types;
 72 }
 73 
 74 static void
 75 tracker_writeback_xmp_class_finalize (TrackerWritebackXMPClass *klass)
 76 {
 77 	xmp_terminate ();
 78 }
 79 
 80 static void
 81 tracker_writeback_xmp_init (TrackerWritebackXMP *wbx)
 82 {
 83 }
 84 
 85 static const gchar * const *
 86 writeback_xmp_content_types (TrackerWritebackFile *wbf)
 87 {
 88 	static const gchar *content_types[] = {
 89 		"image/png",   /* .png files */
 90 		"sketch/png",  /* .sketch.png files on Maemo*/
 91 		"image/jpeg",  /* .jpg & .jpeg files */
 92 		"image/tiff",  /* .tiff & .tif files */
 93 		"video/mp4",   /* .mp4 files */
 94 		"video/3gpp",  /* .3gpp files */
 95 		NULL
 96 	};
 97 
 98 	/* "image/gif"                        .gif files
 99 	   "application/pdf"                  .pdf files
100 	   "application/rdf+xml"              .xmp files
101 	   "application/postscript"           .ps files
102 	   "application/x-shockwave-flash"    .swf files
103 	   "video/quicktime"                  .mov files
104 	   "video/mpeg"                       .mpeg & .mpg files
105 	   "audio/mpeg"                       .mp3, etc files */
106 
107 	return content_types;
108 }
109 
110 static gboolean
111 writeback_xmp_update_file_metadata (TrackerWritebackFile     *wbf,
112                                     GFile                    *file,
113                                     GPtrArray                *values,
114                                     TrackerSparqlConnection  *connection,
115                                     GCancellable             *cancellable,
116                                     GError                  **error)
117 {
118 	gchar *path;
119 	guint n;
120 	XmpFilePtr xmp_files;
121 	XmpPtr xmp;
122 #ifdef DEBUG_XMP
123 	XmpStringPtr str;
124 #endif
125 	GString *keywords = NULL;
126 	const gchar *urn = NULL;
127 
128 	path = g_file_get_path (file);
129 
130 	xmp_files = xmp_files_open_new (path, XMP_OPEN_FORUPDATE);
131 
132 	if (!xmp_files) {
133 		g_set_error (error,
134 		             G_IO_ERROR,
135 		             G_IO_ERROR_FAILED,
136 		             "Can't open '%s' for update with Exempi (Exempi error code = %d)",
137 		             path,
138 		             xmp_get_error ());
139 		g_free (path);
140 		return FALSE;
141 	}
142 
143 	xmp = xmp_files_get_new_xmp (xmp_files);
144 
145 	if (!xmp) {
146 		xmp = xmp_new_empty ();
147 	}
148 
149 #ifdef DEBUG_XMP
150 	str = xmp_string_new ();
151 	g_print ("\nBEFORE: ---- \n");
152 	xmp_serialize_and_format (xmp, str, 0, 0, "\n", "\t", 1);
153 	g_print ("%s\n", xmp_string_cstr (str));
154 	xmp_string_free (str);
155 #endif
156 
157 	for (n = 0; n < values->len; n++) {
158 		const GStrv row = g_ptr_array_index (values, n);
159 
160 		urn = row[1]; /* The urn is at 1 */
161 
162 		if (g_strcmp0 (row[2], TRACKER_NIE_PREFIX "title") == 0) {
163 			xmp_delete_property (xmp, NS_EXIF, "Title");
164 			xmp_set_property (xmp, NS_EXIF, "Title", row[3], 0);
165 			xmp_delete_property (xmp, NS_DC, "title");
166 			xmp_set_property (xmp, NS_DC, "title", row[3], 0);
167 		}
168 
169 		if (g_strcmp0 (row[2], TRACKER_NCO_PREFIX "creator") == 0) {
170 			TrackerSparqlCursor *cursor;
171 			GError *error = NULL;
172 			gchar *query;
173 
174 			query = g_strdup_printf ("SELECT ?fullname { "
175 			                         "  <%s> nco:fullname ?fullname "
176 			                         "}", row[3]);
177 			cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
178 			g_free (query);
179 			if (!error) {
180 				while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
181 					xmp_delete_property (xmp, NS_DC, "creator");
182 					xmp_set_property (xmp, NS_DC, "creator",
183 					                  tracker_sparql_cursor_get_string (cursor, 0, NULL),
184 					                  0);
185 				}
186 			}
187 			g_object_unref (cursor);
188 			g_clear_error (&error);
189 		}
190 
191 		if (g_strcmp0 (row[2], TRACKER_NCO_PREFIX "contributor") == 0) {
192 			TrackerSparqlCursor *cursor;
193 			GError *error = NULL;
194 			gchar *query;
195 
196 			query = g_strdup_printf ("SELECT ?fullname { "
197 			                         "  <%s> nco:fullname ?fullname "
198 			                         "}", row[3]);
199 
200 			cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
201 			g_free (query);
202 			if (!error) {
203 				while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
204 					xmp_delete_property (xmp, NS_DC, "contributor");
205 					xmp_set_property (xmp, NS_DC, "contributor", tracker_sparql_cursor_get_string (cursor, 0, NULL), 0);
206 				}
207 			}
208 			g_object_unref (cursor);
209 			g_clear_error (&error);
210 		}
211 
212 		if (g_strcmp0 (row[2], TRACKER_NIE_PREFIX "description") == 0) {
213 			xmp_delete_property (xmp, NS_DC, "description");
214 			xmp_set_property (xmp, NS_DC, "description", row[3], 0);
215 		}
216 
217 		if (g_strcmp0 (row[2], TRACKER_NIE_PREFIX "copyright") == 0) {
218 			xmp_delete_property (xmp, NS_EXIF, "Copyright");
219 			xmp_set_property (xmp, NS_EXIF, "Copyright", row[3], 0);
220 		}
221 
222 		if (g_strcmp0 (row[2], TRACKER_NIE_PREFIX "comment") == 0) {
223 			xmp_delete_property (xmp, NS_EXIF, "UserComment");
224 			xmp_set_property (xmp, NS_EXIF, "UserComment", row[3], 0);
225 		}
226 
227 		if (g_strcmp0 (row[2], TRACKER_NIE_PREFIX "keyword") == 0) {
228 			if (!keywords) {
229 				keywords = g_string_new (row[3]);
230 			} else {
231 				g_string_append_printf (keywords, ", %s", row[3]);
232 			}
233 		}
234 
235 
236 		if (g_strcmp0 (row[2], TRACKER_NAO_PREFIX "hasTag") == 0) {
237 			TrackerSparqlCursor *cursor;
238 			GError *error = NULL;
239 			gchar *query;
240 
241 			query = g_strdup_printf ("SELECT ?label { "
242 			                         "  <%s> nao:prefLabel ?label "
243 			                         "}", row[3]);
244 
245 			cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
246 			g_free (query);
247 			if (!error) {
248 				while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
249 					if (!keywords) {
250 						keywords = g_string_new (tracker_sparql_cursor_get_string (cursor, 0, NULL));
251 					} else {
252 						g_string_append_printf (keywords, ", %s", tracker_sparql_cursor_get_string (cursor, 0, NULL));
253 					}
254 				}
255 			}
256 			g_object_unref (cursor);
257 			g_clear_error (&error);
258 		}
259 
260 		if (g_strcmp0 (row[2], TRACKER_NIE_PREFIX "contentCreated") == 0) {
261 			xmp_delete_property (xmp, NS_EXIF, "Date");
262 			xmp_set_property (xmp, NS_EXIF, "Date", row[3], 0);
263 			xmp_delete_property (xmp,  NS_DC, "date");
264 			xmp_set_property (xmp,  NS_DC, "date", row[3], 0);
265 		}
266 
267 		if (g_strcmp0 (row[2], TRACKER_NFO_PREFIX "orientation") == 0) {
268 
269 			xmp_delete_property (xmp, NS_EXIF, "Orientation");
270 
271 			if        (g_strcmp0 (row[3], TRACKER_NFO_PREFIX "orientation-top") == 0) {
272 				xmp_set_property (xmp, NS_EXIF, "Orientation", "top - left", 0);
273 			} else if (g_strcmp0 (row[3], TRACKER_NFO_PREFIX "orientation-top-mirror") == 0) {
274 				xmp_set_property (xmp, NS_EXIF, "Orientation", "top - right", 0);
275 			} else if (g_strcmp0 (row[3], TRACKER_NFO_PREFIX "orientation-bottom") == 0) {
276 				xmp_set_property (xmp, NS_EXIF, "Orientation", "bottom - left", 0);
277 			} else if (g_strcmp0 (row[3], TRACKER_NFO_PREFIX "orientation-bottom-mirror") == 0) {
278 				xmp_set_property (xmp, NS_EXIF, "Orientation", "bottom - right", 0);
279 			} else if (g_strcmp0 (row[3], TRACKER_NFO_PREFIX "orientation-left-mirror") == 0) {
280 				xmp_set_property (xmp, NS_EXIF, "Orientation", "left - top", 0);
281 			} else if (g_strcmp0 (row[3], TRACKER_NFO_PREFIX "orientation-right") == 0) {
282 				xmp_set_property (xmp, NS_EXIF, "Orientation", "right - top", 0);
283 			} else if (g_strcmp0 (row[3], TRACKER_NFO_PREFIX "orientation-right-mirror") == 0) {
284 					xmp_set_property (xmp, NS_EXIF, "Orientation", "right - bottom", 0);
285 			} else if (g_strcmp0 (row[3], TRACKER_NFO_PREFIX "orientation-left") == 0) {
286 				xmp_set_property (xmp, NS_EXIF, "Orientation", "left - bottom", 0);
287 			}
288 		}
289 
290 #ifdef SET_TYPICAL_CAMERA_FIELDS
291 		/* Default we don't do this, we shouldn't overwrite fields that are
292 		 * typically set by the camera itself. What do we know (better) than
293 		 * the actual camera did, anyway? Even if the user overwrites them in
294 		 * the RDF store ... (does he know what he's doing anyway?) */
295 
296 		if (g_strcmp0 (row[2], TRACKER_NMM_PREFIX "meteringMode") == 0) {
297 
298 			xmp_delete_property (xmp, NS_EXIF, "MeteringMode");
299 
300 			/* 0 = Unknown
301 			   1 = Average
302 			   2 = CenterWeightedAverage
303 			   3 = Spot
304 			   4 = MultiSpot
305 			   5 = Pattern
306 			   6 = Partial
307 			   255 = other  */
308 
309 			if        (g_strcmp0 (row[3], TRACKER_NMM_PREFIX "metering-mode-center-weighted-average") == 0) {
310 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "0", 0);
311 			} else if (g_strcmp0 (row[3], TRACKER_NMM_PREFIX "metering-mode-average") == 0) {
312 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "1", 0);
313 			} else if (g_strcmp0 (row[3], TRACKER_NMM_PREFIX "metering-mode-spot") == 0) {
314 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "3", 0);
315 			} else if (g_strcmp0 (row[3], TRACKER_NMM_PREFIX "metering-mode-multispot") == 0) {
316 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "4", 0);
317 			} else if (g_strcmp0 (row[3], TRACKER_NMM_PREFIX "metering-mode-pattern") == 0) {
318 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "5", 0);
319 			} else if (g_strcmp0 (row[3], TRACKER_NMM_PREFIX "metering-mode-partial") == 0) {
320 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "6", 0);
321 			} else {
322 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "255", 0);
323 			}
324 		}
325 
326 		if (g_strcmp0 (row[2], TRACKER_NMM_PREFIX "whiteBalance") == 0) {
327 
328 			xmp_delete_property (xmp, NS_EXIF, "WhiteBalance");
329 
330 			if (g_strcmp0 (row[3], TRACKER_NMM_PREFIX "white-balance-auto") == 0) {
331 				/* 0 = Auto white balance
332 				 * 1 = Manual white balance */
333 				xmp_set_property (xmp, NS_EXIF, "WhiteBalance", "0", 0);
334 			} else {
335 				xmp_set_property (xmp, NS_EXIF, "WhiteBalance", "1", 0);
336 			}
337 		}
338 
339 		if (g_strcmp0 (row[2], TRACKER_NMM_PREFIX "flash") == 0) {
340 
341 			xmp_delete_property (xmp, NS_EXIF, "Flash");
342 
343 			if (g_strcmp0 (row[3], TRACKER_NMM_PREFIX "flash-on") == 0) {
344 				/* 0 = Flash did not fire
345 				 * 1 = Flash fired */
346 				xmp_set_property (xmp, NS_EXIF, "Flash", "1", 0);
347 			} else {
348 				xmp_set_property (xmp, NS_EXIF, "Flash", "0", 0);
349 			}
350 		}
351 
352 
353 		/* TODO: Don't write row[3] as-is here, read xmp_specification.pdf,
354 		   page 84 (bottom). */
355 
356 		if (g_strcmp0 (row[2], TRACKER_NMM_PREFIX "focalLength") == 0) {
357 			xmp_delete_property (xmp, NS_EXIF, "FocalLength");
358 			xmp_set_property (xmp, NS_EXIF, "FocalLength", row[3], 0);
359 		}
360 
361 		if (g_strcmp0 (row[2], TRACKER_NMM_PREFIX "exposureTime") == 0) {
362 			xmp_delete_property (xmp, NS_EXIF, "ExposureTime");
363 			xmp_set_property (xmp, NS_EXIF, "ExposureTime", row[3], 0);
364 		}
365 
366 		if (g_strcmp0 (row[2], TRACKER_NMM_PREFIX "isoSpeed") == 0) {
367 			xmp_delete_property (xmp, NS_EXIF, "ISOSpeedRatings");
368 			xmp_set_property (xmp, NS_EXIF, "ISOSpeedRatings", row[3], 0);
369 		}
370 
371 		if (g_strcmp0 (row[2], TRACKER_NMM_PREFIX "fnumber") == 0) {
372 			xmp_delete_property (xmp, NS_EXIF, "FNumber");
373 			xmp_set_property (xmp, NS_EXIF, "FNumber", row[3], 0);
374 		}
375 
376 
377 		/* Totally deprecated: this uses nfo:Equipment nowadays */
378 		if (g_strcmp0 (row[2], TRACKER_NMM_PREFIX "camera") == 0) {
379 			gchar *work_on = g_strdup (row[3]);
380 			gchar *ptr = strchr (work_on, ' ');
381 
382 			if (ptr) {
383 
384 				*ptr = '\0';
385 				ptr++;
386 
387 				xmp_delete_property (xmp, NS_EXIF, "Make");
388 				xmp_set_property (xmp, NS_EXIF, "Make", work_on, 0);
389 				xmp_delete_property (xmp, NS_EXIF, "Model");
390 				xmp_set_property (xmp, NS_EXIF, "Model", ptr, 0);
391 			} else {
392 				xmp_delete_property (xmp, NS_EXIF, "Make");
393 				xmp_delete_property (xmp, NS_EXIF, "Model");
394 				xmp_set_property (xmp, NS_EXIF, "Model", work_on, 0);
395 			}
396 
397 			g_free (work_on);
398 		}
399 #endif /* SET_TYPICAL_CAMERA_FIELDS */
400 
401 		if (g_strcmp0 (row[2], TRACKER_NFO_PREFIX "heading") == 0) {
402 			xmp_delete_property (xmp, NS_EXIF, "GPSImgDirection");
403 			xmp_set_property (xmp, NS_EXIF, "GPSImgDirection", row[3], 0);
404 		}
405 	}
406 
407 	if (urn != NULL) {
408 		TrackerSparqlCursor *cursor;
409 		GError *error = NULL;
410 		gchar *query;
411 
412 		query = g_strdup_printf ("SELECT "
413 		                         "nco:locality (?addr) "
414 		                         "nco:region (?addr) "
415 		                         "nco:streetAddress (?addr) "
416 		                         "nco:country (?addr) "
417 		                         "slo:altitude (?loc) "
418 		                         "slo:longitude (?loc) "
419 		                         "slo:latitude (?loc) "
420 		                         "WHERE { <%s> slo:location ?loc . "
421 		                                 "?loc slo:postalAddress ?addr . }",
422 		                         urn);
423 
424 		cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
425 		g_free (query);
426 		if (!error) {
427 			if (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
428 				const gchar *city = NULL, *subl = NULL, *country = NULL,
429 				            *state = NULL, *altitude = NULL, *longitude = NULL,
430 				            *latitude = NULL;
431 
432 				if (tracker_sparql_cursor_get_value_type (cursor, 0) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
433 					city = tracker_sparql_cursor_get_string (cursor, 0, NULL);
434 				}
435 
436 				if (tracker_sparql_cursor_get_value_type (cursor, 1) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
437 					state = tracker_sparql_cursor_get_string (cursor, 1, NULL);
438 				}
439 
440 				if (tracker_sparql_cursor_get_value_type (cursor, 2) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
441 					subl = tracker_sparql_cursor_get_string (cursor, 2, NULL);
442 				}
443 
444 				if (tracker_sparql_cursor_get_value_type (cursor, 3) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
445 					country = tracker_sparql_cursor_get_string (cursor, 3, NULL);
446 				}
447 
448 				if (tracker_sparql_cursor_get_value_type (cursor, 4) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
449 					altitude = tracker_sparql_cursor_get_string (cursor, 4, NULL);
450 				}
451 
452 				if (tracker_sparql_cursor_get_value_type (cursor, 5) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
453 					longitude = tracker_sparql_cursor_get_string (cursor, 5, NULL);
454 				}
455 				
456 				if (tracker_sparql_cursor_get_value_type (cursor, 6) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
457 					latitude = tracker_sparql_cursor_get_string (cursor, 6, NULL);
458 				}
459 
460 				/* TODO: A lot of these location fields are pretty vague and ambigious.
461 				 * We should go through them one by one and ensure that all of them are
462 				 * used sanely */
463 
464 				xmp_delete_property (xmp, NS_IPTC4XMP, "City");
465 				xmp_delete_property (xmp, NS_PHOTOSHOP, "City");
466 				if (city != NULL) {
467 					xmp_set_property (xmp, NS_IPTC4XMP, "City", city, 0);
468 					xmp_set_property (xmp, NS_PHOTOSHOP, "City", city, 0);
469 				}
470 
471 				xmp_delete_property (xmp, NS_IPTC4XMP, "State");
472 				xmp_delete_property (xmp, NS_IPTC4XMP, "Province");
473 				xmp_delete_property (xmp, NS_PHOTOSHOP, "State");
474 				if (state != NULL) {
475 					xmp_set_property (xmp, NS_IPTC4XMP, "State", state, 0);
476 					xmp_set_property (xmp, NS_IPTC4XMP, "Province", state, 0);
477 					xmp_set_property (xmp, NS_PHOTOSHOP, "State", state, 0);
478 				}
479 
480 				xmp_delete_property (xmp, NS_IPTC4XMP, "SubLocation");
481 				xmp_delete_property (xmp, NS_PHOTOSHOP, "Location");
482 				if (subl != NULL) {
483 					xmp_set_property (xmp, NS_IPTC4XMP, "SubLocation", subl, 0);
484 					xmp_set_property (xmp, NS_PHOTOSHOP, "Location", subl, 0);
485 				}
486 
487 				xmp_delete_property (xmp, NS_PHOTOSHOP, "Country");
488 				xmp_delete_property (xmp, NS_IPTC4XMP, "Country");
489 				xmp_delete_property (xmp, NS_IPTC4XMP, "PrimaryLocationName");
490 				xmp_delete_property (xmp, NS_IPTC4XMP, "CountryName");
491 				if (country != NULL) {
492 					xmp_set_property (xmp, NS_PHOTOSHOP, "Country", country, 0);
493 					xmp_set_property (xmp, NS_IPTC4XMP, "Country", country, 0);
494 					xmp_set_property (xmp, NS_IPTC4XMP, "PrimaryLocationName", country, 0);
495 					xmp_set_property (xmp, NS_IPTC4XMP, "CountryName", country, 0);
496 				}
497 
498 				xmp_delete_property (xmp, NS_EXIF, "GPSAltitude");
499 				if (altitude != NULL) {
500 					xmp_set_property (xmp, NS_EXIF, "GPSAltitude", altitude, 0);
501 				}
502 
503 				xmp_delete_property (xmp, NS_EXIF, "GPSLongitude");
504 				if (longitude != NULL) {
505 					double coord = atof (longitude);
506 					double degrees, minutes;
507 					gchar *val;
508 
509 					minutes = modf (coord, &degrees);
510 
511 					val = g_strdup_printf ("%3d,%f%c",
512 					                       (int) fabs(degrees),
513 					                       minutes,
514 					                       coord >= 0 ? 'E' : 'W');
515 
516 					xmp_set_property (xmp, NS_EXIF, "GPSLongitude", val, 0);
517 
518 					g_free (val);
519 				}
520 
521 				xmp_delete_property (xmp, NS_EXIF, "GPSLatitude");
522 				if (latitude != NULL) {
523 					double coord = atof (latitude);
524 					double degrees, minutes;
525 					gchar *val;
526 
527 					minutes = modf (coord, &degrees);
528 
529 					val = g_strdup_printf ("%3d,%f%c",
530 					                       (int) fabs(degrees),
531 					                       minutes,
532 					                       coord >= 0 ? 'N' : 'S');
533 
534 					xmp_set_property (xmp, NS_EXIF, "GPSLatitude", val, 0);
535 
536 					g_free (val);
537 				}
538 			}
539 		}
540 
541 		g_object_unref (cursor);
542 		g_clear_error (&error);
543 	}
544 
545 	if (keywords) {
546 		xmp_delete_property (xmp, NS_DC, "subject");
547 		xmp_set_property (xmp, NS_DC, "subject", keywords->str, 0);
548 		g_string_free (keywords, TRUE);
549 	}
550 
551 #ifdef DEBUG_XMP
552 	g_print ("\nAFTER: ---- \n");
553 	str = xmp_string_new ();
554 	xmp_serialize_and_format (xmp, str, 0, 0, "\n", "\t", 1);
555 	g_print ("%s\n", xmp_string_cstr (str));
556 	xmp_string_free (str);
557 	g_print ("\n --------- \n");
558 #endif
559 
560 	if (xmp_files_can_put_xmp (xmp_files, xmp)) {
561 		xmp_files_put_xmp (xmp_files, xmp);
562 	}
563 
564 	/* Note: We don't currently use XMP_CLOSE_SAFEUPDATE because it uses
565 	 * a hidden temporary file in the same directory, which is then
566 	 * renamed to the final name. This triggers two events:
567 	 *  - DELETE(A) + MOVE(.hidden->A)
568 	 * and we really don't want the first DELETE(A) here
569 	 */
570 	xmp_files_close (xmp_files, XMP_CLOSE_NOOPTION);
571 
572 	xmp_free (xmp);
573 	xmp_files_free (xmp_files);
574 	g_free (path);
575 
576 	return TRUE;
577 }
578 
579 TrackerWriteback *
580 writeback_module_create (GTypeModule *module)
581 {
582 	tracker_writeback_xmp_register_type (module);
583 
584 	return g_object_new (TRACKER_TYPE_WRITEBACK_XMP, NULL);
585 }
586 
587 const gchar * const *
588 writeback_module_get_rdf_types (void)
589 {
590 	static const gchar *rdf_types[] = {
591 		TRACKER_NFO_PREFIX "Image",
592 		TRACKER_NFO_PREFIX "Audio",
593 		TRACKER_NFO_PREFIX "Video",
594 		NULL
595 	};
596 
597 	return rdf_types;
598 }