3 Commits
1.0.0 ... 1.0.1

Author SHA1 Message Date
cDc
87c343b4f1 fix linux compile and increase version 2017-12-09 14:53:52 +02:00
cDc
af81c87222 add input stream interface (with override ex. for memory and file input) 2017-12-09 13:38:32 +02:00
cDc
36f7056799 try recover missing EXIF filds from XMP 2017-12-09 11:59:09 +02:00
5 changed files with 270 additions and 165 deletions

View File

@@ -1,8 +1,4 @@
cmake_minimum_required(VERSION 2.6) cmake_minimum_required(VERSION 3.1)
cmake_policy(VERSION 2.6)
if(POLICY CMP0063)
cmake_policy(SET CMP0063 OLD)
endif()
project(TinyEXIF) project(TinyEXIF)
include(GNUInstallDirs) include(GNUInstallDirs)
@@ -14,12 +10,13 @@ find_package(tinyxml2 REQUIRED)
################################ ################################
# set lib version here # set lib version here
set(GENERIC_LIB_VERSION "1.0.0") set(GENERIC_LIB_VERSION "1.0.1")
set(GENERIC_LIB_SOVERSION "1") set(GENERIC_LIB_SOVERSION "1")
################################ ################################
# Add definitions # Add definitions
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
################################ ################################

View File

