/*
  TinyEXIF.cpp -- A simple ISO C++ library to parse basic EXIF and XMP
                  information from a JPEG file.

  Copyright (c) 2015-2025 Seacave
  cdc.seacave@gmail.com
  MIT License
*/

#include "TinyEXIF.h"
#include <cstddef>
#include <cstdio>
#include <cmath>
#include <cfloat>
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>

#ifndef TINYEXIF_NO_XMP_SUPPORT
#include <tinyxml2.h>
#endif // TINYEXIF_NO_XMP_SUPPORT

#ifdef _MSC_VER
namespace {
    int strcasecmp(const char* a, const char* b) {
        return _stricmp(a, b);
    }
}
#else
#include <string.h>
#endif


namespace Tools {

	// search string inside a string, case sensitive
	static const char* strrnstr(const char* haystack, const char* needle, size_t len) {
		const size_t needle_len(strlen(needle));
		if (0 == needle_len)
			return haystack;
		if (len <= needle_len)
			return NULL;
		for (size_t i=len-needle_len; i-- > 0; ) {
			if (haystack[0] == needle[0] &&
				0 == strncmp(haystack, needle, needle_len))
				return haystack;
			haystack++;
		}
		return NULL;
	}

	// split an input string with a delimiter and fill a string vector
	static void strSplit(const std::string& str, char delim, std::vector<std::string>& values) {
		values.clear();
		std::string::size_type start(0), end(0);
		while (end != std::string::npos) {
			end = str.find(delim, start);
			values.emplace_back(str.substr(start, end-start));
			start = end + 1;
		}
	}

	// make sure the given degrees value is between -180 and 180
	static double NormD180(double d) {
		return (d = fmod(d+180.0, 360.0)) < 0 ? d+180.0 : d-180.0;
	}

} // namespace Tools


namespace TinyEXIF {

enum JPEG_MARKERS {
	JM_START = 0xFF,
	JM_SOF0  = 0xC0,
	JM_SOF1  = 0xC1,
	JM_SOF2  = 0xC2,
	JM_SOF3  = 0xC3,
	JM_DHT   = 0xC4,
	JM_SOF5  = 0xC5,
	JM_SOF6  = 0xC6,
	JM_SOF7  = 0xC7,
	JM_JPG   = 0xC8,
	JM_SOF9  = 0xC9,
	JM_SOF10 = 0xCA,
	JM_SOF11 = 0xCB,
	JM_DAC   = 0xCC,
	JM_SOF13 = 0xCD,
	JM_SOF14 = 0xCE,
	JM_SOF15 = 0xCF,
	JM_RST0  = 0xD0,
	JM_RST1  = 0xD1,
	JM_RST2  = 0xD2,
	JM_RST3  = 0xD3,
	JM_RST4  = 0xD4,
	JM_RST5  = 0xD5,
	JM_RST6  = 0xD6,
	JM_RST7  = 0xD7,
	JM_SOI   = 0xD8,
	JM_EOI   = 0xD9,
	JM_SOS   = 0xDA,
	JM_DQT	 = 0xDB,
	JM_DNL	 = 0xDC,
	JM_DRI	 = 0xDD,
	JM_DHP	 = 0xDE,
	JM_EXP	 = 0xDF,
	JM_APP0	 = 0xE0,
	JM_APP1  = 0xE1, // EXIF and XMP
	JM_APP2  = 0xE2,
	JM_APP3  = 0xE3,
	JM_APP4  = 0xE4,
	JM_APP5  = 0xE5,
	JM_APP6  = 0xE6,
	JM_APP7  = 0xE7,
	JM_APP8  = 0xE8,
	JM_APP9  = 0xE9,
	JM_APP10 = 0xEA,
	JM_APP11 = 0xEB,
	JM_APP12 = 0xEC,
	JM_APP13 = 0xED, // IPTC
	JM_APP14 = 0xEE,
	JM_APP15 = 0xEF,
	JM_JPG0	 = 0xF0,
	JM_JPG1	 = 0xF1,
	JM_JPG2	 = 0xF2,
	JM_JPG3	 = 0xF3,
	JM_JPG4	 = 0xF4,
	JM_JPG5	 = 0xF5,
	JM_JPG6	 = 0xF6,
	JM_JPG7	 = 0xF7,
	JM_JPG8	 = 0xF8,
	JM_JPG9	 = 0xF9,
	JM_JPG10 = 0xFA,
	JM_JPG11 = 0xFB,
	JM_JPG12 = 0xFC,
	JM_JPG13 = 0xFD,
	JM_COM   = 0xFE
};


// Parser helper
class EntryParser {
private:
	const uint8_t* buf;
	const unsigned len;
	const unsigned tiff_header_start;
	const bool alignIntel; // byte alignment (defined in EXIF header)
	unsigned offs; // current offset into buffer
	uint16_t tag, format;
	uint32_t length;

public:
	EntryParser(const uint8_t* _buf, unsigned _len, unsigned _tiff_header_start, bool _alignIntel)
		: buf(_buf), len(_len), tiff_header_start(_tiff_header_start), alignIntel(_alignIntel), offs(0) {}

