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