X-Git-Url: https://git.street.me.uk/andy/viking.git/blobdiff_plain/469856bf9dec2656433baaa976b9af84d79769ae..c2cf03332f51a9fa992220124635f737399fba91:/src/geotag_exif.c diff --git a/src/geotag_exif.c b/src/geotag_exif.c index 4b77ca71..8d1005b1 100644 --- a/src/geotag_exif.c +++ b/src/geotag_exif.c @@ -2,7 +2,7 @@ /* * viking -- GPS Data and Topo Analyzer, Explorer, and Manager * - * Copyright (C) 2011, Rob Norris + * Copyright (C) 2011-2014, Rob Norris * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,15 +22,15 @@ /* * This uses EXIF information from images to create waypoints at those positions - * TODO: allow writing of image location: - * . Via correlation with a track (c.f. gpscorrelate) (multiple images) - * . Via screen position (individual image) on an existing waypoint * - * 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 intial 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 #include "geotag_exif.h" +#include "config.h" #include "globals.h" #include "file.h" @@ -40,9 +40,42 @@ #include #include #include +#include +#ifdef HAVE_LIBGEXIV2 +#include +#endif +#ifdef HAVE_LIBEXIF #include #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 */ @@ -79,7 +112,7 @@ static gchar* geotag_get_exif_comment ( ExifData *ed ) exif_entry_get_value ( ee, str, 128 ); return g_strdup ( str ); } - + // Otherwise nothing found return NULL; } @@ -120,6 +153,99 @@ static gdouble Rational2Double ( unsigned char *data, int offset, ExifByteOrder return ans; } +static struct LatLon get_latlon ( ExifData *ed ) +{ + struct LatLon ll = { 0.0, 0.0 }; + const struct LatLon ll0 = { 0.0, 0.0 }; + + gchar str[128]; + ExifEntry *ee; + // + // Lat & Long is necessary to form a waypoint. + // + ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE); + if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) ) + return ll0; + + ll.lat = Rational2Double ( ee->data, + exif_format_get_size(ee->format), + exif_data_get_byte_order(ed) ); + + ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE_REF); + if ( ee ) { + exif_entry_get_value ( ee, str, 128 ); + if ( str[0] == 'S' ) + ll.lat = -ll.lat; + } + + ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE); + if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) ) + return ll0; + + ll.lon = Rational2Double ( ee->data, + exif_format_get_size(ee->format), + exif_data_get_byte_order(ed) ); + + ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE_REF); + if ( ee ) { + exif_entry_get_value ( ee, str, 128 ); + if ( str[0] == 'W' ) + ll.lon = -ll.lon; + } + + return ll; +} +#endif + +/** + * a_geotag_get_position: + * + * @filename: The (JPG) file with EXIF information in it + * + * Returns: The position in LatLon format. + * It will be 0,0 if some kind of failure occurs. + */ +struct LatLon a_geotag_get_position ( const gchar *filename ) +{ + 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 ); + + // Detect EXIF load failure + if ( !ed ) + return ll; + + ExifEntry *ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID); + // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0" + if ( ! ( ee && ee->components == 4 ) ) + goto MyReturn0; + + ll = get_latlon ( ed ); + +MyReturn0: + // Finished with EXIF + exif_data_free ( ed ); +#endif +#endif + + return ll; +} + /** * a_geotag_create_waypoint_from_file: * @filename: The image file to process @@ -135,6 +261,38 @@ VikWaypoint* a_geotag_create_waypoint_from_file ( const gchar *filename, VikCoor *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; @@ -159,51 +317,12 @@ VikWaypoint* a_geotag_create_waypoint_from_file ( const gchar *filename, VikCoor //if ( ! ( ee->data[0] == 2 && ee->data[2] == 0 && ee->data[3] == 0 ) ) // goto MyReturn; + ll = get_latlon ( ed ); - ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_MAP_DATUM); - if ( ! ( ee && ee->components > 0 && ee->format == EXIF_FORMAT_ASCII ) ) + // Hopefully won't have valid images at 0,0! + if ( ll.lat == 0.0 && ll.lon == 0.0 ) goto MyReturn; - // If map datum specified - only deal in WGS-84 - the defacto standard - if ( ee && ee->components > 0 ) { - exif_entry_get_value ( ee, str, 128 ); - if ( strncmp (str, "WGS-84", 6) ) - goto MyReturn; - } - - // - // Lat & Long is necessary to form a waypoint. - // - ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE); - if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) ) - goto MyReturn; - - ll.lat = Rational2Double ( ee->data, - exif_format_get_size(ee->format), - exif_data_get_byte_order(ed) ); - - ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE_REF); - if ( ee ) { - exif_entry_get_value ( ee, str, 128 ); - if ( str[0] == 'S' ) - ll.lat = -ll.lat; - } - - ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE); - if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) ) - goto MyReturn; - - ll.lon = Rational2Double ( ee->data, - exif_format_get_size(ee->format), - exif_data_get_byte_order(ed) ); - - ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE_REF); - if ( ee ) { - exif_entry_get_value ( ee, str, 128 ); - if ( str[0] == 'W' ) - ll.lon = -ll.lon; - } - // // Not worried if none of the other fields exist, as can default the values to something // @@ -245,29 +364,46 @@ VikWaypoint* a_geotag_create_waypoint_from_file ( const gchar *filename, VikCoor MyReturn: // Finished with EXIF exif_data_free ( ed ); +#endif +#endif return wp; } /** - * a_geotag_create_waypoint_positioned: + * a_geotag_waypoint_positioned: * @filename: The image file to process * @coord: The location for positioning the Waypoint * @name: Returns a name for the Waypoint (can be NULL) + * @waypoint: An existing waypoint to update (can be NULL to generate a new waypoint) * - * Returns: An allocated Waypoint or NULL if Waypoint could not be generated + * Returns: An allocated waypoint if the input waypoint is NULL, + * otherwise the passed in waypoint is updated * * Here EXIF processing is used to get non position related information (i.e. just the comment) * */ -VikWaypoint* a_geotag_create_waypoint_positioned ( const gchar *filename, VikCoord coord, gdouble alt, gchar **name ) +VikWaypoint* a_geotag_waypoint_positioned ( const gchar *filename, VikCoord coord, gdouble alt, gchar **name, VikWaypoint *wp ) { *name = NULL; - VikWaypoint *wp = vik_waypoint_new(); - wp->visible = TRUE; + if ( wp == NULL ) { + // Need to create waypoint + wp = vik_waypoint_new(); + wp->visible = TRUE; + } 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 @@ -286,10 +422,11 @@ VikWaypoint* a_geotag_create_waypoint_positioned ( const gchar *filename, VikCoo // Finished with EXIF exif_data_free ( ed ); } +#endif +#endif vik_waypoint_set_image ( wp, filename ); - return wp; } @@ -306,7 +443,23 @@ VikWaypoint* a_geotag_create_waypoint_positioned ( const gchar *filename, VikCoo gchar* a_geotag_get_exif_date_from_file ( const gchar *filename, gboolean *has_GPS_info ) { 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 @@ -323,19 +476,30 @@ gchar* a_geotag_get_exif_date_from_file ( const gchar *filename, gboolean *has_G } // Check GPS Info - *has_GPS_info = FALSE; - + ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID); // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0" if ( ee && ee->components == 4 ) *has_GPS_info = TRUE; - exif_data_free ( ed ); + // Check other basic GPS fields exist too + // I have encountered some images which have just the EXIF_TAG_GPS_VERSION_ID but nothing else + // So to confirm check more EXIF GPS TAGS: + ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE); + if ( !ee ) + *has_GPS_info = FALSE; + ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE); + if ( !ee ) + *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 */ @@ -553,6 +717,7 @@ static void convert_to_entry (const char *set_value, gdouble gdvalue, ExifEntry if ( value_p ) g_warning (_("Warning; Too many components specified!")); } +#endif /** * a_geotag_write_exif_gps: @@ -572,6 +737,26 @@ gint a_geotag_write_exif_gps ( const gchar *filename, VikCoord coord, gdouble al if ( no_change_mtime ) stat ( filename, &stat_save ); +#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 @@ -650,6 +835,10 @@ gint a_geotag_write_exif_gps ( const gchar *filename, VikCoord coord, gdouble al result = 2; } + exif_data_free ( ed ); +#endif +#endif + if ( no_change_mtime ) { // Restore mtime, using the saved value struct stat stat_tmp;