	void Init(unsigned _offs) {
		offs = _offs - 12;
	}

	void ParseTag() {
		offs  += 12;
		tag    = parse16(buf + offs, alignIntel);
		format = parse16(buf + offs + 2, alignIntel);
		length = parse32(buf + offs + 4, alignIntel);
	}

	const uint8_t* GetBuffer() const { return buf; }
	unsigned GetOffset() const { return offs; }
	bool IsIntelAligned() const { return alignIntel; }

	uint16_t GetTag() const { return tag; }
	uint32_t GetLength() const { return length; }
	uint32_t GetData() const { return parse32(buf + offs + 8, alignIntel); }
	uint32_t GetSubIFD() const { return tiff_header_start + GetData(); }

	bool IsShort() const { return format == 3; }
	bool IsLong() const { return format == 4; }
	bool IsRational() const { return format == 5 || format == 10; }
	bool IsSRational() const { return format == 10; }
	bool IsFloat() const { return format == 11; }
	bool IsUndefined() const { return format == 7; }

	std::string FetchString() const {
		return parseString(buf, length, GetData(), tiff_header_start, len, alignIntel);
	}
	bool Fetch(std::string& val) const {
		if (format != 2 || length == 0)
			return false;
		val = FetchString();
		return true;
	}
	bool Fetch(uint8_t& val) const {
		if ((format != 1 && format != 2 && format != 6) || length == 0)
			return false;
		val = parse8(buf + offs + 8);
		return true;
	}
	bool Fetch(uint16_t& val) const {
		if (!IsShort() || length == 0)
			return false;
		val = parse16(buf + offs + 8, alignIntel);
		return true;
	}
	bool Fetch(uint16_t& val, uint32_t idx) const {
		if (!IsShort() || length <= idx)
			return false;
		const uint32_t offset = GetSubIFD() + idx*2;
		if (offset + 2 > len)
			return false;
		val = parse16(buf + offset, alignIntel);
		return true;
	}
	bool Fetch(uint32_t& val) const {
		if (!IsLong() || length == 0)
			return false;
		val = parse32(buf + offs + 8, alignIntel);
		return true;
	}
	bool Fetch(float& val) const {
		if (!IsFloat() || length == 0)
			return false;
		val = parseFloat(buf + offs + 8, alignIntel);
		return true;
	}
	bool Fetch(double& val) const {
		if (!IsRational() || length == 0)
			return false;
		val = parseRational(buf + GetSubIFD(), alignIntel, IsSRational());
		return true;
	}
	bool Fetch(double& val, uint32_t idx) const {
		if (!IsRational() || length <= idx)
			return false;
		const uint32_t offset = GetSubIFD() + idx*8;
		if (offset + 8 > len)
			return false;
		val = parseRational(buf + offset, alignIntel, IsSRational());
		return true;
	}

	bool FetchFloat(double& val) const {
		float _val;
		if (!Fetch(_val))
			return false;
		val = _val;
		return true;
	}

public:
	static uint8_t parse8(const uint8_t* buf) {
		return buf[0];
	}
	static uint16_t parse16(const uint8_t* buf, bool intel) {
		if (intel)
			return ((uint16_t)buf[1]<<8) | buf[0];
		return ((uint16_t)buf[0]<<8) | buf[1];
	}
	static uint32_t parse32(const uint8_t* buf, bool intel) {
		if (intel)
			return ((uint32_t)buf[3]<<24) |
				((uint32_t)buf[2]<<16) |
				((uint32_t)buf[1]<<8)  |
				buf[0];
		return ((uint32_t)buf[0]<<24) |
			((uint32_t)buf[1]<<16) |
			((uint32_t)buf[2]<<8)  |
			buf[3];
	}
	static float parseFloat(const uint8_t* buf, bool intel) {
		union {
			uint32_t i;
			float f;
		} i2f;
		i2f.i = parse32(buf, intel);
		return i2f.f;
	}
	static double parseRational(const uint8_t* buf, bool intel, bool isSigned) {
		const uint32_t denominator = parse32(buf+4, intel);
		if (denominator == 0)
			return 0.0;
		const uint32_t numerator = parse32(buf, intel);
		return isSigned ?
			(double)(int32_t)numerator/(double)(int32_t)denominator :
			(double)numerator/(double)denominator;
	}
	static std::string parseString(const uint8_t* buf,
		unsigned num_components,
		unsigned data,
		unsigned base,
		unsigned len,
		bool intel)
	{
		std::string value;
		if (num_components <= 4) {
			value.resize(num_components);
			char j = intel ? 0 : 24;
			char j_m = intel ? -8 : 8;
			for (unsigned i=0; i<num_components; ++i, j -= j_m)
				value[i] = (data >> j) & 0xff;
			if (value[num_components-1] == '\0')
				value.resize(num_components-1);
		} else
		if (base+data+num_components <= len) {
			const char* const sz((const char*)buf+base+data);
			unsigned num(0);
			while (num < num_components && sz[num] != '\0')
				++num;
			while (num && sz[num-1] == ' ')
				--num;
			value.assign(sz, num);
		}
		return value;
	}
};


// Constructors
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);
}


