diff --git a/TinyEXIF.cpp b/TinyEXIF.cpp index 974ce17..5e1800c 100644 --- a/TinyEXIF.cpp +++ b/TinyEXIF.cpp @@ -258,12 +258,12 @@ public: // Constructors EXIFInfo::EXIFInfo() : Fields(FIELD_NA) { } +EXIFInfo::EXIFInfo(EXIFStream& stream) { + parseFrom(stream); +} EXIFInfo::EXIFInfo(const uint8_t* data, unsigned length) { parseFrom(data, length); } -EXIFInfo::EXIFInfo(const std::string& data) { - parseFrom(data); -} // Parse tag as Image IFD @@ -615,18 +615,16 @@ void EXIFInfo::parseIFDGPS(EntryParser& parser) { // Locates the JM_APP1 segment and parses it using // parseFromEXIFSegment() or parseFromXMPSegment() // -int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) { +int EXIFInfo::parseFrom(EXIFStream& stream) { clear(); + if (!stream.IsValid()) + return PARSE_INVALID_JPEG; // 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. - 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; - 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. // 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 int operator () (int code=PARSE_ABSENT_DATA) const { return val&FIELD_ALL ? (int)PARSE_SUCCESS : code; } } app1s(Fields); - for (unsigned pos=2; pos+2<=len; ) { + while ((buf=stream.GetBuffer(2)) != NULL) { // find next marker; // in cases of markers appended after the compressed data, // optional JM_START fill bytes may precede the marker - if (buf[pos++] != JM_START) + if (*buf++ != JM_START) break; uint8_t marker; - while ((marker=buf[pos++]) == JM_START && pos len) - return app1s(PARSE_INVALID_JPEG); + break; + default: // 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(); } -int EXIFInfo::parseFrom(const std::string& data) { - return parseFrom((const uint8_t*)data.data(), (unsigned)data.length()); +int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) { + 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)); } // diff --git a/TinyEXIF.h b/TinyEXIF.h index 80aa49d..12b8cd9 100644 --- a/TinyEXIF.h +++ b/TinyEXIF.h @@ -75,22 +75,42 @@ enum FieldCode { 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 { public: EXIFInfo(); + EXIFInfo(EXIFStream& stream); EXIFInfo(const uint8_t* data, unsigned length); - EXIFInfo(const std::string& data); // 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 'length': The length of the JPEG image. // 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(const uint8_t* data, unsigned length); - int parseFrom(const std::string& data); // 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 diff --git a/main.cpp b/main.cpp index 05984bf..5e1e1b5 100644 --- a/main.cpp +++ b/main.cpp @@ -9,110 +9,130 @@ #include // std::vector #include // 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 buffer; +}; + int main(int argc, const char** argv) { if (argc != 2) { - std::cout << "Usage: TinyEXIF " << "\n"; + std::cout << "Usage: TinyEXIF \n"; return -1; } // read entire image file - std::ifstream file(argv[1], std::ifstream::in|std::ifstream::binary); - if (!file.is_open()) { + EXIFStreamFile stream(argv[1]); + if (!stream.IsValid()) { std::cout << "error: can not open '" << argv[1] << "'\n"; return -2; } - file.seekg(0,std::ios::end); - std::streampos length = file.tellg(); - file.seekg(0,std::ios::beg); - std::vector data(length); - file.read((char*)data.data(), length); // parse image EXIF and XMP metadata - TinyEXIF::EXIFInfo imageEXIF(data.data(), (unsigned)length); - if (imageEXIF.Fields) { - if (imageEXIF.ImageWidth || imageEXIF.ImageHeight) - std::cout << "ImageResolution " << imageEXIF.ImageWidth << "x" << imageEXIF.ImageHeight << " pixels" << "\n"; - if (imageEXIF.RelatedImageWidth || imageEXIF.RelatedImageHeight) - std::cout << "RelatedImageResolution " << imageEXIF.RelatedImageWidth << "x" << imageEXIF.RelatedImageHeight << " pixels" << "\n"; - if (!imageEXIF.ImageDescription.empty()) - std::cout << "Description " << imageEXIF.ImageDescription << "\n"; - if (!imageEXIF.Make.empty() || !imageEXIF.Model.empty()) - std::cout << "CameraModel " << imageEXIF.Make << " - " << imageEXIF.Model << "\n"; - if (!imageEXIF.SerialNumber.empty()) - std::cout << "SerialNumber " << imageEXIF.SerialNumber << "\n"; - if (imageEXIF.Orientation) - std::cout << "Orientation " << imageEXIF.Orientation << "\n"; - if (imageEXIF.XResolution || imageEXIF.YResolution || imageEXIF.ResolutionUnit) - std::cout << "Resolution " << imageEXIF.XResolution << "x" << imageEXIF.YResolution << " (" << imageEXIF.ResolutionUnit << ")\n"; - if (imageEXIF.BitsPerSample) - std::cout << "BitsPerSample " << imageEXIF.BitsPerSample << "\n"; - if (!imageEXIF.Software.empty()) - std::cout << "Software " << imageEXIF.Software << "\n"; - if (!imageEXIF.DateTime.empty()) - std::cout << "DateTime " << imageEXIF.DateTime << "\n"; - if (!imageEXIF.DateTimeOriginal.empty()) - std::cout << "DateTimeOriginal " << imageEXIF.DateTimeOriginal << "\n"; - if (!imageEXIF.DateTimeDigitized.empty()) - std::cout << "DateTimeDigitized " << imageEXIF.DateTimeDigitized << "\n"; - if (!imageEXIF.SubSecTimeOriginal.empty()) - std::cout << "SubSecTimeOriginal " << imageEXIF.SubSecTimeOriginal << "\n"; - if (!imageEXIF.Copyright.empty()) - std::cout << "Copyright " << imageEXIF.Copyright << "\n"; - std::cout << "ExposureTime " << std::setprecision(10) << imageEXIF.ExposureTime << " s" << "\n"; - std::cout << "FNumber " << imageEXIF.FNumber << "\n"; - std::cout << "ExposureProgram " << imageEXIF.ExposureProgram << "\n"; - std::cout << "ISOSpeed " << imageEXIF.ISOSpeedRatings << "\n"; - std::cout << "ShutterSpeedValue " << std::setprecision(10) << imageEXIF.ShutterSpeedValue << "\n"; - std::cout << "ApertureValue " << std::setprecision(10) << imageEXIF.ApertureValue << "\n"; - std::cout << "BrightnessValue " << std::setprecision(10) << imageEXIF.BrightnessValue << "\n"; - std::cout << "ExposureBiasValue " << imageEXIF.ExposureBiasValue << "\n"; - std::cout << "SubjectDistance " << imageEXIF.SubjectDistance << "\n"; - std::cout << "FocalLength " << imageEXIF.FocalLength << " mm" << "\n"; - std::cout << "Flash " << imageEXIF.Flash << "\n"; - if (!imageEXIF.SubjectArea.empty()) { - std::cout << "SubjectArea"; - for (uint16_t val: imageEXIF.SubjectArea) - std::cout << " " << val; - std::cout << "\n"; - } - std::cout << "MeteringMode " << imageEXIF.MeteringMode << "\n"; - std::cout << "LightSource " << imageEXIF.LightSource << "\n"; - std::cout << "ProjectionType " << imageEXIF.ProjectionType << "\n"; - std::cout << "LensInfo.FStopMin " << imageEXIF.LensInfo.FStopMin << "\n"; - std::cout << "LensInfo.FStopMax " << imageEXIF.LensInfo.FStopMax << "\n"; - std::cout << "LensInfo.FocalLengthMin " << imageEXIF.LensInfo.FocalLengthMin << " mm" << "\n"; - std::cout << "LensInfo.FocalLengthMax " << imageEXIF.LensInfo.FocalLengthMax << " mm" << "\n"; - std::cout << "LensInfo.DigitalZoomRatio " << imageEXIF.LensInfo.DigitalZoomRatio << "\n"; - std::cout << "LensInfo.FocalLengthIn35mm " << imageEXIF.LensInfo.FocalLengthIn35mm << "\n"; - std::cout << "LensInfo.FocalPlaneXResolution " << std::setprecision(10) << imageEXIF.LensInfo.FocalPlaneXResolution << "\n"; - std::cout << "LensInfo.FocalPlaneYResolution " << std::setprecision(10) << imageEXIF.LensInfo.FocalPlaneYResolution << "\n"; - std::cout << "LensInfo.FocalPlaneResolutionUnit " << imageEXIF.LensInfo.FocalPlaneResolutionUnit << "\n"; - if (!imageEXIF.LensInfo.Make.empty() || !imageEXIF.LensInfo.Model.empty()) - std::cout << "LensInfo.Model " << imageEXIF.LensInfo.Make << " - " << imageEXIF.LensInfo.Model << "\n"; - if (imageEXIF.GeoLocation.hasLatLon()) { - std::cout << "GeoLocation.Latitude " << std::setprecision(10) << imageEXIF.GeoLocation.Latitude << "\n"; - std::cout << "GeoLocation.Longitude " << std::setprecision(10) << imageEXIF.GeoLocation.Longitude << "\n"; - } - if (imageEXIF.GeoLocation.hasAltitude()) { - std::cout << "GeoLocation.Altitude " << imageEXIF.GeoLocation.Altitude << " m" << "\n"; - std::cout << "GeoLocation.AltitudeRef " << (int)imageEXIF.GeoLocation.AltitudeRef << "\n"; - } - if (imageEXIF.GeoLocation.hasRelativeAltitude()) - std::cout << "GeoLocation.RelativeAltitude " << imageEXIF.GeoLocation.RelativeAltitude << "\n"; - if (imageEXIF.GeoLocation.hasOrientation()) { - std::cout << "GeoLocation.RollDegree " << imageEXIF.GeoLocation.RollDegree << "\n"; - std::cout << "GeoLocation.PitchDegree " << imageEXIF.GeoLocation.PitchDegree << "\n"; - std::cout << "GeoLocation.YawDegree " << imageEXIF.GeoLocation.YawDegree << "\n"; - } - std::cout << "GeoLocation.GPSDOP " << imageEXIF.GeoLocation.GPSDOP << "\n"; - std::cout << "GeoLocation.GPSDifferential " << imageEXIF.GeoLocation.GPSDifferential << "\n"; - if (!imageEXIF.GeoLocation.GPSMapDatum.empty()) - std::cout << "GeoLocation.GPSMapDatum " << imageEXIF.GeoLocation.GPSMapDatum << "\n"; - if (!imageEXIF.GeoLocation.GPSTimeStamp.empty()) - std::cout << "GeoLocation.GPSTimeStamp " << imageEXIF.GeoLocation.GPSTimeStamp << "\n"; - if (!imageEXIF.GeoLocation.GPSDateStamp.empty()) - std::cout << "GeoLocation.GPSDateStamp " << imageEXIF.GeoLocation.GPSDateStamp << "\n"; + TinyEXIF::EXIFInfo imageEXIF(stream); + if (!imageEXIF.Fields) { + std::cout << "error: no EXIF or XMP metadata\n"; + return -3; } - return 0; + + // print extracted metadata + if (imageEXIF.ImageWidth || imageEXIF.ImageHeight) + std::cout << "ImageResolution " << imageEXIF.ImageWidth << "x" << imageEXIF.ImageHeight << " pixels" << "\n"; + if (imageEXIF.RelatedImageWidth || imageEXIF.RelatedImageHeight) + std::cout << "RelatedImageResolution " << imageEXIF.RelatedImageWidth << "x" << imageEXIF.RelatedImageHeight << " pixels" << "\n"; + if (!imageEXIF.ImageDescription.empty()) + std::cout << "Description " << imageEXIF.ImageDescription << "\n"; + if (!imageEXIF.Make.empty() || !imageEXIF.Model.empty()) + std::cout << "CameraModel " << imageEXIF.Make << " - " << imageEXIF.Model << "\n"; + if (!imageEXIF.SerialNumber.empty()) + std::cout << "SerialNumber " << imageEXIF.SerialNumber << "\n"; + if (imageEXIF.Orientation) + std::cout << "Orientation " << imageEXIF.Orientation << "\n"; + if (imageEXIF.XResolution || imageEXIF.YResolution || imageEXIF.ResolutionUnit) + std::cout << "Resolution " << imageEXIF.XResolution << "x" << imageEXIF.YResolution << " (" << imageEXIF.ResolutionUnit << ")\n"; + if (imageEXIF.BitsPerSample) + std::cout << "BitsPerSample " << imageEXIF.BitsPerSample << "\n"; + if (!imageEXIF.Software.empty()) + std::cout << "Software " << imageEXIF.Software << "\n"; + if (!imageEXIF.DateTime.empty()) + std::cout << "DateTime " << imageEXIF.DateTime << "\n"; + if (!imageEXIF.DateTimeOriginal.empty()) + std::cout << "DateTimeOriginal " << imageEXIF.DateTimeOriginal << "\n"; + if (!imageEXIF.DateTimeDigitized.empty()) + std::cout << "DateTimeDigitized " << imageEXIF.DateTimeDigitized << "\n"; + if (!imageEXIF.SubSecTimeOriginal.empty()) + std::cout << "SubSecTimeOriginal " << imageEXIF.SubSecTimeOriginal << "\n"; + if (!imageEXIF.Copyright.empty()) + std::cout << "Copyright " << imageEXIF.Copyright << "\n"; + std::cout << "ExposureTime " << std::setprecision(10) << imageEXIF.ExposureTime << " s" << "\n"; + std::cout << "FNumber " << imageEXIF.FNumber << "\n"; + std::cout << "ExposureProgram " << imageEXIF.ExposureProgram << "\n"; + std::cout << "ISOSpeed " << imageEXIF.ISOSpeedRatings << "\n"; + std::cout << "ShutterSpeedValue " << std::setprecision(10) << imageEXIF.ShutterSpeedValue << "\n"; + std::cout << "ApertureValue " << std::setprecision(10) << imageEXIF.ApertureValue << "\n"; + std::cout << "BrightnessValue " << std::setprecision(10) << imageEXIF.BrightnessValue << "\n"; + std::cout << "ExposureBiasValue " << imageEXIF.ExposureBiasValue << "\n"; + std::cout << "SubjectDistance " << imageEXIF.SubjectDistance << "\n"; + std::cout << "FocalLength " << imageEXIF.FocalLength << " mm" << "\n"; + std::cout << "Flash " << imageEXIF.Flash << "\n"; + if (!imageEXIF.SubjectArea.empty()) { + std::cout << "SubjectArea"; + for (uint16_t val: imageEXIF.SubjectArea) + std::cout << " " << val; + std::cout << "\n"; + } + std::cout << "MeteringMode " << imageEXIF.MeteringMode << "\n"; + std::cout << "LightSource " << imageEXIF.LightSource << "\n"; + std::cout << "ProjectionType " << imageEXIF.ProjectionType << "\n"; + std::cout << "LensInfo.FStopMin " << imageEXIF.LensInfo.FStopMin << "\n"; + std::cout << "LensInfo.FStopMax " << imageEXIF.LensInfo.FStopMax << "\n"; + std::cout << "LensInfo.FocalLengthMin " << imageEXIF.LensInfo.FocalLengthMin << " mm" << "\n"; + std::cout << "LensInfo.FocalLengthMax " << imageEXIF.LensInfo.FocalLengthMax << " mm" << "\n"; + std::cout << "LensInfo.DigitalZoomRatio " << imageEXIF.LensInfo.DigitalZoomRatio << "\n"; + std::cout << "LensInfo.FocalLengthIn35mm " << imageEXIF.LensInfo.FocalLengthIn35mm << "\n"; + std::cout << "LensInfo.FocalPlaneXResolution " << std::setprecision(10) << imageEXIF.LensInfo.FocalPlaneXResolution << "\n"; + std::cout << "LensInfo.FocalPlaneYResolution " << std::setprecision(10) << imageEXIF.LensInfo.FocalPlaneYResolution << "\n"; + std::cout << "LensInfo.FocalPlaneResolutionUnit " << imageEXIF.LensInfo.FocalPlaneResolutionUnit << "\n"; + if (!imageEXIF.LensInfo.Make.empty() || !imageEXIF.LensInfo.Model.empty()) + std::cout << "LensInfo.Model " << imageEXIF.LensInfo.Make << " - " << imageEXIF.LensInfo.Model << "\n"; + if (imageEXIF.GeoLocation.hasLatLon()) { + std::cout << "GeoLocation.Latitude " << std::setprecision(10) << imageEXIF.GeoLocation.Latitude << "\n"; + std::cout << "GeoLocation.Longitude " << std::setprecision(10) << imageEXIF.GeoLocation.Longitude << "\n"; + } + if (imageEXIF.GeoLocation.hasAltitude()) { + std::cout << "GeoLocation.Altitude " << imageEXIF.GeoLocation.Altitude << " m" << "\n"; + std::cout << "GeoLocation.AltitudeRef " << (int)imageEXIF.GeoLocation.AltitudeRef << "\n"; + } + if (imageEXIF.GeoLocation.hasRelativeAltitude()) + std::cout << "GeoLocation.RelativeAltitude " << imageEXIF.GeoLocation.RelativeAltitude << "\n"; + if (imageEXIF.GeoLocation.hasOrientation()) { + std::cout << "GeoLocation.RollDegree " << imageEXIF.GeoLocation.RollDegree << "\n"; + std::cout << "GeoLocation.PitchDegree " << imageEXIF.GeoLocation.PitchDegree << "\n"; + std::cout << "GeoLocation.YawDegree " << imageEXIF.GeoLocation.YawDegree << "\n"; + } + std::cout << "GeoLocation.GPSDOP " << imageEXIF.GeoLocation.GPSDOP << "\n"; + std::cout << "GeoLocation.GPSDifferential " << imageEXIF.GeoLocation.GPSDifferential << "\n"; + if (!imageEXIF.GeoLocation.GPSMapDatum.empty()) + std::cout << "GeoLocation.GPSMapDatum " << imageEXIF.GeoLocation.GPSMapDatum << "\n"; + if (!imageEXIF.GeoLocation.GPSTimeStamp.empty()) + std::cout << "GeoLocation.GPSTimeStamp " << imageEXIF.GeoLocation.GPSTimeStamp << "\n"; + if (!imageEXIF.GeoLocation.GPSDateStamp.empty()) + std::cout << "GeoLocation.GPSDateStamp " << imageEXIF.GeoLocation.GPSDateStamp << "\n"; + return EXIT_SUCCESS; }