add input stream interface (with override ex. for memory and file input)

This commit is contained in:
cDc
2017-12-09 13:38:32 +02:00
parent 36f7056799
commit af81c87222
3 changed files with 185 additions and 124 deletions

View File

@@ -258,12 +258,12 @@ public:
// Constructors // Constructors
EXIFInfo::EXIFInfo() : Fields(FIELD_NA) { EXIFInfo::EXIFInfo() : Fields(FIELD_NA) {
} }
EXIFInfo::EXIFInfo(EXIFStream& stream) {
parseFrom(stream);
}
EXIFInfo::EXIFInfo(const uint8_t* data, unsigned length) { EXIFInfo::EXIFInfo(const uint8_t* data, unsigned length) {
parseFrom(data, length); parseFrom(data, length);
} }
EXIFInfo::EXIFInfo(const std::string& data) {
parseFrom(data);
}
// Parse tag as Image IFD // Parse tag as Image IFD
@@ -615,18 +615,16 @@ void EXIFInfo::parseIFDGPS(EntryParser& parser) {
// Locates the JM_APP1 segment and parses it using // Locates the JM_APP1 segment and parses it using
// parseFromEXIFSegment() or parseFromXMPSegment() // parseFromEXIFSegment() or parseFromXMPSegment()
// //
int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) { int EXIFInfo::parseFrom(EXIFStream& stream) {
clear(); clear();
if (!stream.IsValid())
return PARSE_INVALID_JPEG;
// Sanity check: all JPEG files start with 0xFFD8 and end with 0xFFD9 // Sanity check: all JPEG files start with 0xFFD8 and end with 0xFFD9
// This check also ensures that the user has supplied a correct value for len. // This check also ensures that the user has supplied a correct value for len.
if (!buf || len < 16) const uint8_t* buf(stream.GetBuffer(2));
if (buf == NULL || buf[0] != JM_START || buf[1] != JM_SOI)
return PARSE_INVALID_JPEG; return PARSE_INVALID_JPEG;
if (buf[0] != JM_START || buf[1] != JM_SOI)
return PARSE_INVALID_JPEG;
// not always valid, sometimes 0xFF is added for padding
//if (buf[len-2] != JM_START || buf[len-1] != JM_EOI)
// return PARSE_INVALID_JPEG;
// Scan for JM_APP1 header (bytes 0xFF 0xE1) and parse its length. // Scan for JM_APP1 header (bytes 0xFF 0xE1) and parse its length.
// Exit if both EXIF and XMP sections were parsed. // Exit if both EXIF and XMP sections were parsed.
@@ -637,15 +635,16 @@ int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
inline operator uint32_t& () { return val; } inline operator uint32_t& () { return val; }
inline int operator () (int code=PARSE_ABSENT_DATA) const { return val&FIELD_ALL ? (int)PARSE_SUCCESS : code; } inline int operator () (int code=PARSE_ABSENT_DATA) const { return val&FIELD_ALL ? (int)PARSE_SUCCESS : code; }
} app1s(Fields); } app1s(Fields);
for (unsigned pos=2; pos+2<=len; ) { while ((buf=stream.GetBuffer(2)) != NULL) {
// find next marker; // find next marker;
// in cases of markers appended after the compressed data, // in cases of markers appended after the compressed data,
// optional JM_START fill bytes may precede the marker // optional JM_START fill bytes may precede the marker
if (buf[pos++] != JM_START) if (*buf++ != JM_START)
break; break;
uint8_t marker; uint8_t marker;
while ((marker=buf[pos++]) == JM_START && pos<len); while ((marker=buf[0]) == JM_START && (buf=stream.GetBuffer(1)) != NULL);
// select marker // select marker
uint16_t sectionLength;
switch (marker) { switch (marker) {
case 0x00: case 0x00:
case 0x01: case 0x01:
@@ -663,12 +662,15 @@ int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
case JM_SOS: // start of stream: and we're done case JM_SOS: // start of stream: and we're done
case JM_EOI: // no data? not good case JM_EOI: // no data? not good
return app1s(); return app1s();
case JM_APP1: { case JM_APP1:
const uint16_t section_length(EntryParser::parse16(buf + pos, false)); if ((buf=stream.GetBuffer(2)) == NULL)
int ret; return app1s(PARSE_INVALID_JPEG);
switch (ret=parseFromEXIFSegment(buf + pos + 2, section_length - 2)) { sectionLength = EntryParser::parse16(buf, false);
if (sectionLength <= 2 || (buf=stream.GetBuffer(sectionLength-=2)) == NULL)
return app1s(PARSE_INVALID_JPEG);
switch (int ret=parseFromEXIFSegment(buf, sectionLength)) {
case PARSE_ABSENT_DATA: case PARSE_ABSENT_DATA:
switch (ret=parseFromXMPSegment(buf + pos + 2, section_length - 2)) { switch (ret=parseFromXMPSegment(buf, sectionLength)) {
case PARSE_ABSENT_DATA: case PARSE_ABSENT_DATA:
break; break;
case PARSE_SUCCESS: case PARSE_SUCCESS:
@@ -686,22 +688,41 @@ int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
default: default:
return app1s(ret); // some error return app1s(ret); // some error
} }
} break;
default: { default:
// read section length
const uint16_t section_length(EntryParser::parse16(buf + pos, false));
if (pos + section_length > len)
return app1s(PARSE_INVALID_JPEG);
// skip the section // skip the section
pos += section_length; if ((buf=stream.GetBuffer(2)) == NULL ||
} (sectionLength=EntryParser::parse16(buf, false)) <= 2 ||
!stream.SkipBuffer(sectionLength-2))
return app1s(PARSE_INVALID_JPEG);
} }
} }
return app1s(); return app1s();
} }
int EXIFInfo::parseFrom(const std::string& data) { int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
return parseFrom((const uint8_t*)data.data(), (unsigned)data.length()); class EXIFStreamBuffer : public EXIFStream {
public:
EXIFStreamBuffer(const uint8_t* buf, unsigned len)
: it(buf), end(buf+len) {}
bool IsValid() const override {
return it != NULL;
}
const uint8_t* GetBuffer(unsigned desiredLength) override {
const uint8_t* const itNext(it+desiredLength);
if (itNext >= end)
return NULL;
const uint8_t* const begin(it);
it = itNext;
return begin;
}
bool SkipBuffer(unsigned desiredLength) override {
return GetBuffer(desiredLength) != NULL;
}
private:
const uint8_t* it, * const end;
};
return parseFrom(EXIFStreamBuffer(buf, len));
} }
// //

