From 72bbd56202dda4a05927027eafa3888f3f3d073e Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Mon, 17 Nov 2014 22:46:41 +0000 Subject: [PATCH] SF Bugs#112: Enable using libgexiv2 for writing geotag image data to void XMP data loss. Use libgexiv2 which is the C interface to Exiv2 C++ library. Requires installation of the package named libgexiv2-dev or similar. If libgexiv2 is not available, then it's possible to run configure with --with-libexif to force the use of the libexif (and then 'libjpeg' code to write EXIF data only to JPEGs but may lose XMP data) --- configure.ac | 16 +++- src/Makefile.am | 7 +- src/dialog.c | 3 + src/geotag_exif.c | 150 +++++++++++++++++++++++++++++++++-- viking.spec.in | 2 +- win32/configure_and_make.bat | 3 +- 6 files changed, 170 insertions(+), 11 deletions(-) diff --git a/configure.ac b/configure.ac index 0d7d4278..8d934266 100644 --- a/configure.ac +++ b/configure.ac @@ -228,6 +228,8 @@ case $ac_cv_enable_geocaches in esac AM_CONDITIONAL([GEOCACHES], [test x$ac_cv_enable_geocaches = xyes]) +# Geotagging +AC_ARG_WITH(libexif, AC_HELP_STRING([--with-libexif], [Force usage of libexif instead of libgexiv2])) AC_ARG_ENABLE(geotag, AC_HELP_STRING([--enable-geotag], [enable Geotag Support (default is enable).]), [ac_cv_enable_geotag=$enableval], @@ -236,11 +238,21 @@ AC_CACHE_CHECK([whether to enable Geotag Support], [ac_cv_enable_geotag], [ac_cv_enable_geotag=yes]) case $ac_cv_enable_geotag in yes) - AC_CHECK_LIB(exif,exif_loader_new,,AC_MSG_ERROR([libexif is needed for Geotag features[,] but is not found. The feature can be disabled with --disable-geotag])) + AS_IF([test x$with_libexif = xyes], + AC_CHECK_HEADER([libexif/exif-data.h],[],AC_MSG_ERROR([exif-data.h is needed but not found - you will need to install package 'libexif-dev' or similar])) + AC_CHECK_LIB(exif,exif_loader_new,, AC_MSG_ERROR([libexif is not found but it has been forcibly required])), + # gexiv2.h relies on glib so a simple compile check fails. + #AC_CHECK_HEADER([gexiv2/gexiv2.h],,AC_MSG_ERROR([Error msg...])) + AC_CHECK_LIB(gexiv2,gexiv2_metadata_new,, AC_MSG_ERROR([libgexiv2 is needed but not found - you will need to install package 'libgexiv2-dev' or similar. The feature can be disabled with --disable-geotag])) + ) AC_DEFINE(VIK_CONFIG_GEOTAG, [], [GEOTAG STUFF]) ;; esac AM_CONDITIONAL([GEOTAG], [test x$ac_cv_enable_geotag = xyes]) +AM_CONDITIONAL([GEXIV2], [test x$ac_cv_lib_gexiv2_gexiv2_metadata_new = xyes] ) +# Tested with gexiv2 0.10.2, but probably would work with older versions; may be all of them... +#AM_COND_IF([GEXIV2], [PKG_CHECK_MODULES([GEXIV2], [gexiv2 >= 0.6.1])]) +AM_CONDITIONAL([LIBEXIF], [test x$ac_cv_lib_exif_exif_loader_new = xyes] ) AC_ARG_ENABLE(dem24k, AC_HELP_STRING([--enable-dem24k], [enable USGS 24k DEM (default is disable) download source. Requires dem24k.pl script in path.]), @@ -420,7 +432,7 @@ echo "Open Street Map : $ac_cv_enable_openstreetmap" echo "BlueMarble : $ac_cv_enable_bluemarble" echo "Geonames : $ac_cv_enable_geonames" echo "Geocaches Acquire : $ac_cv_enable_geocaches" -echo "Geotag Support : $ac_cv_enable_geotag" +echo "Geotag Support : $ac_cv_enable_geotag (libgexiv2=$ac_cv_lib_gexiv2_gexiv2_metadata_new libexif=$ac_cv_lib_exif_exif_loader_new)" echo "USGS 24k DEM : $ac_cv_enable_dem24k" echo "Realtime GPS Tracking : $ac_cv_enable_realtimegpstracking" echo "bzip2 Support : $ac_cv_enable_bzip2" diff --git a/src/Makefile.am b/src/Makefile.am index d17999d3..167b391f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -198,7 +198,12 @@ if GEOTAG libviking_a_SOURCES += \ datasource_geotag.c \ geotag_exif.c geotag_exif.h \ - viktrwlayer_geotag.c viktrwlayer_geotag.h \ + viktrwlayer_geotag.c viktrwlayer_geotag.h +endif + +# libexif doesn't have write support of EXIF info - so reused this code from command line exif tool +if LIBEXIF +libviking_a_SOURCES += \ libjpeg/jpeg-data.c libjpeg/jpeg-data.h \ libjpeg/jpeg-marker.c libjpeg/jpeg-marker.h endif diff --git a/src/dialog.c b/src/dialog.c index aaa77bbe..cc1438ae 100644 --- a/src/dialog.c +++ b/src/dialog.c @@ -636,6 +636,9 @@ void a_dialog_about ( GtkWindow *parent ) #ifdef HAVE_LIBGPS "libgps", #endif +#ifdef HAVE_LIBGEXIV2 + "libgexiv2", +#endif #ifdef HAVE_LIBEXIF "libexif", #endif diff --git a/src/geotag_exif.c b/src/geotag_exif.c index ca3adf90..8d1005b1 100644 --- a/src/geotag_exif.c +++ b/src/geotag_exif.c @@ -23,11 +23,14 @@ /* * 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 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" @@ -38,9 +41,41 @@ #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 */ @@ -77,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; } @@ -160,6 +195,7 @@ static struct LatLon get_latlon ( ExifData *ed ) return ll; } +#endif /** * a_geotag_get_position: @@ -173,6 +209,20 @@ 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 ); @@ -190,6 +240,8 @@ struct LatLon a_geotag_get_position ( const gchar *filename ) MyReturn0: // Finished with EXIF exif_data_free ( ed ); +#endif +#endif return ll; } @@ -209,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; @@ -280,6 +364,8 @@ VikWaypoint* a_geotag_create_waypoint_from_file ( const gchar *filename, VikCoor MyReturn: // Finished with EXIF exif_data_free ( ed ); +#endif +#endif return wp; } @@ -308,6 +394,16 @@ VikWaypoint* a_geotag_waypoint_positioned ( const gchar *filename, VikCoord coor 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 @@ -326,6 +422,8 @@ VikWaypoint* a_geotag_waypoint_positioned ( const gchar *filename, VikCoord coor // Finished with EXIF exif_data_free ( ed ); } +#endif +#endif vik_waypoint_set_image ( wp, filename ); @@ -347,6 +445,21 @@ gchar* a_geotag_get_exif_date_from_file ( const gchar *filename, gboolean *has_G 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 @@ -380,11 +493,13 @@ gchar* a_geotag_get_exif_date_from_file ( const gchar *filename, gboolean *has_G *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 */ @@ -602,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: @@ -621,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 @@ -699,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; @@ -709,7 +849,5 @@ gint a_geotag_write_exif_gps ( const gchar *filename, VikCoord coord, gdouble al utime ( filename, &utb ); } - exif_data_free ( ed ); - return result; } diff --git a/viking.spec.in b/viking.spec.in index 5c203562..71733b19 100644 --- a/viking.spec.in +++ b/viking.spec.in @@ -22,7 +22,7 @@ BuildRequires: gpsd-devel BuildRequires: gettext perl(XML::Parser) BuildRequires: intltool BuildRequires: libxslt -BuildRequires: libexif-devel +BuildRequires: libgexiv2-devel BuildRequires: libbz2-devel BuildRequires: libmagic-devel BuildRequires: libsqlite3-devel diff --git a/win32/configure_and_make.bat b/win32/configure_and_make.bat index fe91504b..4e194512 100644 --- a/win32/configure_and_make.bat +++ b/win32/configure_and_make.bat @@ -6,6 +6,7 @@ :: set PATH=%PATH%;%SystemDrive%\Mingw\bin;%SystemDrive%\msys\1.0\bin pushd .. -sh configure CFLAGS="-DWINDOWS -mwindows" LIBCURL=-lcurldll LIBS=-lzdll --disable-realtime-gps-tracking --disable-scrollkeeper --enable-windows +:: ATM Don't have build method for libgexiv2, so use the fallback of libexif +sh configure CFLAGS="-DWINDOWS -mwindows" LIBCURL=-lcurldll LIBS=-lzdll --with-libexif --disable-realtime-gps-tracking --disable-scrollkeeper --enable-windows popd make.bat -- 2.39.5