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