// Parse tag as Image IFD
void EXIFInfo::parseIFDImage(EntryParser& parser, unsigned& exif_sub_ifd_offset, unsigned& gps_sub_ifd_offset) {
	switch (parser.GetTag()) {
	case 0x0102:
		// Bits per sample
		parser.Fetch(BitsPerSample);
		break;

	case 0x010e:
		// Image description
		parser.Fetch(ImageDescription);
		break;

	case 0x010f:
		// Camera maker
		parser.Fetch(Make);
		break;

	case 0x0110:
		// Camera model
		parser.Fetch(Model);
		break;

	case 0x0112:
		// Orientation of image
		parser.Fetch(Orientation);
		break;

	case 0x011a:
		// XResolution 
		parser.Fetch(XResolution);
		break;

	case 0x011b:
		// YResolution 
		parser.Fetch(YResolution);
		break;

	case 0x0128:
		// Resolution Unit
		parser.Fetch(ResolutionUnit);
		break;

	case 0x0131:
		// Software used for image
		parser.Fetch(Software);
		break;

	case 0x0132:
		// EXIF/TIFF date/time of image modification
		parser.Fetch(DateTime);
		break;

	case 0x1001:
		// Original Image width
		if (!parser.Fetch(RelatedImageWidth)) {
			uint16_t _RelatedImageWidth;
			if (parser.Fetch(_RelatedImageWidth))
				RelatedImageWidth = _RelatedImageWidth;
		}
		break;

	case 0x1002:
		// Original Image height
		if (!parser.Fetch(RelatedImageHeight)) {
			uint16_t _RelatedImageHeight;
			if (parser.Fetch(_RelatedImageHeight))
				RelatedImageHeight = _RelatedImageHeight;
		}
		break;

	case 0x8298:
		// Copyright information
		parser.Fetch(Copyright);
		break;

	case 0x8769:
		// EXIF SubIFD offset
		exif_sub_ifd_offset = parser.GetSubIFD();
		break;

	case 0x8825:
		// GPS IFS offset
		gps_sub_ifd_offset = parser.GetSubIFD();
		break;

	default:
		// Try to parse as EXIF tag, as some images store them in here
		parseIFDExif(parser);
		break;
	}
}

