Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9fb19bed2 | |||
|
|
ccd676f1b9 | ||
|
|
6e56015f56 | ||
|
|
d75f772ffa | ||
|
|
a41f1c89f7 | ||
|
|
915d0e353b | ||
|
|
0574cbf4f2 | ||
|
|
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/
|
||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
11
README.md
@@ -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"
|
||||||
|
|||||||
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)
|
||||||
349
TinyEXIF.cpp
@@ -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
|
||||||
|
|||||||
30
TinyEXIF.h
@@ -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
|
||||||
|
|||||||
46
main.cpp
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||