/*
* This uses EXIF information from images to create waypoints at those positions
*
- * For the implementation I have chosen to use libexif, which keeps Viking a pure C program
- * For an alternative implementation (a la gpscorrelate), one could use libeviv2 but it appears to be C++ only.
+ * The initial implementation uses libexif, which keeps Viking a pure C program.
+ * Now libgexiv2 is available (in C as a wrapper around the more powerful libexiv2 [C++]) so this is the preferred build.
+ * The attentative reader will notice the use of gexiv2 is a lot simpler as well.
+ * For the time being the libexif code + build is still made available.
*/
#include <string.h>
#include "geotag_exif.h"
+#include "config.h"
#include "globals.h"
#include "file.h"
#include <sys/stat.h>
+#ifdef HAVE_UTIME_H
#include <utime.h>
+#endif
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
+#ifdef HAVE_LIBGEXIV2
+#include <gexiv2/gexiv2.h>
+#endif
+#ifdef HAVE_LIBEXIF
#include <libexif/exif-data.h>
#include "libjpeg/jpeg-data.h"
+#endif
+#ifdef HAVE_LIBGEXIV2
+/**
+ * Attempt to get a single comment from the various exif fields
+ */
+static gchar* geotag_get_exif_comment ( GExiv2Metadata *gemd )
+{
+ //
+ // Try various options to create a comment
+ //
+ if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.ImageDescription" ) )
+ return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.ImageDescription" ) );
+
+ if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPComment" ) )
+ return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPComment" ) );
+
+ if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPSubject" ) )
+ return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPSubject" ) );
+
+ if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.DateTimeOriginal" ) )
+ return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.DateTimeOriginal" ) );
+
+ // Otherwise nothing found
+ return NULL;
+}
+#endif
+
+#ifdef HAVE_LIBEXIF
/**
* Attempt to get a single comment from the various exif fields
*/
exif_entry_get_value ( ee, str, 128 );
return g_strdup ( str );
}
-
+
// Otherwise nothing found
return NULL;
}
return ll;
}
+#endif
/**
* a_geotag_get_position:
{
struct LatLon ll = { 0.0, 0.0 };
+#ifdef HAVE_LIBGEXIV2
+ GExiv2Metadata *gemd = gexiv2_metadata_new ();
+ if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
+ gdouble lat;
+ gdouble lon;
+ gdouble alt;
+ if ( gexiv2_metadata_get_gps_info ( gemd, &lon, &lat, &alt ) ) {
+ ll.lat = lat;
+ ll.lon = lon;
+ }
+ }
+ gexiv2_metadata_free ( gemd );
+#else
+#ifdef HAVE_LIBEXIF
// open image with libexif
ExifData *ed = exif_data_new_from_file ( filename );
MyReturn0:
// Finished with EXIF
exif_data_free ( ed );
+#endif
+#endif
return ll;
}
*name = NULL;
VikWaypoint *wp = NULL;
+#ifdef HAVE_LIBGEXIV2
+ GExiv2Metadata *gemd = gexiv2_metadata_new ();
+ if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
+ gdouble lat;
+ gdouble lon;
+ gdouble alt;
+ if ( gexiv2_metadata_get_gps_info ( gemd, &lon, &lat, &alt ) ) {
+ struct LatLon ll;
+ ll.lat = lat;
+ ll.lon = lon;
+
+ //
+ // Now create Waypoint with acquired information
+ //
+ wp = vik_waypoint_new();
+ wp->visible = TRUE;
+ // Set info from exif values
+ // Location
+ vik_coord_load_from_latlon ( &(wp->coord), vcmode, &ll );
+ // Altitude
+ wp->altitude = alt;
+
+ if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPTitle" ) )
+ *name = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPTitle" ) );
+ wp->comment = geotag_get_exif_comment ( gemd );
+
+ vik_waypoint_set_image ( wp, filename );
+ }
+ }
+ gexiv2_metadata_free ( gemd );
+#else
+#ifdef HAVE_LIBEXIF
// TODO use log?
//ExifLog *log = NULL;
MyReturn:
// Finished with EXIF
exif_data_free ( ed );
+#endif
+#endif
return wp;
}
wp->coord = coord;
wp->altitude = alt;
+#ifdef HAVE_LIBGEXIV2
+ GExiv2Metadata *gemd = gexiv2_metadata_new ();
+ if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
+ wp->comment = geotag_get_exif_comment ( gemd );
+ if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPTitle" ) )
+ *name = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPTitle" ) );
+ }
+ gexiv2_metadata_free ( gemd );
+#else
+#ifdef HAVE_LIBEXIF
ExifData *ed = exif_data_new_from_file ( filename );
// Set info from exif values
// Finished with EXIF
exif_data_free ( ed );
}
+#endif
+#endif
vik_waypoint_set_image ( wp, filename );
gchar* datetime = NULL;
*has_GPS_info = FALSE;
+#ifdef HAVE_LIBGEXIV2
+ GExiv2Metadata *gemd = gexiv2_metadata_new ();
+ if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
+ gdouble lat, lon;
+ *has_GPS_info = ( gexiv2_metadata_get_gps_longitude(gemd,&lon) && gexiv2_metadata_get_gps_latitude(gemd,&lat) );
+
+ // Prefer 'Photo' version over 'Image'
+ if ( gexiv2_metadata_has_tag ( gemd, "Exif.Photo.DateTimeOriginal" ) )
+ datetime = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Photo.DateTimeOriginal" ) );
+ else
+ datetime = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.DateTimeOriginal" ) );
+ }
+ gexiv2_metadata_free ( gemd );
+#else
+#ifdef HAVE_LIBEXIF
ExifData *ed = exif_data_new_from_file ( filename );
// Detect EXIF load failure
*has_GPS_info = FALSE;
exif_data_free ( ed );
-
+#endif
+#endif
return datetime;
}
+#ifdef HAVE_LIBEXIF
/**! If the entry doesn't exist, create it.
* Based on exif command line action_create_value function in exif 0.6.20
*/
if ( value_p )
g_warning (_("Warning; Too many components specified!"));
}
+#endif
/**
* a_geotag_write_exif_gps:
// Save mtime for later use
struct stat stat_save;
if ( no_change_mtime )
- stat ( filename, &stat_save );
-
+ if ( stat ( filename, &stat_save ) != 0 )
+ g_warning ( "%s couldn't read: %s", __FUNCTION__, filename );
+
+#ifdef HAVE_LIBGEXIV2
+ GExiv2Metadata *gemd = gexiv2_metadata_new ();
+ if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
+ struct LatLon ll;
+ vik_coord_to_latlon ( &coord, &ll );
+ if ( ! gexiv2_metadata_set_gps_info ( gemd, ll.lon, ll.lat, alt ) ) {
+ result = 1; // Failed
+ }
+ else {
+ GError *error = NULL;
+ if ( ! gexiv2_metadata_save_file ( gemd, filename, &error ) ) {
+ result = 2;
+ g_warning ( "Write EXIF failure:%s" , error->message );
+ g_error_free ( error );
+ }
+ }
+ }
+ gexiv2_metadata_free ( gemd );
+#else
+#ifdef HAVE_LIBEXIF
/*
Appears libexif doesn't actually support writing EXIF data directly to files
Thus embed command line exif writing method within Viking
result = 2;
}
+ exif_data_free ( ed );
+#endif
+#endif
+
if ( no_change_mtime ) {
// Restore mtime, using the saved value
struct stat stat_tmp;
struct utimbuf utb;
- stat ( filename, &stat_tmp );
+ (void)stat ( filename, &stat_tmp );
utb.actime = stat_tmp.st_atime;
utb.modtime = stat_save.st_mtime;
- utime ( filename, &utb );
+ // Not security critical, thus potential Time of Check Time of Use race condition is not bad
+ // coverity[toctou]
+ if ( g_utime ( filename, &utb ) != 0 )
+ g_warning ( "%s couldn't set time on: %s", __FUNCTION__, filename );
}
return result;