From fd5dd24dd8d22102077b0c5eff16c8561a9011fe Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Wed, 10 Jan 2024 15:02:14 +0000 Subject: [PATCH] move exif node to use supported exifreader library This is a breaking change To close #1042 --- utility/exif/94-exif.js | 97 ++++++++++++++------------------------- utility/exif/README.md | 5 ++ utility/exif/package.json | 6 +-- 3 files changed, 42 insertions(+), 66 deletions(-) diff --git a/utility/exif/94-exif.js b/utility/exif/94-exif.js index 378cb2b9..421eed74 100644 --- a/utility/exif/94-exif.js +++ b/utility/exif/94-exif.js @@ -14,7 +14,7 @@ module.exports = function(RED) { if (this.mode === "worldmap") { this.property = "payload.content"; } else { this.property = n.property || "payload"; } var node = this; - var ExifImage = require('exif').ExifImage; + var ExifReader = require('exifreader'); /*** * Extracts GPS location information from Exif data. If enough information is @@ -24,39 +24,20 @@ module.exports = function(RED) { * Assume that the GPS data saved into Exif provides a valid value */ function addMsgLocationDataFromExifGPSData(msg,val) { - var gpsData = msg.exif.gps; // declaring variable purely to make checks more readable - if (gpsData.GPSAltitude) { + if (msg.exif.GPSAltitude) { /* istanbul ignore else */ if (!msg.location) { msg.location = {}; } - msg.location.alt = gpsData.GPSAltitude; + msg.location.alt = parseFloat(msg.exif.GPSAltitude); } - if (gpsData.GPSLatitudeRef && gpsData.GPSLatitude && gpsData.GPSLongitudeRef && gpsData.GPSLongitude) { // location can be determined, OK - // The data provided in Exif is in degrees, minutes, seconds, this is to be converted into a single floating point degree - if (gpsData.GPSLatitude.length === 3) { // OK to convert latitude - if (gpsData.GPSLongitude.length === 3) { // OK to convert longitude - var latitude = convertDegreesMinutesSecondsToDecimals(gpsData.GPSLatitude[0], gpsData.GPSLatitude[1], gpsData.GPSLatitude[2]); - latitude = Math.round(latitude * 100000)/100000; // 5dp is approx 1m resolution... - // (N)orth means positive latitude, (S)outh means negative latitude - if (gpsData.GPSLatitudeRef.toString() === 'S' || gpsData.GPSLatitudeRef.toString() === 's') { - latitude = latitude * -1; - } - var longitude = convertDegreesMinutesSecondsToDecimals(gpsData.GPSLongitude[0], gpsData.GPSLongitude[1], gpsData.GPSLongitude[2]); - longitude = Math.round(longitude * 100000)/100000; // 5dp is approx 1m resolution... - // (E)ast means positive longitude, (W)est means negative longitude - if (gpsData.GPSLongitudeRef.toString() === 'W' || gpsData.GPSLongitudeRef.toString() === 'w') { - longitude = longitude * -1; - } - // Create location property if not exists - if (!msg.location) { msg.location = {}; } - msg.location.lat = latitude; - msg.location.lon = longitude; - } - else { - node.log("Invalid longitude data, no location information has been added to the message."); - } + if (msg.exif.GPSLatitudeRef && msg.exif.GPSLatitude && msg.exif.GPSLongitudeRef && msg.exif.GPSLongitude) { // location can be determined, OK + if (!msg.location) { msg.location = {}; } + msg.location.lat = msg.exif.GPSLatitude; + msg.location.lon = msg.exif.GPSLongitude; + if (msg.exif.GPSLatitudeRef == "South latitude") { + msg.location.lat *= -1; } - else { - node.log("Invalid latitude data, no location information has been added to the message."); + if (msg.exif.GPSLongitudeRef == "West longitude") { + msg.location.lon *= -1; } } else { @@ -65,24 +46,27 @@ module.exports = function(RED) { if (msg.location) { msg.location.arc = { ranges: [100,300,500], - pan: gpsData.GPSImgDirection, - fov: (2 * Math.atan(36 / (2 * msg.exif.exif.FocalLengthIn35mmFormat)) * 180 / Math.PI), + pan: msg.exif.GPSImgDirection ?? msg.exif.GimbalYawDegree, + fov: (2 * Math.atan(36 / (2 * msg.exif.FocalLengthIn35mmFilm)) * 180 / Math.PI), color: '#aaaa00' } msg.location.icon = "fa-camera fa-lg"; + if (msg.exif.Make === "DJI") { msg.location.icon = "quad"; } + if (msg.exif.Make === "Potensic") { msg.location.icon = "quad"; } + if (msg.exif.Make === "Parrot") { msg.location.icon = "quad"; } msg.location.iconColor = "orange"; var na; var pop = ""; if (val.hasOwnProperty("name")) { na = val.name; } else if (msg.hasOwnProperty("filename")) { na = msg.filename.split('/').pop(); - pop = "Timestamp: "+msg.exif.image.ModifyDate+"
"; + pop = "Timestamp: "+msg.exif.DateTimeOriginal+"
"; } - else { na = msg.exif.image.Make+"_"+msg.exif.image.ModifyDate; } + else { na = msg.exif.Make+"_"+msg.exif.DateTimeOriginal; } msg.location.name = na; msg.location.layer = "Images"; - if (msg.exif.image.ImageDescription) { - pop = "Caption: "+msg.exif.image.ImageDescription+"
"+pop; + if (msg.exif.ImageDescription) { + pop = "Caption: "+msg.exif.ImageDescription+"
"+pop; } pop += '' if (msg.location.lat && msg.location.lon) { @@ -104,34 +88,21 @@ module.exports = function(RED) { } } if (Buffer.isBuffer(value)) { // or a proper jpg buffer - new ExifImage({ image:value }, function (error, exifData) { - if (error) { - if (node.mode !== "worldmap") { - node.log(error.toString()); - } - else { - msg.location = {name:msg.payload.name, lat:msg.payload.lat, lon:msg.payload.lon, layer:"Images", icon:"fa-camera fa-lg", draggable:true}; - msg.location.popup = '
'; - } + msg.exif = ExifReader.load(msg.payload); + for (const p in msg.exif) { + msg.exif[p] = msg.exif[p].description + if (!isNaN(Number(msg.exif[p]))) { + msg.exif[p] = Number(msg.exif[p]) } - else { - if (exifData) { - msg.exif = exifData; - if ((exifData.hasOwnProperty("gps")) && (Object.keys(exifData.gps).length !== 0)) { - addMsgLocationDataFromExifGPSData(msg,value); - } - //else { node.log("The incoming image did not contain Exif GPS data."); } - } - else { - node.warn("The incoming image did not contain any Exif data, nothing to do."); - } - } - if (node.mode === "worldmap") { - msg.payload = msg.location; - delete msg.location; - } - node.send(msg); - }); + } + if (msg.exif && msg.exif.hasOwnProperty("GPSLatitude") && msg.exif.hasOwnProperty("GPSLongitude")) { + addMsgLocationDataFromExifGPSData(msg,value); + } + if (node.mode === "worldmap") { + msg.payload = msg.location || {}; + delete msg.location; + } + node.send(msg); } else { node.error("Invalid payload received, the Exif node cannot proceed, no messages sent.",msg); diff --git a/utility/exif/README.md b/utility/exif/README.md index bc659d1b..8034a838 100644 --- a/utility/exif/README.md +++ b/utility/exif/README.md @@ -10,6 +10,9 @@ Run the following command in your Node-RED user directory - typically `~/.node-r npm install node-red-node-exif +## Breaking Change - v1 + +This node now uses the more supported exifreader library so that it handles more features and recent exif spec uodates, for example data associated with drones. The output message has consequently changed into a much flatter set of properties and thus breaks all existing (0.x) instances. Usage ----- @@ -21,3 +24,5 @@ This node expects an incoming JPEG image as a buffer. If Exif data is present, i If the Exif data also contains location information this is extracted as `msg.location`. `msg.payload` retains the original, unmodified image buffer. + +You can set it into "worldmap" node - in this mode the payload contains the "location" data, not the original image, but can be sent directly to a node-red-contrib-worldmap node for visualisation. diff --git a/utility/exif/package.json b/utility/exif/package.json index d5c4b9dd..c7aad104 100644 --- a/utility/exif/package.json +++ b/utility/exif/package.json @@ -1,12 +1,12 @@ { "name": "node-red-node-exif", - "version": "0.4.1", + "version": "1.0.0", "description": "A Node-RED node that extracts Exif information from JPEG image buffers.", "dependencies": { - "exif": "^0.6.0" + "exifreader": "^4.20.0" }, "bundledDependencies": [ - "exif" + "exifreader" ], "repository": { "type": "git",