1
|
// ***************************************************************** -*- C++ -*-
|
2
|
// geotag.cpp, $Rev: 2286 $
|
3
|
// Sample program to read gpx files and update images with GPS tags
|
4
|
|
5
|
#include <exiv2/exiv2.hpp>
|
6
|
|
7
|
#include <iostream>
|
8
|
#include <iomanip>
|
9
|
#include <cassert>
|
10
|
#include <algorithm>
|
11
|
|
12
|
#include <stdio.h>
|
13
|
#include <stdlib.h>
|
14
|
#include <time.h>
|
15
|
#include <string.h>
|
16
|
#include <sys/stat.h>
|
17
|
#include <sys/types.h>
|
18
|
|
19
|
#include <expat.h>
|
20
|
|
21
|
#include <vector>
|
22
|
#include <string>
|
23
|
|
24
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
25
|
# ifndef __MINGW__
|
26
|
# define __MINGW__
|
27
|
# endif
|
28
|
#endif
|
29
|
|
30
|
using namespace std;
|
31
|
|
32
|
#ifndef lengthof
|
33
|
#define lengthof(x) (sizeof(*x)/sizeof(x))
|
34
|
#endif
|
35
|
#ifndef nil
|
36
|
#define nil NULL
|
37
|
#endif
|
38
|
|
39
|
#if defined(_MSC_VER) || defined(__MINGW__)
|
40
|
#include <windows.h>
|
41
|
char* realpath(const char* file,char* path);
|
42
|
#define lstat _stat
|
43
|
#define stat _stat
|
44
|
#if _MSC_VER < 1400
|
45
|
#define strcpy_s(d,l,s) strcpy(d,s)
|
46
|
#define strcat_s(d,l,s) strcat(d,s)
|
47
|
#endif
|
48
|
#endif
|
49
|
|
50
|
#if ! defined(_MSC_VER)
|
51
|
#include <dirent.h>
|
52
|
#include <unistd.h>
|
53
|
#include <sys/param.h>
|
54
|
#define stricmp strcasecmp
|
55
|
#endif
|
56
|
|
57
|
#ifndef _MAX_PATH
|
58
|
#define _MAX_PATH 1024
|
59
|
#endif
|
60
|
|
61
|
#define UNUSED(x) (void)(x)
|
62
|
|
63
|
// prototypes
|
64
|
class Options;
|
65
|
int getFileType(const char* path ,Options& options);
|
66
|
int getFileType(std::string& path,Options& options);
|
67
|
|
68
|
string getExifTime(const time_t t);
|
69
|
time_t parseTime(const char* ,bool bAdjust=false);
|
70
|
int timeZoneAdjust();
|
71
|
|
72
|
// platform specific code
|
73
|
#if defined(_MSC_VER) || defined(__MINGW__)
|
74
|
char* realpath(const char* file,char* path)
|
75
|
{
|
76
|
char* result = (char*) malloc(_MAX_PATH);
|
77
|
if (result) GetFullPathName(file,_MAX_PATH,result,NULL);
|
78
|
return result ;
|
79
|
UNUSED(path);
|
80
|
}
|
81
|
#endif
|
82
|
|
83
|
// Command-line parser
|
84
|
class Options {
|
85
|
public:
|
86
|
bool verbose;
|
87
|
bool help;
|
88
|
bool version;
|
89
|
bool dst;
|
90
|
bool dryrun;
|
91
|
|
92
|
Options()
|
93
|
{
|
94
|
verbose = false;
|
95
|
help = false;
|
96
|
version = false;
|
97
|
dst = false;
|
98
|
dryrun = false;
|
99
|
}
|
100
|
|
101
|
virtual ~Options() {} ;
|
102
|
} ;
|
103
|
|
104
|
enum
|
105
|
{ resultOK=0
|
106
|
, resultSyntaxError
|
107
|
, resultSelectFailed
|
108
|
};
|
109
|
|
110
|
enum // keyword indices
|
111
|
{ kwHELP = 0
|
112
|
, kwVERSION
|
113
|
, kwDST
|
114
|
, kwDRYRUN
|
115
|
, kwVERBOSE
|
116
|
, kwADJUST
|
117
|
, kwTZ
|
118
|
, kwDELTA
|
119
|
, kwMAX // manages keyword array
|
120
|
, kwNEEDVALUE // bogus keywords for error reporting
|
121
|
, kwSYNTAX // -- ditto --
|
122
|
, kwNOVALUE = -kwVERBOSE // keywords <= kwNOVALUE are flags (no value needed)
|
123
|
};
|
124
|
|
125
|
// file types supported
|
126
|
enum
|
127
|
{ typeUnknown = 0
|
128
|
, typeDirectory = 1
|
129
|
, typeImage = 2
|
130
|
, typeXML = 3
|
131
|
, typeFile = 4
|
132
|
, typeDoc = 5
|
133
|
, typeCode = 6
|
134
|
, typeMax = 7
|
135
|
};
|
136
|
|
137
|
// Position (from gpx file)
|
138
|
class Position
|
139
|
{
|
140
|
public:
|
141
|
Position(time_t time,double lat,double lon,double ele) : time_(time),lon_(lon),lat_(lat),ele_(ele) {};
|
142
|
Position() { time_=0 ; lon_=0.0 ; lat_=0.0 ; ele_=0.0 ; };
|
143
|
virtual ~Position() {} ;
|
144
|
// copy constructor
|
145
|
Position(const Position& o) : time_(o.time_),lon_(o.lon_),lat_(o.lat_),ele_(o.ele_) {};
|
146
|
|
147
|
// instance methods
|
148
|
bool good() { return time_ || lon_ || lat_ || ele_ ; }
|
149
|
std::string getTimeString() { if ( times_.empty() ) times_ = getExifTime(time_) ; return times_; }
|
150
|
time_t getTime() { return time_ ; }
|
151
|
std::string toString();
|
152
|
|
153
|
// getters/setters
|
154
|
double lat() {return lat_ ;}
|
155
|
double lon() {return lon_ ;}
|
156
|
double ele() {return ele_ ;}
|
157
|
int delta() {return delta_ ;}
|
158
|
void delta(int delta) {delta_=delta ;}
|
159
|
|
160
|
// data
|
161
|
private:
|
162
|
time_t time_;
|
163
|
double lon_ ;
|
164
|
double lat_ ;
|
165
|
double ele_ ;
|
166
|
std::string times_;
|
167
|
int delta_;
|
168
|
|
169
|
// public static data
|
170
|
public:
|
171
|
static int adjust_ ;
|
172
|
static int tz_ ;
|
173
|
static int dst_ ;
|
174
|
static time_t deltaMax_;
|
175
|
|
176
|
// public static member functions
|
177
|
public:
|
178
|
static int Adjust() {return Position::adjust_ + Position::tz_ + Position::dst_ ;}
|
179
|
static int tz() {return tz_ ;}
|
180
|
static int dst() {return dst_ ;}
|
181
|
static int adjust() {return adjust_;}
|
182
|
|
183
|
static std::string toExifString(double d,bool bRational,bool bLat);
|
184
|
static std::string toExifString(double d);
|
185
|
static std::string toExifTimeStamp(std::string& t);
|
186
|
};
|
187
|
|
188
|
std::string Position::toExifTimeStamp(std::string& t)
|
189
|
{
|
190
|
char result[200];
|
191
|
const char* arg = t.c_str();
|
192
|
int HH = 0 ;
|
193
|
int mm = 0 ;
|
194
|
int SS = 0 ;
|
195
|
if ( strstr(arg,":") || strstr(arg,"-") ) {
|
196
|
int YY,MM,DD ;
|
197
|
char a,b,c,d,e ;
|
198
|
sscanf(arg,"%d%c%d%c%d%c%d%c%d%c%d",&YY,&a,&MM,&b,&DD,&c,&HH,&d,&mm,&e,&SS);
|
199
|
}
|
200
|
sprintf(result,"%d/1 %d/1 %d/1",HH,mm,SS);
|
201
|
return std::string(result);
|
202
|
}
|
203
|
|
204
|
std::string Position::toExifString(double d)
|
205
|
{
|
206
|
char result[200];
|
207
|
d *= 100;
|
208
|
sprintf(result,"%d/100",abs((int)d));
|
209
|
return std::string(result);
|
210
|
}
|
211
|
|
212
|
std::string Position::toExifString(double d,bool bRational,bool bLat)
|
213
|
{
|
214
|
const char* NS = d>=0.0?"N":"S";
|
215
|
const char* EW = d>=0.0?"E":"W";
|
216
|
const char* NSEW = bLat ? NS: EW;
|
217
|
if ( d < 0 ) d = -d;
|
218
|
int deg = (int) d;
|
219
|
d -= deg;
|
220
|
d *= 60;
|
221
|
int min = (int) d ;
|
222
|
d -= min;
|
223
|
d *= 60;
|
224
|
int sec = (int)d;
|
225
|
char result[200];
|
226
|
sprintf(result,bRational ? "%d/1 %d/1 %d/1%s" : "%03d.%02d'%02d\"%s" ,deg,min,sec,bRational?"":NSEW);
|
227
|
return std::string(result);
|
228
|
}
|
229
|
|
230
|
std::string Position::toString()
|
231
|
{
|
232
|
char result[200];
|
233
|
std::string sLat = Position::toExifString(lat_,false,true );
|
234
|
std::string sLon = Position::toExifString(lon_,false,false);
|
235
|
sprintf(result,"%s %s %-8.3f",sLon.c_str(),sLat.c_str(),ele_);
|
236
|
return std::string(result);
|
237
|
}
|
238
|
|
239
|
int Position::adjust_ = 0;
|
240
|
int Position::tz_ = timeZoneAdjust();
|
241
|
int Position::dst_ = 0;
|
242
|
time_t Position::deltaMax_ = 60 ;
|
243
|
|
244
|
// globals
|
245
|
typedef std::map<time_t,Position> TimeDict_t;
|
246
|
typedef std::map<time_t,Position>::iterator TimeDict_i;
|
247
|
typedef std::vector<std::string> strings_t;
|
248
|
TimeDict_t gTimeDict ;
|
249
|
strings_t gFiles;
|
250
|
|
251
|
///////////////////////////////////////////////////////////
|
252
|
// UserData - used by XML Parser
|
253
|
class UserData
|
254
|
{
|
255
|
public:
|
256
|
UserData(Options& options) : indent(0),count(0),nTrkpt(0),bTime(false),bEle(false),options_(options) {};
|
257
|
virtual ~UserData() {} ;
|
258
|
|
259
|
// public data members
|
260
|
int indent;
|
261
|
size_t count ;
|
262
|
Position now ;
|
263
|
Position prev;
|
264
|
int nTrkpt;
|
265
|
bool bTime ;
|
266
|
bool bEle ;
|
267
|
double ele;
|
268
|
double lat;
|
269
|
double lon;
|
270
|
std::string xmlt;
|
271
|
std::string exift;
|
272
|
time_t time;
|
273
|
Options& options_;
|
274
|
// static public data memembers
|
275
|
};
|
276
|
|
277
|
// XML Parser Callbacks
|
278
|
static void startElement(void* userData, const char* name, const char** atts )
|
279
|
{
|
280
|
UserData* me = (UserData*) userData;
|
281
|
//for ( int i = 0 ; i < me->indent ; i++ ) printf(" ");
|
282
|
//printf("begin %s\n",name);
|
283
|
me->bTime = strcmp(name,"time")==0;
|
284
|
me->bEle = strcmp(name,"ele")==0;
|
285
|
|
286
|
if ( strcmp(name,"trkpt")==0 ) {
|
287
|
me->nTrkpt++;
|
288
|
while ( *atts ) {
|
289
|
const char* a=atts[0];
|
290
|
const char* v=atts[1];
|
291
|
if ( !strcmp(a,"lat") ) me->lat = atof(v);
|
292
|
if ( !strcmp(a,"lon") ) me->lon = atof(v);
|
293
|
atts += 2 ;
|
294
|
}
|
295
|
}
|
296
|
me->count++ ;
|
297
|
me->indent++ ;
|
298
|
}
|
299
|
|
300
|
static void endElement(void* userData, const char* name)
|
301
|
{
|
302
|
UserData* me = (UserData*) userData;
|
303
|
me->indent-- ;
|
304
|
if ( strcmp(name,"trkpt")==0 ) {
|
305
|
|
306
|
me->nTrkpt--;
|
307
|
me->now = Position(me->time,me->lat,me->lon,me->ele) ;
|
308
|
|
309
|
if ( !me->prev.good() && me->options_.verbose ) {
|
310
|
printf("trkseg %s begin ",me->now.getTimeString().c_str());
|
311
|
}
|
312
|
|
313
|
// printf("lat,lon = %f,%f ele = %f xml = %s exif = %s\n",me->lat,me->lon,me->ele,me->xmlt.c_str(),me->exift.c_str());
|
314
|
|
315
|
// if we have a good previous position
|
316
|
// add missed entries to timedict
|
317
|
//if ( me->prev.good() && (me->now.getTime() - me->prev.getTime()) < Position::timeDiffMax ) {
|
318
|
// time_t missed = me->prev.getTime() ;
|
319
|
// while ( ++missed < me->now.getTime() )
|
320
|
// gTimeDict[missed] = me->prev ; // Position(missed,me->lat,me->lon,me->ele) ;
|
321
|
//}
|
322
|
|
323
|
// remember our location and put it in gTimeDict
|
324
|
gTimeDict[me->time] = me->now ;
|
325
|
me->prev = me->now ;
|
326
|
}
|
327
|
if ( strcmp(name,"trkseg")==0 && me->options_.verbose ) {
|
328
|
printf("%s end\n",me->now.getTimeString().c_str());
|
329
|
}
|
330
|
}
|
331
|
|
332
|
void charHandler(void* userData,const char* s,int len)
|
333
|
{
|
334
|
UserData* me = (UserData*) userData;
|
335
|
|
336
|
if ( me->nTrkpt == 1 ) {
|
337
|
char buffer[100];
|
338
|
int l_max = 98 ; // lengthof(buffer) -2 ;
|
339
|
|
340
|
if ( me->bTime && len > 5 ) {
|
341
|
if ( len < l_max ) {
|
342
|
memcpy(buffer,s,len);
|
343
|
buffer[len]=0;
|
344
|
char* b = buffer ;
|
345
|
while ( *b == ' ' && b < buffer+len ) b++ ;
|
346
|
me->xmlt = b ;
|
347
|
me->time = parseTime(me->xmlt.c_str());
|
348
|
me->exift = getExifTime(me->time);
|
349
|
}
|
350
|
me->bTime=false;
|
351
|
}
|
352
|
if ( me->bEle && len > 2 ) {
|
353
|
if ( len < l_max ) {
|
354
|
memcpy(buffer,s,len);
|
355
|
buffer[len]=0;
|
356
|
char* b = buffer ;
|
357
|
while ( *b == ' ' && b < buffer+len ) b++ ;
|
358
|
me->ele = atof(b);
|
359
|
}
|
360
|
me->bEle=false;
|
361
|
}
|
362
|
}
|
363
|
}
|
364
|
|
365
|
///////////////////////////////////////////////////////////
|
366
|
// Time Functions
|
367
|
time_t parseTime(const char* arg,bool bAdjust)
|
368
|
{
|
369
|
time_t result = 0 ;
|
370
|
try {
|
371
|
//559 rmills@rmills-imac:~/bin $ exiv2 -pa ~/R.jpg | grep -i date
|
372
|
//Exif.Image.DateTime Ascii 20 2009:08:03 08:58:57
|
373
|
//Exif.Photo.DateTimeOriginal Ascii 20 2009:08:03 08:58:57
|
374
|
//Exif.Photo.DateTimeDigitized Ascii 20 2009:08:03 08:58:57
|
375
|
//Exif.GPSInfo.GPSDateStamp Ascii 21 2009-08-03T15:58:57Z
|
376
|
|
377
|
// <time>2012-07-14T17:33:16Z</time>
|
378
|
|
379
|
if ( strstr(arg,":") || strstr(arg,"-") ) {
|
380
|
int YY,MM,DD,HH,mm,SS ;
|
381
|
char a,b,c,d,e ;
|
382
|
sscanf(arg,"%d%c%d%c%d%c%d%c%d%c%d",&YY,&a,&MM,&b,&DD,&c,&HH,&d,&mm,&e,&SS);
|
383
|
|
384
|
struct tm T;
|
385
|
#if 0
|
386
|
int tm_sec; /* seconds (0 - 60) */
|
387
|
int tm_min; /* minutes (0 - 59) */
|
388
|
int tm_hour; /* hours (0 - 23) */
|
389
|
int tm_mday; /* day of month (1 - 31) */
|
390
|
int tm_mon; /* month of year (0 - 11) */
|
391
|
int tm_year; /* year - 1900 */
|
392
|
int tm_wday; /* day of week (Sunday = 0) */
|
393
|
int tm_yday; /* day of year (0 - 365) */
|
394
|
int tm_isdst; /* is summer time in effect? */
|
395
|
char *tm_zone; /* abbreviation of timezone name */
|
396
|
long tm_gmtoff; /* offset from UTC in seconds */
|
397
|
#endif
|
398
|
memset(&T,0,sizeof(T));
|
399
|
T.tm_min = mm ;
|
400
|
T.tm_hour = HH ;
|
401
|
T.tm_sec = SS ;
|
402
|
if ( bAdjust ) T.tm_sec -= Position::Adjust();
|
403
|
T.tm_year = YY -1900 ;
|
404
|
T.tm_mon = MM -1 ;
|
405
|
T.tm_mday = DD ;
|
406
|
T.tm_isdst = -1 ; // determine value automatically (otherwise hour may shift)
|
407
|
result = mktime(&T);
|
408
|
}
|
409
|
} catch ( ... ) {};
|
410
|
return result ;
|
411
|
}
|
412
|
|
413
|
// West of GMT is negative (PDT = Pacific Daylight = -07:00 == -25200 seconds
|
414
|
int timeZoneAdjust()
|
415
|
{
|
416
|
time_t now = time(NULL);
|
417
|
int offset;
|
418
|
|
419
|
#if defined(_MSC_VER) || defined(__MINGW__)
|
420
|
TIME_ZONE_INFORMATION TimeZoneInfo;
|
421
|
GetTimeZoneInformation( &TimeZoneInfo );
|
422
|
offset = - (((int)TimeZoneInfo.Bias + (int)TimeZoneInfo.DaylightBias) * 60);
|
423
|
UNUSED(now);
|
424
|
#elif defined(__CYGWIN__)
|
425
|
struct tm lcopy = *localtime(&now);
|
426
|
time_t gmt = timegm(&lcopy) ; // timegm modifies lcopy
|
427
|
offset = (int) ( ((long signed int) gmt) - ((long signed int) now) ) ;
|
428
|
#elif defined(OS_SOLARIS)
|
429
|
struct tm local = *localtime(&now) ;
|
430
|
time_t local_tt = (int) mktime(&local);
|
431
|
time_t time_gmt = (int) mktime(gmtime(&now));
|
432
|
offset = time_gmt - local_tt;
|
433
|
#else
|
434
|
struct tm local = *localtime(&now) ;
|
435
|
offset = local.tm_gmtoff ;
|
436
|
#endif
|
437
|
|
438
|
#if 0
|
439
|
// debugging code
|
440
|
struct tm utc = *gmtime(&now);
|
441
|
printf("utc : offset = %6d dst = %d time = %s", 0 ,utc .tm_isdst, asctime(&utc ));
|
442
|
printf("local: offset = %6d dst = %d time = %s", offset,local.tm_isdst, asctime(&local));
|
443
|
printf("timeZoneAdjust = %6d\n",offset);
|
444
|
#endif
|
445
|
return offset ;
|
446
|
}
|
447
|
|
448
|
string getExifTime(const time_t t)
|
449
|
{
|
450
|
static char result[100];
|
451
|
strftime(result,sizeof(result),"%Y-%m-%d %H:%M:%S",localtime(&t));
|
452
|
return result ;
|
453
|
}
|
454
|
|
455
|
std::string makePath(std::string dir,std::string file)
|
456
|
{
|
457
|
return dir + std::string(EXV_SEPARATOR_STR) + file ;
|
458
|
}
|
459
|
|
460
|
const char* makePath(const char* dir,const char* file)
|
461
|
{
|
462
|
static char result[_MAX_PATH] ;
|
463
|
std::string r = makePath(std::string(dir),std::string(file));
|
464
|
strcpy(result,r.c_str());
|
465
|
return result;
|
466
|
}
|
467
|
|
468
|
// file utilities
|
469
|
bool readDir(const char* path,Options& options)
|
470
|
{
|
471
|
bool bResult = false;
|
472
|
|
473
|
#ifdef _MSC_VER
|
474
|
DWORD attrs = GetFileAttributes(path);
|
475
|
bool bOKAttrs = attrs != INVALID_FILE_ATTRIBUTES;
|
476
|
bool bIsDir = (attrs & FILE_ATTRIBUTE_DIRECTORY) ? true : false ;
|
477
|
|
478
|
if( bOKAttrs && bIsDir ) {
|
479
|
bResult = true ;
|
480
|
|
481
|
char search[_MAX_PATH+10];
|
482
|
strcpy_s(search,_MAX_PATH,path);
|
483
|
strcat_s(search,_MAX_PATH,"\\*");
|
484
|
|
485
|
WIN32_FIND_DATA ffd;
|
486
|
HANDLE hFind = FindFirstFile(search, &ffd);
|
487
|
BOOL bGo = hFind != INVALID_HANDLE_VALUE;
|
488
|
|
489
|
if ( bGo ) {
|
490
|
while ( bGo ) {
|
491
|
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
492
|
{
|
493
|
// _tprintf(TEXT(" %s <DIR>\n"), ffd.cFileName);
|
494
|
}
|
495
|
else
|
496
|
{
|
497
|
std::string pathName = makePath(path,std::string(ffd.cFileName));
|
498
|
if ( getFileType(pathName,options) == typeImage ) {
|
499
|
gFiles.push_back( pathName );
|
500
|
}
|
501
|
}
|
502
|
bGo = FindNextFile(hFind, &ffd) != 0;
|
503
|
}
|
504
|
// CloseHandle(hFind);
|
505
|
}
|
506
|
}
|
507
|
#else
|
508
|
DIR* dir = opendir (path);
|
509
|
if (dir != NULL)
|
510
|
{
|
511
|
bResult = true;
|
512
|
struct dirent* ent;
|
513
|
|
514
|
// print all the files and directories within directory
|
515
|
while ((ent = readdir (dir)) != NULL)
|
516
|
{
|
517
|
std::string pathName = makePath(path,ent->d_name);
|
518
|
struct stat buf ;
|
519
|
lstat(path, &buf );
|
520
|
if ( ent->d_name[0] != '.' ) {
|
521
|
|
522
|
// printf("reading %s => %s\n",ent->d_name,pathName.c_str());
|
523
|
if ( getFileType(pathName,options) == typeImage ) {
|
524
|
gFiles.push_back( pathName );
|
525
|
}
|
526
|
}
|
527
|
}
|
528
|
closedir (dir);
|
529
|
}
|
530
|
#endif
|
531
|
return bResult ;
|
532
|
}
|
533
|
|
534
|
inline size_t sip(FILE* f,char* buffer,size_t max_len,size_t len)
|
535
|
{
|
536
|
while ( !feof(f) && len < max_len && buffer[len-1] != '>')
|
537
|
buffer[len++] = fgetc(f);
|
538
|
return len;
|
539
|
}
|
540
|
|
541
|
bool readXML(const char* path,Options& options)
|
542
|
{
|
543
|
FILE* f = fopen(path,"r");
|
544
|
XML_Parser parser = XML_ParserCreate(NULL);
|
545
|
bool bResult = f && parser ;
|
546
|
if ( bResult ) {
|
547
|
char buffer[8*1024];
|
548
|
UserData me(options) ;
|
549
|
|
550
|
XML_SetUserData (parser, &me);
|
551
|
XML_SetElementHandler (parser, startElement, endElement);
|
552
|
XML_SetCharacterDataHandler(parser,charHandler);
|
553
|
|
554
|
// a little sip at the data
|
555
|
size_t len = fread(buffer,1,sizeof(buffer)-100,f);
|
556
|
const char* lead = "<?xml" ;
|
557
|
bResult = strncmp(lead,buffer,strlen(lead))==0;
|
558
|
|
559
|
// swallow it
|
560
|
if ( bResult ) {
|
561
|
len = sip(f,buffer,sizeof buffer,len);
|
562
|
bResult = XML_Parse(parser, buffer,(int)len, len == 0 ) == XML_STATUS_OK;
|
563
|
}
|
564
|
|
565
|
// drink the rest of the file
|
566
|
while ( bResult && len != 0 ) {
|
567
|
len = fread(buffer,1,sizeof(buffer)-100,f);
|
568
|
len = sip(f,buffer,sizeof buffer,len);
|
569
|
bResult = XML_Parse(parser, buffer,(int)len, len == 0 ) == XML_STATUS_OK;
|
570
|
};
|
571
|
}
|
572
|
|
573
|
if ( f ) fclose(f);
|
574
|
if ( parser ) XML_ParserFree(parser);
|
575
|
|
576
|
return bResult ;
|
577
|
}
|
578
|
|
579
|
bool readImage(const char* path,Options& /* options */)
|
580
|
{
|
581
|
using namespace Exiv2;
|
582
|
bool bResult = false ;
|
583
|
|
584
|
try {
|
585
|
Image::AutoPtr image = ImageFactory::open(path);
|
586
|
if ( image.get() ) {
|
587
|
image->readMetadata();
|
588
|
ExifData &exifData = image->exifData();
|
589
|
bResult = !exifData.empty();
|
590
|
}
|
591
|
} catch ( ... ) {};
|
592
|
return bResult ;
|
593
|
}
|
594
|
|
595
|
time_t readImageTime(std::string path,std::string* pS=NULL)
|
596
|
{
|
597
|
using namespace Exiv2;
|
598
|
|
599
|
time_t result = 0 ;
|
600
|
static std::map<std::string,time_t> cache;
|
601
|
if ( cache.count(path) == 1 ) return cache[path];
|
602
|
|
603
|
const char* dateStrings[] =
|
604
|
{ "Exif.Photo.DateTimeOriginal"
|
605
|
, "Exif.Photo.DateTimeDigitized"
|
606
|
, "Exif.Image.DateTime"
|
607
|
, NULL
|
608
|
};
|
609
|
const char* ds = dateStrings[0] ;
|
610
|
|
611
|
while ( !result && ds++ ) {
|
612
|
try {
|
613
|
Image::AutoPtr image = ImageFactory::open(path);
|
614
|
if ( image.get() ) {
|
615
|
image->readMetadata();
|
616
|
ExifData &exifData = image->exifData();
|
617
|
// printf("%s => %s\n",(ds-1), exifData[ds-1].toString().c_str());
|
618
|
result = parseTime(exifData[ds-1].toString().c_str(),true);
|
619
|
if ( result && pS ) *pS = exifData[ds-1].toString();
|
620
|
}
|
621
|
} catch ( ... ) {};
|
622
|
}
|
623
|
if ( result ) cache[path] = result;
|
624
|
return result ;
|
625
|
}
|
626
|
|
627
|
bool sina(const char* s,const char** a)
|
628
|
{
|
629
|
bool bResult = false ;
|
630
|
int i = 0 ;
|
631
|
while ( *s == '-' ) s++;
|
632
|
while ( !bResult && a[i]) {
|
633
|
const char* A = a[i] ;
|
634
|
while ( *A == '-' ) A++ ;
|
635
|
bResult = stricmp(s,A)==0;
|
636
|
i++;
|
637
|
}
|
638
|
return bResult;
|
639
|
}
|
640
|
|
641
|
int readFile(const char* path,Options /* options */)
|
642
|
{
|
643
|
FILE* f = fopen(path,"r");
|
644
|
int nResult = f ? typeFile : typeUnknown;
|
645
|
if ( f ) {
|
646
|
const char* docs[] = { ".doc",".txt", nil };
|
647
|
const char* code[] = { ".cpp",".h" ,".pl" ,".py" ,".pyc", nil };
|
648
|
const char* ext = strstr(path,".");
|
649
|
if ( ext ) {
|
650
|
if ( sina(ext,docs) ) nResult = typeDoc;
|
651
|
if ( sina(ext,code) ) nResult = typeCode;
|
652
|
}
|
653
|
}
|
654
|
if ( f ) fclose(f) ;
|
655
|
|
656
|
return nResult ;
|
657
|
}
|
658
|
|
659
|
Position* searchTimeDict(TimeDict_t& td, const time_t& time,long long delta)
|
660
|
{
|
661
|
Position* result = NULL;
|
662
|
for ( int t = 0 ; !result && t < delta ; t++ ) {
|
663
|
for ( int x = 0 ; !result && x < 2 ; x++ ) {
|
664
|
int T = t * ((x==0)?-1:1);
|
665
|
if ( td.count(time+T) ) {
|
666
|
result = &td[time+T];
|
667
|
result->delta(T);
|
668
|
}
|
669
|
}
|
670
|
}
|
671
|
return result;
|
672
|
}
|
673
|
|
674
|
int getFileType(std::string& path,Options& options) { return getFileType(path.c_str(),options); }
|
675
|
int getFileType(const char* path,Options& options)
|
676
|
{
|
677
|
return readXML (path,options) ? typeXML
|
678
|
: readDir (path,options) ? typeDirectory
|
679
|
: readImage(path,options) ? typeImage
|
680
|
: readFile (path,options)
|
681
|
;
|
682
|
}
|
683
|
|
684
|
int version(const char* program)
|
685
|
{
|
686
|
printf("%s: %s %s\n",program,__DATE__,__TIME__);
|
687
|
return 0;
|
688
|
}
|
689
|
|
690
|
int help(const char* program,char const* words[],int nWords,bool /*bVerbose*/)
|
691
|
{
|
692
|
printf("usage: %s ",program);
|
693
|
for ( int i = 0 ; i < nWords ; i++ ) {
|
694
|
if ( words[i] )
|
695
|
printf("%c-%s%s",i?'|':'{',words[i],i>(-kwNOVALUE)?" value":"");
|
696
|
}
|
697
|
printf("} path+\n");
|
698
|
return 0;
|
699
|
}
|
700
|
|
701
|
int compare(const char* a,const char* b)
|
702
|
{
|
703
|
int result=*a && *b;
|
704
|
while ( result && *a && *b) {
|
705
|
char A=*a++;
|
706
|
char B=*b++;
|
707
|
result=tolower(A)==tolower(B);
|
708
|
}
|
709
|
return result;
|
710
|
}
|
711
|
|
712
|
int find(const char* arg,char const* words[],int nWords)
|
713
|
{
|
714
|
if ( arg[0] != '-' ) return kwSYNTAX;
|
715
|
|
716
|
int result=0;
|
717
|
int count =0;
|
718
|
|
719
|
for ( int i = 0 ; i < nWords ; i++) {
|
720
|
int j = 0 ;
|
721
|
while ( arg[j] == '-' ) j++;
|
722
|
if ( ::compare(arg+j,words[i]) ) {
|
723
|
result = i ;
|
724
|
count++;
|
725
|
}
|
726
|
}
|
727
|
|
728
|
return count==1?result:kwSYNTAX;
|
729
|
}
|
730
|
|
731
|
int parseTZ(const char* adjust)
|
732
|
{
|
733
|
int h=0;
|
734
|
int m=0;
|
735
|
char c ;
|
736
|
try {
|
737
|
sscanf(adjust,"%d%c%d",&h,&c,&m);
|
738
|
} catch ( ... ) {} ;
|
739
|
|
740
|
return (3600*h)+(60*m);
|
741
|
}
|
742
|
|
743
|
bool mySort(std::string a,std::string b)
|
744
|
{
|
745
|
time_t A = readImageTime(a);
|
746
|
time_t B = readImageTime(b);
|
747
|
return (A<B);
|
748
|
}
|
749
|
|
750
|
int main(int argc,const char* argv[])
|
751
|
{
|
752
|
int result=0;
|
753
|
const char* program = argv[0];
|
754
|
|
755
|
const char* types[typeMax];
|
756
|
types[typeUnknown ] = "unknown";
|
757
|
types[typeDirectory] = "directory";
|
758
|
types[typeImage ] = "image";
|
759
|
types[typeXML ] = "xml";
|
760
|
types[typeDoc ] = "doc";
|
761
|
types[typeCode ] = "code";
|
762
|
types[typeFile ] = "file";
|
763
|
|
764
|
char const* keywords[kwMAX];
|
765
|
memset(keywords,0,sizeof(keywords));
|
766
|
keywords[kwHELP ] = "help";
|
767
|
keywords[kwVERSION ] = "version";
|
768
|
keywords[kwVERBOSE ] = "verbose";
|
769
|
keywords[kwDRYRUN ] = "dryrun";
|
770
|
keywords[kwDST ] = "dst";
|
771
|
keywords[kwADJUST ] = "adjust";
|
772
|
keywords[kwTZ ] = "tz";
|
773
|
keywords[kwDELTA ] = "delta";
|
774
|
|
775
|
map<std::string,string> shorts;
|
776
|
shorts["-?"] = "-help";
|
777
|
shorts["-h"] = "-help";
|
778
|
shorts["-v"] = "-verbose";
|
779
|
shorts["-V"] = "-version";
|
780
|
shorts["-d"] = "-dst";
|
781
|
shorts["-a"] = "-adjust";
|
782
|
shorts["-t"] = "-tz";
|
783
|
shorts["-D"] = "-delta";
|
784
|
shorts["-s"] = "-delta";
|
785
|
shorts["-X"] = "-dryrun";
|
786
|
|
787
|
Options options ;
|
788
|
options.help = sina(keywords[kwHELP ],argv) || argc < 2;
|
789
|
options.verbose = sina(keywords[kwVERBOSE],argv);
|
790
|
options.dryrun = sina(keywords[kwDRYRUN ],argv);
|
791
|
options.version = sina(keywords[kwVERSION],argv);
|
792
|
options.dst = sina(keywords[kwDST ],argv);
|
793
|
options.dryrun = sina(keywords[kwDRYRUN ],argv);
|
794
|
|
795
|
for ( int i = 1 ; !result && i < argc ; i++ ) {
|
796
|
const char* arg = argv[i++];
|
797
|
if ( shorts.count(arg) ) arg = shorts[arg].c_str();
|
798
|
|
799
|
const char* value = argv[i ];
|
800
|
int ivalue = ::atoi(value?value:"0");
|
801
|
int key = ::find(arg,keywords,kwMAX);
|
802
|
int needv = key < kwMAX && key > (-kwNOVALUE);
|
803
|
|
804
|
if (!needv ) i--;
|
805
|
if ( needv && !value) key = kwNEEDVALUE;
|
806
|
|
807
|
switch ( key ) {
|
808
|
case kwDST : options.dst = true ; break;
|
809
|
case kwHELP : options.help = true ; break;
|
810
|
case kwVERSION : options.version = true ; break;
|
811
|
case kwDRYRUN : options.dryrun = true ; break;
|
812
|
case kwVERBOSE : options.verbose = true ; break;
|
813
|
case kwTZ : Position::tz_ = parseTZ(value);break;
|
814
|
case kwADJUST : Position::adjust_ = ivalue;break;
|
815
|
case kwDELTA : Position::deltaMax_= ivalue;break;
|
816
|
case kwNEEDVALUE: fprintf(stderr,"error: %s requires a value\n",arg); result = resultSyntaxError ; break ;
|
817
|
case kwSYNTAX : default:
|
818
|
{
|
819
|
int type = getFileType(arg,options) ;
|
820
|
if ( options.verbose ) printf("%s %s ",arg,types[type]) ;
|
821
|
if ( type == typeImage ) {
|
822
|
time_t t = readImageTime(std::string(arg)) ;
|
823
|
char* path = realpath(arg,NULL);
|
824
|
if ( t && path ) {
|
825
|
if ( options.verbose) printf("%s %ld %s",path,(long int)t,asctime(localtime(&t)));
|
826
|
gFiles.push_back(path);
|
827
|
}
|
828
|
if ( path ) :: free((void*) path);
|
829
|
}
|
830
|
if ( type == typeUnknown ) {
|
831
|
fprintf(stderr,"error: illegal syntax %s\n",arg);
|
832
|
result = resultSyntaxError ;
|
833
|
}
|
834
|
if ( options.verbose ) printf("\n") ;
|
835
|
}break;
|
836
|
}
|
837
|
}
|
838
|
|
839
|
if ( options.help ) ::help(program,keywords,kwMAX,options.verbose);
|
840
|
if ( options.version ) ::version(program);
|
841
|
|
842
|
if ( !result ) {
|
843
|
sort(gFiles.begin(),gFiles.end(),mySort);
|
844
|
if ( options.dst ) Position::dst_ = 3600;
|
845
|
if ( options.verbose ) {
|
846
|
int t = Position::tz();
|
847
|
int d = Position::dst();
|
848
|
int a = Position::adjust();
|
849
|
int A = Position::Adjust();
|
850
|
int s = A ;
|
851
|
int h = s/3600;
|
852
|
s-= h*3600;
|
853
|
s = abs(s);
|
854
|
int m = s/60 ;
|
855
|
s-= m*60 ;
|
856
|
printf("tz,dst,adjust = %d,%d,%d total = %dsecs (= %d:%d:%d)\n",t,d,a,A,h,m,s);
|
857
|
}
|
858
|
for ( size_t p = 0 ; !options.dryrun && p < gFiles.size() ; p++ ) {
|
859
|
std::string arg = gFiles[p] ;
|
860
|
std::string stamp ;
|
861
|
try {
|
862
|
time_t t = readImageTime(arg,&stamp) ;
|
863
|
Position* pPos = searchTimeDict(gTimeDict,t,Position::deltaMax_);
|
864
|
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(gFiles[p]);
|
865
|
if ( image.get() ) {
|
866
|
image->readMetadata();
|
867
|
Exiv2::ExifData& exifData = image->exifData();
|
868
|
#if 0
|
869
|
/*
|
870
|
char* keys[]={ "Exif.Image.GPSTag"
|
871
|
, "Exif.GPSInfo.GPSProcessingMethod"
|
872
|
, "Exif.GPSInfo.GPSAltitudeRef"
|
873
|
, "Exif.GPSInfo.GPSVersionID"
|
874
|
, "Exif.GPSInfo.GPSProcessingMethod"
|
875
|
, "Exif.GPSInfo.GPSVersionID"
|
876
|
, "Exif.GPSInfo.GPSMapDatum"
|
877
|
, "Exif.GPSInfo.GPSLatitude"
|
878
|
, "Exif.GPSInfo.GPSLongitude"
|
879
|
, "Exif.GPSInfo.GPSAltitude"
|
880
|
, "Exif.GPSInfo.GPSAltitudeRef"
|
881
|
, "Exif.GPSInfo.GPSLatitudeRef"
|
882
|
, "Exif.GPSInfo.GPSLongitudeRef"
|
883
|
, "Exif.GPSInfo.GPSDateStamp"
|
884
|
, "Exif.GPSInfo.GPSTimeStamp"
|
885
|
};
|
886
|
static int bPrint = true ;
|
887
|
for ( int k = 0 ; k < 15 ; k++ ) {
|
888
|
try {
|
889
|
if ( bPrint ) printf("erasing %s\n",keys[k]);
|
890
|
Exiv2::ExifKey key = Exiv2::ExifKey(keys[k]);
|
891
|
Exiv2::ExifData::iterator kk = exifData.findKey(key);
|
892
|
if ( kk != exifData.end() ) exifData.erase(kk);
|
893
|
} catch (...) {};
|
894
|
}
|
895
|
bPrint = false;
|
896
|
*/
|
897
|
#endif
|
898
|
#if 0
|
899
|
Exiv2::ExifData::const_iterator end = exifData.end();
|
900
|
for (Exiv2::ExifData::iterator i = exifData.begin(); i != end; ++i) {
|
901
|
char name[100];
|
902
|
strcpy(name,i->key().c_str());
|
903
|
// std::cout << "sniff " << i->key() << std::endl;
|
904
|
if ( strstr(name,"GPS") ) {
|
905
|
Exiv2::ExifData::iterator pos;
|
906
|
Exiv2::ExifKey exifKey = Exiv2::ExifKey(name);
|
907
|
pos = exifData.findKey(exifKey);
|
908
|
while( pos != exifData.end()) {
|
909
|
exifData.erase(pos);
|
910
|
}
|
911
|
}
|
912
|
}
|
913
|
#endif
|
914
|
if ( pPos ) {
|
915
|
/*
|
916
|
struct _stat buf;
|
917
|
int result;
|
918
|
char timebuf[26];
|
919
|
char* filename = "crt_stat.c";
|
920
|
errno_t err;
|
921
|
|
922
|
// Get data associated with "crt_stat.c":
|
923
|
result = _stat( filename, &buf );
|
924
|
|
925
|
int _utime(
|
926
|
const char *filename,
|
927
|
struct _utimbuf *times
|
928
|
);
|
929
|
*/
|
930
|
|
931
|
exifData["Exif.GPSInfo.GPSProcessingMethod" ] = "65 83 67 73 73 0 0 0 72 89 66 82 73 68 45 70 73 88"; // ASCII HYBRID-FIX
|
932
|
exifData["Exif.GPSInfo.GPSVersionID" ] = "2 2 0 0";
|
933
|
exifData["Exif.GPSInfo.GPSMapDatum" ] = "WGS-84";
|
934
|
|
935
|
exifData["Exif.GPSInfo.GPSLatitude" ] = Position::toExifString(pPos->lat(),true,true);
|
936
|
exifData["Exif.GPSInfo.GPSLongitude" ] = Position::toExifString(pPos->lon(),true,false);
|
937
|
exifData["Exif.GPSInfo.GPSAltitude" ] = Position::toExifString(pPos->ele());
|
938
|
|
939
|
exifData["Exif.GPSInfo.GPSAltitudeRef" ] = pPos->ele()<0.0?"1":"0";
|
940
|
exifData["Exif.GPSInfo.GPSLatitudeRef" ] = pPos->lat()>0?"N":"S";
|
941
|
exifData["Exif.GPSInfo.GPSLongitudeRef" ] = pPos->lon()>0?"E":"W";
|
942
|
|
943
|
exifData["Exif.GPSInfo.GPSDateStamp" ] = stamp;
|
944
|
exifData["Exif.GPSInfo.GPSTimeStamp" ] = Position::toExifTimeStamp(stamp);
|
945
|
exifData["Exif.Image.GPSTag" ] = 4908;
|
946
|
|
947
|
printf("%s %s % 2d\n",arg.c_str(),pPos->toString().c_str(),pPos->delta());
|
948
|
} else {
|
949
|
printf("%s *** not in time dict ***\n",arg.c_str());
|
950
|
}
|
951
|
image->writeMetadata();
|
952
|
}
|
953
|
} catch ( ... ) {};
|
954
|
}
|
955
|
}
|
956
|
|
957
|
return result ;
|
958
|
}
|
959
|
|
960
|
// That's all Folks!
|
961
|
////
|