7 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
5 changed files with 122 additions and 46 deletions

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1)
cmake_minimum_required(VERSION 3.12)
project(TinyEXIF)
include(GNUInstallDirs)

View File

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

View File

@@ -32,20 +32,27 @@
*/
#include "TinyEXIF.h"
#ifndef TINYEXIF_NO_XMP_SUPPORT
#include <tinyxml2.h>
#endif // TINYEXIF_NO_XMP_SUPPORT
#include <cstdint>
#include <cstdio>
#include <cmath>
#include <cfloat>
#include <vector>
#include <algorithm>
#include <iostream>
#ifdef _MSC_VER
#include <tchar.h>
namespace {
int strcasecmp(const char* a, const char* b) {
return _stricmp(a, b);
}
}
#else
#include <strings.h>
#define _tcsncmp strncmp
#define _tcsicmp strcasecmp
#include <string.h>
#endif
@@ -60,7 +67,7 @@ namespace Tools {
return NULL;
for (size_t i=len-needle_len; i-- > 0; ) {
if (haystack[0] == needle[0] &&
0 == _tcsncmp(haystack, needle, needle_len))
0 == strncmp(haystack, needle, needle_len))
return haystack;
haystack++;
}
@@ -332,6 +339,9 @@ EXIFInfo::EXIFInfo() : Fields(FIELD_NA) {
EXIFInfo::EXIFInfo(EXIFStream& stream) {
parseFrom(stream);
}
EXIFInfo::EXIFInfo(std::istream& stream) {
parseFrom(stream);
}
EXIFInfo::EXIFInfo(const uint8_t* data, unsigned length) {
parseFrom(data, length);
}
@@ -434,11 +444,13 @@ void EXIFInfo::parseIFDImage(EntryParser& parser, unsigned& exif_sub_ifd_offset,
void EXIFInfo::parseIFDExif(EntryParser& parser) {
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:
@@ -622,7 +634,7 @@ void EXIFInfo::parseIFDExif(EntryParser& parser) {
void EXIFInfo::parseIFDMakerNote(EntryParser& parser) {
const unsigned startOff = parser.GetOffset();
const uint32_t off = parser.GetSubIFD();
if (0 != _tcsicmp(Make.c_str(), "DJI"))
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())
@@ -632,7 +644,7 @@ void EXIFInfo::parseIFDMakerNote(EntryParser& parser) {
--num_entries;
std::string maker;
if (parser.GetTag() == 1 && parser.Fetch(maker)) {
if (0 == _tcsicmp(maker.c_str(), "DJI")) {
if (0 == strcasecmp(maker.c_str(), "DJI")) {
while (--num_entries >= 0) {
parser.ParseTag();
switch (parser.GetTag()) {
@@ -808,6 +820,7 @@ int EXIFInfo::parseFrom(EXIFStream& stream) {
return app1s(PARSE_INVALID_JPEG);
switch (int ret=parseFromEXIFSegment(buf, sectionLength)) {
case PARSE_ABSENT_DATA:
#ifndef TINYEXIF_NO_XMP_SUPPORT
switch (ret=parseFromXMPSegment(buf, sectionLength)) {
case PARSE_ABSENT_DATA:
break;
@@ -818,6 +831,7 @@ int EXIFInfo::parseFrom(EXIFStream& stream) {
default:
return app1s(ret); // some error
}
#endif // TINYEXIF_NO_XMP_SUPPORT
break;
case PARSE_SUCCESS:
if ((app1s|=FIELD_EXIF) == FIELD_ALL)
@@ -838,6 +852,36 @@ int EXIFInfo::parseFrom(EXIFStream& stream) {
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) {
class EXIFStreamBuffer : public EXIFStream {
public:
@@ -970,6 +1014,8 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
return PARSE_SUCCESS;
}
#ifndef TINYEXIF_NO_XMP_SUPPORT
//
// Main parsing function for a XMP segment.
// Do a sanity check by looking for bytes "http://ns.adobe.com/xap/1.0/\0".
@@ -1029,11 +1075,11 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
if (element != NULL) {
const char* const szProjectionType(element->GetText());
if (szProjectionType != NULL) {
if (0 == _tcsicmp(szProjectionType, "perspective"))
if (0 == strcasecmp(szProjectionType, "perspective"))
ProjectionType = 1;
else
if (0 == _tcsicmp(szProjectionType, "equirectangular") ||
0 == _tcsicmp(szProjectionType, "spherical"))
if (0 == strcasecmp(szProjectionType, "equirectangular") ||
0 == strcasecmp(szProjectionType, "spherical"))
ProjectionType = 2;
}
}
@@ -1058,9 +1104,20 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
}
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 == _tcsicmp(Make.c_str(), "DJI") || (szAbout != NULL && 0 == _tcsicmp(szAbout, "DJI Meta Data"))) {
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);
@@ -1070,7 +1127,7 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
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")) {
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
@@ -1080,7 +1137,7 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
ParseXMP::Value(document, "Camera:GPSXYAccuracy", GeoLocation.AccuracyXY);
ParseXMP::Value(document, "Camera:GPSZAccuracy", GeoLocation.AccuracyZ);
} else
if (0 == _tcsicmp(Make.c_str(), "PARROT")) {
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) ||
@@ -1092,10 +1149,19 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
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;
}
#endif // TINYEXIF_NO_XMP_SUPPORT
void EXIFInfo::Geolocation_t::parseCoords() {
// Convert GPS latitude
@@ -1143,6 +1209,13 @@ 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() {
Fields = FIELD_NA;
@@ -1230,6 +1303,15 @@ void EXIFInfo::clear() {
GeoLocation.LonComponents.minutes = 0;
GeoLocation.LonComponents.seconds = 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

View File

@@ -34,6 +34,7 @@
#ifndef __TINYEXIF_H__
#define __TINYEXIF_H__
#include <cstdint>
#include <string>
#include <vector>
@@ -100,6 +101,7 @@ class TINYEXIF_LIB EXIFInfo {
public:
EXIFInfo();
EXIFInfo(EXIFStream& stream);
EXIFInfo(std::istream& stream); // NB: the stream must have been opened in binary mode
EXIFInfo(const uint8_t* data, unsigned length);
// Parsing function for an entire JPEG image stream.
@@ -110,6 +112,7 @@ public:
// RETURN: PARSE_SUCCESS (0) on success with 'result' filled out
// error code otherwise, as defined by the PARSE_* macros
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);
// Parsing function for an EXIF segment. This is used internally by parseFrom()
@@ -117,11 +120,13 @@ public:
// available (i.e., a blob starting with the bytes "Exif\0\0").
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()
// 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").
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.
// Should be called before parsing a new stream.
@@ -298,6 +303,17 @@ public:
bool hasOrientation() const; // Return true if (roll,yaw,pitch) is available
bool hasSpeed() const; // Return true if (speedX,speedY,speedZ) is available
} 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

View File

@@ -9,27 +9,6 @@
#include <vector> // std::vector
#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)
{
if (argc != 2) {
@@ -37,9 +16,9 @@ int main(int argc, const char** argv)
return -1;
}
// read entire image file
EXIFStreamFile stream(argv[1]);
if (!stream.IsValid()) {
// open a stream to read just the necessary parts of the image file
std::ifstream stream(argv[1], std::ios::binary);
if (!stream) {
std::cout << "error: can not open '" << argv[1] << "'\n";
return -2;
}
@@ -147,5 +126,9 @@ int main(int argc, const char** argv)
std::cout << "GeoLocation.GPSTimeStamp " << imageEXIF.GeoLocation.GPSTimeStamp << "\n";
if (!imageEXIF.GeoLocation.GPSDateStamp.empty())
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;
}