]> git.street.me.uk Git - andy/viking.git/blame - src/geotag_exif.c
Fix translatable string with variable argument
[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 *
5 * Copyright (C) 2011, Rob Norris <rw_norris@hotmail.com>
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
RN
25 *
26 * For the implementation I have chosen to use libexif, which keeps Viking a pure C program
27 * For an alternative implementation (a la gpscorrelate), one could use libeviv2 but it appears to be C++ only.
28 */
29#include <string.h>
30#include "geotag_exif.h"
31#include "globals.h"
32#include "file.h"
33
b3eb3b98
RN
34#include <sys/stat.h>
35#include <utime.h>
36#include <stdlib.h>
37#include <ctype.h>
38#include <math.h>
39#include <glib/gi18n.h>
d9599cb1 40#include <glib/gstdio.h>
f75d0233 41#include <libexif/exif-data.h>
b3eb3b98 42#include "libjpeg/jpeg-data.h"
f75d0233
RN
43
44/**
45 * Attempt to get a single comment from the various exif fields
46 */
47static gchar* geotag_get_exif_comment ( ExifData *ed )
48{
49 gchar str[128];
50 ExifEntry *ee;
51 //
52 // Try various options to create a comment
53 //
54 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_IMAGE_DESCRIPTION);
55 if ( ee ) {
56 exif_entry_get_value ( ee, str, 128 );
57 return g_strdup ( str );
58 }
59
60 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_COMMENT);
61 if ( ee ) {
62 exif_entry_get_value ( ee, str, 128 );
63 return g_strdup ( str );
64 }
65
66 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_SUBJECT);
67 if ( ee ) {
68 exif_entry_get_value ( ee, str, 128 );
69 return g_strdup ( str );
70 }
71
72 // Consider using these for existing GPS info??
73 //#define EXIF_TAG_GPS_TIME_STAMP 0x0007
74 //#define EXIF_TAG_GPS_DATE_STAMP 0x001d
75 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL);
76 if ( ee ) {
77 exif_entry_get_value ( ee, str, 128 );
78 return g_strdup ( str );
79 }
80
81 // Otherwise nothing found
82 return NULL;
83}
84
85/**
86 * Handles 3 part location Rationals
87 * Handles 1 part rational (must specify 0 for the offset)
88 */
89static gdouble Rational2Double ( unsigned char *data, int offset, ExifByteOrder order )
90{
91 // Explaination from GPS Correlate 'exif-gps.cpp' v 1.6.1
92 // What we are trying to do here is convert the three rationals:
93 // dd/v mm/v ss/v
94 // To a decimal
95 // dd.dddddd...
96 // dd/v is easy: result = dd/v.
97 // mm/v is harder:
98 // mm
99 // -- / 60 = result.
100 // v
101 // ss/v is sorta easy.
102 // ss
103 // -- / 3600 = result
104 // v
105 // Each part is added to the final number.
106 gdouble ans;
107 ExifRational er;
108 er = exif_get_rational (data, order);
109 ans = (gdouble)er.numerator / (gdouble)er.denominator;
110 if (offset <= 0)
111 return ans;
112
113 er = exif_get_rational (data+(1*offset), order);
114 ans = ans + ( ( (gdouble)er.numerator / (gdouble)er.denominator ) / 60.0 );
115 er = exif_get_rational (data+(2*offset), order);
116 ans = ans + ( ( (gdouble)er.numerator / (gdouble)er.denominator ) / 3600.0 );
117
118 return ans;
119}
120
121/**
122 * a_geotag_create_waypoint_from_file:
123 * @filename: The image file to process
124 * @vcmode: The current location mode to use in the positioning of Waypoint
125 * @name: Returns a name for the Waypoint (can be NULL)
126 *
127 * Returns: An allocated Waypoint or NULL if Waypoint could not be generated (e.g. no EXIF info)
128 *
129 */
130VikWaypoint* a_geotag_create_waypoint_from_file ( const gchar *filename, VikCoordMode vcmode, gchar **name )
131{
132 // Default return values (for failures)
133 *name = NULL;
134 VikWaypoint *wp = NULL;
135
136 // TODO use log?
137 //ExifLog *log = NULL;
138
139 // open image with libexif
140 ExifData *ed = exif_data_new_from_file ( filename );
141
142 // Detect EXIF load failure
143 if ( !ed )
144 // return with no Waypoint
145 return wp;
146
147 struct LatLon ll;
148
149 gchar str[128];
150 ExifEntry *ee;
151
152 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID);
153 // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0"
154 if ( ! ( ee && ee->components == 4 ) )
155 goto MyReturn;
156 // Could test for these versions explicitly but may have byte order issues...
157 //if ( ! ( ee->data[0] == 2 && ee->data[2] == 0 && ee->data[3] == 0 ) )
158 // goto MyReturn;
159
f75d0233
RN
160 //
161 // Lat & Long is necessary to form a waypoint.
162 //
163 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE);
164 if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) )
165 goto MyReturn;
166
167 ll.lat = Rational2Double ( ee->data,
168 exif_format_get_size(ee->format),
169 exif_data_get_byte_order(ed) );
170
171 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE_REF);
172 if ( ee ) {
173 exif_entry_get_value ( ee, str, 128 );
174 if ( str[0] == 'S' )
175 ll.lat = -ll.lat;
176 }
177
178 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE);
179 if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) )
180 goto MyReturn;
181
182 ll.lon = Rational2Double ( ee->data,
183 exif_format_get_size(ee->format),
184 exif_data_get_byte_order(ed) );
185
186 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE_REF);
187 if ( ee ) {
188 exif_entry_get_value ( ee, str, 128 );
189 if ( str[0] == 'W' )
190 ll.lon = -ll.lon;
191 }
192
193 //
194 // Not worried if none of the other fields exist, as can default the values to something
195 //
196
197 gdouble alt = VIK_DEFAULT_ALTITUDE;
198 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_ALTITUDE);
199 if ( ee && ee->components == 1 && ee->format == EXIF_FORMAT_RATIONAL ) {
200 alt = Rational2Double ( ee->data,
201 0,
202 exif_data_get_byte_order(ed) );
203
204 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_ALTITUDE_REF);
205 if ( ee && ee->components == 1 && ee->format == EXIF_FORMAT_BYTE && ee->data[0] == 1 )
206 alt = -alt;
207 }
208
209 // Name
210 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_TITLE);
211 if ( ee ) {
212 exif_entry_get_value ( ee, str, 128 );
213 *name = g_strdup ( str );
214 }
215
216 //
217 // Now create Waypoint with acquired information
218 //
219 wp = vik_waypoint_new();
220 wp->visible = TRUE;
221 // Set info from exif values
222 // Location
223 vik_coord_load_from_latlon ( &(wp->coord), vcmode, &ll );
224 // Altitude
225 wp->altitude = alt;
226
227 wp->comment = geotag_get_exif_comment ( ed );
228
229 vik_waypoint_set_image ( wp, filename );
230
231MyReturn:
232 // Finished with EXIF
233 exif_data_free ( ed );
234
235 return wp;
236}
237
238/**
cbac0d22 239 * a_geotag_waypoint_positioned:
f75d0233
RN
240 * @filename: The image file to process
241 * @coord: The location for positioning the Waypoint
242 * @name: Returns a name for the Waypoint (can be NULL)
cbac0d22 243 * @waypoint: An existing waypoint to update (can be NULL to generate a new waypoint)
f75d0233 244 *
cbac0d22
RN
245 * Returns: An allocated waypoint if the input waypoint is NULL,
246 * otherwise the passed in waypoint is updated
f75d0233
RN
247 *
248 * Here EXIF processing is used to get non position related information (i.e. just the comment)
249 *
250 */
cbac0d22 251VikWaypoint* a_geotag_waypoint_positioned ( const gchar *filename, VikCoord coord, gdouble alt, gchar **name, VikWaypoint *wp )
f75d0233
RN
252{
253 *name = NULL;
cbac0d22
RN
254 if ( wp == NULL ) {
255 // Need to create waypoint
256 wp = vik_waypoint_new();
257 wp->visible = TRUE;
258 }
f75d0233
RN
259 wp->coord = coord;
260 wp->altitude = alt;
261
262 ExifData *ed = exif_data_new_from_file ( filename );
263
264 // Set info from exif values
265 if ( ed ) {
266 wp->comment = geotag_get_exif_comment ( ed );
267
268 gchar str[128];
269 ExifEntry *ee;
270 // Name
271 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_TITLE);
272 if ( ee ) {
273 exif_entry_get_value ( ee, str, 128 );
274 *name = g_strdup ( str );
275 }
276
277 // Finished with EXIF
278 exif_data_free ( ed );
279 }
280
281 vik_waypoint_set_image ( wp, filename );
282
f75d0233
RN
283 return wp;
284}
b3eb3b98
RN
285
286/**
287 * a_geotag_get_exif_date_from_file:
288 * @filename: The image file to process
289 * @has_GPS_info: Returns whether the file has existing GPS information
290 *
291 * Returns: An allocated string with the date and time in EXIF_DATE_FORMAT, otherwise NULL if some kind of failure
292 *
293 * Here EXIF processing is used to get time information
294 *
295 */
296gchar* a_geotag_get_exif_date_from_file ( const gchar *filename, gboolean *has_GPS_info )
297{
298 gchar* datetime = NULL;
06949e05 299 *has_GPS_info = FALSE;
b3eb3b98
RN
300
301 ExifData *ed = exif_data_new_from_file ( filename );
302
303 // Detect EXIF load failure
304 if ( !ed )
305 return datetime;
306
d9599cb1
RN
307 // Also allows us to remember what directory we where using
308 gchar *dir = g_path_get_dirname ( filename );
309 if ( dir ) {
310 if ( g_chdir ( dir ) ) {
311 g_warning ( "Could not change directory to %s", dir );
312 }
313 g_free (dir);
314 }
315
b3eb3b98
RN
316 gchar str[128];
317 ExifEntry *ee;
318
319 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL);
320 if ( ee ) {
321 exif_entry_get_value ( ee, str, 128 );
322 datetime = g_strdup ( str );
323 }
324
325 // Check GPS Info
06949e05 326
b3eb3b98
RN
327 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID);
328 // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0"
329 if ( ee && ee->components == 4 )
330 *has_GPS_info = TRUE;
331
06949e05
RN
332 // Check other basic GPS fields exist too
333 // I have encountered some images which have just the EXIF_TAG_GPS_VERSION_ID but nothing else
334 // So to confirm check more EXIF GPS TAGS:
335 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE);
336 if ( !ee )
337 *has_GPS_info = FALSE;
338 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE);
339 if ( !ee )
340 *has_GPS_info = FALSE;
341
b3eb3b98
RN
342 exif_data_free ( ed );
343
344 return datetime;
345}
346
347
348/**! If the entry doesn't exist, create it.
349 * Based on exif command line action_create_value function in exif 0.6.20
350 */
351static ExifEntry* my_exif_create_value (ExifData *ed, ExifTag tag, ExifIfd ifd)
352{
353 ExifEntry *e = exif_content_get_entry (ed->ifd[ifd], tag);
354 if ( !e ) {
355 e = exif_entry_new ();
356 exif_content_add_entry (ed->ifd[ifd], e);
357
358 exif_entry_initialize (e, tag);
359
360 // exif_entry_initialize doesn't seem to do much, especially for the GPS tags
361 // so have to setup fields ourselves:
362 e->tag = tag;
363
364 if ( tag == EXIF_TAG_GPS_VERSION_ID ) {
365 e->format = EXIF_FORMAT_BYTE;
366 e->components = 4;
367 e->size = sizeof (char) * e->components;
368 if ( e->data )
369 g_free (e->data);
370 e->data = g_malloc (e->size);
371 }
372 if ( tag == EXIF_TAG_GPS_MAP_DATUM ||
373 tag == EXIF_TAG_GPS_LATITUDE_REF || tag == EXIF_TAG_GPS_LONGITUDE_REF ||
374 tag == EXIF_TAG_GPS_PROCESSING_METHOD ) {
375 e->format = EXIF_FORMAT_ASCII;
376 // NB Allocation is handled later on when the actual string used is known
377 }
378 if ( tag == EXIF_TAG_GPS_LATITUDE || tag == EXIF_TAG_GPS_LONGITUDE ) {
379 e->format = EXIF_FORMAT_RATIONAL;
380 e->components = 3;
381 e->size = sizeof (ExifRational) * e->components;
382 if ( e->data )
383 g_free (e->data);
384 e->data = g_malloc (e->size);
385 }
386 if ( tag == EXIF_TAG_GPS_ALTITUDE ) {
387 e->format = EXIF_FORMAT_RATIONAL;
388 e->components = 1;
389 e->size = sizeof (ExifRational) * e->components;
390 if ( e->data )
391 g_free (e->data);
392 e->data = g_malloc (e->size);
393 }
394 if ( tag == EXIF_TAG_GPS_ALTITUDE_REF ) {
395 e->components = 1;
396 e->size = sizeof (char) * e->components;
397 if ( e->data )
398 g_free (e->data);
399 e->data = g_malloc (e->size);
400 }
401 /* The entry has been added to the IFD, so we can unref it */
402 //exif_entry_unref(e);
403 // Crashes later on, when saving to jpeg if the above unref is enabled!!
404 // ?Some other malloc problem somewhere?
405 }
406 return e;
407}
408
409/** Heavily based on convert_arg_to_entry from exif command line tool.
410 * But without ExifLog, exitting, use of g_* io functions
411 * and can take a gdouble value instead of a string
412 */
413static void convert_to_entry (const char *set_value, gdouble gdvalue, ExifEntry *e, ExifByteOrder o)
414{
415 unsigned int i, numcomponents;
416 char *value_p = NULL;
417 char *buf = NULL;
418 /*
419 * ASCII strings are handled separately,
420 * since they don't require any conversion.
421 */
422 if (e->format == EXIF_FORMAT_ASCII ||
423 e->tag == EXIF_TAG_USER_COMMENT) {
424 if (e->data) g_free (e->data);
425 e->components = strlen (set_value) + 1;
426 if (e->tag == EXIF_TAG_USER_COMMENT)
427 e->components += 8 - 1;
428 e->size = sizeof (char) * e->components;
429 e->data = g_malloc (e->size);
430 if (!e->data) {
431 g_warning (_("Not enough memory."));
432 return;
433 }
434 if (e->tag == EXIF_TAG_USER_COMMENT) {
435 /* assume ASCII charset */
436 /* TODO: get this from the current locale */
437 memcpy ((char *) e->data, "ASCII\0\0\0", 8);
438 memcpy ((char *) e->data + 8, set_value,
439 strlen (set_value));
440 } else
441 strcpy ((char *) e->data, set_value);
442 return;
443 }
444
445 /*
446 * Make sure we can handle this entry
447 */
448 if ((e->components == 0) && *set_value) {
449 g_warning (_("Setting a value for this tag is unsupported!"));
450 return;
451 }
452
453 gboolean use_string = (set_value != NULL);
454 if ( use_string ) {
455 /* Copy the string so we can modify it */
456 buf = g_strdup (set_value);
457 if (!buf)
458 return;
459 value_p = strtok (buf, " ");
460 }
461
462 numcomponents = e->components;
463 for (i = 0; i < numcomponents; ++i) {
464 unsigned char s;
465
466 if ( use_string ) {
467 if (!value_p) {
468 g_warning (_("Too few components specified (need %d, found %d)\n"), numcomponents, i);
469 return;
470 }
471 if (!isdigit(*value_p) && (*value_p != '+') && (*value_p != '-')) {
472 g_warning (_("Numeric value expected\n"));
473 return;
474 }
475 }
476
477 s = exif_format_get_size (e->format);
478 switch (e->format) {
479 case EXIF_FORMAT_ASCII:
480 g_warning (_("This shouldn't happen!"));
481 return;
482 break;
483 case EXIF_FORMAT_SHORT:
484 exif_set_short (e->data + (s * i), o, atoi (value_p));
485 break;
486 case EXIF_FORMAT_SSHORT:
487 exif_set_sshort (e->data + (s * i), o, atoi (value_p));
488 break;
489 case EXIF_FORMAT_RATIONAL: {
490 ExifRational er;
491
492 double val = 0.0 ;
493 if ( use_string && value_p )
494 val = fabs (atol (value_p));
495 else
496 val = fabs (gdvalue);
497
498 if ( i == 0 ) {
499 // One (or first) part rational
500
501 // Sneak peek into tag as location tags need rounding down to give just the degrees part
502 if ( e->tag == EXIF_TAG_GPS_LATITUDE || e->tag == EXIF_TAG_GPS_LONGITUDE ) {
503 er.numerator = (ExifLong) floor ( val );
504 er.denominator = 1.0;
505 }
506 else {
507 // I don't see any point in doing anything too complicated here,
508 // such as trying to work out the 'best' denominator
509 // For the moment use KISS principle.
510 // Fix a precision of 1/100 metre as that's more than enough for GPS accuracy especially altitudes!
511 er.denominator = 100.0;
512 er.numerator = (ExifLong) (val * er.denominator);
513 }
514 }
515
516 // Now for Location 3 part rationals do Mins and Seconds format
517
518 // Rounded down minutes
519 if ( i == 1 ) {
520 er.denominator = 1.0;
521 er.numerator = (ExifLong) ( (int) floor ( ( val - floor (val) ) * 60.0 ) );
522 }
523
524 // Finally seconds
525 if ( i == 2 ) {
526 er.denominator = 100.0;
527
528 // Fractional minute.
529 double FracPart = ((val - floor(val)) * 60) - (double)(int) floor ( ( val - floor (val) ) * 60.0 );
530 er.numerator = (ExifLong) ( (int)floor(FracPart * 6000) ); // Convert to seconds.
531 }
532 exif_set_rational (e->data + (s * i), o, er );
533 break;
534 }
535 case EXIF_FORMAT_LONG:
536 exif_set_long (e->data + (s * i), o, atol (value_p));
537 break;
538 case EXIF_FORMAT_SLONG:
539 exif_set_slong (e->data + (s * i), o, atol (value_p));
540 break;
541 case EXIF_FORMAT_BYTE:
542 case EXIF_FORMAT_SBYTE:
543 case EXIF_FORMAT_UNDEFINED: /* treat as byte array */
544 e->data[s * i] = atoi (value_p);
545 break;
546 case EXIF_FORMAT_FLOAT:
547 case EXIF_FORMAT_DOUBLE:
548 case EXIF_FORMAT_SRATIONAL:
549 default:
550 g_warning (_("Not yet implemented!"));
551 return;
552 }
553
554 if ( use_string )
555 value_p = strtok (NULL, " ");
556
557 }
558
559 g_free (buf);
560
561 if ( use_string )
562 if ( value_p )
563 g_warning (_("Warning; Too many components specified!"));
564}
565
566/**
567 * a_geotag_write_exif_gps:
568 * @filename: The image file to save information in
569 * @coord: The location
570 * @alt: The elevation
571 *
572 * Returns: A value indicating success: 0, or some other value for failure
573 *
574 */
575gint a_geotag_write_exif_gps ( const gchar *filename, VikCoord coord, gdouble alt, gboolean no_change_mtime )
576{
577 gint result = 0; // OK so far...
578
579 // Save mtime for later use
580 struct stat stat_save;
581 if ( no_change_mtime )
582 stat ( filename, &stat_save );
583
584 /*
585 Appears libexif doesn't actually support writing EXIF data directly to files
586 Thus embed command line exif writing method within Viking
587 (for example this is done by Enlightment - http://www.enlightenment.org/ )
588 This appears to be JPEG only, but is probably 99% of our use case
589 Alternatively consider using libexiv2 and C++...
590 */
591
592 // Actual EXIF settings here...
593 JPEGData *jdata;
594
595 /* Parse the JPEG file. */
596 jdata = jpeg_data_new ();
597 jpeg_data_load_file (jdata, filename);
598
599 // Get current values
600 ExifData *ed = exif_data_new_from_file ( filename );
601 if ( !ed )
602 ed = exif_data_new ();
603
604 // Update ExifData with our new settings
605 ExifEntry *ee;
606 //
607 // 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
608 // (unless of course there is some bug in the setting of the ID, that prevents subsequent tags)
609 //
610
611 ee = my_exif_create_value (ed, EXIF_TAG_GPS_ALTITUDE, EXIF_IFD_GPS);
612 convert_to_entry ( NULL, alt, ee, exif_data_get_byte_order(ed) );
613
614 // byte 0 meaning "sea level" or 1 if the value is negative.
615 ee = my_exif_create_value (ed, EXIF_TAG_GPS_ALTITUDE_REF, EXIF_IFD_GPS);
616 convert_to_entry ( alt < 0.0 ? "1" : "0", 0.0, ee, exif_data_get_byte_order(ed) );
617
618 ee = my_exif_create_value (ed, EXIF_TAG_GPS_PROCESSING_METHOD, EXIF_IFD_GPS);
619 // see http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/GPS.html
620 convert_to_entry ( "MANUAL", 0.0, ee, exif_data_get_byte_order(ed) );
621
622 ee = my_exif_create_value (ed, EXIF_TAG_GPS_MAP_DATUM, EXIF_IFD_GPS);
623 convert_to_entry ( "WGS-84", 0.0, ee, exif_data_get_byte_order(ed) );
624
625 struct LatLon ll;
626 vik_coord_to_latlon ( &coord, &ll );
627
628 ee = my_exif_create_value (ed, EXIF_TAG_GPS_LATITUDE_REF, EXIF_IFD_GPS);
629 // N or S
630 convert_to_entry ( ll.lat < 0.0 ? "S" : "N", 0.0, ee, exif_data_get_byte_order(ed) );
631
632 ee = my_exif_create_value (ed, EXIF_TAG_GPS_LATITUDE, EXIF_IFD_GPS);
633 convert_to_entry ( NULL, ll.lat, ee, exif_data_get_byte_order(ed) );
634
635 ee = my_exif_create_value (ed, EXIF_TAG_GPS_LONGITUDE_REF, EXIF_IFD_GPS);
636 // E or W
637 convert_to_entry ( ll.lon < 0.0 ? "W" : "E", 0.0, ee, exif_data_get_byte_order(ed) );
638
639 ee = my_exif_create_value (ed, EXIF_TAG_GPS_LONGITUDE, EXIF_IFD_GPS);
640 convert_to_entry ( NULL, ll.lon, ee, exif_data_get_byte_order(ed) );
641
642 ee = my_exif_create_value (ed, EXIF_TAG_GPS_VERSION_ID, EXIF_IFD_GPS);
643 //convert_to_entry ( "2 0 0 0", 0.0, ee, exif_data_get_byte_order(ed) );
644 convert_to_entry ( "2 2 0 0", 0.0, ee, exif_data_get_byte_order(ed) );
645
646 jpeg_data_set_exif_data (jdata, ed);
647
648 if ( jdata ) {
649 /* Save the modified image. */
650 result = jpeg_data_save_file (jdata, filename);
651
652 // Convert result from 1 for success, 0 for failure into our scheme
653 result = !result;
654
655 jpeg_data_unref (jdata);
656 }
657 else {
658 // Epic fail - file probably not a JPEG
659 result = 2;
660 }
661
662 if ( no_change_mtime ) {
663 // Restore mtime, using the saved value
664 struct stat stat_tmp;
665 struct utimbuf utb;
666 stat ( filename, &stat_tmp );
667 utb.actime = stat_tmp.st_atime;
668 utb.modtime = stat_save.st_mtime;
669 utime ( filename, &utb );
670 }
671
672 return result;
673}