// Parse tag as Exif IFD
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:
		// Exposure time in seconds
		parser.Fetch(ExposureTime);
		break;

	case 0x829d:
		// FNumber
		parser.Fetch(FNumber);
		break;

	case 0x8822:
		// Exposure Program
		parser.Fetch(ExposureProgram);
		break;

	case 0x8827:
		// ISO Speed Rating
		parser.Fetch(ISOSpeedRatings);
		break;

	case 0x9003:
		// Original date and time
		parser.Fetch(DateTimeOriginal);
		break;

	case 0x9004:
		// Digitization date and time
		parser.Fetch(DateTimeDigitized);
		break;

	case 0x9201:
		// Shutter speed value
		parser.Fetch(ShutterSpeedValue);
		ShutterSpeedValue = 1.0/exp(ShutterSpeedValue*log(2));
		break;

	case 0x9202:
		// Aperture value
		parser.Fetch(ApertureValue);
		ApertureValue = exp(ApertureValue*log(2)*0.5);
		break;

	case 0x9203:
		// Brightness value
		parser.Fetch(BrightnessValue);
		break;

	case 0x9204:
		// Exposure bias value 
		parser.Fetch(ExposureBiasValue);
		break;

	case 0x9206:
		// Subject distance
		parser.Fetch(SubjectDistance);
		break;

	case 0x9207:
		// Metering mode
		parser.Fetch(MeteringMode);
		break;

	case 0x9208:
		// Light source
		parser.Fetch(LightSource);
		break;

	case 0x9209:
		// Flash info
		parser.Fetch(Flash);
		break;

	case 0x920a:
		// Focal length
		parser.Fetch(FocalLength);
		break;

	case 0x9214:
		// Subject area
		if (parser.IsShort() && parser.GetLength() > 1) {
			SubjectArea.resize(parser.GetLength());
			for (uint32_t i=0; i<parser.GetLength(); ++i)
				parser.Fetch(SubjectArea[i], i);
		}
		break;

	case 0x927c:
		// MakerNote
		parseIFDMakerNote(parser);
		break;

	case 0x9291:
		// Fractions of seconds for DateTimeOriginal
		parser.Fetch(SubSecTimeOriginal);
		break;

	case 0xa002:
		// EXIF Image width
		if (!parser.Fetch(ImageWidth)) {
			uint16_t _ImageWidth;
			if (parser.Fetch(_ImageWidth))
				ImageWidth = _ImageWidth;
		}
		break;

	case 0xa003:
		// EXIF Image height
		if (!parser.Fetch(ImageHeight)) {
			uint16_t _ImageHeight;
			if (parser.Fetch(_ImageHeight))
				ImageHeight = _ImageHeight;
		}
		break;

	case 0xa20e:
		// Focal plane X resolution
		parser.Fetch(LensInfo.FocalPlaneXResolution);
		break;

	case 0xa20f:
		// Focal plane Y resolution
		parser.Fetch(LensInfo.FocalPlaneYResolution);
		break;

	case 0xa210:
		// Focal plane resolution unit
		parser.Fetch(LensInfo.FocalPlaneResolutionUnit);
		break;

	case 0xa215:
		// Exposure Index and ISO Speed Rating are often used interchangeably
		if (ISOSpeedRatings == 0) {
			double ExposureIndex;
			if (parser.Fetch(ExposureIndex))
				ISOSpeedRatings = (uint16_t)ExposureIndex;
		}
		break;

	case 0xa404:
		// Digital Zoom Ratio
		parser.Fetch(LensInfo.DigitalZoomRatio);
		break;

	case 0xa405:
		// Focal length in 35mm film
		if (!parser.Fetch(LensInfo.FocalLengthIn35mm)) {
			uint16_t _FocalLengthIn35mm;
			if (parser.Fetch(_FocalLengthIn35mm))
				LensInfo.FocalLengthIn35mm = (double)_FocalLengthIn35mm;
		}
		break;

	case 0xa431:
		// Serial number of the camera
		parser.Fetch(SerialNumber);
		break;

	case 0xa432:
		// Focal length and FStop.
		if (parser.Fetch(LensInfo.FocalLengthMin, 0))
			if (parser.Fetch(LensInfo.FocalLengthMax, 1))
				if (parser.Fetch(LensInfo.FStopMin, 2))
					parser.Fetch(LensInfo.FStopMax, 3);
		break;

	case 0xa433:
		// Lens make.
		parser.Fetch(LensInfo.Make);
		break;

	case 0xa434:
		// Lens model.
		parser.Fetch(LensInfo.Model);
		break;
	}
}

// Parse tag as MakerNote IFD
void EXIFInfo::parseIFDMakerNote(EntryParser& parser) {
	const unsigned startOff = parser.GetOffset();
	const uint32_t off = parser.GetSubIFD();
	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())
		return;
	parser.Init(off+2);
	parser.ParseTag();
	--num_entries;
	std::string maker;
	if (parser.GetTag() == 1 && parser.Fetch(maker)) {
		if (0 == strcasecmp(maker.c_str(), "DJI")) {
			while (--num_entries >= 0) {
				parser.ParseTag();
				switch (parser.GetTag()) {
				case 3:
					// SpeedX
					parser.FetchFloat(GeoLocation.SpeedX);
					break;

				case 4:
					// SpeedY
					parser.FetchFloat(GeoLocation.SpeedY);
					break;

				case 5:
					// SpeedZ
					parser.FetchFloat(GeoLocation.SpeedZ);
					break;

				case 9:
					// Camera Pitch
					parser.FetchFloat(GeoLocation.PitchDegree);
					break;

				case 10:
					// Camera Yaw
					parser.FetchFloat(GeoLocation.YawDegree);
					break;

				case 11:
					// Camera Roll
					parser.FetchFloat(GeoLocation.RollDegree);
					break;
				}
			}
		}
	}
	parser.Init(startOff+12);
}

// Parse tag as GPS IFD
void EXIFInfo::parseIFDGPS(EntryParser& parser) {
	switch (parser.GetTag()) {
	case 1:
		// GPS north or south
		parser.Fetch(GeoLocation.LatComponents.direction);
		break;

	case 2:
		// GPS latitude
		if (parser.IsRational() && parser.GetLength() == 3) {
			parser.Fetch(GeoLocation.LatComponents.degrees, 0);
			parser.Fetch(GeoLocation.LatComponents.minutes, 1);
			parser.Fetch(GeoLocation.LatComponents.seconds, 2);
		}
		break;

	case 3:
		// GPS east or west
		parser.Fetch(GeoLocation.LonComponents.direction);
		break;

	case 4:
		// GPS longitude
		if (parser.IsRational() && parser.GetLength() == 3) {
			parser.Fetch(GeoLocation.LonComponents.degrees, 0);
			parser.Fetch(GeoLocation.LonComponents.minutes, 1);
			parser.Fetch(GeoLocation.LonComponents.seconds, 2);
		}
		break;

	case 5:
		// GPS altitude reference (below or above sea level)
		parser.Fetch((uint8_t&)GeoLocation.AltitudeRef);
		break;

	case 6:
		// GPS altitude
		parser.Fetch(GeoLocation.Altitude);
		break;

	case 7:
		// GPS timestamp
		if (parser.IsRational() && parser.GetLength() == 3) {
			double h,m,s;
			parser.Fetch(h, 0);
			parser.Fetch(m, 1);
			parser.Fetch(s, 2);
			char buffer[256];
			snprintf(buffer, 256, "%g %g %g", h, m, s);
			GeoLocation.GPSTimeStamp = buffer;
		}
		break;

	case 11:
		// Indicates the GPS DOP (data degree of precision)
		parser.Fetch(GeoLocation.GPSDOP);
		break;

	case 18:
		// GPS geodetic survey data
		parser.Fetch(GeoLocation.GPSMapDatum);
		break;

	case 29:
		// GPS date-stamp
		parser.Fetch(GeoLocation.GPSDateStamp);
		break;

	case 30:
		// GPS differential indicates whether differential correction is applied to the GPS receiver
		parser.Fetch(GeoLocation.GPSDifferential);
		break;
	}
}


