19 Commits

Author SHA1 Message Date
d9fb19bed2 Обновить CMakeLists.txt 2024-06-17 12:59:28 +04:00
Sebastian Martin Dicke
ccd676f1b9 include cstdint (#17) 2023-07-06 14:46:34 +03:00
simfeo
6e56015f56 Support for Google Camera motion photo metadata (#12) 2021-04-10 18:52:26 +03:00
Juha Reunanen
d75f772ffa Add constructor that takes generic std::istream objects (#11) 2021-04-02 20:31:04 +03:00
Juha Reunanen
a41f1c89f7 Fix MSVC++ UNICODE builds (#9)
* Problem: in MSVC++ UNICODE builds, the _tcsncmp and _tcsicmp defined via <tchar.h> need wchar_t* input, but that's not what we have available here

Solution:
1) in place of _tcsncmp, just use strncmp, which ought to be standard: http://www.cplusplus.com/reference/cstring/strncmp/
2) in place of _tcsicmp, call strcasecmp, and in MSVC++ builds create a wrapper that actually calls _stricmp (and never _wcsicmp)

* #include <string.h> and not <strings.h>
2021-03-25 15:37:51 +02:00
Juha Reunanen
915d0e353b Make XMP support optional (no need for tinyxml2) (#10) 2021-03-25 15:33:24 +02:00
shinji-yoshida
0574cbf4f2 parse GPano:PosePitchDegrees and GPano:PoseRollDegrees (#8) 2021-02-04 18:46:22 +02:00
cDc
c57a5fec1a fix tinyxml2 namespace 2019-01-16 15:26:30 +02:00
cDc
5bbfe8f70c add Sentera camera support to XMP 2019-01-11 15:28:25 +02:00
cDc
cb40ffa537 add test images 2019-01-11 15:24:23 +02:00
cDc
405b8a1693 parse XMP stored inside EXIF 2018-10-09 12:20:43 +03:00
cDc
22ba2704c9 parse XMP for more DJI cameras 2018-09-24 13:18:40 +03:00
cDc
8b383a2809 fix rational/s-rational EXIF parsing 2018-09-24 13:18:05 +03:00
cDc
ac39905f11 add maker check for DJI too 2018-09-24 12:33:57 +03:00
cDc
98dc256e0a add PARROT camera support to XMP 2018-08-15 13:44:36 +03:00
cDc
e9c19435c3 parse GPS accuracy 2018-07-18 15:41:35 +03:00
cDc
04969097a4 add MakerNote support for DJI 2018-07-10 10:00:24 +03:00
cDc
8e3316cb41 extract calibration information 2018-07-04 18:30:26 +03:00
cDc
c02203795b add senseFly support 2018-07-04 18:29:53 +03:00
39 changed files with 423 additions and 79 deletions

40
.gitignore vendored Normal file
View File

@@ -0,0 +1,40 @@
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Custom
*.tmp
*.exif.txt
*.exif.txt.txt
.DS_Store
CMakeSettings.json
.vs/
.idea/
.vscode/
bin/
binaries/

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1) cmake_minimum_required(VERSION 3.12)
project(TinyEXIF) project(TinyEXIF)
include(GNUInstallDirs) include(GNUInstallDirs)
@@ -84,7 +84,7 @@ if(BUILD_SHARED_LIBS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251") # needs to have dll-interface set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251") # needs to have dll-interface
endif() endif()
target_link_libraries(TinyEXIF tinyxml2) target_link_libraries(TinyEXIF tinyxml2::tinyxml2)
set_target_properties(TinyEXIF PROPERTIES set_target_properties(TinyEXIF PROPERTIES
COMPILE_DEFINITIONS "TINYEXIF_EXPORT" COMPILE_DEFINITIONS "TINYEXIF_EXPORT"
VERSION "${GENERIC_LIB_VERSION}" VERSION "${GENERIC_LIB_VERSION}"
@@ -121,7 +121,7 @@ endif()
if(BUILD_STATIC_LIBS) if(BUILD_STATIC_LIBS)
add_library(TinyEXIFstatic STATIC TinyEXIF.cpp TinyEXIF.h) add_library(TinyEXIFstatic STATIC TinyEXIF.cpp TinyEXIF.h)
target_link_libraries(TinyEXIFstatic tinyxml2) target_link_libraries(TinyEXIFstatic tinyxml2::tinyxml2)
set_target_properties(TinyEXIFstatic PROPERTIES set_target_properties(TinyEXIFstatic PROPERTIES
OUTPUT_NAME TinyEXIF OUTPUT_NAME TinyEXIF
VERSION "${GENERIC_LIB_VERSION}" VERSION "${GENERIC_LIB_VERSION}"
@@ -162,7 +162,7 @@ if(BUILD_DEMO)
target_compile_definitions(TinyEXIFdemo PRIVATE TINYEXIF_IMPORT) target_compile_definitions(TinyEXIFdemo PRIVATE TINYEXIF_IMPORT)
else(BUILD_STATIC_LIBS) else(BUILD_STATIC_LIBS)
add_dependencies(TinyEXIFdemo TinyEXIFstatic) add_dependencies(TinyEXIFdemo TinyEXIFstatic)
target_link_libraries(TinyEXIFdemo TinyEXIFstatic tinyxml2) target_link_libraries(TinyEXIFdemo TinyEXIFstatic tinyxml2::tinyxml2)
endif() endif()
endif() endif()

View File

@@ -18,16 +18,11 @@ int main(int argc, const char** argv) {
return -1; return -1;
} }
// read entire image file // open a stream to read just the necessary parts of the image file
std::ifstream file(argv[1], std::ifstream::in|std::ifstream::binary); std::ifstream istream(argv[1], std::ifstream::binary);
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(), length); TinyEXIF::EXIFInfo imageEXIF(istream);
if (imageEXIF.Fields) if (imageEXIF.Fields)
std::cout std::cout
<< "Image Description " << imageEXIF.ImageDescription << "\n" << "Image Description " << imageEXIF.ImageDescription << "\n"

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

BIN
Samples/1103806289718.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

BIN
Samples/20160108-162501.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
Samples/ARTstorXMP.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
Samples/Anafi.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 KiB

BIN
Samples/Bebop_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
Samples/Bebop_2_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

BIN
Samples/GettyVilla0001.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

BIN
Samples/IPTCpanel.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
Samples/VRAexample012.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
Samples/bb-android.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
Samples/calib.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

BIN
Samples/crosa.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

BIN
Samples/down-mirrored.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

BIN
Samples/evil1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
Samples/example005.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

BIN
Samples/lens_info.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

BIN
Samples/lukas12p.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
Samples/ok.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
Samples/problem.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 KiB

BIN
Samples/right.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
Samples/sensefly.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

BIN
Samples/short-ascii-II.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 KiB

BIN
Samples/short-ascii-MM.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
Samples/sony-alpha-6000.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

BIN
Samples/test1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
Samples/test2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
Samples/test3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

18
TestSamples.py Normal file
View File

@@ -0,0 +1,18 @@
import os
import sys
import subprocess
source = os.path.join(os.path.dirname(sys.argv[0]), 'Samples')
TinyEXIF = os.path.join(source, 'TinyEXIF')
exiftool = 'exiftool'
ext = 'exif.txt'
if len(sys.argv) > 1:
ext = sys.argv[1]
for root, dirs, filenames in os.walk(source):
for f in filenames:
if f[-4:].lower() == '.jpg':
fullpath = os.path.join(source, f)
imgexif = fullpath[:-3] + ext
print('parse ' + fullpath + ' to ' + imgexif)
subprocess.Popen(TinyEXIF + ' ' + fullpath + ' > ' + imgexif, shell=True)
subprocess.Popen(exiftool + ' -n -s -G ' + fullpath + ' > ' + imgexif + '.txt', shell=True)

View File

@@ -32,23 +32,67 @@
*/ */
#include "TinyEXIF.h" #include "TinyEXIF.h"
#ifndef TINYEXIF_NO_XMP_SUPPORT
#include <tinyxml2.h> #include <tinyxml2.h>
#endif // TINYEXIF_NO_XMP_SUPPORT
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cmath> #include <cmath>
#include <cfloat> #include <cfloat>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <iostream>
#ifdef _MSC_VER #ifdef _MSC_VER
#include <tchar.h> namespace {
int strcasecmp(const char* a, const char* b) {
return _stricmp(a, b);
}
}
#else #else
#include <strings.h> #include <string.h>
#define _tcsncmp strncmp
#define _tcsicmp strcasecmp
#endif #endif
namespace Tools {
// search string inside a string, case sensitive
static const char* strrnstr(const char* haystack, const char* needle, size_t len) {
const size_t needle_len(strlen(needle));
if (0 == needle_len)
return haystack;
if (len <= needle_len)
return NULL;
for (size_t i=len-needle_len; i-- > 0; ) {
if (haystack[0] == needle[0] &&
0 == strncmp(haystack, needle, needle_len))
return haystack;
haystack++;
}
return NULL;
}
// split an input string with a delimiter and fill a string vector
static void strSplit(const std::string& str, char delim, std::vector<std::string>& values) {
values.clear();
std::string::size_type start(0), end(0);
while (end != std::string::npos) {
end = str.find(delim, start);
values.emplace_back(str.substr(start, end-start));
start = end + 1;
}
}
// make sure the given degrees value is between -180 and 180
static double NormD180(double d) {
return (d = fmod(d+180.0, 360.0)) < 0 ? d+180.0 : d-180.0;
}
} // namespace Tools
namespace TinyEXIF { namespace TinyEXIF {
enum JPEG_MARKERS { enum JPEG_MARKERS {
@@ -145,6 +189,10 @@ public:
length = parse32(buf + offs + 4, alignIntel); length = parse32(buf + offs + 4, alignIntel);
} }
const uint8_t* GetBuffer() const { return buf; }
unsigned GetOffset() const { return offs; }
bool IsIntelAligned() const { return alignIntel; }
uint16_t GetTag() const { return tag; } uint16_t GetTag() const { return tag; }
uint32_t GetLength() const { return length; } uint32_t GetLength() const { return length; }
uint32_t GetData() const { return parse32(buf + offs + 8, alignIntel); } uint32_t GetData() const { return parse32(buf + offs + 8, alignIntel); }
@@ -153,11 +201,17 @@ public:
bool IsShort() const { return format == 3; } bool IsShort() const { return format == 3; }
bool IsLong() const { return format == 4; } bool IsLong() const { return format == 4; }
bool IsRational() const { return format == 5 || format == 10; } bool IsRational() const { return format == 5 || format == 10; }
bool IsSRational() const { return format == 10; }
bool IsFloat() const { return format == 11; }
bool IsUndefined() const { return format == 7; }
std::string FetchString() const {
return parseString(buf, length, GetData(), tiff_header_start, len, alignIntel);
}
bool Fetch(std::string& val) const { bool Fetch(std::string& val) const {
if (format != 2 || length == 0) if (format != 2 || length == 0)
return false; return false;
val = parseEXIFString(buf, length, GetData(), tiff_header_start, len, alignIntel); val = FetchString();
return true; return true;
} }
bool Fetch(uint8_t& val) const { bool Fetch(uint8_t& val) const {
@@ -184,16 +238,30 @@ public:
val = parse32(buf + offs + 8, alignIntel); val = parse32(buf + offs + 8, alignIntel);
return true; return true;
} }
bool Fetch(float& val) const {
if (!IsFloat() || length == 0)
return false;
val = parseFloat(buf + offs + 8, alignIntel);
return true;
}
bool Fetch(double& val) const { bool Fetch(double& val) const {
if (!IsRational() || length == 0) if (!IsRational() || length == 0)
return false; return false;
val = parseEXIFRational(buf + GetSubIFD(), alignIntel); val = parseRational(buf + GetSubIFD(), alignIntel, IsSRational());
return true; return true;
} }
bool Fetch(double& val, uint32_t idx) const { bool Fetch(double& val, uint32_t idx) const {
if (!IsRational() || length <= idx) if (!IsRational() || length <= idx)
return false; return false;
val = parseEXIFRational(buf + GetSubIFD() + idx*8, alignIntel); val = parseRational(buf + GetSubIFD() + idx*8, alignIntel, IsSRational());
return true;
}
bool FetchFloat(double& val) const {
float _val;
if (!Fetch(_val))
return false;
val = _val;
return true; return true;
} }
@@ -217,14 +285,24 @@ public:
((uint32_t)buf[2]<<8) | ((uint32_t)buf[2]<<8) |
buf[3]; buf[3];
} }
static double parseEXIFRational(const uint8_t* buf, bool intel) { static float parseFloat(const uint8_t* buf, bool intel) {
union {
uint32_t i;
float f;
} i2f;
i2f.i = parse32(buf, intel);
return i2f.f;
}
static double parseRational(const uint8_t* buf, bool intel, bool isSigned) {
const uint32_t denominator = parse32(buf+4, intel); const uint32_t denominator = parse32(buf+4, intel);
if (denominator == 0) if (denominator == 0)
return 0.0; return 0.0;
const uint32_t numerator = parse32(buf, intel); const uint32_t numerator = parse32(buf, intel);
return (double)(int32_t)numerator/(double)(int32_t)denominator; return isSigned ?
(double)(int32_t)numerator/(double)(int32_t)denominator :
(double)numerator/(double)denominator;
} }
static std::string parseEXIFString(const uint8_t* buf, static std::string parseString(const uint8_t* buf,
unsigned num_components, unsigned num_components,
unsigned data, unsigned data,
unsigned base, unsigned base,
@@ -261,6 +339,9 @@ EXIFInfo::EXIFInfo() : Fields(FIELD_NA) {
EXIFInfo::EXIFInfo(EXIFStream& stream) { EXIFInfo::EXIFInfo(EXIFStream& stream) {
parseFrom(stream); parseFrom(stream);
} }
EXIFInfo::EXIFInfo(std::istream& 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);
} }
@@ -362,6 +443,16 @@ void EXIFInfo::parseIFDImage(EntryParser& parser, unsigned& exif_sub_ifd_offset,
// Parse tag as Exif IFD // Parse tag as Exif IFD
void EXIFInfo::parseIFDExif(EntryParser& parser) { void EXIFInfo::parseIFDExif(EntryParser& parser) {
switch (parser.GetTag()) { switch (parser.GetTag()) {
case 0x02bc:
#ifndef TINYEXIF_NO_XMP_SUPPORT
// XMP Metadata (Adobe technote 9-14-02)
if (parser.IsUndefined()) {
const std::string strXML(parser.FetchString());
parseFromXMPSegmentXML(strXML.c_str(), (unsigned)strXML.length());
}
#endif // TINYEXIF_NO_XMP_SUPPORT
break;
case 0x829a: case 0x829a:
// Exposure time in seconds // Exposure time in seconds
parser.Fetch(ExposureTime); parser.Fetch(ExposureTime);
@@ -448,6 +539,11 @@ void EXIFInfo::parseIFDExif(EntryParser& parser) {
} }
break; break;
case 0x927c:
// MakerNote
parseIFDMakerNote(parser);
break;
case 0x9291: case 0x9291:
// Fractions of seconds for DateTimeOriginal // Fractions of seconds for DateTimeOriginal
parser.Fetch(SubSecTimeOriginal); parser.Fetch(SubSecTimeOriginal);
@@ -534,6 +630,60 @@ void EXIFInfo::parseIFDExif(EntryParser& parser) {
} }
} }
// Parse tag as MakerNote IFD
void EXIFInfo::parseIFDMakerNote(EntryParser& parser) {
const unsigned startOff = parser.GetOffset();
const uint32_t off = parser.GetSubIFD();
if (0 != strcasecmp(Make.c_str(), "DJI"))
return;
int num_entries = EntryParser::parse16(parser.GetBuffer()+off, parser.IsIntelAligned());
if (uint32_t(2 + 12 * num_entries) > parser.GetLength())
return;
parser.Init(off+2);
parser.ParseTag();
--num_entries;
std::string maker;
if (parser.GetTag() == 1 && parser.Fetch(maker)) {
if (0 == strcasecmp(maker.c_str(), "DJI")) {
while (--num_entries >= 0) {
parser.ParseTag();
switch (parser.GetTag()) {
case 3:
// SpeedX
parser.FetchFloat(GeoLocation.SpeedX);
break;
case 4:
// SpeedY
parser.FetchFloat(GeoLocation.SpeedY);
break;
case 5:
// SpeedZ
parser.FetchFloat(GeoLocation.SpeedZ);
break;
case 9:
// Camera Pitch
parser.FetchFloat(GeoLocation.PitchDegree);
break;
case 10:
// Camera Yaw
parser.FetchFloat(GeoLocation.YawDegree);
break;
case 11:
// Camera Roll
parser.FetchFloat(GeoLocation.RollDegree);
break;
}
}
}
}
parser.Init(startOff+12);
}
// Parse tag as GPS IFD // Parse tag as GPS IFD
void EXIFInfo::parseIFDGPS(EntryParser& parser) { void EXIFInfo::parseIFDGPS(EntryParser& parser) {
switch (parser.GetTag()) { switch (parser.GetTag()) {
@@ -670,6 +820,7 @@ int EXIFInfo::parseFrom(EXIFStream& stream) {
return app1s(PARSE_INVALID_JPEG); return app1s(PARSE_INVALID_JPEG);
switch (int ret=parseFromEXIFSegment(buf, sectionLength)) { switch (int ret=parseFromEXIFSegment(buf, sectionLength)) {
case PARSE_ABSENT_DATA: case PARSE_ABSENT_DATA:
#ifndef TINYEXIF_NO_XMP_SUPPORT
switch (ret=parseFromXMPSegment(buf, sectionLength)) { switch (ret=parseFromXMPSegment(buf, sectionLength)) {
case PARSE_ABSENT_DATA: case PARSE_ABSENT_DATA:
break; break;
@@ -680,6 +831,7 @@ int EXIFInfo::parseFrom(EXIFStream& stream) {
default: default:
return app1s(ret); // some error return app1s(ret); // some error
} }
#endif // TINYEXIF_NO_XMP_SUPPORT
break; break;
case PARSE_SUCCESS: case PARSE_SUCCESS:
if ((app1s|=FIELD_EXIF) == FIELD_ALL) if ((app1s|=FIELD_EXIF) == FIELD_ALL)
@@ -700,6 +852,36 @@ int EXIFInfo::parseFrom(EXIFStream& stream) {
return app1s(); return app1s();
} }
int EXIFInfo::parseFrom(std::istream& stream) {
class EXIFStdStream : public EXIFStream {
public:
EXIFStdStream(std::istream& stream)
: stream(stream) {
// Would be nice to assert here that the stream was opened in binary mode, but
// apparently that's not possible: https://stackoverflow.com/a/224259/19254
}
bool IsValid() const override {
return !!stream;
}
const uint8_t* GetBuffer(unsigned desiredLength) override {
buffer.resize(desiredLength);
if (!stream.read(reinterpret_cast<char*>(buffer.data()), desiredLength))
return NULL;
return buffer.data();
}
bool SkipBuffer(unsigned desiredLength) override {
return (bool)stream.seekg(desiredLength, std::ios::cur);
}
private:
std::istream& stream;
std::vector<uint8_t> buffer;
};
EXIFStdStream streamWrapper(stream);
return parseFrom(streamWrapper);
}
int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) { int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
class EXIFStreamBuffer : public EXIFStream { class EXIFStreamBuffer : public EXIFStream {
public: public:
@@ -832,6 +1014,8 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
return PARSE_SUCCESS; return PARSE_SUCCESS;
} }
#ifndef TINYEXIF_NO_XMP_SUPPORT
// //
// Main parsing function for a XMP segment. // Main parsing function for a XMP segment.
// Do a sanity check by looking for bytes "http://ns.adobe.com/xap/1.0/\0". // Do a sanity check by looking for bytes "http://ns.adobe.com/xap/1.0/\0".
@@ -842,23 +1026,6 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
// PARAM: 'len' length of buffer // PARAM: 'len' length of buffer
// //
int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) { int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
struct Tools {
static const char* strrnstr(const char* haystack, const char* needle, size_t len) {
const size_t needle_len(strlen(needle));
if (0 == needle_len)
return haystack;
if (len <= needle_len)
return NULL;
for (size_t i=len-needle_len; i-- > 0; ) {
if (haystack[0] == needle[0] &&
0 == _tcsncmp(haystack, needle, needle_len))
return haystack;
haystack++;
}
return NULL;
}
};
unsigned offs = 29; // current offset into buffer unsigned offs = 29; // current offset into buffer
if (!buf || len < offs) if (!buf || len < offs)
return PARSE_ABSENT_DATA; return PARSE_ABSENT_DATA;
@@ -866,17 +1033,18 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
return PARSE_ABSENT_DATA; return PARSE_ABSENT_DATA;
if (offs >= len) if (offs >= len)
return PARSE_CORRUPT_DATA; return PARSE_CORRUPT_DATA;
len -= offs; return parseFromXMPSegmentXML((const char*)(buf + offs), len - offs);
}
int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
// Skip xpacket end section so that tinyxml2 lib parses the section correctly. // Skip xpacket end section so that tinyxml2 lib parses the section correctly.
const char* const strXMP((const char*)(buf + offs)), *strEnd; const char* szEnd(Tools::strrnstr(szXML, "<?xpacket end=", len));
if ((strEnd=Tools::strrnstr(strXMP, "<?xpacket end=", len)) != NULL) if (szEnd != NULL)
len = (unsigned)(strEnd - strXMP); len = (unsigned)(szEnd - szXML);
// Try parsing the XML packet. // Try parsing the XML packet.
tinyxml2::XMLDocument doc; tinyxml2::XMLDocument doc;
const tinyxml2::XMLElement* document; const tinyxml2::XMLElement* document;
if (doc.Parse(strXMP, len) != tinyxml2::XML_SUCCESS || if (doc.Parse(szXML, len) != tinyxml2::XML_SUCCESS ||
((document=doc.FirstChildElement("x:xmpmeta")) == NULL && (document=doc.FirstChildElement("xmp:xmpmeta")) == NULL) || ((document=doc.FirstChildElement("x:xmpmeta")) == NULL && (document=doc.FirstChildElement("xmp:xmpmeta")) == NULL) ||
(document=document->FirstChildElement("rdf:RDF")) == NULL || (document=document->FirstChildElement("rdf:RDF")) == NULL ||
(document=document->FirstChildElement("rdf:Description")) == NULL) (document=document->FirstChildElement("rdf:Description")) == NULL)
@@ -907,25 +1075,93 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
if (element != NULL) { if (element != NULL) {
const char* const szProjectionType(element->GetText()); const char* const szProjectionType(element->GetText());
if (szProjectionType != NULL) { if (szProjectionType != NULL) {
if (0 == _tcsicmp(szProjectionType, "perspective")) if (0 == strcasecmp(szProjectionType, "perspective"))
ProjectionType = 1; ProjectionType = 1;
else if (0 == _tcsicmp(szProjectionType, "equirectangular") || else
0 == _tcsicmp(szProjectionType, "spherical")) if (0 == strcasecmp(szProjectionType, "equirectangular") ||
0 == strcasecmp(szProjectionType, "spherical"))
ProjectionType = 2; ProjectionType = 2;
} }
} }
} }
// Try parsing the XMP content for DJI info. // Try parsing the XMP content for supported maker's info.
document->QueryDoubleAttribute("drone-dji:AbsoluteAltitude", &GeoLocation.Altitude); struct ParseXMP {
document->QueryDoubleAttribute("drone-dji:RelativeAltitude", &GeoLocation.RelativeAltitude); // try yo fetch the value both from the attribute and child element
document->QueryDoubleAttribute("drone-dji:GimbalRollDegree", &GeoLocation.RollDegree); // and parse if needed rational numbers stored as string fraction
document->QueryDoubleAttribute("drone-dji:GimbalPitchDegree", &GeoLocation.PitchDegree); static bool Value(const tinyxml2::XMLElement* document, const char* name, double& value) {
document->QueryDoubleAttribute("drone-dji:GimbalYawDegree", &GeoLocation.YawDegree); const char* szAttribute = document->Attribute(name);
if (szAttribute == NULL) {
const tinyxml2::XMLElement* const element(document->FirstChildElement(name));
if (element == NULL || (szAttribute=element->GetText()) == NULL)
return false;
}
std::vector<std::string> values;
Tools::strSplit(szAttribute, '/', values);
switch (values.size()) {
case 1: value = strtod(values.front().c_str(), NULL); return true;
case 2: value = strtod(values.front().c_str(), NULL)/strtod(values.back().c_str(), NULL); return true;
}
return false;
}
// same as previous function but with unsigned int results
static bool Value(const tinyxml2::XMLElement* document, const char* name, uint32_t& value) {
const char* szAttribute = document->Attribute(name);
if (szAttribute == NULL) {
const tinyxml2::XMLElement* const element(document->FirstChildElement(name));
if (element == NULL || (szAttribute = element->GetText()) == NULL)
return false;
}
value = strtoul(szAttribute, NULL, 0); return true;
return false;
}
};
const char* szAbout(document->Attribute("rdf:about"));
if (0 == strcasecmp(Make.c_str(), "DJI") || (szAbout != NULL && 0 == strcasecmp(szAbout, "DJI Meta Data"))) {
ParseXMP::Value(document, "drone-dji:AbsoluteAltitude", GeoLocation.Altitude);
ParseXMP::Value(document, "drone-dji:RelativeAltitude", GeoLocation.RelativeAltitude);
ParseXMP::Value(document, "drone-dji:GimbalRollDegree", GeoLocation.RollDegree);
ParseXMP::Value(document, "drone-dji:GimbalPitchDegree", GeoLocation.PitchDegree);
ParseXMP::Value(document, "drone-dji:GimbalYawDegree", GeoLocation.YawDegree);
ParseXMP::Value(document, "drone-dji:CalibratedFocalLength", Calibration.FocalLength);
ParseXMP::Value(document, "drone-dji:CalibratedOpticalCenterX", Calibration.OpticalCenterX);
ParseXMP::Value(document, "drone-dji:CalibratedOpticalCenterY", Calibration.OpticalCenterY);
} else
if (0 == strcasecmp(Make.c_str(), "senseFly") || 0 == strcasecmp(Make.c_str(), "Sentera")) {
ParseXMP::Value(document, "Camera:Roll", GeoLocation.RollDegree);
if (ParseXMP::Value(document, "Camera:Pitch", GeoLocation.PitchDegree)) {
// convert to DJI format: senseFly uses pitch 0 as NADIR, whereas DJI -90
GeoLocation.PitchDegree = Tools::NormD180(GeoLocation.PitchDegree-90.0);
}
ParseXMP::Value(document, "Camera:Yaw", GeoLocation.YawDegree);
ParseXMP::Value(document, "Camera:GPSXYAccuracy", GeoLocation.AccuracyXY);
ParseXMP::Value(document, "Camera:GPSZAccuracy", GeoLocation.AccuracyZ);
} else
if (0 == strcasecmp(Make.c_str(), "PARROT")) {
ParseXMP::Value(document, "Camera:Roll", GeoLocation.RollDegree) ||
ParseXMP::Value(document, "drone-parrot:CameraRollDegree", GeoLocation.RollDegree);
if (ParseXMP::Value(document, "Camera:Pitch", GeoLocation.PitchDegree) ||
ParseXMP::Value(document, "drone-parrot:CameraPitchDegree", GeoLocation.PitchDegree)) {
// convert to DJI format: senseFly uses pitch 0 as NADIR, whereas DJI -90
GeoLocation.PitchDegree = Tools::NormD180(GeoLocation.PitchDegree-90.0);
}
ParseXMP::Value(document, "Camera:Yaw", GeoLocation.YawDegree) ||
ParseXMP::Value(document, "drone-parrot:CameraYawDegree", GeoLocation.YawDegree);
ParseXMP::Value(document, "Camera:AboveGroundAltitude", GeoLocation.RelativeAltitude);
}
ParseXMP::Value(document, "GPano:PosePitchDegrees", GPano.PosePitchDegrees);
ParseXMP::Value(document, "GPano:PoseRollDegrees", GPano.PoseRollDegrees);
// parse GCamera:MicroVideo
if (document->Attribute("GCamera:MicroVideo")) {
ParseXMP::Value(document, "GCamera:MicroVideo", MicroVideo.HasMicroVideo);
ParseXMP::Value(document, "GCamera:MicroVideoVersion", MicroVideo.MicroVideoVersion);
ParseXMP::Value(document, "GCamera:MicroVideoOffset", MicroVideo.MicroVideoOffset);
}
return PARSE_SUCCESS; return PARSE_SUCCESS;
} }
#endif // TINYEXIF_NO_XMP_SUPPORT
void EXIFInfo::Geolocation_t::parseCoords() { void EXIFInfo::Geolocation_t::parseCoords() {
// Convert GPS latitude // Convert GPS latitude
@@ -969,7 +1205,17 @@ bool EXIFInfo::Geolocation_t::hasRelativeAltitude() const {
bool EXIFInfo::Geolocation_t::hasOrientation() const { bool EXIFInfo::Geolocation_t::hasOrientation() const {
return RollDegree != DBL_MAX && PitchDegree != DBL_MAX && YawDegree != DBL_MAX; return RollDegree != DBL_MAX && PitchDegree != DBL_MAX && YawDegree != DBL_MAX;
} }
bool EXIFInfo::Geolocation_t::hasSpeed() const {
return SpeedX != DBL_MAX && SpeedY != DBL_MAX && SpeedZ != DBL_MAX;
}
bool EXIFInfo::GPano_t::hasPosePitchDegrees() const {
return PosePitchDegrees != DBL_MAX;
}
bool EXIFInfo::GPano_t::hasPoseRollDegrees() const {
return PoseRollDegrees != DBL_MAX;
}
void EXIFInfo::clear() { void EXIFInfo::clear() {
Fields = FIELD_NA; Fields = FIELD_NA;
@@ -1012,6 +1258,11 @@ void EXIFInfo::clear() {
ProjectionType = 0; ProjectionType = 0;
SubjectArea.clear(); SubjectArea.clear();
// Calibration
Calibration.FocalLength = 0;
Calibration.OpticalCenterX = 0;
Calibration.OpticalCenterY = 0;
// LensInfo // LensInfo
LensInfo.FocalLengthMax = 0; LensInfo.FocalLengthMax = 0;
LensInfo.FocalLengthMin = 0; LensInfo.FocalLengthMin = 0;
@@ -1034,6 +1285,11 @@ void EXIFInfo::clear() {
GeoLocation.RollDegree = DBL_MAX; GeoLocation.RollDegree = DBL_MAX;
GeoLocation.PitchDegree = DBL_MAX; GeoLocation.PitchDegree = DBL_MAX;
GeoLocation.YawDegree = DBL_MAX; GeoLocation.YawDegree = DBL_MAX;
GeoLocation.SpeedX = DBL_MAX;
GeoLocation.SpeedY = DBL_MAX;
GeoLocation.SpeedZ = DBL_MAX;
GeoLocation.AccuracyXY = 0;
GeoLocation.AccuracyZ = 0;
GeoLocation.GPSDOP = 0; GeoLocation.GPSDOP = 0;
GeoLocation.GPSDifferential = 0; GeoLocation.GPSDifferential = 0;
GeoLocation.GPSMapDatum = ""; GeoLocation.GPSMapDatum = "";
@@ -1047,6 +1303,15 @@ void EXIFInfo::clear() {
GeoLocation.LonComponents.minutes = 0; GeoLocation.LonComponents.minutes = 0;
GeoLocation.LonComponents.seconds = 0; GeoLocation.LonComponents.seconds = 0;
GeoLocation.LonComponents.direction = 0; GeoLocation.LonComponents.direction = 0;
// GPano
GPano.PosePitchDegrees = DBL_MAX;
GPano.PoseRollDegrees = DBL_MAX;
// Video metadata
MicroVideo.HasMicroVideo = 0;
MicroVideo.MicroVideoVersion = 0;
MicroVideo.MicroVideoOffset = 0;
} }
} // namespace TinyEXIF } // namespace TinyEXIF

View File

@@ -34,6 +34,7 @@
#ifndef __TINYEXIF_H__ #ifndef __TINYEXIF_H__
#define __TINYEXIF_H__ #define __TINYEXIF_H__
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -100,6 +101,7 @@ class TINYEXIF_LIB EXIFInfo {
public: public:
EXIFInfo(); EXIFInfo();
EXIFInfo(EXIFStream& stream); EXIFInfo(EXIFStream& stream);
EXIFInfo(std::istream& stream); // NB: the stream must have been opened in binary mode
EXIFInfo(const uint8_t* data, unsigned length); EXIFInfo(const uint8_t* data, unsigned length);
// Parsing function for an entire JPEG image stream. // Parsing function for an entire JPEG image stream.
@@ -110,6 +112,7 @@ public:
// 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(EXIFStream& stream);
int parseFrom(std::istream& stream); // NB: the stream must have been opened in binary mode
int parseFrom(const uint8_t* data, unsigned length); int parseFrom(const uint8_t* data, unsigned length);
// Parsing function for an EXIF segment. This is used internally by parseFrom() // Parsing function for an EXIF segment. This is used internally by parseFrom()
@@ -117,10 +120,13 @@ public:
// available (i.e., a blob starting with the bytes "Exif\0\0"). // available (i.e., a blob starting with the bytes "Exif\0\0").
int parseFromEXIFSegment(const uint8_t* buf, unsigned len); int parseFromEXIFSegment(const uint8_t* buf, unsigned len);
#ifndef TINYEXIF_NO_XMP_SUPPORT
// Parsing function for an XMP segment. This is used internally by parseFrom() // Parsing function for an XMP segment. This is used internally by parseFrom()
// but can be called for special cases where only the XMP section is // but can be called for special cases where only the XMP section is
// available (i.e., a blob starting with the bytes "http://ns.adobe.com/xap/1.0/\0"). // available (i.e., a blob starting with the bytes "http://ns.adobe.com/xap/1.0/\0").
int parseFromXMPSegment(const uint8_t* buf, unsigned len); int parseFromXMPSegment(const uint8_t* buf, unsigned len);
int parseFromXMPSegmentXML(const char* szXML, unsigned len);
#endif // TINYEXIF_NO_XMP_SUPPORT
// Set all data members to default values. // Set all data members to default values.
// Should be called before parsing a new stream. // Should be called before parsing a new stream.
@@ -133,6 +139,8 @@ private:
void parseIFDExif(EntryParser&); void parseIFDExif(EntryParser&);
// Parse tag as GPS IFD. // Parse tag as GPS IFD.
void parseIFDGPS(EntryParser&); void parseIFDGPS(EntryParser&);
// Parse tag as MakerNote IFD.
void parseIFDMakerNote(EntryParser&);
public: public:
// Data fields // Data fields
@@ -239,6 +247,11 @@ public:
// 2: location of the main subject as coordinates (first value is the X coordinate and second is the Y coordinate) // 2: location of the main subject as coordinates (first value is the X coordinate and second is the Y coordinate)
// 3: area of the main subject as a circle (first value is the center X coordinate, second is the center Y coordinate, and third is the diameter) // 3: area of the main subject as a circle (first value is the center X coordinate, second is the center Y coordinate, and third is the diameter)
// 4: area of the main subject as a rectangle (first value is the center X coordinate, second is the center Y coordinate, third is the width of the area, and fourth is the height of the area) // 4: area of the main subject as a rectangle (first value is the center X coordinate, second is the center Y coordinate, third is the width of the area, and fourth is the height of the area)
struct TINYEXIF_LIB Calibration_t { // Camera calibration information
double FocalLength; // Focal length (pixels)
double OpticalCenterX; // Principal point X (pixels)
double OpticalCenterY; // Principal point Y (pixels)
} Calibration;
struct TINYEXIF_LIB LensInfo_t { // Lens information struct TINYEXIF_LIB LensInfo_t { // Lens information
double FStopMin; // Min aperture (f-stop) double FStopMin; // Min aperture (f-stop)
double FStopMax; // Max aperture (f-stop) double FStopMax; // Max aperture (f-stop)
@@ -265,6 +278,11 @@ public:
double RollDegree; // Flight roll in degrees double RollDegree; // Flight roll in degrees
double PitchDegree; // Flight pitch in degrees double PitchDegree; // Flight pitch in degrees
double YawDegree; // Flight yaw in degrees double YawDegree; // Flight yaw in degrees
double SpeedX; // Flight speed on X in meters/second
double SpeedY; // Flight speed on Y in meters/second
double SpeedZ; // Flight speed on Z in meters/second
double AccuracyXY; // GPS accuracy on XY in meters
double AccuracyZ; // GPS accuracy on Z in meters
double GPSDOP; // GPS DOP (data degree of precision) double GPSDOP; // GPS DOP (data degree of precision)
uint16_t GPSDifferential; // Differential correction applied to the GPS receiver (may not exist) uint16_t GPSDifferential; // Differential correction applied to the GPS receiver (may not exist)
// 0: measurement without differential correction // 0: measurement without differential correction
@@ -283,7 +301,19 @@ public:
bool hasAltitude() const; // Return true if (alt) is available bool hasAltitude() const; // Return true if (alt) is available
bool hasRelativeAltitude()const;// Return true if (rel_alt) is available bool hasRelativeAltitude()const;// Return true if (rel_alt) is available
bool hasOrientation() const; // Return true if (roll,yaw,pitch) is available bool hasOrientation() const; // Return true if (roll,yaw,pitch) is available
bool hasSpeed() const; // Return true if (speedX,speedY,speedZ) is available
} GeoLocation; } GeoLocation;
struct TINYEXIF_LIB GPano_t { // Spherical metadata. https://developers.google.com/streetview/spherical-metadata
double PosePitchDegrees; // Pitch, measured in degrees above the horizon, for the center in the image. Value must be >= -90 and <= 90.
double PoseRollDegrees; // Roll, measured in degrees, of the image where level with the horizon is 0. As roll increases, the horizon rotates counterclockwise in the image. Value must be > -180 and <= 180.
bool hasPosePitchDegrees() const; // Return true if PosePitchDegrees is available
bool hasPoseRollDegrees() const; // Return true if PoseRollDegrees is available
} GPano;
struct TINYEXIF_LIB MicroVideo_t { // Google camera video file in metadata
uint32_t HasMicroVideo; // not zero if exists
uint32_t MicroVideoVersion; // just regularinfo
uint32_t MicroVideoOffset; // offset from end of file
} MicroVideo;
}; };
} // namespace TinyEXIF } // namespace TinyEXIF

View File

@@ -9,27 +9,6 @@
#include <vector> // std::vector #include <vector> // std::vector
#include <iomanip> // std::setprecision #include <iomanip> // std::setprecision
class EXIFStreamFile : public TinyEXIF::EXIFStream {
public:
explicit 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) {
@@ -37,9 +16,9 @@ int main(int argc, const char** argv)
return -1; return -1;
} }
// read entire image file // open a stream to read just the necessary parts of the image file
EXIFStreamFile stream(argv[1]); std::ifstream stream(argv[1], std::ios::binary);
if (!stream.IsValid()) { if (!stream) {
std::cout << "error: can not open '" << argv[1] << "'\n"; std::cout << "error: can not open '" << argv[1] << "'\n";
return -2; return -2;
} }
@@ -100,6 +79,12 @@ int main(int argc, const char** argv)
std::cout << "MeteringMode " << imageEXIF.MeteringMode << "\n"; std::cout << "MeteringMode " << imageEXIF.MeteringMode << "\n";
std::cout << "LightSource " << imageEXIF.LightSource << "\n"; std::cout << "LightSource " << imageEXIF.LightSource << "\n";
std::cout << "ProjectionType " << imageEXIF.ProjectionType << "\n"; std::cout << "ProjectionType " << imageEXIF.ProjectionType << "\n";
if (imageEXIF.Calibration.FocalLength != 0)
std::cout << "Calibration.FocalLength " << imageEXIF.Calibration.FocalLength << " pixels" << "\n";
if (imageEXIF.Calibration.OpticalCenterX != 0)
std::cout << "Calibration.OpticalCenterX " << imageEXIF.Calibration.OpticalCenterX << " pixels" << "\n";
if (imageEXIF.Calibration.OpticalCenterY != 0)
std::cout << "Calibration.OpticalCenterY " << imageEXIF.Calibration.OpticalCenterY << " pixels" << "\n";
std::cout << "LensInfo.FStopMin " << imageEXIF.LensInfo.FStopMin << "\n"; std::cout << "LensInfo.FStopMin " << imageEXIF.LensInfo.FStopMin << "\n";
std::cout << "LensInfo.FStopMax " << imageEXIF.LensInfo.FStopMax << "\n"; std::cout << "LensInfo.FStopMax " << imageEXIF.LensInfo.FStopMax << "\n";
std::cout << "LensInfo.FocalLengthMin " << imageEXIF.LensInfo.FocalLengthMin << " mm" << "\n"; std::cout << "LensInfo.FocalLengthMin " << imageEXIF.LensInfo.FocalLengthMin << " mm" << "\n";
@@ -120,12 +105,19 @@ int main(int argc, const char** argv)
std::cout << "GeoLocation.AltitudeRef " << (int)imageEXIF.GeoLocation.AltitudeRef << "\n"; std::cout << "GeoLocation.AltitudeRef " << (int)imageEXIF.GeoLocation.AltitudeRef << "\n";
} }
if (imageEXIF.GeoLocation.hasRelativeAltitude()) if (imageEXIF.GeoLocation.hasRelativeAltitude())
std::cout << "GeoLocation.RelativeAltitude " << imageEXIF.GeoLocation.RelativeAltitude << "\n"; std::cout << "GeoLocation.RelativeAltitude " << imageEXIF.GeoLocation.RelativeAltitude << " m" << "\n";
if (imageEXIF.GeoLocation.hasOrientation()) { if (imageEXIF.GeoLocation.hasOrientation()) {
std::cout << "GeoLocation.RollDegree " << imageEXIF.GeoLocation.RollDegree << "\n"; std::cout << "GeoLocation.RollDegree " << imageEXIF.GeoLocation.RollDegree << "\n";
std::cout << "GeoLocation.PitchDegree " << imageEXIF.GeoLocation.PitchDegree << "\n"; std::cout << "GeoLocation.PitchDegree " << imageEXIF.GeoLocation.PitchDegree << "\n";
std::cout << "GeoLocation.YawDegree " << imageEXIF.GeoLocation.YawDegree << "\n"; std::cout << "GeoLocation.YawDegree " << imageEXIF.GeoLocation.YawDegree << "\n";
} }
if (imageEXIF.GeoLocation.hasSpeed()) {
std::cout << "GeoLocation.SpeedX " << imageEXIF.GeoLocation.SpeedX << " m/s" << "\n";
std::cout << "GeoLocation.SpeedY " << imageEXIF.GeoLocation.SpeedY << " m/s" << "\n";
std::cout << "GeoLocation.SpeedZ " << imageEXIF.GeoLocation.SpeedZ << " m/s" << "\n";
}
if (imageEXIF.GeoLocation.AccuracyXY > 0 || imageEXIF.GeoLocation.AccuracyZ > 0)
std::cout << "GeoLocation.GPSAccuracy XY " << imageEXIF.GeoLocation.AccuracyXY << " m" << " Z " << imageEXIF.GeoLocation.AccuracyZ << " m" << "\n";
std::cout << "GeoLocation.GPSDOP " << imageEXIF.GeoLocation.GPSDOP << "\n"; std::cout << "GeoLocation.GPSDOP " << imageEXIF.GeoLocation.GPSDOP << "\n";
std::cout << "GeoLocation.GPSDifferential " << imageEXIF.GeoLocation.GPSDifferential << "\n"; std::cout << "GeoLocation.GPSDifferential " << imageEXIF.GeoLocation.GPSDifferential << "\n";
if (!imageEXIF.GeoLocation.GPSMapDatum.empty()) if (!imageEXIF.GeoLocation.GPSMapDatum.empty())
@@ -134,5 +126,9 @@ int main(int argc, const char** argv)
std::cout << "GeoLocation.GPSTimeStamp " << imageEXIF.GeoLocation.GPSTimeStamp << "\n"; std::cout << "GeoLocation.GPSTimeStamp " << imageEXIF.GeoLocation.GPSTimeStamp << "\n";
if (!imageEXIF.GeoLocation.GPSDateStamp.empty()) if (!imageEXIF.GeoLocation.GPSDateStamp.empty())
std::cout << "GeoLocation.GPSDateStamp " << imageEXIF.GeoLocation.GPSDateStamp << "\n"; std::cout << "GeoLocation.GPSDateStamp " << imageEXIF.GeoLocation.GPSDateStamp << "\n";
if (imageEXIF.GPano.hasPosePitchDegrees())
std::cout << "GPano.PosePitchDegrees " << imageEXIF.GPano.PosePitchDegrees << "\n";
if (imageEXIF.GPano.hasPoseRollDegrees())
std::cout << "GPano.PoseRollDegrees " << imageEXIF.GPano.PoseRollDegrees << "\n";
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }