Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c57a5fec1a | ||
|
|
5bbfe8f70c | ||
|
|
cb40ffa537 | ||
|
|
405b8a1693 | ||
|
|
22ba2704c9 | ||
|
|
8b383a2809 | ||
|
|
ac39905f11 | ||
|
|
98dc256e0a | ||
|
|
e9c19435c3 | ||
|
|
04969097a4 | ||
|
|
8e3316cb41 | ||
|
|
c02203795b |
40
.gitignore
vendored
Normal 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/
|
||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
BIN
Samples/0007f2e26768eac8fe6b2f4d7c3c3dd0.jpg
Normal file
|
After Width: | Height: | Size: 693 KiB |
BIN
Samples/003431ad6fd3b86a5493105ef3c8db49.jpg
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
Samples/0412a3a08dfa2071074063c41c4a024e.jpg
Normal file
|
After Width: | Height: | Size: 404 KiB |
BIN
Samples/0a7da7e9f53d687224da8185f225b931.jpg
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
Samples/1103806289718.jpg
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
Samples/20160108-162501.jpg
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
Samples/ARTstorXMP.jpg
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
Samples/Anafi.jpg
Normal file
|
After Width: | Height: | Size: 707 KiB |
BIN
Samples/Bebop_2.jpg
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
Samples/Bebop_2_1.jpg
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
Samples/GettyVilla0001.jpg
Normal file
|
After Width: | Height: | Size: 404 KiB |
BIN
Samples/IPTCpanel.jpg
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
Samples/VRAexample012.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
Samples/bb-android.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Samples/calib.jpg
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
Samples/crosa.jpg
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
Samples/down-mirrored.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Samples/ec5a9432aee2247124451de9ac3d0807.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
Samples/evil1.jpg
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
Samples/example005.jpg
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
Samples/lens_info.jpg
Normal file
|
After Width: | Height: | Size: 897 B |
BIN
Samples/lukas12p.jpg
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
Samples/ok.jpg
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
Samples/problem.jpg
Normal file
|
After Width: | Height: | Size: 967 KiB |
BIN
Samples/right.jpg
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
Samples/sensefly.jpg
Normal file
|
After Width: | Height: | Size: 298 KiB |
BIN
Samples/short-ascii-II.jpg
Normal file
|
After Width: | Height: | Size: 872 KiB |
BIN
Samples/short-ascii-MM.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
Samples/sony-alpha-6000.jpg
Normal file
|
After Width: | Height: | Size: 457 KiB |
BIN
Samples/test1.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
Samples/test2.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Samples/test3.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
18
TestSamples.py
Normal 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)
|
||||||
257
TinyEXIF.cpp
@@ -49,6 +49,43 @@
|
|||||||
#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 == _tcsncmp(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 +182,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 +194,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 +231,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 +278,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,
|
||||||
@@ -362,6 +433,14 @@ 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:
|
||||||
|
// XMP Metadata (Adobe technote 9-14-02)
|
||||||
|
if (parser.IsUndefined()) {
|
||||||
|
const std::string strXML(parser.FetchString());
|
||||||
|
parseFromXMPSegmentXML(strXML.c_str(), (unsigned)strXML.length());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 0x829a:
|
case 0x829a:
|
||||||
// Exposure time in seconds
|
// Exposure time in seconds
|
||||||
parser.Fetch(ExposureTime);
|
parser.Fetch(ExposureTime);
|
||||||
@@ -448,6 +527,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 +618,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 != _tcsicmp(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 == _tcsicmp(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()) {
|
||||||
@@ -842,23 +980,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 +987,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)
|
||||||
@@ -909,19 +1031,67 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
|
|||||||
if (szProjectionType != NULL) {
|
if (szProjectionType != NULL) {
|
||||||
if (0 == _tcsicmp(szProjectionType, "perspective"))
|
if (0 == _tcsicmp(szProjectionType, "perspective"))
|
||||||
ProjectionType = 1;
|
ProjectionType = 1;
|
||||||
else if (0 == _tcsicmp(szProjectionType, "equirectangular") ||
|
else
|
||||||
0 == _tcsicmp(szProjectionType, "spherical"))
|
if (0 == _tcsicmp(szProjectionType, "equirectangular") ||
|
||||||
|
0 == _tcsicmp(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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const char* szAbout(document->Attribute("rdf:about"));
|
||||||
|
if (0 == _tcsicmp(Make.c_str(), "DJI") || (szAbout != NULL && 0 == _tcsicmp(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 == _tcsicmp(Make.c_str(), "senseFly") || 0 == _tcsicmp(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 == _tcsicmp(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);
|
||||||
|
}
|
||||||
|
|
||||||
return PARSE_SUCCESS;
|
return PARSE_SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -969,6 +1139,9 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void EXIFInfo::clear() {
|
void EXIFInfo::clear() {
|
||||||
@@ -1012,6 +1185,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 +1212,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 = "";
|
||||||
|
|||||||
14
TinyEXIF.h
@@ -121,6 +121,7 @@ public:
|
|||||||
// 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);
|
||||||
|
|
||||||
// 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 +134,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 +242,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 +273,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,6 +296,7 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
15
main.cpp
@@ -100,6 +100,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 +126,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())
|
||||||
|
|||||||