//
// Locates the JM_APP1 segment and parses it using
// parseFromEXIFSegment() or parseFromXMPSegment()
//
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.
	const uint8_t* buf(stream.GetBuffer(2));
	if (buf == NULL || buf[0] != JM_START || buf[1] != JM_SOI)
		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.
	struct APP1S {
		uint32_t& val;
		inline APP1S(uint32_t& v) : val(v) {}
		inline operator uint32_t () const { 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; }
	} app1s(Fields);
	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++ != JM_START)
			break;
		uint8_t marker;
		while ((marker=buf[0]) == JM_START && (buf=stream.GetBuffer(1)) != NULL);
		// select marker
		uint16_t sectionLength;
		switch (marker) {
		case 0x00:
		case 0x01:
		case JM_START:
		case JM_RST0:
		case JM_RST1:
		case JM_RST2:
		case JM_RST3:
		case JM_RST4:
		case JM_RST5:
		case JM_RST6:
		case JM_RST7:
		case JM_SOI:
			break;
		case JM_SOS: // start of stream: and we're done
		case JM_EOI: // no data? not good
			return app1s();
		case JM_APP1:
			if ((buf=stream.GetBuffer(2)) == NULL)
				return app1s(PARSE_INVALID_JPEG);
			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:
#ifndef TINYEXIF_NO_XMP_SUPPORT
				switch (ret=parseFromXMPSegment(buf, sectionLength)) {
				case PARSE_ABSENT_DATA:
					break;
				case PARSE_SUCCESS:
					if ((app1s|=FIELD_XMP) == FIELD_ALL)
						return PARSE_SUCCESS;
					break;
				default:
					return app1s(ret); // some error
				}
#endif // TINYEXIF_NO_XMP_SUPPORT
				break;
			case PARSE_SUCCESS:
				if ((app1s|=FIELD_EXIF) == FIELD_ALL)
					return PARSE_SUCCESS;
				break;
			default:
				return app1s(ret); // some error
			}
			break;
		default:
			// skip the section
			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(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:
		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);
}

