<cfscript>
/* 2016-02-16 Exiv2 (shared on 2022-07-06)
   By James Moberg - SunStar Media
   Requires command-line version of Exiv2 http://www.exiv2.org/
   All data returned in a struct. Based on different filetypes & available metadata, keys may not always exist.
   Requirements: CF2016u3+ or Lucee 4.5+
   Blog: https://dev.to/gamesover/use-exiv2-to-extract-gps-data-from-images-using-cfml-3maa
   Tweet: https://twitter.com/gamesover/status/1544747532881903616
   Forum: https://community.adobe.com/t5/coldfusion-discussions/using-imagegetexifmetadata-to-try-to-get-gps-coordinates-of-an-image/m-p/13053016
*/
public struct function exiv2(required string filepath, string exePath="") output=false hint="Extract image metadata (Exif, IPTC, XMP metadata and ICC Profile) using Exiv2" {
	local.data = [:];
	if ( fileExists(arguments.filepath)) {
		// -Pnt = Print Report (P) w/tag name (t) and interpreted (translated) human readable data (t)
		cfexecute(arguments="-Pnt #arguments.filepath#", variable="local.raw", name=arguments.exePath, timeout=15);
		// 4 Space delimited values. key, datatype,
		for (local.line in listToArray(local.raw, chr(10))){
			local.line = trim(local.line);
			if (listLen(local.line, " ") > 1) {
				local.temp = trim(listFirst(local.line, " "));
				local.data["#local.temp#"] = trim(listRest(local.line, " "));
			}
		}
	}
	// DEBUGGING local.data["raw"] = local.Raw;
	if (structCount(local.data)) {
		// Add decimal Latitude/Longitude
		local.data["Latitude"] = 0;
		local.data["Longitude"] = 0;
		// Parse sexagesimal latitude/longitude and convert to decimal
		if (structKeyExists(local.data, "GPSLatitude") && len(local.data.GPSLatitude) && val(local.data.GPSLatitude)
				and structKeyExists(local.data, "GPSLatitudeRef") && len(local.data.GPSLatitudeRef)
				and structKeyExists(local.data, "GPSLongitude") && len(local.data.GPSLongitude) && val(local.data.GPSLongitude)
				and structKeyExists(local.data, "GPSLongitudeRef") && len(local.data.GPSLongitudeRef)
			){
			local.latitudeParts = listToArray(replace(replace(local.data["GPSLatitude"],"""",""), "deg",""""), "'""");
			local.data["Latitude"] = (local.latitudeParts[1] + (local.latitudeParts[2] / 60) + (local.latitudeParts[3] / 3600));
			if ((local.data.GPSLatitudeRef == "South")){
				local.data["Latitude"] *= -1;
			}
			local.longitudeParts = listToArray(replace(replace(local.data["GPSLongitude"],"""",""), "deg",""""), "'""");
			local.data["Longitude"] = (local.longitudeParts[1] + (local.longitudeParts[2] / 60) + (local.longitudeParts[3] / 3600));
			if ((local.data.GPSLongitudeRef == "West")){
				local.data["Longitude"] *= -1;
			}
		}
		// Parse date Exif values to dates. Consolidate GPSDateStamp & GPSTimeStamp (UTC)
		for (local.key in local.data) {
			if (local.key == "GPSDateStamp" && listLen(local.data[local.key],":") == 3 && isDate(replace(local.data[local.key],":","-", "all"))) {
				if (structKeyExists(local.data, "GPSTimeStamp") && listLen(local.data["GPSTimeStamp"],":") == 3) {
					local.data["GPSDateStamp"] = dateFormat(parseDateTime(replace(local.data[local.key],":","-", "all")), "yyyy-mm-dd") & " " & local.data["GPSTimeStamp"];
				} else {
					local.data[local.key] = dateFormat(parseDateTime(replace(local.data[local.key],":","-", "all")), "yyyy-mm-dd") & "00:00:00";
				}
			} else if (find("DateTime", local.key) && listLen(local.data[local.key],":") == 5 && isDate(replace(replace(local.data[local.key],":","-"),":","-"))) {
				local.tempDate = parseDateTime(replace(replace(local.data[local.key],":","-"),":","-"));
				local.data[local.key] = dateTimeFormat(local.tempDate,"yyyy-mm-dd") & " " & timeFormat(local.tempDate,"HH:nn:ss");
			}
		}
	} else {
		local.data = [
			"Error": "Error accessing file"
			,"Warning": "Unable to analyze #arguments.filepath#"
			,"input": "#arguments.exePath# -Pnt #arguments.filepath#"
			,"output": (local.keyExists("raw")) ? local.raw : ""
		];
	}
	return local.data;
}
</cfscript>