View File

@@ -75,22 +75,42 @@ enum FieldCode {
class EntryParser; class EntryParser;
// //
// Class responsible for storing and parsing EXIF information from a JPEG blob // Interface class responsible for fetching stream data to be parsed
//
class TINYEXIF_LIB EXIFStream {
public:
virtual ~EXIFStream() {}
// Check the state of the stream.
virtual bool IsValid() const = 0;
// Return the pointer to the beginning of the desired size buffer
// following current buffer position.
virtual const uint8_t* GetBuffer(unsigned desiredLength) = 0;
// Advance current buffer position with the desired size;
// return false if stream ends in less than the desired size.
virtual bool SkipBuffer(unsigned desiredLength) = 0;
};
//
// Class responsible for storing and parsing EXIF & XMP metadata from a JPEG stream
// //
class TINYEXIF_LIB EXIFInfo { class TINYEXIF_LIB EXIFInfo {
public: public:
EXIFInfo(); EXIFInfo();
EXIFInfo(EXIFStream& stream);
EXIFInfo(const uint8_t* data, unsigned length); EXIFInfo(const uint8_t* data, unsigned length);
EXIFInfo(const std::string& data);
// Parsing function for an entire JPEG image stream. // Parsing function for an entire JPEG image stream.
// //
// PARAM 'stream': Interface to fetch JPEG image stream.
// PARAM 'data': A pointer to a JPEG image. // PARAM 'data': A pointer to a JPEG image.
// PARAM 'length': The length of the JPEG image. // PARAM 'length': The length of the JPEG image.
// RETURN: PARSE_SUCCESS (0) on success with 'result' filled out // RETURN: PARSE_SUCCESS (0) on success with 'result' filled out
// error code otherwise, as defined by the PARSE_* macros // error code otherwise, as defined by the PARSE_* macros
int parseFrom(EXIFStream& stream);
int parseFrom(const uint8_t* data, unsigned length); int parseFrom(const uint8_t* data, unsigned length);
int parseFrom(const std::string& data);
// Parsing function for an EXIF segment. This is used internally by parseFrom() // Parsing function for an EXIF segment. This is used internally by parseFrom()
// but can be called for special cases where only the EXIF section is // but can be called for special cases where only the EXIF section is

206
main.cpp
View File

@@ -9,110 +9,130 @@
#include <vector> // std::vector #include <vector> // std::vector
#include <iomanip> // std::setprecision #include <iomanip> // std::setprecision
class EXIFStreamFile : public TinyEXIF::EXIFStream {
public:
EXIFStreamFile(const char* fileName)
: file(fileName, std::ifstream::in|std::ifstream::binary) {}
bool IsValid() const override {
return file.is_open();
}
const uint8_t* GetBuffer(unsigned desiredLength) override {
buffer.resize(desiredLength);
if (!file.read((char*)buffer.data(), desiredLength))
return NULL;
return buffer.data();
}
bool SkipBuffer(unsigned desiredLength) override {
return (bool)file.seekg(desiredLength,std::ios::cur);
}
private:
std::ifstream file;
std::vector<uint8_t> buffer;
};
int main(int argc, const char** argv) int main(int argc, const char** argv)
{ {
if (argc != 2) { if (argc != 2) {
std::cout << "Usage: TinyEXIF <image_file>" << "\n"; std::cout << "Usage: TinyEXIF <image_file>\n";
return -1; return -1;
} }
// read entire image file // read entire image file
std::ifstream file(argv[1], std::ifstream::in|std::ifstream::binary); EXIFStreamFile stream(argv[1]);
if (!file.is_open()) { if (!stream.IsValid()) {
std::cout << "error: can not open '" << argv[1] << "'\n"; std::cout << "error: can not open '" << argv[1] << "'\n";
return -2; return -2;
} }
file.seekg(0,std::ios::end);
std::streampos length = file.tellg();
file.seekg(0,std::ios::beg);
std::vector<uint8_t> data(length);
file.read((char*)data.data(), length);
// parse image EXIF and XMP metadata // parse image EXIF and XMP metadata
TinyEXIF::EXIFInfo imageEXIF(data.data(), (unsigned)length); TinyEXIF::EXIFInfo imageEXIF(stream);
if (imageEXIF.Fields) { if (!imageEXIF.Fields) {
if (imageEXIF.ImageWidth || imageEXIF.ImageHeight) std::cout << "error: no EXIF or XMP metadata\n";
std::cout << "ImageResolution " << imageEXIF.ImageWidth << "x" << imageEXIF.ImageHeight << " pixels" << "\n"; return -3;
if (imageEXIF.RelatedImageWidth || imageEXIF.RelatedImageHeight)
std::cout << "RelatedImageResolution " << imageEXIF.RelatedImageWidth << "x" << imageEXIF.RelatedImageHeight << " pixels" << "\n";
if (!imageEXIF.ImageDescription.empty())
std::cout << "Description " << imageEXIF.ImageDescription << "\n";
if (!imageEXIF.Make.empty() || !imageEXIF.Model.empty())
std::cout << "CameraModel " << imageEXIF.Make << " - " << imageEXIF.Model << "\n";
if (!imageEXIF.SerialNumber.empty())
std::cout << "SerialNumber " << imageEXIF.SerialNumber << "\n";
if (imageEXIF.Orientation)
std::cout << "Orientation " << imageEXIF.Orientation << "\n";
if (imageEXIF.XResolution || imageEXIF.YResolution || imageEXIF.ResolutionUnit)
std::cout << "Resolution " << imageEXIF.XResolution << "x" << imageEXIF.YResolution << " (" << imageEXIF.ResolutionUnit << ")\n";
if (imageEXIF.BitsPerSample)
std::cout << "BitsPerSample " << imageEXIF.BitsPerSample << "\n";
if (!imageEXIF.Software.empty())
std::cout << "Software " << imageEXIF.Software << "\n";
if (!imageEXIF.DateTime.empty())
std::cout << "DateTime " << imageEXIF.DateTime << "\n";
if (!imageEXIF.DateTimeOriginal.empty())
std::cout << "DateTimeOriginal " << imageEXIF.DateTimeOriginal << "\n";
if (!imageEXIF.DateTimeDigitized.empty())
std::cout << "DateTimeDigitized " << imageEXIF.DateTimeDigitized << "\n";
if (!imageEXIF.SubSecTimeOriginal.empty())
std::cout << "SubSecTimeOriginal " << imageEXIF.SubSecTimeOriginal << "\n";
if (!imageEXIF.Copyright.empty())
std::cout << "Copyright " << imageEXIF.Copyright << "\n";
std::cout << "ExposureTime " << std::setprecision(10) << imageEXIF.ExposureTime << " s" << "\n";
std::cout << "FNumber " << imageEXIF.FNumber << "\n";
std::cout << "ExposureProgram " << imageEXIF.ExposureProgram << "\n";
std::cout << "ISOSpeed " << imageEXIF.ISOSpeedRatings << "\n";
std::cout << "ShutterSpeedValue " << std::setprecision(10) << imageEXIF.ShutterSpeedValue << "\n";
std::cout << "ApertureValue " << std::setprecision(10) << imageEXIF.ApertureValue << "\n";
std::cout << "BrightnessValue " << std::setprecision(10) << imageEXIF.BrightnessValue << "\n";
std::cout << "ExposureBiasValue " << imageEXIF.ExposureBiasValue << "\n";
std::cout << "SubjectDistance " << imageEXIF.SubjectDistance << "\n";
std::cout << "FocalLength " << imageEXIF.FocalLength << " mm" << "\n";
std::cout << "Flash " << imageEXIF.Flash << "\n";
if (!imageEXIF.SubjectArea.empty()) {
std::cout << "SubjectArea";
for (uint16_t val: imageEXIF.SubjectArea)
std::cout << " " << val;
std::cout << "\n";
}
std::cout << "MeteringMode " << imageEXIF.MeteringMode << "\n";
std::cout << "LightSource " << imageEXIF.LightSource << "\n";
std::cout << "ProjectionType " << imageEXIF.ProjectionType << "\n";
std::cout << "LensInfo.FStopMin " << imageEXIF.LensInfo.FStopMin << "\n";
std::cout << "LensInfo.FStopMax " << imageEXIF.LensInfo.FStopMax << "\n";
std::cout << "LensInfo.FocalLengthMin " << imageEXIF.LensInfo.FocalLengthMin << " mm" << "\n";
std::cout << "LensInfo.FocalLengthMax " << imageEXIF.LensInfo.FocalLengthMax << " mm" << "\n";
std::cout << "LensInfo.DigitalZoomRatio " << imageEXIF.LensInfo.DigitalZoomRatio << "\n";
std::cout << "LensInfo.FocalLengthIn35mm " << imageEXIF.LensInfo.FocalLengthIn35mm << "\n";
std::cout << "LensInfo.FocalPlaneXResolution " << std::setprecision(10) << imageEXIF.LensInfo.FocalPlaneXResolution << "\n";
std::cout << "LensInfo.FocalPlaneYResolution " << std::setprecision(10) << imageEXIF.LensInfo.FocalPlaneYResolution << "\n";
std::cout << "LensInfo.FocalPlaneResolutionUnit " << imageEXIF.LensInfo.FocalPlaneResolutionUnit << "\n";
if (!imageEXIF.LensInfo.Make.empty() || !imageEXIF.LensInfo.Model.empty())
std::cout << "LensInfo.Model " << imageEXIF.LensInfo.Make << " - " << imageEXIF.LensInfo.Model << "\n";
if (imageEXIF.GeoLocation.hasLatLon()) {
std::cout << "GeoLocation.Latitude " << std::setprecision(10) << imageEXIF.GeoLocation.Latitude << "\n";
std::cout << "GeoLocation.Longitude " << std::setprecision(10) << imageEXIF.GeoLocation.Longitude << "\n";
}
if (imageEXIF.GeoLocation.hasAltitude()) {
std::cout << "GeoLocation.Altitude " << imageEXIF.GeoLocation.Altitude << " m" << "\n";
std::cout << "GeoLocation.AltitudeRef " << (int)imageEXIF.GeoLocation.AltitudeRef << "\n";
}
if (imageEXIF.GeoLocation.hasRelativeAltitude())
std::cout << "GeoLocation.RelativeAltitude " << imageEXIF.GeoLocation.RelativeAltitude << "\n";
if (imageEXIF.GeoLocation.hasOrientation()) {
std::cout << "GeoLocation.RollDegree " << imageEXIF.GeoLocation.RollDegree << "\n";
std::cout << "GeoLocation.PitchDegree " << imageEXIF.GeoLocation.PitchDegree << "\n";
std::cout << "GeoLocation.YawDegree " << imageEXIF.GeoLocation.YawDegree << "\n";
}
std::cout << "GeoLocation.GPSDOP " << imageEXIF.GeoLocation.GPSDOP << "\n";
std::cout << "GeoLocation.GPSDifferential " << imageEXIF.GeoLocation.GPSDifferential << "\n";
if (!imageEXIF.GeoLocation.GPSMapDatum.empty())
std::cout << "GeoLocation.GPSMapDatum " << imageEXIF.GeoLocation.GPSMapDatum << "\n";
if (!imageEXIF.GeoLocation.GPSTimeStamp.empty())
std::cout << "GeoLocation.GPSTimeStamp " << imageEXIF.GeoLocation.GPSTimeStamp << "\n";
if (!imageEXIF.GeoLocation.GPSDateStamp.empty())
std::cout << "GeoLocation.GPSDateStamp " << imageEXIF.GeoLocation.GPSDateStamp << "\n";
} }
return 0;
// print extracted metadata
if (imageEXIF.ImageWidth || imageEXIF.ImageHeight)
std::cout << "ImageResolution " << imageEXIF.ImageWidth << "x" << imageEXIF.ImageHeight << " pixels" << "\n";
if (imageEXIF.RelatedImageWidth || imageEXIF.RelatedImageHeight)
std::cout << "RelatedImageResolution " << imageEXIF.RelatedImageWidth << "x" << imageEXIF.RelatedImageHeight << " pixels" << "\n";
if (!imageEXIF.ImageDescription.empty())
std::cout << "Description " << imageEXIF.ImageDescription << "\n";
if (!imageEXIF.Make.empty() || !imageEXIF.Model.empty())
std::cout << "CameraModel " << imageEXIF.Make << " - " << imageEXIF.Model << "\n";
if (!imageEXIF.SerialNumber.empty())
std::cout << "SerialNumber " << imageEXIF.SerialNumber << "\n";
if (imageEXIF.Orientation)
std::cout << "Orientation " << imageEXIF.Orientation << "\n";
if (imageEXIF.XResolution || imageEXIF.YResolution || imageEXIF.ResolutionUnit)
std::cout << "Resolution " << imageEXIF.XResolution << "x" << imageEXIF.YResolution << " (" << imageEXIF.ResolutionUnit << ")\n";
if (imageEXIF.BitsPerSample)
std::cout << "BitsPerSample " << imageEXIF.BitsPerSample << "\n";
if (!imageEXIF.Software.empty())
std::cout << "Software " << imageEXIF.Software << "\n";
if (!imageEXIF.DateTime.empty())
std::cout << "DateTime " << imageEXIF.DateTime << "\n";
if (!imageEXIF.DateTimeOriginal.empty())
std::cout << "DateTimeOriginal " << imageEXIF.DateTimeOriginal << "\n";
if (!imageEXIF.DateTimeDigitized.empty())
std::cout << "DateTimeDigitized " << imageEXIF.DateTimeDigitized << "\n";
if (!imageEXIF.SubSecTimeOriginal.empty())
std::cout << "SubSecTimeOriginal " << imageEXIF.SubSecTimeOriginal << "\n";
if (!imageEXIF.Copyright.empty())
std::cout << "Copyright " << imageEXIF.Copyright << "\n";
std::cout << "ExposureTime " << std::setprecision(10) << imageEXIF.ExposureTime << " s" << "\n";
std::cout << "FNumber " << imageEXIF.FNumber << "\n";
std::cout << "ExposureProgram " << imageEXIF.ExposureProgram << "\n";
std::cout << "ISOSpeed " << imageEXIF.ISOSpeedRatings << "\n";
std::cout << "ShutterSpeedValue " << std::setprecision(10) << imageEXIF.ShutterSpeedValue << "\n";
std::cout << "ApertureValue " << std::setprecision(10) << imageEXIF.ApertureValue << "\n";
std::cout << "BrightnessValue " << std::setprecision(10) << imageEXIF.BrightnessValue << "\n";
std::cout << "ExposureBiasValue " << imageEXIF.ExposureBiasValue << "\n";
std::cout << "SubjectDistance " << imageEXIF.SubjectDistance << "\n";
std::cout << "FocalLength " << imageEXIF.FocalLength << " mm" << "\n";
std::cout << "Flash " << imageEXIF.Flash << "\n";
if (!imageEXIF.SubjectArea.empty()) {
std::cout << "SubjectArea";
for (uint16_t val: imageEXIF.SubjectArea)
std::cout << " " << val;
std::cout << "\n";
}
std::cout << "MeteringMode " << imageEXIF.MeteringMode << "\n";
std::cout << "LightSource " << imageEXIF.LightSource << "\n";
std::cout << "ProjectionType " << imageEXIF.ProjectionType << "\n";
std::cout << "LensInfo.FStopMin " << imageEXIF.LensInfo.FStopMin << "\n";
std::cout << "LensInfo.FStopMax " << imageEXIF.LensInfo.FStopMax << "\n";
std::cout << "LensInfo.FocalLengthMin " << imageEXIF.LensInfo.FocalLengthMin << " mm" << "\n";
std::cout << "LensInfo.FocalLengthMax " << imageEXIF.LensInfo.FocalLengthMax << " mm" << "\n";
std::cout << "LensInfo.DigitalZoomRatio " << imageEXIF.LensInfo.DigitalZoomRatio << "\n";
std::cout << "LensInfo.FocalLengthIn35mm " << imageEXIF.LensInfo.FocalLengthIn35mm << "\n";
std::cout << "LensInfo.FocalPlaneXResolution " << std::setprecision(10) << imageEXIF.LensInfo.FocalPlaneXResolution << "\n";
std::cout << "LensInfo.FocalPlaneYResolution " << std::setprecision(10) << imageEXIF.LensInfo.FocalPlaneYResolution << "\n";
std::cout << "LensInfo.FocalPlaneResolutionUnit " << imageEXIF.LensInfo.FocalPlaneResolutionUnit << "\n";
if (!imageEXIF.LensInfo.Make.empty() || !imageEXIF.LensInfo.Model.empty())
std::cout << "LensInfo.Model " << imageEXIF.LensInfo.Make << " - " << imageEXIF.LensInfo.Model << "\n";
if (imageEXIF.GeoLocation.hasLatLon()) {
std::cout << "GeoLocation.Latitude " << std::setprecision(10) << imageEXIF.GeoLocation.Latitude << "\n";
std::cout << "GeoLocation.Longitude " << std::setprecision(10) << imageEXIF.GeoLocation.Longitude << "\n";
}
if (imageEXIF.GeoLocation.hasAltitude()) {
std::cout << "GeoLocation.Altitude " << imageEXIF.GeoLocation.Altitude << " m" << "\n";
std::cout << "GeoLocation.AltitudeRef " << (int)imageEXIF.GeoLocation.AltitudeRef << "\n";
}
if (imageEXIF.GeoLocation.hasRelativeAltitude())
std::cout << "GeoLocation.RelativeAltitude " << imageEXIF.GeoLocation.RelativeAltitude << "\n";
if (imageEXIF.GeoLocation.hasOrientation()) {
std::cout << "GeoLocation.RollDegree " << imageEXIF.GeoLocation.RollDegree << "\n";
std::cout << "GeoLocation.PitchDegree " << imageEXIF.GeoLocation.PitchDegree << "\n";
std::cout << "GeoLocation.YawDegree " << imageEXIF.GeoLocation.YawDegree << "\n";
}
std::cout << "GeoLocation.GPSDOP " << imageEXIF.GeoLocation.GPSDOP << "\n";
std::cout << "GeoLocation.GPSDifferential " << imageEXIF.GeoLocation.GPSDifferential << "\n";
if (!imageEXIF.GeoLocation.GPSMapDatum.empty())
std::cout << "GeoLocation.GPSMapDatum " << imageEXIF.GeoLocation.GPSMapDatum << "\n";
if (!imageEXIF.GeoLocation.GPSTimeStamp.empty())
std::cout << "GeoLocation.GPSTimeStamp " << imageEXIF.GeoLocation.GPSTimeStamp << "\n";
if (!imageEXIF.GeoLocation.GPSDateStamp.empty())
std::cout << "GeoLocation.GPSDateStamp " << imageEXIF.GeoLocation.GPSDateStamp << "\n";
return EXIT_SUCCESS;
} }