|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
// Sample program to read gpx files and update images with GPS tags
|
|
|
|
|
|
|
|
#include <expat.h>
|
|
|
|
#include <exiv2/exiv2.hpp>
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <filesystem>
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
#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 <windows.h>
|
|
|
|
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 <dirent.h>
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#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(time_t t);
|
|
|
|
time_t parseTime(const char*, bool bAdjust = false);
|
|
|
|
int timeZoneAdjust();
|
|
|
|
|
|
|
|
// Command-line parser
|
|
|
|
class Options {
|
|
|
|
public:
|
|
|
|
bool verbose{false};
|
|
|
|
bool help{false};
|
|
|
|
bool version{false};
|
|
|
|
bool dst{false};
|
|
|
|
bool dryrun{false};
|
|
|
|
bool ascii{false};
|
|
|
|
|
|
|
|
Options() = default;
|
|
|
|
virtual ~Options() = default;
|
|
|
|
};
|
|
|
|
|
|
|
|
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
|
|
|
|
using TimeDict_t = std::map<time_t, Position>;
|
|
|
|
using TimeDict_i = std::map<time_t, Position>::iterator;
|
|
|
|
using strings_t = std::vector<std::string>;
|
|
|
|
const char* gDeg = nullptr; // 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) {
|
|
|
|
}
|
|
|
|
|
|
|
|
Position() = default;
|
|
|
|
virtual ~Position() = default;
|
|
|
|
|
|
|
|
// instance methods
|
|
|
|
bool good() const {
|
|
|
|
return time_ || lon_ || lat_ || ele_;
|
|
|
|
}
|
|
|
|
std::string getTimeString() {
|
|
|
|
if (times_.empty())
|
|
|
|
times_ = getExifTime(time_);
|
|
|
|
return times_;
|
|
|
|
}
|
|
|
|
time_t getTime() const {
|
|
|
|
return time_;
|
|
|
|
}
|
|
|
|
std::string toString() const;
|
|
|
|
|
|
|
|
// getters/setters
|
|
|
|
double lat() const {
|
|
|
|
return lat_;
|
|
|
|
}
|
|
|
|
double lon() const {
|
|
|
|
return lon_;
|
|
|
|
}
|
|
|
|
double ele() const {
|
|
|
|
return ele_;
|
|
|
|
}
|
|
|
|
int delta() const {
|
|
|
|
return delta_;
|
|
|
|
}
|
|
|
|
void delta(int delta) {
|
|
|
|
delta_ = delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
// data
|
|
|
|
private:
|
|
|
|
time_t time_{0};
|
|
|
|
double lon_{0.0};
|
|
|
|
double lat_{0.0};
|
|
|
|
double ele_{0.0};
|
|
|
|
std::string times_;
|
|
|
|
int delta_{0};
|
|
|
|
|
|
|
|
// public static data
|
|
|
|
public:
|
|
|
|
static int adjust_;
|
|
|
|
static int tz_;
|
|
|
|
static int dst_;
|
|
|
|
static time_t deltaMax_;
|
|
|
|
|
|
|
|
// public static member functions
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
snprintf(result, sizeof(result), "%d/1 %d/1 %d/1", HH, mm, SS1);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Position::toExifString(double d) {
|
|
|
|
char result[200];
|
|
|
|
d *= 100;
|
|
|
|
snprintf(result, sizeof(result), "%d/100", abs(static_cast<int>(d)));
|
|
|
|
return 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;
|
|
|
|
auto deg = static_cast<int>(d);
|
|
|
|
d -= deg;
|
|
|
|
d *= 60;
|
|
|
|
auto min = static_cast<int>(d);
|
|
|
|
d -= min;
|
|
|
|
d *= 60;
|
|
|
|
auto sec = static_cast<int>(d);
|
|
|
|
char result[200];
|
|
|
|
if (bRational)
|
|
|
|
snprintf(result, sizeof(result), "%d/1 %d/1 %d/1", deg, min, sec);
|
|
|
|
else
|
|
|
|
snprintf(result, sizeof(result), "%03d%s%02d'%02d\"%s", deg, gDeg, min, sec, NSEW);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Position::toString() const {
|
|
|
|
char result[200];
|
|
|
|
std::string sLat = Position::toExifString(lat_, false, true);
|
|
|
|
std::string sLon = Position::toExifString(lon_, false, false);
|
|
|
|
snprintf(result, sizeof(result), "%s %s %-8.3f", sLon.c_str(), sLat.c_str(), ele_);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaults
|
|
|
|
int Position::adjust_ = 0;
|
|
|
|
int Position::tz_ = timeZoneAdjust();
|
|
|
|
int Position::dst_ = 0;
|
|
|
|
time_t Position::deltaMax_ = 60;
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
// UserData - used by XML Parser
|
|
|
|
struct UserData final {
|
|
|
|
explicit UserData(Options& options) : options_(options) {
|
|
|
|
}
|
|
|
|
|
|
|
|
// public data members
|
|
|
|
int indent{0};
|
|
|
|
size_t count{0};
|
|
|
|
Position now;
|
|
|
|
Position prev;
|
|
|
|
int nTrkpt{0};
|
|
|
|
bool bTime{false};
|
|
|
|
bool bEle{false};
|
|
|
|
double ele{0.};
|
|
|
|
double lat{0.};
|
|
|
|
double lon{0.};
|
|
|
|
std::string xmlt;
|
|
|
|
std::string exift;
|
|
|
|
time_t time{0};
|
|
|
|
Options& options_;
|
|
|
|
// static public data members
|
|
|
|
};
|
|
|
|
|
|
|
|
// XML Parser Callbacks
|
|
|
|
static void startElement(void* userData, const char* name, const char** atts) {
|
|
|
|
auto me = static_cast<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) {
|
|
|
|
auto me = static_cast<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) {
|
|
|
|
auto me = static_cast<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
|
|
|
|
|
|
|
|
// <time>2012-07-14T17:33:16Z</time>
|
|
|
|
|
|
|
|
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 = {};
|
|
|
|
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() {
|
|
|
|
[[maybe_unused]] time_t now = time(nullptr);
|
|
|
|
int offset;
|
|
|
|
|
|
|
|
#if defined(_MSC_VER) || defined(__MINGW__)
|
|
|
|
TIME_ZONE_INFORMATION TimeZoneInfo;
|
|
|
|
GetTimeZoneInformation(&TimeZoneInfo);
|
|
|
|
offset = -(((int)TimeZoneInfo.Bias + (int)TimeZoneInfo.DaylightBias) * 60);
|
|
|
|
#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 <DIR>\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 != nullptr) {
|
|
|
|
bResult = true;
|
|
|
|
struct dirent* ent;
|
|
|
|
|
|
|
|
// print all the files and directories within directory
|
|
|
|
while ((ent = readdir(dir)) != nullptr) {
|
|
|
|
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(nullptr);
|
|
|
|
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 = "<?xml";
|
|
|
|
bResult = strncmp(lead, buffer, strlen(lead)) == 0;
|
|
|
|
|
|
|
|
// swallow it
|
|
|
|
if (bResult) {
|
|
|
|
len = sip(f, buffer, sizeof buffer, len);
|
|
|
|
bResult = XML_Parse(parser, buffer, static_cast<int>(len), len == 0) == XML_STATUS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// drink the rest of the file
|
|
|
|
while (bResult && len != 0) {
|
|
|
|
len = fread(buffer, 1, sizeof(buffer) - 100, f);
|
|
|
|
len = sip(f, buffer, sizeof buffer, len);
|
|
|
|
bResult = XML_Parse(parser, buffer, static_cast<int>(len), len == 0) == XML_STATUS_OK;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (f)
|
|
|
|
fclose(f);
|
|
|
|
if (parser)
|
|
|
|
XML_ParserFree(parser);
|
|
|
|
|
|
|
|
return bResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool readImage(const char* path, Options& /* options */) {
|
|
|
|
using namespace Exiv2;
|
|
|
|
bool bResult = false;
|
|
|
|
|
|
|
|
try {
|
|
|
|
Image::UniquePtr image = ImageFactory::open(path);
|
|
|
|
if (image.get()) {
|
|
|
|
image->readMetadata();
|
|
|
|
ExifData& exifData = image->exifData();
|
|
|
|
bResult = !exifData.empty();
|
|
|
|
}
|
|
|
|
} catch (...) {
|
|
|
|
};
|
|
|
|
return bResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
time_t readImageTime(const std::string& path, std::string* pS = nullptr) {
|
|
|
|
using namespace Exiv2;
|
|
|
|
|
|
|
|
time_t result = 0;
|
|
|
|
|
|
|
|
const char* dateStrings[] = {"Exif.Photo.DateTimeOriginal", "Exif.Photo.DateTimeDigitized", "Exif.Image.DateTime",
|
|
|
|
nullptr};
|
|
|
|
|
|
|
|
for (size_t i = 0; !result && dateStrings[i]; i++) {
|
|
|
|
const char* dateString = dateStrings[i];
|
|
|
|
try {
|
|
|
|
Image::UniquePtr 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, const 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", nullptr};
|
|
|
|
const char* code[] = {".cpp", ".h", ".pl", ".py", ".pyc", nullptr};
|
|
|
|
if (sina(ext, docs))
|
|
|
|
nResult = typeDoc;
|
|
|
|
if (sina(ext, code))
|
|
|
|
nResult = typeCode;
|
|
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
return nResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
Position* searchTimeDict(TimeDict_t& td, const time_t& time, long long delta) {
|
|
|
|
Position* result = nullptr;
|
|
|
|
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 < B);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, const char* argv[]) {
|
|
|
|
Exiv2::XmpParser::initialize();
|
|
|
|
::atexit(Exiv2::XmpParser::terminate);
|
|
|
|
#ifdef EXV_ENABLE_BMFF
|
|
|
|
Exiv2::enableBMFF();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int result = 0;
|
|
|
|
const char* program = argv[0];
|
|
|
|
|
|
|
|
const char* types[typeMax];
|
|
|
|
types[typeUnknown] = "unknown";
|
|
|
|
types[typeDirectory] = "directory";
|
|
|
|
types[typeImage] = "image";
|
|
|
|
types[typeXML] = "xml";
|
|
|
|
types[typeDoc] = "doc";
|
|
|
|
types[typeCode] = "code";
|
|
|
|
types[typeFile] = "file";
|
|
|
|
|
|
|
|
char const* keywords[kwMAX] = {};
|
|
|
|
keywords[kwHELP] = "help";
|
|
|
|
keywords[kwVERSION] = "version";
|
|
|
|
keywords[kwVERBOSE] = "verbose";
|
|
|
|
keywords[kwDRYRUN] = "dryrun";
|
|
|
|
keywords[kwASCII] = "ascii";
|
|
|
|
keywords[kwDST] = "dst";
|
|
|
|
keywords[kwADJUST] = "adjust";
|
|
|
|
keywords[kwTZ] = "tz";
|
|
|
|
keywords[kwDELTA] = "delta";
|
|
|
|
|
|
|
|
map<std::string, string> 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));
|
|
|
|
auto p = std::filesystem::absolute(std::filesystem::path(arg));
|
|
|
|
std::string path = p.string();
|
|
|
|
if (t && !path.empty()) {
|
|
|
|
if (options.verbose)
|
|
|
|
printf("%s %ld %s", path.c_str(), static_cast<long int>(t), asctime(localtime(&t)));
|
|
|
|
gFiles.push_back(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 (auto&& path : gFiles) {
|
|
|
|
std::string stamp;
|
|
|
|
try {
|
|
|
|
time_t t = readImageTime(path, &stamp);
|
|
|
|
Position* pPos = searchTimeDict(gTimeDict, t, Position::deltaMax_);
|
|
|
|
Exiv2::Image::UniquePtr 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!
|
|
|
|
////
|