//
// Main parsing function for an EXIF segment.
// Do a sanity check by looking for bytes "Exif\0\0".
// The marker has to contain at least the TIFF header, otherwise the
// JM_APP1 data is corrupt. So the minimum length specified here has to be:
//   6 bytes: "Exif\0\0" string
//   2 bytes: TIFF header (either "II" or "MM" string)
//   2 bytes: TIFF magic (short 0x2a00 in Motorola byte order)
//   4 bytes: Offset to first IFD
// =========
//  14 bytes
//
// PARAM: 'buf' start of the EXIF TIFF, which must be the bytes "Exif\0\0".
// PARAM: 'len' length of buffer
//
int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
	unsigned offs = 6; // current offset into buffer
	if (!buf || len < offs)
		return PARSE_ABSENT_DATA;
	if (!std::equal(buf, buf+offs, "Exif\0\0"))
		return PARSE_ABSENT_DATA;

	// Now parsing the TIFF header. The first two bytes are either "II" or
	// "MM" for Intel or Motorola byte alignment. Sanity check by parsing
	// the uint16_t that follows, making sure it equals 0x2a. The
	// last 4 bytes are an offset into the first IFD, which are added to 
	// the global offset counter. For this block, we expect the following
	// minimum size:
	//  2 bytes: 'II' or 'MM'
	//  2 bytes: 0x002a
	//  4 bytes: offset to first IDF
	// -----------------------------
	//  8 bytes
	if (offs + 8 > len)
		return PARSE_CORRUPT_DATA;
	const uint32_t _ONE32 = 1;
	const bool IS_LITTLE_ENDIAN = reinterpret_cast<uint8_t const*>(&_ONE32)[0] == 1;
	bool alignIntel;
	if (buf[offs] == 'I' && buf[offs+1] == 'I')
		alignIntel = IS_LITTLE_ENDIAN; // 1: Intel byte alignment
	else
	if (buf[offs] == 'M' && buf[offs+1] == 'M')
		alignIntel = !IS_LITTLE_ENDIAN; // 0: Motorola byte alignment
	else
		return PARSE_UNKNOWN_BYTEALIGN;
	EntryParser parser(buf, len, offs, alignIntel);
	offs += 2;
	if (0x2a != EntryParser::parse16(buf + offs, alignIntel))
		return PARSE_CORRUPT_DATA;
	offs += 2;
	const unsigned first_ifd_offset = EntryParser::parse32(buf + offs, alignIntel);
	offs += first_ifd_offset - 4;
	if (offs >= len)
		return PARSE_CORRUPT_DATA;

	// 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
	// first two bytes of the IFD section contain the number of directory
	// entries in the section. The last 4 bytes of the IFD contain an offset
	// to the next IFD, which means this IFD must contain exactly 6 + 12 * num
	// bytes of data.
	// Note that it's possible that the next IFD offset doesn't exist,
	// so here the last 4 bytes are considered optional.
	if (offs + 2 > len)
		return PARSE_CORRUPT_DATA;
	unsigned num_entries = EntryParser::parse16(buf + offs, alignIntel);
	if (offs + 2 + 12 * num_entries > len)
		return PARSE_CORRUPT_DATA;
	unsigned exif_sub_ifd_offset = len;
	unsigned gps_sub_ifd_offset  = len;
	parser.Init(offs+2);
	while (num_entries-- > 0) {
		parser.ParseTag();
		parseIFDImage(parser, exif_sub_ifd_offset, gps_sub_ifd_offset);
	}

	// Jump to the EXIF SubIFD if it exists and parse all the information
	// there. Note that it's possible that the EXIF SubIFD doesn't exist.
	// The EXIF SubIFD contains most of the interesting information that a
	// typical user might want.
	if (exif_sub_ifd_offset + 4 <= len) {
		offs = exif_sub_ifd_offset;
		num_entries = EntryParser::parse16(buf + offs, alignIntel);
		if (offs + 2 + 12 * num_entries > len)
			return PARSE_CORRUPT_DATA;
		parser.Init(offs+2);
		while (num_entries-- > 0) {
			parser.ParseTag();
			parseIFDExif(parser);
		}
	}

	// Jump to the GPS SubIFD if it exists and parse all the information
	// there. Note that it's possible that the GPS SubIFD doesn't exist.
	if (gps_sub_ifd_offset + 4 <= len) {
		offs = gps_sub_ifd_offset;
		num_entries = EntryParser::parse16(buf + offs, alignIntel);
		if (offs + 2 + 12 * num_entries > len)
			return PARSE_CORRUPT_DATA;
		parser.Init(offs+2);
		while (num_entries-- > 0) {
			parser.ParseTag();
			parseIFDGPS(parser);
		}
		GeoLocation.parseCoords();
	}

	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".
