// ***************************************************************** -*- C++ -*- /* * Copyright (C) 2004-2021 Exiv2 authors * This program is part of the Exiv2 distribution. * * 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 the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. */ // geotag.cpp // Sample program to read gpx files and update images with GPS tags // g++ geotag.cpp -o geotag -lexiv2 -lexpat #include #include "unused.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__MINGW32__) || defined(__MINGW64__) # ifndef __MINGW__ # define __MINGW__ # endif #endif using namespace std; #ifndef lengthof #define lengthof(x) (sizeof(*x)/sizeof(x)) #endif #if defined(_MSC_VER) || defined(__MINGW__) #include char* realpath(const char* file,char* path); #define lstat _stat #define stat _stat #if _MSC_VER < 1400 #define strcpy_s(d,l,s) strcpy(d,s) #define strcat_s(d,l,s) strcat(d,s) #endif #endif #if ! defined(_MSC_VER) #include #include #include #define stricmp strcasecmp #endif #ifndef _MAX_PATH #define _MAX_PATH 1024 #endif // prototypes class Options; int getFileType(const char* path ,Options& options); int getFileType(std::string& path,Options& options); string getExifTime(const time_t t); time_t parseTime(const char* ,bool bAdjust=false); int timeZoneAdjust(); // platform specific code #if defined(_MSC_VER) || defined(__MINGW__) char* realpath(const char* file,char* path) { char* result = (char*) malloc(_MAX_PATH); if (result) GetFullPathName(file,_MAX_PATH,result,NULL); return result ; UNUSED(path); } #endif // Command-line parser class Options { public: bool verbose; bool help; bool version; bool dst; bool dryrun; bool ascii; Options() { verbose = false; help = false; version = false; dst = false; dryrun = false; ascii = false; } virtual ~Options() {} ; } ; enum { resultOK=0 , resultSyntaxError , resultSelectFailed }; enum // keyword indices { kwHELP = 0 , kwVERSION , kwDST , kwDRYRUN , kwASCII , kwVERBOSE , kwADJUST , kwTZ , kwDELTA , kwMAX // manages keyword array , kwNEEDVALUE // bogus keywords for error reporting , kwSYNTAX // -- ditto -- , kwNOVALUE = -kwVERBOSE // keywords <= kwNOVALUE are flags (no value needed) }; // file types supported enum { typeUnknown = 0 , typeDirectory = 1 , typeImage = 2 , typeXML = 3 , typeFile = 4 , typeDoc = 5 , typeCode = 6 , typeMax = 7 }; // forward declaration class Position; // globals typedef std::map TimeDict_t; typedef std::map::iterator TimeDict_i; typedef std::vector strings_t; const char* gDeg = NULL ; // string "°" or "deg" TimeDict_t gTimeDict ; strings_t gFiles; // Position (from gpx file) class Position { public: Position(time_t time, double lat, double lon, double ele) : time_(time) , lon_(lon) , lat_(lat) , ele_(ele) , delta_(0) {} Position(): time_(0) , lon_(0.0) , lat_(0.0) , ele_(0.0) , delta_(0) { } virtual ~Position() {} // copy constructor Position(const Position& o) : time_(o.time_) , lon_(o.lon_) , lat_(o.lat_) , ele_(o.ele_) , delta_(o.delta_) {} // instance methods bool good() { return time_ || lon_ || lat_ || ele_ ; } std::string getTimeString() { if ( times_.empty() ) times_ = getExifTime(time_) ; return times_; } time_t getTime() { return time_ ; } std::string toString(); // getters/setters double lat() {return lat_ ;} double lon() {return lon_ ;} double ele() {return ele_ ;} int delta() {return delta_ ;} void delta(int delta) {delta_=delta ;} // data private: time_t time_; double lon_ ; double lat_ ; double ele_ ; std::string times_; int delta_; // public static data public: static int adjust_ ; static int tz_ ; static int dst_ ; static time_t deltaMax_; // public static member functions public: static int Adjust() {return Position::adjust_ + Position::tz_ + Position::dst_ ;} static int tz() {return tz_ ;} static int dst() {return dst_ ;} static int adjust() {return adjust_;} static std::string toExifString(double d,bool bRational,bool bLat); static std::string toExifString(double d); static std::string toExifTimeStamp(std::string& t); }; std::string Position::toExifTimeStamp(std::string& t) { char result[200]; const char* arg = t.c_str(); int HH = 0 ; int mm = 0 ; int SS1 = 0 ; if ( strstr(arg,":") || strstr(arg,"-") ) { int YY=0,MM=0,DD=0; char a=0,b=0,c=0,d=0,e=0; sscanf(arg,"%d%c%d%c%d%c%d%c%d%c%d",&YY,&a,&MM,&b,&DD,&c,&HH,&d,&mm,&e,&SS1); } sprintf(result,"%d/1 %d/1 %d/1",HH,mm,SS1); return std::string(result); } std::string Position::toExifString(double d) { char result[200]; d *= 100; sprintf(result,"%d/100",abs((int)d)); return std::string(result); } std::string Position::toExifString(double d,bool bRational,bool bLat) { const char* NS = d>=0.0?"N":"S"; const char* EW = d>=0.0?"E":"W"; const char* NSEW = bLat ? NS: EW; if ( d < 0 ) d = -d; int deg = (int) d; d -= deg; d *= 60; int min = (int) d ; d -= min; d *= 60; int sec = (int)d; char result[200]; if ( bRational ) sprintf(result,"%d/1 %d/1 %d/1" ,deg,min,sec); else sprintf(result,"%03d%s%02d'%02d\"%s" ,deg,gDeg,min,sec,NSEW); return std::string(result); } std::string Position::toString() { char result[200]; std::string sLat = Position::toExifString(lat_,false,true ); std::string sLon = Position::toExifString(lon_,false,false); sprintf(result,"%s %s %-8.3f",sLon.c_str(),sLat.c_str(),ele_); return std::string(result); } // defaults int Position::adjust_ = 0; int Position::tz_ = timeZoneAdjust(); int Position::dst_ = 0; time_t Position::deltaMax_ = 60 ; /////////////////////////////////////////////////////////// // UserData - used by XML Parser class UserData { public: explicit UserData(Options& options): indent(0) , count(0) , nTrkpt(0) , bTime(false) , bEle(false) , ele(0.0) , lat(0.0) , lon(0.0) , options_(options) {} virtual ~UserData() {} // public data members int indent; size_t count ; Position now ; Position prev; int nTrkpt; bool bTime ; bool bEle ; double ele; double lat; double lon; std::string xmlt; std::string exift; time_t time; Options& options_; // static public data memembers }; // XML Parser Callbacks static void startElement(void* userData, const char* name, const char** atts ) { UserData* me = (UserData*) userData; //for ( int i = 0 ; i < me->indent ; i++ ) printf(" "); //printf("begin %s\n",name); me->bTime = strcmp(name,"time")==0; me->bEle = strcmp(name,"ele")==0; if ( strcmp(name,"trkpt")==0 ) { me->nTrkpt++; while ( *atts ) { const char* a=atts[0]; const char* v=atts[1]; if ( !strcmp(a,"lat") ) me->lat = atof(v); if ( !strcmp(a,"lon") ) me->lon = atof(v); atts += 2 ; } } me->count++ ; me->indent++ ; } static void endElement(void* userData, const char* name) { UserData* me = (UserData*) userData; me->indent-- ; if ( strcmp(name,"trkpt")==0 ) { me->nTrkpt--; me->now = Position(me->time,me->lat,me->lon,me->ele) ; if ( !me->prev.good() && me->options_.verbose ) { printf("trkseg %s begin ",me->now.getTimeString().c_str()); } // remember our location and put it in gTimeDict gTimeDict[me->time] = me->now ; me->prev = me->now ; } if ( strcmp(name,"trkseg")==0 && me->options_.verbose ) { printf("%s end\n",me->now.getTimeString().c_str()); } } void charHandler(void* userData,const char* s,int len) { UserData* me = (UserData*) userData; if ( me->nTrkpt == 1 ) { char buffer[100]; int l_max = 98 ; // lengthof(buffer) -2 ; if ( me->bTime && len > 5 ) { if ( len < l_max ) { memcpy(buffer,s,len); buffer[len]=0; char* b = buffer ; while ( *b == ' ' && b < buffer+len ) b++ ; me->xmlt = b ; me->time = parseTime(me->xmlt.c_str()); me->exift = getExifTime(me->time); } me->bTime=false; } if ( me->bEle && len > 2 ) { if ( len < l_max ) { memcpy(buffer,s,len); buffer[len]=0; char* b = buffer ; while ( *b == ' ' && b < buffer+len ) b++ ; me->ele = atof(b); } me->bEle=false; } } } /////////////////////////////////////////////////////////// // Time Functions time_t parseTime(const char* arg,bool bAdjust) { time_t result = 0 ; try { //559 rmills@rmills-imac:~/bin $ exiv2 -pa ~/R.jpg | grep -i date //Exif.Image.DateTime Ascii 20 2009:08:03 08:58:57 //Exif.Photo.DateTimeOriginal Ascii 20 2009:08:03 08:58:57 //Exif.Photo.DateTimeDigitized Ascii 20 2009:08:03 08:58:57 //Exif.GPSInfo.GPSDateStamp Ascii 21 2009-08-03T15:58:57Z // if ( strstr(arg,":") || strstr(arg,"-") ) { int YY=0,MM=0,DD=0,HH=0,mm=0,SS1=0; char a=0,b=0,c=0,d=0,e=0; sscanf(arg,"%d%c%d%c%d%c%d%c%d%c%d",&YY,&a,&MM,&b,&DD,&c,&HH,&d,&mm,&e,&SS1); struct tm T; memset(&T,0,sizeof(T)); T.tm_min = mm ; T.tm_hour = HH ; T.tm_sec = SS1 ; if ( bAdjust ) T.tm_sec -= Position::Adjust(); T.tm_year = YY -1900 ; T.tm_mon = MM -1 ; T.tm_mday = DD ; T.tm_isdst = -1 ; // determine value automatically (otherwise hour may shift) result = mktime(&T); } } catch ( ... ) {}; return result ; } // West of GMT is negative (PDT = Pacific Daylight = -07:00 == -25200 seconds int timeZoneAdjust() { time_t now = time(NULL); int offset; #if defined(_MSC_VER) || defined(__MINGW__) TIME_ZONE_INFORMATION TimeZoneInfo; GetTimeZoneInformation( &TimeZoneInfo ); offset = - (((int)TimeZoneInfo.Bias + (int)TimeZoneInfo.DaylightBias) * 60); UNUSED(now); #elif defined(__CYGWIN__) struct tm lcopy = *localtime(&now); time_t gmt = timegm(&lcopy) ; // timegm modifies lcopy offset = (int) ( ((long signed int) gmt) - ((long signed int) now) ) ; #elif defined(OS_SOLARIS) || defined(__sun__) struct tm local = *localtime(&now) ; time_t local_tt = (int) mktime(&local); time_t time_gmt = (int) mktime(gmtime(&now)); offset = time_gmt - local_tt; #else struct tm local = *localtime(&now) ; offset = local.tm_gmtoff ; #if EXIV2_DEBUG_MESSAGES struct tm utc = *gmtime(&now); printf("utc : offset = %6d dst = %d time = %s", 0 ,utc .tm_isdst, asctime(&utc )); printf("local: offset = %6d dst = %d time = %s", offset,local.tm_isdst, asctime(&local)); printf("timeZoneAdjust = %6d\n",offset); #endif #endif return offset ; } string getExifTime(const time_t t) { static char result[100]; strftime(result,sizeof(result),"%Y-%m-%d %H:%M:%S",localtime(&t)); return result ; } std::string makePath(const std::string& dir, const std::string& file) { return dir + std::string(EXV_SEPARATOR_STR) + file ; } const char* makePath(const char* dir,const char* file) { static char result[_MAX_PATH] ; std::string r = makePath(std::string(dir),std::string(file)); strcpy(result,r.c_str()); return result; } // file utilities bool readDir(const char* path,Options& options) { bool bResult = false; #ifdef _MSC_VER DWORD attrs = GetFileAttributes(path); bool bOKAttrs = attrs != INVALID_FILE_ATTRIBUTES; bool bIsDir = (attrs & FILE_ATTRIBUTE_DIRECTORY) ? true : false ; if( bOKAttrs && bIsDir ) { bResult = true ; char search[_MAX_PATH+10]; strcpy_s(search,_MAX_PATH,path); strcat_s(search,_MAX_PATH,"\\*"); WIN32_FIND_DATA ffd; HANDLE hFind = FindFirstFile(search, &ffd); BOOL bGo = hFind != INVALID_HANDLE_VALUE; if ( bGo ) { while ( bGo ) { if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { // _tprintf(TEXT(" %s \n"), ffd.cFileName); } else { std::string pathName = makePath(path,std::string(ffd.cFileName)); if ( getFileType(pathName,options) == typeImage ) { gFiles.push_back( pathName ); } } bGo = FindNextFile(hFind, &ffd) != 0; } // CloseHandle(hFind); } } #else DIR* dir = opendir (path); if (dir != NULL) { bResult = true; struct dirent* ent; // print all the files and directories within directory while ((ent = readdir (dir)) != NULL) { std::string pathName = makePath(path,ent->d_name); struct stat buf ; lstat(path, &buf ); if ( ent->d_name[0] != '.' ) { // printf("reading %s => %s\n",ent->d_name,pathName.c_str()); if ( getFileType(pathName,options) == typeImage ) { gFiles.push_back( pathName ); } } } closedir (dir); } #endif return bResult ; } inline size_t sip(FILE* f,char* buffer,size_t max_len,size_t len) { while ( !feof(f) && len < max_len && buffer[len-1] != '>') buffer[len++] = fgetc(f); return len; } bool readXML(const char* path,Options& options) { FILE* f = fopen(path,"r"); XML_Parser parser = XML_ParserCreate(NULL); bool bResult = f && parser ; if ( bResult ) { char buffer[8*1024]; UserData me(options) ; XML_SetUserData (parser, &me); XML_SetElementHandler (parser, startElement, endElement); XML_SetCharacterDataHandler(parser,charHandler); // a little sip at the data size_t len = fread(buffer,1,sizeof(buffer)-100,f); const char* lead = "readMetadata(); ExifData &exifData = image->exifData(); bResult = !exifData.empty(); } } catch ( ... ) {}; return bResult ; } time_t readImageTime(const std::string& path,std::string* pS=NULL) { using namespace Exiv2; time_t result = 0 ; const char* dateStrings[] = { "Exif.Photo.DateTimeOriginal" , "Exif.Photo.DateTimeDigitized" , "Exif.Image.DateTime" , NULL }; for (size_t i = 0; !result && dateStrings[i]; i++) { const char* dateString = dateStrings[i] ; try { Image::AutoPtr image = ImageFactory::open(path); if ( image.get() ) { image->readMetadata(); ExifData &exifData = image->exifData(); // printf("%s => %s\n",dateString, exifData[dateString].toString().c_str()); result = parseTime(exifData[dateString].toString().c_str(),true); if ( result && pS ) *pS = exifData[dateString].toString(); } } catch ( ... ) {}; } return result ; } bool sina(const char* s,const char** a) { bool bResult = false ; int i = 0 ; while ( *s == '-' ) s++; while ( !bResult && a[i]) { const char* A = a[i] ; while ( *A == '-' ) A++ ; bResult = stricmp(s,A)==0; i++; } return bResult; } int readFile(const char* path,Options /* options */) { FILE* f = fopen(path,"r"); int nResult = f ? typeFile : typeUnknown; if ( f ) { const char* ext = strstr(path,"."); if ( ext ) { const char* docs[] = { ".doc",".txt", NULL }; const char* code[] = { ".cpp",".h" ,".pl" ,".py" ,".pyc", NULL }; if ( sina(ext,docs) ) nResult = typeDoc; if ( sina(ext,code) ) nResult = typeCode; } } if ( f ) fclose(f) ; return nResult ; } Position* searchTimeDict(TimeDict_t& td, const time_t& time,long long delta) { Position* result = NULL; for ( int t = 0 ; !result && t < delta ; t++ ) { for ( int x = 0 ; !result && x < 2 ; x++ ) { int T = t * ((x==0)?-1:1); if ( td.count(time+T) ) { result = &td[time+T]; result->delta(T); } } } return result; } int getFileType(std::string& path,Options& options) { return getFileType(path.c_str(),options); } int getFileType(const char* path,Options& options) { return readXML (path,options) ? typeXML : readDir (path,options) ? typeDirectory : readImage(path,options) ? typeImage : readFile (path,options) ; } int version(const char* program) { printf("%s: %s %s\n",program,__DATE__,__TIME__); return 0; } int help(const char* program,char const* words[],int nWords,bool /*bVerbose*/) { printf("usage: %s ",program); for ( int i = 0 ; i < nWords ; i++ ) { if ( words[i] ) printf("%c-%s%s",i?'|':'{',words[i],i>(-kwNOVALUE)?" value":""); } printf("}+ path+\n"); return 0; } int compare(const char* a,const char* b) { int result=*a && *b; while ( result && *a && *b) { char A=*a++; char B=*b++; result=tolower(A)==tolower(B); } return result; } int find(const char* arg,char const* words[],int nWords) { if ( arg[0] != '-' ) return kwSYNTAX; int result=0; int count =0; for ( int i = 0 ; i < nWords ; i++) { int j = 0 ; while ( arg[j] == '-' ) j++; if ( ::compare(arg+j,words[i]) ) { result = i ; count++; } } return count==1?result:kwSYNTAX; } int parseTZ(const char* adjust) { int h=0; int m=0; char c=0; try { sscanf(adjust,"%d%c%d",&h,&c,&m); } catch ( ... ) {} ; return (3600*h)+(60*m); } bool mySort(const std::string& a, const std::string& b) { time_t A = readImageTime(a); time_t B = readImageTime(b); return (A shorts; shorts["-?"] = "-help"; shorts["-h"] = "-help"; shorts["-v"] = "-verbose"; shorts["-V"] = "-version"; shorts["-d"] = "-dst"; shorts["-a"] = "-adjust"; shorts["-t"] = "-tz"; shorts["-D"] = "-delta"; shorts["-s"] = "-delta"; shorts["-X"] = "-dryrun"; shorts["-a"] = "-ascii"; Options options ; options.help = sina(keywords[kwHELP ],argv) || argc < 2; options.verbose = sina(keywords[kwVERBOSE],argv); options.dryrun = sina(keywords[kwDRYRUN ],argv); options.version = sina(keywords[kwVERSION],argv); options.dst = sina(keywords[kwDST ],argv); options.ascii = sina(keywords[kwASCII ],argv); for ( int i = 1 ; !result && i < argc ; i++ ) { const char* arg = argv[i++]; if ( shorts.count(arg) ) arg = shorts[arg].c_str(); const char* value = argv[i ]; int ivalue = ::atoi(value?value:"0"); int key = ::find(arg,keywords,kwMAX); int needv = key < kwMAX && key > (-kwNOVALUE); if (!needv ) i--; if ( needv && !value) key = kwNEEDVALUE; switch ( key ) { case kwDST : options.dst = true ; break; case kwHELP : options.help = true ; break; case kwVERSION : options.version = true ; break; case kwDRYRUN : options.dryrun = true ; break; case kwVERBOSE : options.verbose = true ; break; case kwASCII : options.ascii = true ; break; case kwTZ : Position::tz_ = parseTZ(value);break; case kwADJUST : Position::adjust_ = ivalue;break; case kwDELTA : Position::deltaMax_ = ivalue;break; case kwNEEDVALUE: fprintf(stderr,"error: %s requires a value\n",arg); result = resultSyntaxError ; break ; case kwSYNTAX : default: { int type = getFileType(arg,options) ; if ( options.verbose ) printf("%s %s ",arg,types[type]) ; if ( type == typeImage ) { time_t t = readImageTime(std::string(arg)) ; #ifdef __APPLE__ char buffer[1024]; #else char* buffer = NULL; #endif char* path = realpath(arg,buffer); if ( t && path ) { if ( options.verbose) printf("%s %ld %s",path,(long int)t,asctime(localtime(&t))); gFiles.push_back(path); } if ( path && path != buffer ) ::free((void*) path); } if ( type == typeUnknown ) { fprintf(stderr,"error: illegal syntax %s\n",arg); result = resultSyntaxError ; } } break; } } if ( options.help ) ::help(program,keywords,kwMAX,options.verbose); if ( options.version ) ::version(program); gDeg = options.ascii ? "deg" : "°"; if ( !result ) { sort(gFiles.begin(),gFiles.end(),mySort); if ( options.dst ) Position::dst_ = 3600; if ( options.verbose ) { int t = Position::tz(); int d = Position::dst(); int a = Position::adjust(); int A = Position::Adjust(); int s = A ; int h = s/3600; s-= h*3600; s = abs(s); int m = s/60 ; s-= m*60 ; printf("tz,dst,adjust = %d,%d,%d total = %dsecs (= %d:%d:%d)\n",t,d,a,A,h,m,s); } /* if ( options.verbose ) { printf("Time Dictionary\n"); for ( TimeDict_i it = gTimeDict.begin() ; it != gTimeDict.end() ; it++ ) { std::string sTime = getExifTime(it->first); Position* pPos = &it->second; std::string sPos = Position::toExifString(pPos->lat(),false,true) + " " + Position::toExifString(pPos->lon(),false,true) ; printf("%s %s\n",sTime.c_str(), sPos.c_str()); } } */ for ( size_t p = 0 ; p < gFiles.size() ; p++ ) { std::string path = gFiles[p] ; std::string stamp ; try { time_t t = readImageTime(path,&stamp) ; Position* pPos = searchTimeDict(gTimeDict,t,Position::deltaMax_); Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path); if ( image.get() ) { image->readMetadata(); Exiv2::ExifData& exifData = image->exifData(); if ( pPos ) { exifData["Exif.GPSInfo.GPSProcessingMethod" ] = "charset=Ascii HYBRID-FIX"; exifData["Exif.GPSInfo.GPSVersionID" ] = "2 2 0 0"; exifData["Exif.GPSInfo.GPSMapDatum" ] = "WGS-84"; exifData["Exif.GPSInfo.GPSLatitude" ] = Position::toExifString(pPos->lat(),true,true); exifData["Exif.GPSInfo.GPSLongitude" ] = Position::toExifString(pPos->lon(),true,false); exifData["Exif.GPSInfo.GPSAltitude" ] = Position::toExifString(pPos->ele()); exifData["Exif.GPSInfo.GPSAltitudeRef" ] = pPos->ele()<0.0?"1":"0"; exifData["Exif.GPSInfo.GPSLatitudeRef" ] = pPos->lat()>0?"N":"S"; exifData["Exif.GPSInfo.GPSLongitudeRef" ] = pPos->lon()>0?"E":"W"; exifData["Exif.GPSInfo.GPSDateStamp" ] = stamp; exifData["Exif.GPSInfo.GPSTimeStamp" ] = Position::toExifTimeStamp(stamp); exifData["Exif.Image.GPSTag" ] = 4908; printf("%s %s % 2d\n",path.c_str(),pPos->toString().c_str(),pPos->delta()); } else { printf("%s *** not in time dict ***\n",path.c_str()); } if ( !options.dryrun ) image->writeMetadata(); } } catch ( ... ) {}; } } return result ; } // That's all Folks! ////