@@ -38,3 +38,25 @@ int main(int argc, const char** argv) {
} }
``` ```
See `main.cpp` for more details. See `main.cpp` for more details.
## Copyright
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -13,9 +13,9 @@
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
-- Redistributions of source code must retain the above copyright notice, - Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer. this list of conditions and the following disclaimer.
-- Redistributions in binary form must reproduce the above copyright notice, - Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
@@ -258,12 +258,12 @@ public:
// Constructors // Constructors
EXIFInfo::EXIFInfo() : Fields(FIELD_NA) { EXIFInfo::EXIFInfo() : Fields(FIELD_NA) {
} }
EXIFInfo::EXIFInfo(EXIFStream& stream) {
parseFrom(stream);
}
EXIFInfo::EXIFInfo(const uint8_t* data, unsigned length) { EXIFInfo::EXIFInfo(const uint8_t* data, unsigned length) {
parseFrom(data, length); parseFrom(data, length);
} }
EXIFInfo::EXIFInfo(const std::string& data) {
parseFrom(data);
}
// Parse tag as Image IFD // Parse tag as Image IFD
@@ -615,18 +615,16 @@ void EXIFInfo::parseIFDGPS(EntryParser& parser) {
// Locates the JM_APP1 segment and parses it using // Locates the JM_APP1 segment and parses it using
// parseFromEXIFSegment() or parseFromXMPSegment() // parseFromEXIFSegment() or parseFromXMPSegment()
// //
int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) { int EXIFInfo::parseFrom(EXIFStream& stream) {
clear(); clear();
if (!stream.IsValid())
return PARSE_INVALID_JPEG;
// Sanity check: all JPEG files start with 0xFFD8 and end with 0xFFD9 // Sanity check: all JPEG files start with 0xFFD8 and end with 0xFFD9
// This check also ensures that the user has supplied a correct value for len. // This check also ensures that the user has supplied a correct value for len.
if (!buf || len < 16) const uint8_t* buf(stream.GetBuffer(2));
if (buf == NULL || buf[0] != JM_START || buf[1] != JM_SOI)
return PARSE_INVALID_JPEG; return PARSE_INVALID_JPEG;
if (buf[0] != JM_START || buf[1] != JM_SOI)
return PARSE_INVALID_JPEG;
// not always valid, sometimes 0xFF is added for padding
//if (buf[len-2] != JM_START || buf[len-1] != JM_EOI)
// return PARSE_INVALID_JPEG;
// Scan for JM_APP1 header (bytes 0xFF 0xE1) and parse its length. // Scan for JM_APP1 header (bytes 0xFF 0xE1) and parse its length.
// Exit if both EXIF and XMP sections were parsed. // Exit if both EXIF and XMP sections were parsed.
@@ -637,19 +635,20 @@ int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
inline operator uint32_t& () { return val; } inline operator uint32_t& () { return val; }
inline int operator () (int code=PARSE_ABSENT_DATA) const { return val&FIELD_ALL ? (int)PARSE_SUCCESS : code; } inline int operator () (int code=PARSE_ABSENT_DATA) const { return val&FIELD_ALL ? (int)PARSE_SUCCESS : code; }
} app1s(Fields); } app1s(Fields);
for (unsigned pos=2; pos<len; ) { while ((buf=stream.GetBuffer(2)) != NULL) {
// find next marker // find next marker;
uint8_t marker, prev(0); // in cases of markers appended after the compressed data,
do { // optional JM_START fill bytes may precede the marker
marker = buf[pos++]; if (*buf++ != JM_START)
if (marker != JM_START && prev == JM_START)
break; break;
prev = marker; uint8_t marker;
} while (pos<len); while ((marker=buf[0]) == JM_START && (buf=stream.GetBuffer(1)) != NULL);
// select marker // select marker
uint16_t sectionLength;
switch (marker) { switch (marker) {
case 0x00: case 0x00:
case 0x01: case 0x01:
case JM_START:
case JM_RST0: case JM_RST0:
case JM_RST1: case JM_RST1:
case JM_RST2: case JM_RST2:
@@ -663,12 +662,15 @@ int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
case JM_SOS: // start of stream: and we're done case JM_SOS: // start of stream: and we're done
case JM_EOI: // no data? not good case JM_EOI: // no data? not good
return app1s(); return app1s();
case JM_APP1: { case JM_APP1:
const uint16_t section_length(EntryParser::parse16(buf + pos, false)); if ((buf=stream.GetBuffer(2)) == NULL)
int ret; return app1s(PARSE_INVALID_JPEG);
switch (ret=parseFromEXIFSegment(buf + pos + 2, section_length - 2)) { sectionLength = EntryParser::parse16(buf, false);
if (sectionLength <= 2 || (buf=stream.GetBuffer(sectionLength-=2)) == NULL)
return app1s(PARSE_INVALID_JPEG);
switch (int ret=parseFromEXIFSegment(buf, sectionLength)) {
case PARSE_ABSENT_DATA: case PARSE_ABSENT_DATA:
switch (ret=parseFromXMPSegment(buf + pos + 2, section_length - 2)) { switch (ret=parseFromXMPSegment(buf, sectionLength)) {
case PARSE_ABSENT_DATA: case PARSE_ABSENT_DATA:
break; break;
case PARSE_SUCCESS: case PARSE_SUCCESS:
@@ -686,22 +688,42 @@ int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
default: default:
return app1s(ret); // some error return app1s(ret); // some error
} }
} break;
default: { default:
// read section length
const uint16_t section_length(EntryParser::parse16(buf + pos, false));
if (pos + section_length > len)
return app1s(PARSE_INVALID_JPEG);
// skip the section // skip the section
pos += section_length; if ((buf=stream.GetBuffer(2)) == NULL ||
} (sectionLength=EntryParser::parse16(buf, false)) <= 2 ||
!stream.SkipBuffer(sectionLength-2))
return app1s(PARSE_INVALID_JPEG);
} }
} }
return app1s(); return app1s();
} }
int EXIFInfo::parseFrom(const std::string& data) { int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
return parseFrom((const uint8_t*)data.data(), (unsigned)data.length()); class EXIFStreamBuffer : public EXIFStream {
public:
explicit EXIFStreamBuffer(const uint8_t* buf, unsigned len)
: it(buf), end(buf+len) {}
bool IsValid() const override {
return it != NULL;
}
const uint8_t* GetBuffer(unsigned desiredLength) override {
const uint8_t* const itNext(it+desiredLength);
if (itNext >= end)
return NULL;
const uint8_t* const begin(it);
it = itNext;
return begin;
}
bool SkipBuffer(unsigned desiredLength) override {
return GetBuffer(desiredLength) != NULL;
}
private:
const uint8_t* it, * const end;
};
EXIFStreamBuffer stream(buf, len);
return parseFrom(stream);
} }
// //
@@ -851,7 +873,7 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
if ((strEnd=Tools::strrnstr(strXMP, "<?xpacket end=", len)) != NULL) if ((strEnd=Tools::strrnstr(strXMP, "<?xpacket end=", len)) != NULL)
len = (unsigned)(strEnd - strXMP); len = (unsigned)(strEnd - strXMP);
// Now 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(strXMP, len) != tinyxml2::XML_SUCCESS ||
@@ -860,7 +882,26 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
(document=document->FirstChildElement("rdf:Description")) == NULL) (document=document->FirstChildElement("rdf:Description")) == NULL)
return PARSE_ABSENT_DATA; return PARSE_ABSENT_DATA;
// Now try parsing the XMP content for projection type. // Try parsing the XMP content for tiff details.
if (Orientation == 0) {
uint32_t _Orientation(0);
document->QueryUnsignedAttribute("tiff:Orientation", &_Orientation);
Orientation = (uint16_t)_Orientation;
}
if (ImageWidth == 0 && ImageHeight == 0) {
document->QueryUnsignedAttribute("tiff:ImageWidth", &ImageWidth);
if (document->QueryUnsignedAttribute("tiff:ImageHeight", &ImageHeight) != tinyxml2::XML_SUCCESS)
document->QueryUnsignedAttribute("tiff:ImageLength", &ImageHeight) ;
}
if (XResolution == 0 && YResolution == 0 && ResolutionUnit == 0) {
document->QueryDoubleAttribute("tiff:XResolution", &XResolution);
document->QueryDoubleAttribute("tiff:YResolution", &YResolution);
uint32_t _ResolutionUnit(0);
document->QueryUnsignedAttribute("tiff:ResolutionUnit", &_ResolutionUnit);
ResolutionUnit = (uint16_t)_ResolutionUnit;
}
// Try parsing the XMP content for projection type.
{ {
const tinyxml2::XMLElement* const element(document->FirstChildElement("GPano:ProjectionType")); const tinyxml2::XMLElement* const element(document->FirstChildElement("GPano:ProjectionType"));
if (element != NULL) { if (element != NULL) {
@@ -875,7 +916,7 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
} }
} }
// Now try parsing the XMP content for DJI info. // Try parsing the XMP content for DJI info.
document->QueryDoubleAttribute("drone-dji:AbsoluteAltitude", &GeoLocation.Altitude); document->QueryDoubleAttribute("drone-dji:AbsoluteAltitude", &GeoLocation.Altitude);
document->QueryDoubleAttribute("drone-dji:RelativeAltitude", &GeoLocation.RelativeAltitude); document->QueryDoubleAttribute("drone-dji:RelativeAltitude", &GeoLocation.RelativeAltitude);
document->QueryDoubleAttribute("drone-dji:GimbalRollDegree", &GeoLocation.RollDegree); document->QueryDoubleAttribute("drone-dji:GimbalRollDegree", &GeoLocation.RollDegree);
@@ -887,7 +928,7 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
void EXIFInfo::Geolocation_t::parseCoords() { void EXIFInfo::Geolocation_t::parseCoords() {
// convert GPS latitude // Convert GPS latitude
if (LatComponents.degrees != DBL_MAX || if (LatComponents.degrees != DBL_MAX ||
LatComponents.minutes != 0 || LatComponents.minutes != 0 ||
LatComponents.seconds != 0) { LatComponents.seconds != 0) {
@@ -898,7 +939,7 @@ void EXIFInfo::Geolocation_t::parseCoords() {
if ('S' == LatComponents.direction) if ('S' == LatComponents.direction)
Latitude = -Latitude; Latitude = -Latitude;
} }
// convert GPS longitude // Convert GPS longitude
if (LonComponents.degrees != DBL_MAX || if (LonComponents.degrees != DBL_MAX ||
LonComponents.minutes != 0 || LonComponents.minutes != 0 ||
LonComponents.seconds != 0) { LonComponents.seconds != 0) {
@@ -909,7 +950,7 @@ void EXIFInfo::Geolocation_t::parseCoords() {
if ('W' == LonComponents.direction) if ('W' == LonComponents.direction)
Longitude = -Longitude; Longitude = -Longitude;
} }
// convert GPS altitude // Convert GPS altitude
if (hasAltitude() && if (hasAltitude() &&
AltitudeRef == 1) { AltitudeRef == 1) {
Altitude = -Altitude; Altitude = -Altitude;
@@ -951,6 +992,9 @@ void EXIFInfo::clear() {
RelatedImageWidth = 0; RelatedImageWidth = 0;
RelatedImageHeight= 0; RelatedImageHeight= 0;
Orientation = 0; Orientation = 0;
XResolution = 0;
YResolution = 0;
ResolutionUnit = 0;
BitsPerSample = 0; BitsPerSample = 0;
ExposureTime = 0; ExposureTime = 0;
FNumber = 0; FNumber = 0;

View File

@@ -13,9 +13,9 @@
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
-- Redistributions of source code must retain the above copyright notice, - Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer. this list of conditions and the following disclaimer.
-- Redistributions in binary form must reproduce the above copyright notice, - Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
@@ -39,7 +39,7 @@
#define TINYEXIF_MAJOR_VERSION 1 #define TINYEXIF_MAJOR_VERSION 1
#define TINYEXIF_MINOR_VERSION 0 #define TINYEXIF_MINOR_VERSION 0
#define TINYEXIF_PATCH_VERSION 0 #define TINYEXIF_PATCH_VERSION 1
#ifdef _MSC_VER #ifdef _MSC_VER
# ifdef TINYEXIF_EXPORT # ifdef TINYEXIF_EXPORT
@@ -75,22 +75,42 @@ enum FieldCode {
class EntryParser; class EntryParser;
// //
// Class responsible for storing and parsing EXIF information from a JPEG blob // Interface class responsible for fetching stream data to be parsed
//
class TINYEXIF_LIB EXIFStream {
public:
virtual ~EXIFStream() {}
// Check the state of the stream.
virtual bool IsValid() const = 0;
// Return the pointer to the beginning of the desired size buffer
// following current buffer position.
virtual const uint8_t* GetBuffer(unsigned desiredLength) = 0;
// Advance current buffer position with the desired size;
// return false if stream ends in less than the desired size.
virtual bool SkipBuffer(unsigned desiredLength) = 0;
};
//
// Class responsible for storing and parsing EXIF & XMP metadata from a JPEG stream
// //
class TINYEXIF_LIB EXIFInfo { class TINYEXIF_LIB EXIFInfo {
public: public:
EXIFInfo(); EXIFInfo();
EXIFInfo(EXIFStream& stream);
EXIFInfo(const uint8_t* data, unsigned length); EXIFInfo(const uint8_t* data, unsigned length);
EXIFInfo(const std::string& data);
// Parsing function for an entire JPEG image stream. // Parsing function for an entire JPEG image stream.
// //
// PARAM 'stream': Interface to fetch JPEG image stream.
// PARAM 'data': A pointer to a JPEG image. // PARAM 'data': A pointer to a JPEG image.
// PARAM 'length': The length of the JPEG image. // PARAM 'length': The length of the JPEG image.
// RETURN: PARSE_SUCCESS (0) on success with 'result' filled out // RETURN: PARSE_SUCCESS (0) on success with 'result' filled out
// error code otherwise, as defined by the PARSE_* macros // error code otherwise, as defined by the PARSE_* macros
int parseFrom(EXIFStream& stream);
int parseFrom(const uint8_t* data, unsigned length); int parseFrom(const uint8_t* data, unsigned length);
int parseFrom(const std::string& data);
// Parsing function for an EXIF segment. This is used internally by parseFrom() // Parsing function for an EXIF segment. This is used internally by parseFrom()
// but can be called for special cases where only the EXIF section is // but can be called for special cases where only the EXIF section is

View File

@@ -9,28 +9,49 @@
#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) {
std::cout << "Usage: TinyEXIF <image_file>" << "\n"; std::cout << "Usage: TinyEXIF <image_file>\n";
return -1; return -1;
} }
// read entire image file // read entire image file
std::ifstream file(argv[1], std::ifstream::in|std::ifstream::binary); EXIFStreamFile stream(argv[1]);
if (!file.is_open()) { if (!stream.IsValid()) {
std::cout << "error: can not open '" << argv[1] << "'\n"; std::cout << "error: can not open '" << argv[1] << "'\n";
return -2; return -2;
} }
file.seekg(0,std::ios::end);
std::streampos length = file.tellg();
file.seekg(0,std::ios::beg);
std::vector<uint8_t> data(length);
file.read((char*)data.data(), length);
// parse image EXIF and XMP metadata // parse image EXIF and XMP metadata
TinyEXIF::EXIFInfo imageEXIF(data.data(), (unsigned)length); TinyEXIF::EXIFInfo imageEXIF(stream);
if (imageEXIF.Fields) { if (!imageEXIF.Fields) {
std::cout << "error: no EXIF or XMP metadata\n";
return -3;
}
// print extracted metadata
if (imageEXIF.ImageWidth || imageEXIF.ImageHeight) if (imageEXIF.ImageWidth || imageEXIF.ImageHeight)
std::cout << "ImageResolution " << imageEXIF.ImageWidth << "x" << imageEXIF.ImageHeight << " pixels" << "\n"; std::cout << "ImageResolution " << imageEXIF.ImageWidth << "x" << imageEXIF.ImageHeight << " pixels" << "\n";
if (imageEXIF.RelatedImageWidth || imageEXIF.RelatedImageHeight) if (imageEXIF.RelatedImageWidth || imageEXIF.RelatedImageHeight)
@@ -41,9 +62,11 @@ int main(int argc, const char** argv)
std::cout << "CameraModel " << imageEXIF.Make << " - " << imageEXIF.Model << "\n"; std::cout << "CameraModel " << imageEXIF.Make << " - " << imageEXIF.Model << "\n";
if (!imageEXIF.SerialNumber.empty()) if (!imageEXIF.SerialNumber.empty())
std::cout << "SerialNumber " << imageEXIF.SerialNumber << "\n"; std::cout << "SerialNumber " << imageEXIF.SerialNumber << "\n";
if (imageEXIF.Orientation)
std::cout << "Orientation " << imageEXIF.Orientation << "\n"; std::cout << "Orientation " << imageEXIF.Orientation << "\n";
std::cout << "Resolution " << imageEXIF.XResolution << "x" << imageEXIF.YResolution << "\n"; if (imageEXIF.XResolution || imageEXIF.YResolution || imageEXIF.ResolutionUnit)
std::cout << "ResolutionUnit " << imageEXIF.ResolutionUnit << "\n"; std::cout << "Resolution " << imageEXIF.XResolution << "x" << imageEXIF.YResolution << " (" << imageEXIF.ResolutionUnit << ")\n";
if (imageEXIF.BitsPerSample)
std::cout << "BitsPerSample " << imageEXIF.BitsPerSample << "\n"; std::cout << "BitsPerSample " << imageEXIF.BitsPerSample << "\n";
if (!imageEXIF.Software.empty()) if (!imageEXIF.Software.empty())
std::cout << "Software " << imageEXIF.Software << "\n"; std::cout << "Software " << imageEXIF.Software << "\n";
@@ -111,6 +134,5 @@ 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";
} return EXIT_SUCCESS;
return 0;
} }