// So the minimum length specified here has to be:
//  29 bytes: "http://ns.adobe.com/xap/1.0/\0" string
//
// PARAM: 'buf' start of the XMP header, which must be the bytes "http://ns.adobe.com/xap/1.0/\0".
// PARAM: 'len' length of buffer
//
int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
	unsigned offs = 29; // current offset into buffer
	if (!buf || len < offs)
		return PARSE_ABSENT_DATA;
	if (!std::equal(buf, buf+offs, "http://ns.adobe.com/xap/1.0/\0"))
		return PARSE_ABSENT_DATA;
	if (offs >= len)
		return PARSE_CORRUPT_DATA;
	return parseFromXMPSegmentXML((const char*)(buf + offs), len - offs);
}
int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
	// Skip xpacket end section so that tinyxml2 lib parses the section correctly.
	const char* szEnd(Tools::strrnstr(szXML, "<?xpacket end=", len));
	if (szEnd != NULL)
		len = (unsigned)(szEnd - szXML);

	// Try parsing the XML packet.
	tinyxml2::XMLDocument doc;
	const tinyxml2::XMLElement* document;
	if (doc.Parse(szXML, len) != tinyxml2::XML_SUCCESS ||
		((document=doc.FirstChildElement("x:xmpmeta")) == NULL && (document=doc.FirstChildElement("xmp:xmpmeta")) == NULL) ||
		(document=document->FirstChildElement("rdf:RDF")) == NULL ||
		(document=document->FirstChildElement("rdf:Description")) == NULL)
		return PARSE_ABSENT_DATA;

	// 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"));
	if (element != NULL) {
		const char* const szProjectionType(element->GetText());
		if (szProjectionType != NULL) {
			if (0 == strcasecmp(szProjectionType, "perspective"))
				ProjectionType = 1;
			else
			if (0 == strcasecmp(szProjectionType, "equirectangular") ||
				0 == strcasecmp(szProjectionType, "spherical"))
				ProjectionType = 2;
		}
	}
	}

	// Try parsing the XMP content for supported maker's info.
	struct ParseXMP	{
		// try yo fetch the value both from the attribute and child element
		// and parse if needed rational numbers stored as string fraction
		static bool Value(const tinyxml2::XMLElement* document, const char* name, double& 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;
			}
			std::vector<std::string> values;
			Tools::strSplit(szAttribute, '/', values);
			switch (values.size()) {
			case 1: value = strtod(values.front().c_str(), NULL); return true;
			case 2: value = strtod(values.front().c_str(), NULL)/strtod(values.back().c_str(), NULL); return true;
			}
			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;
		}
		// same as previous function but with std::string
		static bool Value(const tinyxml2::XMLElement* document, const char* name, std::string& 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 = std::string(szAttribute);
			return true;
		}
	};
	const char* szAbout(document->Attribute("rdf:about"));
	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);
		ParseXMP::Value(document, "drone-dji:GimbalPitchDegree", GeoLocation.PitchDegree);
		ParseXMP::Value(document, "drone-dji:GimbalYawDegree", GeoLocation.YawDegree);
		ParseXMP::Value(document, "drone-dji:CalibratedFocalLength", Calibration.FocalLength);
		ParseXMP::Value(document, "drone-dji:CalibratedOpticalCenterX", Calibration.OpticalCenterX);
		ParseXMP::Value(document, "drone-dji:CalibratedOpticalCenterY", Calibration.OpticalCenterY);
		std::string dewarpData;
		ParseXMP::Value(document, "drone-dji:DewarpFlag", Distortion.DewarpFlag);
		ParseXMP::Value(document, "drone-dji:DewarpData", dewarpData);
		std::vector<double> distortionParams;
		size_t pos = dewarpData.find(';');
		if (pos != std::string::npos) {
			std::stringstream ss(dewarpData.substr(pos + 1));
			std::string item;
			while (std::getline(ss, item, ',')) {
				distortionParams.push_back(std::stod(item));
			}
		}
		// The DewarpData string has the following format:
		// date;Fx,Fy,Cx,Cy,K1,K2,P1,P2,K3
		// , where Fx, Fy are focal lengths in pixels, Cx, Cy are optical center offsets from the image center in pixels
		if (distortionParams.size() == 9) {
			Distortion.K1 = distortionParams[4];
			Distortion.K2 = distortionParams[5];
			Distortion.P1 = distortionParams[6];
			Distortion.P2 = distortionParams[7];
			Distortion.K3 = distortionParams[8];
		}
	} else
	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
			GeoLocation.PitchDegree = Tools::NormD180(GeoLocation.PitchDegree-90.0);
		}
		ParseXMP::Value(document, "Camera:Yaw", GeoLocation.YawDegree);
		ParseXMP::Value(document, "Camera:GPSXYAccuracy", GeoLocation.AccuracyXY);
		ParseXMP::Value(document, "Camera:GPSZAccuracy", GeoLocation.AccuracyZ);
	} else
	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) ||
			ParseXMP::Value(document, "drone-parrot:CameraPitchDegree", GeoLocation.PitchDegree)) {
			// convert to DJI format: senseFly uses pitch 0 as NADIR, whereas DJI -90
			GeoLocation.PitchDegree = Tools::NormD180(GeoLocation.PitchDegree-90.0);
		}
		ParseXMP::Value(document, "Camera:Yaw", GeoLocation.YawDegree) ||
		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

bool EXIFInfo::Calibration_t::hasCalibration() const {
	return FocalLength > 0.0 && OpticalCenterX > 0.0 && OpticalCenterY > 0.0;
}

bool EXIFInfo::Distortion_t::hasDewarpFlag() const {
	return DewarpFlag != UINT32_MAX;
}
bool EXIFInfo::Distortion_t::hasDistortion() const {
	return K1 != 0.0 || K2 != 0.0 || P1 != 0.0 || P2 != 0.0 || K3 != 0.0;
}

void EXIFInfo::Geolocation_t::parseCoords() {
	// Convert GPS latitude
	if (LatComponents.degrees != DBL_MAX ||
		LatComponents.minutes != 0 ||
		LatComponents.seconds != 0) {
		Latitude =
			LatComponents.degrees +
			LatComponents.minutes / 60 +
			LatComponents.seconds / 3600;
		if ('S' == LatComponents.direction)
			Latitude = -Latitude;
	}
	// Convert GPS longitude
	if (LonComponents.degrees != DBL_MAX ||
		LonComponents.minutes != 0 ||
		LonComponents.seconds != 0) {
		Longitude =
			LonComponents.degrees +
			LonComponents.minutes / 60 +
			LonComponents.seconds / 3600;
		if ('W' == LonComponents.direction)
			Longitude = -Longitude;
	}
	// Convert GPS altitude
	if (hasAltitude() &&
		AltitudeRef == 1) {
		Altitude = -Altitude;
	}
}

