add status field

This commit is contained in:
cDc
2017-08-12 17:26:28 +03:00
parent 902c1a08fb
commit 4c2f68ef5c
4 changed files with 82 additions and 66 deletions

View File

@@ -27,8 +27,8 @@ int main(int argc, const char** argv) {
file.read((char*)data.data(), length); file.read((char*)data.data(), length);
// parse image EXIF and XMP metadata // parse image EXIF and XMP metadata
TinyEXIF::EXIFInfo imageEXIF; TinyEXIF::EXIFInfo imageEXIF(data.data(), length);
if (imageEXIF.parseFrom(data.data(), length) == TinyEXIF::PARSE_EXIF_SUCCESS) if (imageEXIF.Fields)
std::cout std::cout
<< "Image Description " << imageEXIF.ImageDescription << "\n" << "Image Description " << imageEXIF.ImageDescription << "\n"
<< "Image Resolution " << imageEXIF.ImageWidth << "x" << imageEXIF.ImageHeight << " pixels\n" << "Image Resolution " << imageEXIF.ImageWidth << "x" << imageEXIF.ImageHeight << " pixels\n"

View File

@@ -36,6 +36,7 @@
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cmath> #include <cmath>
#include <cfloat>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
@@ -252,6 +253,17 @@ public:
}; };
// Constructors
EXIFInfo::EXIFInfo() : Fields(FIELD_NA) {
}
EXIFInfo::EXIFInfo(const uint8_t* data, unsigned length) {
parseFrom(data, length);
}
EXIFInfo::EXIFInfo(const std::string& data) {
parseFrom(data);
}
// Parse tag as Image IFD // Parse tag as Image IFD
void EXIFInfo::parseIFDImage(EntryParser& parser, unsigned& exif_sub_ifd_offset, unsigned& gps_sub_ifd_offset) { void EXIFInfo::parseIFDImage(EntryParser& parser, unsigned& exif_sub_ifd_offset, unsigned& gps_sub_ifd_offset) {
switch (parser.GetTag()) { switch (parser.GetTag()) {
@@ -597,31 +609,27 @@ void EXIFInfo::parseIFDGPS(EntryParser& parser) {
// parseFromEXIFSegment() or parseFromXMPSegment() // parseFromEXIFSegment() or parseFromXMPSegment()
// //
int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) { int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
clear();
// 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) if (!buf || len < 16)
return PARSE_EXIF_ERROR_NO_EXIF; return PARSE_INVALID_JPEG;
if (buf[0] != JM_START || buf[1] != JM_SOI) if (buf[0] != JM_START || buf[1] != JM_SOI)
return PARSE_EXIF_ERROR_NO_JPEG; return PARSE_INVALID_JPEG;
// not always valid, sometimes 0xFF is added for padding // not always valid, sometimes 0xFF is added for padding
//if (buf[len-2] != JM_START || buf[len-1] != JM_EOI) //if (buf[len-2] != JM_START || buf[len-1] != JM_EOI)
// return PARSE_EXIF_ERROR_NO_JPEG; // 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.
enum {
APP1_NA = 0,
APP1_EXIF = (1 << 0),
APP1_XMP = (1 << 1),
APP1_ALL = APP1_EXIF|APP1_XMP
};
struct APP1S { struct APP1S {
uint32_t val; uint32_t& val;
inline APP1S() : val(APP1_NA) {} inline APP1S(uint32_t& v) : val(v) {}
inline operator uint32_t () const { return val; } inline operator uint32_t () const { return val; }
inline operator uint32_t& () { return val; } inline operator uint32_t& () { return val; }
inline int operator () (int code=PARSE_EXIF_ERROR_NO_EXIF) const { return (val&APP1_EXIF) == 0 ? code : (int)PARSE_EXIF_SUCCESS; } inline int operator () (int code=PARSE_ABSENT_DATA) const { return val&FIELD_ALL ? (int)PARSE_SUCCESS : code; }
} app1s; } app1s(Fields);
for (unsigned pos=2; pos<len; ) { for (unsigned pos=2; pos<len; ) {
// find next marker // find next marker
uint8_t marker, prev(0); uint8_t marker, prev(0);
@@ -652,21 +660,21 @@ int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
const uint16_t section_length(EntryParser::parse16(buf + pos, false)); const uint16_t section_length(EntryParser::parse16(buf + pos, false));
int ret; int ret;
switch (ret=parseFromEXIFSegment(buf + pos + 2, section_length - 2)) { switch (ret=parseFromEXIFSegment(buf + pos + 2, section_length - 2)) {
case PARSE_EXIF_ERROR_NO_EXIF: case PARSE_ABSENT_DATA:
switch (ret=parseFromXMPSegment(buf + pos + 2, section_length - 2)) { switch (ret=parseFromXMPSegment(buf + pos + 2, section_length - 2)) {
case PARSE_EXIF_ERROR_NO_XMP: case PARSE_ABSENT_DATA:
break; break;
case PARSE_EXIF_SUCCESS: case PARSE_SUCCESS:
if ((app1s|=APP1_XMP) == APP1_ALL) if ((app1s|=FIELD_XMP) == FIELD_ALL)
return PARSE_EXIF_SUCCESS; return PARSE_SUCCESS;
break; break;
default: default:
return app1s(ret); // some error return app1s(ret); // some error
} }
break; break;
case PARSE_EXIF_SUCCESS: case PARSE_SUCCESS:
if ((app1s|=APP1_EXIF) == APP1_ALL) if ((app1s|=FIELD_EXIF) == FIELD_ALL)
return PARSE_EXIF_SUCCESS; return PARSE_SUCCESS;
break; break;
default: default:
return app1s(ret); // some error return app1s(ret); // some error
@@ -676,7 +684,7 @@ int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
// read section length // read section length
const uint16_t section_length(EntryParser::parse16(buf + pos, false)); const uint16_t section_length(EntryParser::parse16(buf + pos, false));
if (pos + section_length > len) if (pos + section_length > len)
return app1s(PARSE_EXIF_ERROR_NO_JPEG); return app1s(PARSE_INVALID_JPEG);
// skip the section // skip the section
pos += section_length; pos += section_length;
} }
@@ -705,13 +713,11 @@ int EXIFInfo::parseFrom(const std::string& data) {
// PARAM: 'len' length of buffer // PARAM: 'len' length of buffer
// //
int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) { int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
unsigned offs = 0; // current offset into buffer unsigned offs = 6; // current offset into buffer
if (!buf || len < 6) if (!buf || len < offs)
return PARSE_EXIF_ERROR_NO_EXIF; return PARSE_ABSENT_DATA;
if (!std::equal(buf, buf+offs, "Exif\0\0"))
if (!std::equal(buf, buf+6, "Exif\0\0")) return PARSE_ABSENT_DATA;
return PARSE_EXIF_ERROR_NO_EXIF;
offs += 6;
// Now parsing the TIFF header. The first two bytes are either "II" or // Now parsing the TIFF header. The first two bytes are either "II" or
// "MM" for Intel or Motorola byte alignment. Sanity check by parsing // "MM" for Intel or Motorola byte alignment. Sanity check by parsing
@@ -725,7 +731,7 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
// ----------------------------- // -----------------------------
// 8 bytes // 8 bytes
if (offs + 8 > len) if (offs + 8 > len)
return PARSE_EXIF_ERROR_CORRUPT; return PARSE_CORRUPT_DATA;
bool alignIntel; bool alignIntel;
if (buf[offs] == 'I' && buf[offs+1] == 'I') if (buf[offs] == 'I' && buf[offs+1] == 'I')
alignIntel = true; // 1: Intel byte alignment alignIntel = true; // 1: Intel byte alignment
@@ -733,16 +739,16 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
if (buf[offs] == 'M' && buf[offs+1] == 'M') if (buf[offs] == 'M' && buf[offs+1] == 'M')
alignIntel = false; // 0: Motorola byte alignment alignIntel = false; // 0: Motorola byte alignment
else else
return PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN; return PARSE_UNKNOWN_BYTEALIGN;
EntryParser parser(buf, len, offs, alignIntel); EntryParser parser(buf, len, offs, alignIntel);
offs += 2; offs += 2;
if (0x2a != EntryParser::parse16(buf + offs, alignIntel)) if (0x2a != EntryParser::parse16(buf + offs, alignIntel))
return PARSE_EXIF_ERROR_CORRUPT; return PARSE_CORRUPT_DATA;
offs += 2; offs += 2;
const unsigned first_ifd_offset = EntryParser::parse32(buf + offs, alignIntel); const unsigned first_ifd_offset = EntryParser::parse32(buf + offs, alignIntel);
offs += first_ifd_offset - 4; offs += first_ifd_offset - 4;
if (offs >= len) if (offs >= len)
return PARSE_EXIF_ERROR_CORRUPT; return PARSE_CORRUPT_DATA;
// Now parsing the first Image File Directory (IFD0, for the main image). // Now parsing the first Image File Directory (IFD0, for the main image).
// An IFD consists of a variable number of 12-byte directory entries. The // An IFD consists of a variable number of 12-byte directory entries. The
@@ -751,10 +757,10 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
// to the next IFD, which means this IFD must contain exactly 6 + 12 * num // to the next IFD, which means this IFD must contain exactly 6 + 12 * num
// bytes of data. // bytes of data.
if (offs + 2 > len) if (offs + 2 > len)
return PARSE_EXIF_ERROR_CORRUPT; return PARSE_CORRUPT_DATA;
int num_entries = EntryParser::parse16(buf + offs, alignIntel); int num_entries = EntryParser::parse16(buf + offs, alignIntel);
if (offs + 6 + 12 * num_entries > len) if (offs + 6 + 12 * num_entries > len)
return PARSE_EXIF_ERROR_CORRUPT; return PARSE_CORRUPT_DATA;
unsigned exif_sub_ifd_offset = len; unsigned exif_sub_ifd_offset = len;
unsigned gps_sub_ifd_offset = len; unsigned gps_sub_ifd_offset = len;
parser.Init(offs+2); parser.Init(offs+2);
@@ -771,7 +777,7 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
offs = exif_sub_ifd_offset; offs = exif_sub_ifd_offset;
int num_entries = EntryParser::parse16(buf + offs, alignIntel); int num_entries = EntryParser::parse16(buf + offs, alignIntel);
if (offs + 6 + 12 * num_entries > len) if (offs + 6 + 12 * num_entries > len)
return PARSE_EXIF_ERROR_CORRUPT; return PARSE_CORRUPT_DATA;
parser.Init(offs+2); parser.Init(offs+2);
while (--num_entries >= 0) { while (--num_entries >= 0) {
parser.ParseTag(); parser.ParseTag();
@@ -785,7 +791,7 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
offs = gps_sub_ifd_offset; offs = gps_sub_ifd_offset;
int num_entries = EntryParser::parse16(buf + offs, alignIntel); int num_entries = EntryParser::parse16(buf + offs, alignIntel);
if (offs + 6 + 12 * num_entries > len) if (offs + 6 + 12 * num_entries > len)
return PARSE_EXIF_ERROR_CORRUPT; return PARSE_CORRUPT_DATA;
parser.Init(offs+2); parser.Init(offs+2);
while (--num_entries >= 0) { while (--num_entries >= 0) {
parser.ParseTag(); parser.ParseTag();
@@ -794,7 +800,7 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
GeoLocation.parseCoords(); GeoLocation.parseCoords();
} }
return PARSE_EXIF_SUCCESS; return PARSE_SUCCESS;
} }
// //
@@ -824,15 +830,13 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
} }
}; };
unsigned offs = 0; // current offset into buffer unsigned offs = 29; // current offset into buffer
if (!buf || len < 29) if (!buf || len < offs)
return PARSE_EXIF_ERROR_NO_XMP; return PARSE_ABSENT_DATA;
if (!std::equal(buf, buf+offs, "http://ns.adobe.com/xap/1.0/\0"))
if (!std::equal(buf, buf+29, "http://ns.adobe.com/xap/1.0/\0")) return PARSE_ABSENT_DATA;
return PARSE_EXIF_ERROR_NO_XMP;
offs += 29;
if (offs >= len) if (offs >= len)
return PARSE_EXIF_ERROR_CORRUPT; return PARSE_CORRUPT_DATA;
len -= offs; len -= offs;
// Skip xpacket end section so that tinyxml2 lib parses the section correctly. // Skip xpacket end section so that tinyxml2 lib parses the section correctly.
@@ -847,7 +851,7 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
((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)
return PARSE_EXIF_SUCCESS; return PARSE_ABSENT_DATA;
// Now try parsing the XMP content for projection type. // Now try parsing the XMP content for projection type.
{ {
@@ -867,11 +871,11 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
// Now try parsing the XMP content for DJI info. // Now 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:FlightRollDegree", &GeoLocation.RollDegree); document->QueryDoubleAttribute("drone-dji:GimbalRollDegree", &GeoLocation.RollDegree);
document->QueryDoubleAttribute("drone-dji:FlightPitchDegree", &GeoLocation.PitchDegree); document->QueryDoubleAttribute("drone-dji:GimbalPitchDegree", &GeoLocation.PitchDegree);
document->QueryDoubleAttribute("drone-dji:FlightYawDegree", &GeoLocation.YawDegree); document->QueryDoubleAttribute("drone-dji:GimbalYawDegree", &GeoLocation.YawDegree);
return PARSE_EXIF_SUCCESS; return PARSE_SUCCESS;
} }
@@ -920,6 +924,8 @@ bool EXIFInfo::Geolocation_t::hasOrientation() const {
void EXIFInfo::clear() { void EXIFInfo::clear() {
Fields = FIELD_NA;
// Strings // Strings
ImageDescription = ""; ImageDescription = "";
Make = ""; Make = "";

View File

@@ -54,12 +54,18 @@
namespace TinyEXIF { namespace TinyEXIF {
enum ErrorCode { enum ErrorCode {
PARSE_EXIF_SUCCESS = 0, // Parse was successful PARSE_SUCCESS = 0, // Parse EXIF and/or XMP was successful
PARSE_EXIF_ERROR_NO_JPEG = 1, // No JPEG markers found in buffer, possibly invalid JPEG file PARSE_INVALID_JPEG = 1, // No JPEG markers found in buffer, possibly invalid JPEG file
PARSE_EXIF_ERROR_NO_EXIF = 2, // No EXIF header found in JPEG file PARSE_UNKNOWN_BYTEALIGN = 2, // Byte alignment specified in EXIF file was unknown (neither Motorola nor Intel)
PARSE_EXIF_ERROR_NO_XMP = 3, // No XMP header found in JPEG file PARSE_ABSENT_DATA = 3, // No EXIF and/or XMP data found in JPEG file
PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN = 4, // Byte alignment specified in EXIF file was unknown (not Motorola or Intel) PARSE_CORRUPT_DATA = 4, // EXIF and/or XMP header was found, but data was corrupted
PARSE_EXIF_ERROR_CORRUPT = 5, // EXIF header was found, but data was corrupted };
enum FieldCode {
FIELD_NA = 0, // No EXIF or XMP data
FIELD_EXIF = (1 << 0), // EXIF data available
FIELD_XMP = (1 << 1), // XMP data available
FIELD_ALL = FIELD_EXIF|FIELD_XMP
}; };
class EntryParser; class EntryParser;
@@ -69,14 +75,16 @@ class EntryParser;
// //
class TINYEXIF_LIB EXIFInfo { class TINYEXIF_LIB EXIFInfo {
public: public:
EXIFInfo() { clear(); } EXIFInfo();
EXIFInfo(const uint8_t* data, unsigned length);
EXIFInfo(const std::string& data);
// Parsing function for an entire JPEG image buffer. // Parsing function for an entire 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_EXIF_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_EXIF_ERROR_* macros // error code otherwise, as defined by the PARSE_* macros
int parseFrom(const uint8_t* data, unsigned length); int parseFrom(const uint8_t* data, unsigned length);
int parseFrom(const std::string& data); int parseFrom(const std::string& data);
@@ -91,6 +99,7 @@ public:
int parseFromXMPSegment(const uint8_t* buf, unsigned len); int parseFromXMPSegment(const uint8_t* buf, unsigned len);
// Set all data members to default values. // Set all data members to default values.
// Should be called before parsing a new stream.
void clear(); void clear();
private: private:
@@ -103,6 +112,7 @@ private:
public: public:
// Data fields // Data fields
uint32_t Fields; // Store if EXIF and/or XMP data fields are available
uint32_t ImageWidth; // Image width reported in EXIF data uint32_t ImageWidth; // Image width reported in EXIF data
uint32_t ImageHeight; // Image height reported in EXIF data uint32_t ImageHeight; // Image height reported in EXIF data
uint32_t RelatedImageWidth; // Original image width reported in EXIF data uint32_t RelatedImageWidth; // Original image width reported in EXIF data

View File

@@ -29,8 +29,8 @@ int main(int argc, const char** argv)
file.read((char*)data.data(), length); file.read((char*)data.data(), length);
// parse image EXIF and XMP metadata // parse image EXIF and XMP metadata
TinyEXIF::EXIFInfo imageEXIF; TinyEXIF::EXIFInfo imageEXIF(data.data(), (unsigned)length);
if (imageEXIF.parseFrom(data.data(), (unsigned)length) == TinyEXIF::PARSE_EXIF_SUCCESS) { if (imageEXIF.Fields) {
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)