]> git.street.me.uk Git - andy/viking.git/blame - src/geotag_exif.c
Remove not very helpful debug message since it can generate large volumes of messages.
[andy/viking.git] / src / geotag_exif.c
CommitLineData
f75d0233
RN
1/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2/*
3 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4 *
d4ec7b44 5 * Copyright (C) 2011-2014, Rob Norris <rw_norris@hotmail.com>
f75d0233
RN
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 */
22
23/*
24 * This uses EXIF information from images to create waypoints at those positions
f75d0233 25 *
72bbd562
RN
26 * The intial implementation uses libexif, which keeps Viking a pure C program.
27 * Now libgexiv2 is available (in C as a wrapper around the more powerful libexiv2 [C++]) so this is the preferred build.
28 * The attentative reader will notice the use of gexiv2 is a lot simpler as well.
29 * For the time being the libexif code + build is still made available.
f75d0233
RN
30 */
31#include <string.h>
32#include "geotag_exif.h"
72bbd562 33#include "config.h"
f75d0233
RN
34#include "globals.h"
35#include "file.h"
36
b3eb3b98
RN
37#include <sys/stat.h>
38#include <utime.h>
39#include <stdlib.h>
40#include <ctype.h>
41#include <math.h>
42#include <glib/gi18n.h>
d9599cb1 43#include <glib/gstdio.h>
72bbd562
RN
44#ifdef HAVE_LIBGEXIV2
45#include <gexiv2/gexiv2.h>
46#endif
47#ifdef HAVE_LIBEXIF
f75d0233 48#include <libexif/exif-data.h>
b3eb3b98 49#include "libjpeg/jpeg-data.h"
72bbd562 50#endif
f75d0233 51
72bbd562
RN
52#ifdef HAVE_LIBGEXIV2
53/**
54 * Attempt to get a single comment from the various exif fields
55 */
56static gchar* geotag_get_exif_comment ( GExiv2Metadata *gemd )
57{
58 //
59 // Try various options to create a comment
60 //
61 if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.ImageDescription" ) )
62 return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.ImageDescription" ) );
63
64 if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPComment" ) )
65 return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPComment" ) );
66
67 if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPSubject" ) )
68 return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPSubject" ) );
69
70 if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.DateTimeOriginal" ) )
71 return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.DateTimeOriginal" ) );
72
73 // Otherwise nothing found
74 return NULL;
75}
76#endif
77
78#ifdef HAVE_LIBEXIF
f75d0233
RN
79/**
80 * Attempt to get a single comment from the various exif fields
81 */
82static gchar* geotag_get_exif_comment ( ExifData *ed )
83{
84 gchar str[128];
85 ExifEntry *ee;
86 //
87 // Try various options to create a comment
88 //
89 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_IMAGE_DESCRIPTION);
90 if ( ee ) {
91 exif_entry_get_value ( ee, str, 128 );
92 return g_strdup ( str );
93 }
94
95 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_COMMENT);
96 if ( ee ) {
97 exif_entry_get_value ( ee, str, 128 );
98 return g_strdup ( str );
99 }
100
101 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_SUBJECT);
102 if ( ee ) {
103 exif_entry_get_value ( ee, str, 128 );
104 return g_strdup ( str );
105 }
106
107 // Consider using these for existing GPS info??
108 //#define EXIF_TAG_GPS_TIME_STAMP 0x0007
109 //#define EXIF_TAG_GPS_DATE_STAMP 0x001d
110 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL);
111 if ( ee ) {
112 exif_entry_get_value ( ee, str, 128 );
113 return g_strdup ( str );
114 }
72bbd562 115
f75d0233
RN
116 // Otherwise nothing found
117 return NULL;
118}
119
120/**
121 * Handles 3 part location Rationals
122 * Handles 1 part rational (must specify 0 for the offset)
123 */
124static gdouble Rational2Double ( unsigned char *data, int offset, ExifByteOrder order )
125{
126 // Explaination from GPS Correlate 'exif-gps.cpp' v 1.6.1
127 // What we are trying to do here is convert the three rationals:
128 // dd/v mm/v ss/v
129 // To a decimal
130 // dd.dddddd...
131 // dd/v is easy: result = dd/v.
132 // mm/v is harder:
133 // mm
134 // -- / 60 = result.
135 // v
136 // ss/v is sorta easy.
137 // ss
138 // -- / 3600 = result
139 // v
140 // Each part is added to the final number.
141 gdouble ans;
142 ExifRational er;
143 er = exif_get_rational (data, order);
144 ans = (gdouble)er.numerator / (gdouble)er.denominator;
145 if (offset <= 0)
146 return ans;
147
148 er = exif_get_rational (data+(1*offset), order);
149 ans = ans + ( ( (gdouble)er.numerator / (gdouble)er.denominator ) / 60.0 );
150 er = exif_get_rational (data+(2*offset), order);
151 ans = ans + ( ( (gdouble)er.numerator / (gdouble)er.denominator ) / 3600.0 );
152
153 return ans;
154}
155
d4ec7b44
RN
156static struct LatLon get_latlon ( ExifData *ed )
157{
158 struct LatLon ll = { 0.0, 0.0 };
159 const struct LatLon ll0 = { 0.0, 0.0 };
160
161 gchar str[128];
162 ExifEntry *ee;
163 //
164 // Lat & Long is necessary to form a waypoint.
165 //
166 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE);
167 if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) )
168 return ll0;
169
170 ll.lat = Rational2Double ( ee->data,
171 exif_format_get_size(ee->format),
172 exif_data_get_byte_order(ed) );
173
174 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE_REF);
175 if ( ee ) {
176 exif_entry_get_value ( ee, str, 128 );
177 if ( str[0] == 'S' )
178 ll.lat = -ll.lat;
179 }
180
181 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE);
182 if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) )
183 return ll0;
184
185 ll.lon = Rational2Double ( ee->data,
186 exif_format_get_size(ee->format),
187 exif_data_get_byte_order(ed) );
188
189 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE_REF);
190 if ( ee ) {
191 exif_entry_get_value ( ee, str, 128 );
192 if ( str[0] == 'W' )
193 ll.lon = -ll.lon;
194 }
195
196 return ll;
197}
72bbd562 198#endif
d4ec7b44
RN
199
200/**
201 * a_geotag_get_position:
202 *
203 * @filename: The (JPG) file with EXIF information in it
204 *
205 * Returns: The position in LatLon format.
206 * It will be 0,0 if some kind of failure occurs.
207 */
208struct LatLon a_geotag_get_position ( const gchar *filename )
209{
210 struct LatLon ll = { 0.0, 0.0 };
211
72bbd562
RN
212#ifdef HAVE_LIBGEXIV2
213 GExiv2Metadata *gemd = gexiv2_metadata_new ();
214 if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
215 gdouble lat;
216 gdouble lon;
217 gdouble alt;
218 if ( gexiv2_metadata_get_gps_info ( gemd, &lon, &lat, &alt ) ) {
219 ll.lat = lat;
220 ll.lon = lon;
221 }
222 }
223 gexiv2_metadata_free ( gemd );
224#else
225#ifdef HAVE_LIBEXIF
d4ec7b44
RN
226 // open image with libexif
227 ExifData *ed = exif_data_new_from_file ( filename );
228
229 // Detect EXIF load failure
230 if ( !ed )
231 return ll;
232
233 ExifEntry *ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID);
234 // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0"
235 if ( ! ( ee && ee->components == 4 ) )
236 goto MyReturn0;
237
238 ll = get_latlon ( ed );
239
240MyReturn0:
241 // Finished with EXIF
242 exif_data_free ( ed );
72bbd562
RN
243#endif
244#endif
d4ec7b44
RN
245
246 return ll;
247}
248
f75d0233
RN
249/**
250 * a_geotag_create_waypoint_from_file:
251 * @filename: The image file to process
252 * @vcmode: The current location mode to use in the positioning of Waypoint
253 * @name: Returns a name for the Waypoint (can be NULL)
254 *
255 * Returns: An allocated Waypoint or NULL if Waypoint could not be generated (e.g. no EXIF info)
256 *
257 */
258VikWaypoint* a_geotag_create_waypoint_from_file ( const gchar *filename, VikCoordMode vcmode, gchar **name )
259{
260 // Default return values (for failures)
261 *name = NULL;
262 VikWaypoint *wp = NULL;
263
72bbd562
RN
264#ifdef HAVE_LIBGEXIV2
265 GExiv2Metadata *gemd = gexiv2_metadata_new ();
266 if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
267 gdouble lat;
268 gdouble lon;
269 gdouble alt;
270 if ( gexiv2_metadata_get_gps_info ( gemd, &lon, &lat, &alt ) ) {
271 struct LatLon ll;
272 ll.lat = lat;
273 ll.lon = lon;
274
275 //
276 // Now create Waypoint with acquired information
277 //
278 wp = vik_waypoint_new();
279 wp->visible = TRUE;
280 // Set info from exif values
281 // Location
282 vik_coord_load_from_latlon ( &(wp->coord), vcmode, &ll );
283 // Altitude
284 wp->altitude = alt;
285
286 if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPTitle" ) )
287 *name = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPTitle" ) );
288 wp->comment = geotag_get_exif_comment ( gemd );
289
290 vik_waypoint_set_image ( wp, filename );
291 }
292 }
293 gexiv2_metadata_free ( gemd );
294#else
295#ifdef HAVE_LIBEXIF
f75d0233
RN
296 // TODO use log?
297 //ExifLog *log = NULL;
298
299 // open image with libexif
300 ExifData *ed = exif_data_new_from_file ( filename );
301
302 // Detect EXIF load failure
303 if ( !ed )
304 // return with no Waypoint
305 return wp;
306
307 struct LatLon ll;
308
309 gchar str[128];
310 ExifEntry *ee;
311
312 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID);
313 // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0"
314 if ( ! ( ee && ee->components == 4 ) )
315 goto MyReturn;
316 // Could test for these versions explicitly but may have byte order issues...
317 //if ( ! ( ee->data[0] == 2 && ee->data[2] == 0 && ee->data[3] == 0 ) )
318 // goto MyReturn;
319
d4ec7b44 320 ll = get_latlon ( ed );
f75d0233 321
d4ec7b44
RN
322 // Hopefully won't have valid images at 0,0!
323 if ( ll.lat == 0.0 && ll.lon == 0.0 )
f75d0233
RN
324 goto MyReturn;
325
f75d0233
RN
326 //
327 // Not worried if none of the other fields exist, as can default the values to something
328 //
329
330 gdouble alt = VIK_DEFAULT_ALTITUDE;
331 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_ALTITUDE);
332 if ( ee && ee->components == 1 && ee->format == EXIF_FORMAT_RATIONAL ) {
333 alt = Rational2Double ( ee->data,
334 0,
335 exif_data_get_byte_order(ed) );
336
337 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_ALTITUDE_REF);
338 if ( ee && ee->components == 1 && ee->format == EXIF_FORMAT_BYTE && ee->data[0] == 1 )
339 alt = -alt;
340 }
341
342 // Name
343 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_TITLE);
344 if ( ee ) {
345 exif_entry_get_value ( ee, str, 128 );
346 *name = g_strdup ( str );
347 }
348
349 //
350 // Now create Waypoint with acquired information
351 //
352 wp = vik_waypoint_new();
353 wp->visible = TRUE;
354 // Set info from exif values
355 // Location
356 vik_coord_load_from_latlon ( &(wp->coord), vcmode, &ll );
357 // Altitude
358 wp->altitude = alt;
359
360 wp->comment = geotag_get_exif_comment ( ed );
361
362 vik_waypoint_set_image ( wp, filename );
363
364MyReturn:
365 // Finished with EXIF
366 exif_data_free ( ed );
72bbd562
RN
367#endif
368#endif
f75d0233
RN
369
370 return wp;
371}
372
373/**
cbac0d22 374 * a_geotag_waypoint_positioned:
f75d0233
RN
375 * @filename: The image file to process
376 * @coord: The location for positioning the Waypoint
377 * @name: Returns a name for the Waypoint (can be NULL)
cbac0d22 378 * @waypoint: An existing waypoint to update (can be NULL to generate a new waypoint)
f75d0233 379 *
cbac0d22
RN
380 * Returns: An allocated waypoint if the input waypoint is NULL,
381 * otherwise the passed in waypoint is updated
f75d0233
RN
382 *
383 * Here EXIF processing is used to get non position related information (i.e. just the comment)
384 *
385 */
cbac0d22 386VikWaypoint* a_geotag_waypoint_positioned ( const gchar *filename, VikCoord coord, gdouble alt, gchar **name, VikWaypoint *wp )
f75d0233
RN
387{
388 *name = NULL;
cbac0d22
RN
389 if ( wp == NULL ) {
390 // Need to create waypoint
391 wp = vik_waypoint_new();
392 wp->visible = TRUE;
393 }
f75d0233
RN
394 wp->coord = coord;
395 wp->altitude = alt;
396
72bbd562
RN
397#ifdef HAVE_LIBGEXIV2
398 GExiv2Metadata *gemd = gexiv2_metadata_new ();
399 if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
400 wp->comment = geotag_get_exif_comment ( gemd );
401 if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPTitle" ) )
402 *name = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPTitle" ) );
403 }
404 gexiv2_metadata_free ( gemd );
405#else
406#ifdef HAVE_LIBEXIF
f75d0233
RN
407 ExifData *ed = exif_data_new_from_file ( filename );
408
409 // Set info from exif values
410 if ( ed ) {
411 wp->comment = geotag_get_exif_comment ( ed );
412
413 gchar str[128];
414 ExifEntry *ee;
415 // Name
416 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_TITLE);
417 if ( ee ) {
418 exif_entry_get_value ( ee, str, 128 );
419 *name = g_strdup ( str );
420 }
421
422 // Finished with EXIF
423 exif_data_free ( ed );
424 }
72bbd562
RN
425#endif
426#endif
f75d0233
RN
427
428 vik_waypoint_set_image ( wp, filename );
429
f75d0233
RN
430 return wp;
431}
b3eb3b98
RN
432
433/**
434 * a_geotag_get_exif_date_from_file:
435 * @filename: The image file to process
436 * @has_GPS_info: Returns whether the file has existing GPS information
437 *
438 * Returns: An allocated string with the date and time in EXIF_DATE_FORMAT, otherwise NULL if some kind of failure
439 *
440 * Here EXIF processing is used to get time information
441 *
442 */
443gchar* a_geotag_get_exif_date_from_file ( const gchar *filename, gboolean *has_GPS_info )
444{
445 gchar* datetime = NULL;
06949e05 446 *has_GPS_info = FALSE;
b3eb3b98 447
72bbd562
RN
448#ifdef HAVE_LIBGEXIV2
449 GExiv2Metadata *gemd = gexiv2_metadata_new ();
450 if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
451 gdouble lat, lon;
452 *has_GPS_info = ( gexiv2_metadata_get_gps_longitude(gemd,&lon) && gexiv2_metadata_get_gps_latitude(gemd,&lat) );
453
454 // Prefer 'Photo' version over 'Image'
455 if ( gexiv2_metadata_has_tag ( gemd, "Exif.Photo.DateTimeOriginal" ) )
456 datetime = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Photo.DateTimeOriginal" ) );
457 else
458 datetime = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.DateTimeOriginal" ) );
459 }
460 gexiv2_metadata_free ( gemd );
461#else
462#ifdef HAVE_LIBEXIF
b3eb3b98
RN
463 ExifData *ed = exif_data_new_from_file ( filename );
464
465 // Detect EXIF load failure
466 if ( !ed )
467 return datetime;
468
469 gchar str[128];
470 ExifEntry *ee;
471
472 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL);
473 if ( ee ) {
474 exif_entry_get_value ( ee, str, 128 );
475 datetime = g_strdup ( str );
476 }
477
478 // Check GPS Info
06949e05 479
b3eb3b98
RN
480 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID);
481 // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0"
482 if ( ee && ee->components == 4 )
483 *has_GPS_info = TRUE;
484
06949e05
RN
485 // Check other basic GPS fields exist too
486 // I have encountered some images which have just the EXIF_TAG_GPS_VERSION_ID but nothing else
487 // So to confirm check more EXIF GPS TAGS:
488 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE);
489 if ( !ee )
490 *has_GPS_info = FALSE;
491 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE);
492 if ( !ee )
493 *has_GPS_info = FALSE;
494
b3eb3b98 495 exif_data_free ( ed );
72bbd562
RN
496#endif
497#endif
b3eb3b98
RN
498 return datetime;
499}
500
501
72bbd562 502#ifdef HAVE_LIBEXIF
b3eb3b98
RN
503/**! If the entry doesn't exist, create it.
504 * Based on exif command line action_create_value function in exif 0.6.20
505 */
506static ExifEntry* my_exif_create_value (ExifData *ed, ExifTag tag, ExifIfd ifd)
507{
508 ExifEntry *e = exif_content_get_entry (ed->ifd[ifd], tag);
509 if ( !e ) {
510 e = exif_entry_new ();
511 exif_content_add_entry (ed->ifd[ifd], e);
512
513 exif_entry_initialize (e, tag);
514
515 // exif_entry_initialize doesn't seem to do much, especially for the GPS tags
516 // so have to setup fields ourselves:
517 e->tag = tag;
518
519 if ( tag == EXIF_TAG_GPS_VERSION_ID ) {
520 e->format = EXIF_FORMAT_BYTE;
521 e->components = 4;
522 e->size = sizeof (char) * e->components;
523 if ( e->data )
524 g_free (e->data);
525 e->data = g_malloc (e->size);
526 }
527 if ( tag == EXIF_TAG_GPS_MAP_DATUM ||
528 tag == EXIF_TAG_GPS_LATITUDE_REF || tag == EXIF_TAG_GPS_LONGITUDE_REF ||
529 tag == EXIF_TAG_GPS_PROCESSING_METHOD ) {
530 e->format = EXIF_FORMAT_ASCII;
531 // NB Allocation is handled later on when the actual string used is known
532 }
533 if ( tag == EXIF_TAG_GPS_LATITUDE || tag == EXIF_TAG_GPS_LONGITUDE ) {
534 e->format = EXIF_FORMAT_RATIONAL;
535 e->components = 3;
536 e->size = sizeof (ExifRational) * e->components;
537 if ( e->data )
538 g_free (e->data);
539 e->data = g_malloc (e->size);
540 }
541 if ( tag == EXIF_TAG_GPS_ALTITUDE ) {
542 e->format = EXIF_FORMAT_RATIONAL;
543 e->components = 1;
544 e->size = sizeof (ExifRational) * e->components;
545 if ( e->data )
546 g_free (e->data);
547 e->data = g_malloc (e->size);
548 }
549 if ( tag == EXIF_TAG_GPS_ALTITUDE_REF ) {
550 e->components = 1;
551 e->size = sizeof (char) * e->components;
552 if ( e->data )
553 g_free (e->data);
554 e->data = g_malloc (e->size);
555 }
556 /* The entry has been added to the IFD, so we can unref it */
557 //exif_entry_unref(e);
558 // Crashes later on, when saving to jpeg if the above unref is enabled!!
559 // ?Some other malloc problem somewhere?
560 }
561 return e;
562}
563
564/** Heavily based on convert_arg_to_entry from exif command line tool.
565 * But without ExifLog, exitting, use of g_* io functions
566 * and can take a gdouble value instead of a string
567 */
568static void convert_to_entry (const char *set_value, gdouble gdvalue, ExifEntry *e, ExifByteOrder o)
569{
570 unsigned int i, numcomponents;
571 char *value_p = NULL;
572 char *buf = NULL;
573 /*
574 * ASCII strings are handled separately,
575 * since they don't require any conversion.
576 */
577 if (e->format == EXIF_FORMAT_ASCII ||
578 e->tag == EXIF_TAG_USER_COMMENT) {
579 if (e->data) g_free (e->data);
580 e->components = strlen (set_value) + 1;
581 if (e->tag == EXIF_TAG_USER_COMMENT)
582 e->components += 8 - 1;
583 e->size = sizeof (char) * e->components;
584 e->data = g_malloc (e->size);
585 if (!e->data) {
586 g_warning (_("Not enough memory."));
587 return;
588 }
589 if (e->tag == EXIF_TAG_USER_COMMENT) {
590 /* assume ASCII charset */
591 /* TODO: get this from the current locale */
592 memcpy ((char *) e->data, "ASCII\0\0\0", 8);
593 memcpy ((char *) e->data + 8, set_value,
594 strlen (set_value));
595 } else
596 strcpy ((char *) e->data, set_value);
597 return;
598 }
599
600 /*
601 * Make sure we can handle this entry
602 */
603 if ((e->components == 0) && *set_value) {
604 g_warning (_("Setting a value for this tag is unsupported!"));
605 return;
606 }
607
608 gboolean use_string = (set_value != NULL);
609 if ( use_string ) {
610 /* Copy the string so we can modify it */
611 buf = g_strdup (set_value);
612 if (!buf)
613 return;
614 value_p = strtok (buf, " ");
615 }
616
617 numcomponents = e->components;
618 for (i = 0; i < numcomponents; ++i) {
619 unsigned char s;
620
621 if ( use_string ) {
622 if (!value_p) {
623 g_warning (_("Too few components specified (need %d, found %d)\n"), numcomponents, i);
624 return;
625 }
626 if (!isdigit(*value_p) && (*value_p != '+') && (*value_p != '-')) {
627 g_warning (_("Numeric value expected\n"));
628 return;
629 }
630 }
631
632 s = exif_format_get_size (e->format);
633 switch (e->format) {
634 case EXIF_FORMAT_ASCII:
635 g_warning (_("This shouldn't happen!"));
636 return;
637 break;
638 case EXIF_FORMAT_SHORT:
639 exif_set_short (e->data + (s * i), o, atoi (value_p));
640 break;
641 case EXIF_FORMAT_SSHORT:
642 exif_set_sshort (e->data + (s * i), o, atoi (value_p));
643 break;
644 case EXIF_FORMAT_RATIONAL: {
645 ExifRational er;
646
647 double val = 0.0 ;
648 if ( use_string && value_p )
649 val = fabs (atol (value_p));
650 else
651 val = fabs (gdvalue);
652
653 if ( i == 0 ) {
654 // One (or first) part rational
655
656 // Sneak peek into tag as location tags need rounding down to give just the degrees part
657 if ( e->tag == EXIF_TAG_GPS_LATITUDE || e->tag == EXIF_TAG_GPS_LONGITUDE ) {
658 er.numerator = (ExifLong) floor ( val );
659 er.denominator = 1.0;
660 }
661 else {
662 // I don't see any point in doing anything too complicated here,
663 // such as trying to work out the 'best' denominator
664 // For the moment use KISS principle.
665 // Fix a precision of 1/100 metre as that's more than enough for GPS accuracy especially altitudes!
666 er.denominator = 100.0;
667 er.numerator = (ExifLong) (val * er.denominator);
668 }
669 }
670
671 // Now for Location 3 part rationals do Mins and Seconds format
672
673 // Rounded down minutes
674 if ( i == 1 ) {
675 er.denominator = 1.0;
676 er.numerator = (ExifLong) ( (int) floor ( ( val - floor (val) ) * 60.0 ) );
677 }
678
679 // Finally seconds
680 if ( i == 2 ) {
681 er.denominator = 100.0;
682
683 // Fractional minute.
684 double FracPart = ((val - floor(val)) * 60) - (double)(int) floor ( ( val - floor (val) ) * 60.0 );
685 er.numerator = (ExifLong) ( (int)floor(FracPart * 6000) ); // Convert to seconds.
686 }
687 exif_set_rational (e->data + (s * i), o, er );
688 break;
689 }
690 case EXIF_FORMAT_LONG:
691 exif_set_long (e->data + (s * i), o, atol (value_p));
692 break;
693 case EXIF_FORMAT_SLONG:
694 exif_set_slong (e->data + (s * i), o, atol (value_p));
695 break;
696 case EXIF_FORMAT_BYTE:
697 case EXIF_FORMAT_SBYTE:
698 case EXIF_FORMAT_UNDEFINED: /* treat as byte array */
699 e->data[s * i] = atoi (value_p);
700 break;
701 case EXIF_FORMAT_FLOAT:
702 case EXIF_FORMAT_DOUBLE:
703 case EXIF_FORMAT_SRATIONAL:
704 default:
705 g_warning (_("Not yet implemented!"));
706 return;
707 }
708
709 if ( use_string )
710 value_p = strtok (NULL, " ");
711
712 }
713
714 g_free (buf);
715
716 if ( use_string )
717 if ( value_p )
718 g_warning (_("Warning; Too many components specified!"));
719}
72bbd562 720#endif
b3eb3b98
RN
721
722/**
723 * a_geotag_write_exif_gps:
724 * @filename: The image file to save information in
725 * @coord: The location
726 * @alt: The elevation
727 *
728 * Returns: A value indicating success: 0, or some other value for failure
729 *
730 */
731gint a_geotag_write_exif_gps ( const gchar *filename, VikCoord coord, gdouble alt, gboolean no_change_mtime )
732{
733 gint result = 0; // OK so far...
734
735 // Save mtime for later use
736 struct stat stat_save;
737 if ( no_change_mtime )
738 stat ( filename, &stat_save );
739
72bbd562
RN
740#ifdef HAVE_LIBGEXIV2
741 GExiv2Metadata *gemd = gexiv2_metadata_new ();
742 if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
743 struct LatLon ll;
744 vik_coord_to_latlon ( &coord, &ll );
745 if ( ! gexiv2_metadata_set_gps_info ( gemd, ll.lon, ll.lat, alt ) ) {
746 result = 1; // Failed
747 }
748 else {
749 GError *error = NULL;
750 if ( ! gexiv2_metadata_save_file ( gemd, filename, &error ) ) {
751 result = 2;
752 g_warning ( "Write EXIF failure:%s" , error->message );
753 g_error_free ( error );
754 }
755 }
756 }
757 gexiv2_metadata_free ( gemd );
758#else
759#ifdef HAVE_LIBEXIF
b3eb3b98
RN
760 /*
761 Appears libexif doesn't actually support writing EXIF data directly to files
762 Thus embed command line exif writing method within Viking
763 (for example this is done by Enlightment - http://www.enlightenment.org/ )
764 This appears to be JPEG only, but is probably 99% of our use case
765 Alternatively consider using libexiv2 and C++...
766 */
767
768 // Actual EXIF settings here...
769 JPEGData *jdata;
770
771 /* Parse the JPEG file. */
772 jdata = jpeg_data_new ();
773 jpeg_data_load_file (jdata, filename);
774
775 // Get current values
776 ExifData *ed = exif_data_new_from_file ( filename );
777 if ( !ed )
778 ed = exif_data_new ();
779
780 // Update ExifData with our new settings
781 ExifEntry *ee;
782 //
783 // I don't understand it, but when saving the 'ed' nothing gets set after putting in the GPS ID tag - so it must come last
784 // (unless of course there is some bug in the setting of the ID, that prevents subsequent tags)
785 //
786
787 ee = my_exif_create_value (ed, EXIF_TAG_GPS_ALTITUDE, EXIF_IFD_GPS);
788 convert_to_entry ( NULL, alt, ee, exif_data_get_byte_order(ed) );
789
790 // byte 0 meaning "sea level" or 1 if the value is negative.
791 ee = my_exif_create_value (ed, EXIF_TAG_GPS_ALTITUDE_REF, EXIF_IFD_GPS);
792 convert_to_entry ( alt < 0.0 ? "1" : "0", 0.0, ee, exif_data_get_byte_order(ed) );
793
794 ee = my_exif_create_value (ed, EXIF_TAG_GPS_PROCESSING_METHOD, EXIF_IFD_GPS);
795 // see http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/GPS.html
796 convert_to_entry ( "MANUAL", 0.0, ee, exif_data_get_byte_order(ed) );
797
798 ee = my_exif_create_value (ed, EXIF_TAG_GPS_MAP_DATUM, EXIF_IFD_GPS);
799 convert_to_entry ( "WGS-84", 0.0, ee, exif_data_get_byte_order(ed) );
800
801 struct LatLon ll;
802 vik_coord_to_latlon ( &coord, &ll );
803
804 ee = my_exif_create_value (ed, EXIF_TAG_GPS_LATITUDE_REF, EXIF_IFD_GPS);
805 // N or S
806 convert_to_entry ( ll.lat < 0.0 ? "S" : "N", 0.0, ee, exif_data_get_byte_order(ed) );
807
808 ee = my_exif_create_value (ed, EXIF_TAG_GPS_LATITUDE, EXIF_IFD_GPS);
809 convert_to_entry ( NULL, ll.lat, ee, exif_data_get_byte_order(ed) );
810
811 ee = my_exif_create_value (ed, EXIF_TAG_GPS_LONGITUDE_REF, EXIF_IFD_GPS);
812 // E or W
813 convert_to_entry ( ll.lon < 0.0 ? "W" : "E", 0.0, ee, exif_data_get_byte_order(ed) );
814
815 ee = my_exif_create_value (ed, EXIF_TAG_GPS_LONGITUDE, EXIF_IFD_GPS);
816 convert_to_entry ( NULL, ll.lon, ee, exif_data_get_byte_order(ed) );
817
818 ee = my_exif_create_value (ed, EXIF_TAG_GPS_VERSION_ID, EXIF_IFD_GPS);
819 //convert_to_entry ( "2 0 0 0", 0.0, ee, exif_data_get_byte_order(ed) );
820 convert_to_entry ( "2 2 0 0", 0.0, ee, exif_data_get_byte_order(ed) );
821
822 jpeg_data_set_exif_data (jdata, ed);
823
824 if ( jdata ) {
825 /* Save the modified image. */
826 result = jpeg_data_save_file (jdata, filename);
827
828 // Convert result from 1 for success, 0 for failure into our scheme
829 result = !result;
830
831 jpeg_data_unref (jdata);
832 }
833 else {
834 // Epic fail - file probably not a JPEG
835 result = 2;
836 }
837
72bbd562
RN
838 exif_data_free ( ed );
839#endif
840#endif
841
b3eb3b98
RN
842 if ( no_change_mtime ) {
843 // Restore mtime, using the saved value
844 struct stat stat_tmp;
845 struct utimbuf utb;
846 stat ( filename, &stat_tmp );
847 utb.actime = stat_tmp.st_atime;
848 utb.modtime = stat_save.st_mtime;
849 utime ( filename, &utb );
850 }
851
852 return result;
853}