add input stream interface (with override ex. for memory and file input)
This commit is contained in:
77
TinyEXIF.cpp
77
TinyEXIF.cpp
@@ -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,15 +635,16 @@ 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+2<=len; ) {
|
while ((buf=stream.GetBuffer(2)) != NULL) {
|
||||||
// find next marker;
|
// find next marker;
|
||||||
// in cases of markers appended after the compressed data,
|
// in cases of markers appended after the compressed data,
|
||||||
// optional JM_START fill bytes may precede the marker
|
// optional JM_START fill bytes may precede the marker
|
||||||
if (buf[pos++] != JM_START)
|
if (*buf++ != JM_START)
|
||||||
break;
|
break;
|
||||||
uint8_t marker;
|
uint8_t marker;
|
||||||
while ((marker=buf[pos++]) == JM_START && 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:
|
||||||
@@ -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,41 @@ 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:
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
return parseFrom(EXIFStreamBuffer(buf, len));
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
26
TinyEXIF.h
26
TinyEXIF.h
@@ -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
|
||||||
|
|||||||
44
main.cpp
44
main.cpp
@@ -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:
|
||||||
|
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)
|
||||||
@@ -113,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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user