bool EXIFInfo::Geolocation_t::hasLatLon() const {
	return Latitude != DBL_MAX && Longitude != DBL_MAX;
}
bool EXIFInfo::Geolocation_t::hasAltitude() const {
	return Altitude != DBL_MAX;
}
bool EXIFInfo::Geolocation_t::hasRelativeAltitude() const {
	return RelativeAltitude != DBL_MAX;
}
bool EXIFInfo::Geolocation_t::hasOrientation() const {
	return RollDegree != DBL_MAX && PitchDegree != DBL_MAX && YawDegree != DBL_MAX;
}
bool EXIFInfo::Geolocation_t::hasSpeed() const {
	return SpeedX != DBL_MAX && SpeedY != DBL_MAX && SpeedZ != DBL_MAX;
}
bool EXIFInfo::Geolocation_t::hasAccuracy() const {
	return AccuracyXY != 0 && AccuracyZ != 0;
}

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;

	// Strings
	ImageDescription  = "";
	Make              = "";
	Model             = "";
	SerialNumber      = "";
	Software          = "";
	DateTime          = "";
	DateTimeOriginal  = "";
	DateTimeDigitized = "";
	SubSecTimeOriginal= "";
	Copyright         = "";

	// Shorts / unsigned / double
	ImageWidth        = 0;
	ImageHeight       = 0;
	RelatedImageWidth = 0;
	RelatedImageHeight= 0;
	Orientation       = 0;
	XResolution       = 0;
	YResolution       = 0;
	ResolutionUnit    = 0;
	BitsPerSample     = 0;
	ExposureTime      = 0;
	FNumber           = 0;
	ExposureProgram   = 0;
	ISOSpeedRatings   = 0;
	ShutterSpeedValue = 0;
	ApertureValue     = 0;
	BrightnessValue   = 0;
	ExposureBiasValue = 0;
	SubjectDistance   = 0;
	FocalLength       = 0;
	Flash             = 0;
	MeteringMode      = 0;
	LightSource       = 0;
	ProjectionType    = 0;
	SubjectArea.clear();

	// Calibration
	Calibration.FocalLength = 0;
	Calibration.OpticalCenterX = 0;
	Calibration.OpticalCenterY = 0;

	// LensInfo
	LensInfo.FocalLengthMax = 0;
	LensInfo.FocalLengthMin = 0;
	LensInfo.FStopMax = 0;
	LensInfo.FStopMin = 0;
	LensInfo.DigitalZoomRatio = 0;
	LensInfo.FocalLengthIn35mm = 0;
	LensInfo.FocalPlaneXResolution = 0;
	LensInfo.FocalPlaneYResolution = 0;
	LensInfo.FocalPlaneResolutionUnit = 0;
	LensInfo.Make = "";
	LensInfo.Model = "";

	// Geolocation
	GeoLocation.Latitude                = DBL_MAX;
	GeoLocation.Longitude               = DBL_MAX;
	GeoLocation.Altitude                = DBL_MAX;
	GeoLocation.AltitudeRef             = 0;
	GeoLocation.RelativeAltitude        = DBL_MAX;
	GeoLocation.RollDegree              = DBL_MAX;
	GeoLocation.PitchDegree             = DBL_MAX;
	GeoLocation.YawDegree               = DBL_MAX;
	GeoLocation.SpeedX                  = DBL_MAX;
	GeoLocation.SpeedY                  = DBL_MAX;
	GeoLocation.SpeedZ                  = DBL_MAX;
	GeoLocation.AccuracyXY              = 0;
	GeoLocation.AccuracyZ               = 0;
	GeoLocation.GPSDOP                  = 0;
	GeoLocation.GPSDifferential         = 0;
	GeoLocation.GPSMapDatum             = "";
	GeoLocation.GPSTimeStamp            = "";
	GeoLocation.GPSDateStamp            = "";
	GeoLocation.LatComponents.degrees   = DBL_MAX;
	GeoLocation.LatComponents.minutes   = 0;
	GeoLocation.LatComponents.seconds   = 0;
	GeoLocation.LatComponents.direction = 0;
	GeoLocation.LonComponents.degrees   = DBL_MAX;
	GeoLocation.LonComponents.minutes   = 0;
	GeoLocation.LonComponents.seconds   = 0;
	GeoLocation.LonComponents.direction = 0;

	// Distortion
	Distortion.DewarpFlag = UINT32_MAX;
	Distortion.K1 = 0;
	Distortion.K2 = 0;
	Distortion.P1 = 0;
	Distortion.P2 = 0;
	Distortion.K3 = 0;

	// GPano
	GPano.PosePitchDegrees = DBL_MAX;
	GPano.PoseRollDegrees = DBL_MAX;

	// Video metadata
	MicroVideo.HasMicroVideo = 0;
	MicroVideo.MicroVideoVersion = 0;
	MicroVideo.MicroVideoOffset = 0;
}

} // namespace TinyEXIF
