move exif node to use supported exifreader library

This is a breaking change
To close #1042
This commit is contained in:
Dave Conway-Jones 2024-01-10 15:02:14 +00:00
parent 3e2d27b9f7
commit fd5dd24dd8
No known key found for this signature in database
GPG Key ID: 1DDB0E91A28C2643
3 changed files with 42 additions and 66 deletions

View File

@ -14,7 +14,7 @@ module.exports = function(RED) {
if (this.mode === "worldmap") { this.property = "payload.content"; } if (this.mode === "worldmap") { this.property = "payload.content"; }
else { this.property = n.property || "payload"; } else { this.property = n.property || "payload"; }
var node = this; var node = this;
var ExifImage = require('exif').ExifImage; var ExifReader = require('exifreader');
/*** /***
* Extracts GPS location information from Exif data. If enough information is * 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 * Assume that the GPS data saved into Exif provides a valid value
*/ */
function addMsgLocationDataFromExifGPSData(msg,val) { function addMsgLocationDataFromExifGPSData(msg,val) {
var gpsData = msg.exif.gps; // declaring variable purely to make checks more readable if (msg.exif.GPSAltitude) {
if (gpsData.GPSAltitude) {
/* istanbul ignore else */ /* istanbul ignore else */
if (!msg.location) { msg.location = {}; } 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 if (msg.exif.GPSLatitudeRef && msg.exif.GPSLatitude && msg.exif.GPSLongitudeRef && msg.exif.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 (!msg.location) { msg.location = {}; }
if (gpsData.GPSLatitude.length === 3) { // OK to convert latitude msg.location.lat = msg.exif.GPSLatitude;
if (gpsData.GPSLongitude.length === 3) { // OK to convert longitude msg.location.lon = msg.exif.GPSLongitude;
var latitude = convertDegreesMinutesSecondsToDecimals(gpsData.GPSLatitude[0], gpsData.GPSLatitude[1], gpsData.GPSLatitude[2]); if (msg.exif.GPSLatitudeRef == "South latitude") {
latitude = Math.round(latitude * 100000)/100000; // 5dp is approx 1m resolution... msg.location.lat *= -1;
// (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.");
}
} }
else { if (msg.exif.GPSLongitudeRef == "West longitude") {
node.log("Invalid latitude data, no location information has been added to the message."); msg.location.lon *= -1;
} }
} }
else { else {
@ -65,24 +46,27 @@ module.exports = function(RED) {
if (msg.location) { if (msg.location) {
msg.location.arc = { msg.location.arc = {
ranges: [100,300,500], ranges: [100,300,500],
pan: gpsData.GPSImgDirection, pan: msg.exif.GPSImgDirection ?? msg.exif.GimbalYawDegree,
fov: (2 * Math.atan(36 / (2 * msg.exif.exif.FocalLengthIn35mmFormat)) * 180 / Math.PI), fov: (2 * Math.atan(36 / (2 * msg.exif.FocalLengthIn35mmFilm)) * 180 / Math.PI),
color: '#aaaa00' color: '#aaaa00'
} }
msg.location.icon = "fa-camera fa-lg"; 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"; msg.location.iconColor = "orange";
var na; var na;
var pop = ""; var pop = "";
if (val.hasOwnProperty("name")) { na = val.name; } if (val.hasOwnProperty("name")) { na = val.name; }
else if (msg.hasOwnProperty("filename")) { else if (msg.hasOwnProperty("filename")) {
na = msg.filename.split('/').pop(); na = msg.filename.split('/').pop();
pop = "Timestamp: "+msg.exif.image.ModifyDate+"<br/>"; pop = "Timestamp: "+msg.exif.DateTimeOriginal+"<br/>";
} }
else { na = msg.exif.image.Make+"_"+msg.exif.image.ModifyDate; } else { na = msg.exif.Make+"_"+msg.exif.DateTimeOriginal; }
msg.location.name = na; msg.location.name = na;
msg.location.layer = "Images"; msg.location.layer = "Images";
if (msg.exif.image.ImageDescription) { if (msg.exif.ImageDescription) {
pop = "Caption: "+msg.exif.image.ImageDescription+"<br/>"+pop; pop = "Caption: "+msg.exif.ImageDescription+"<br/>"+pop;
} }
pop += '<img width="280" src="data:image/jpeg;base64,'+val.toString("base64")+'"/>' pop += '<img width="280" src="data:image/jpeg;base64,'+val.toString("base64")+'"/>'
if (msg.location.lat && msg.location.lon) { if (msg.location.lat && msg.location.lon) {
@ -104,34 +88,21 @@ module.exports = function(RED) {
} }
} }
if (Buffer.isBuffer(value)) { // or a proper jpg buffer if (Buffer.isBuffer(value)) { // or a proper jpg buffer
new ExifImage({ image:value }, function (error, exifData) { msg.exif = ExifReader.load(msg.payload);
if (error) { for (const p in msg.exif) {
if (node.mode !== "worldmap") { msg.exif[p] = msg.exif[p].description
node.log(error.toString()); if (!isNaN(Number(msg.exif[p]))) {
} msg.exif[p] = Number(msg.exif[p])
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 = '<img width="280" src="data:image\/png;base64,'+msg.payload.content.toString('base64')+'"/><br/>';
}
} }
else { }
if (exifData) { if (msg.exif && msg.exif.hasOwnProperty("GPSLatitude") && msg.exif.hasOwnProperty("GPSLongitude")) {
msg.exif = exifData; addMsgLocationDataFromExifGPSData(msg,value);
if ((exifData.hasOwnProperty("gps")) && (Object.keys(exifData.gps).length !== 0)) { }
addMsgLocationDataFromExifGPSData(msg,value); if (node.mode === "worldmap") {
} msg.payload = msg.location || {};
//else { node.log("The incoming image did not contain Exif GPS data."); } delete msg.location;
} }
else { node.send(msg);
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);
});
} }
else { else {
node.error("Invalid payload received, the Exif node cannot proceed, no messages sent.",msg); node.error("Invalid payload received, the Exif node cannot proceed, no messages sent.",msg);

View File

@ -10,6 +10,9 @@ Run the following command in your Node-RED user directory - typically `~/.node-r
npm install node-red-node-exif 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 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`. If the Exif data also contains location information this is extracted as `msg.location`.
`msg.payload` retains the original, unmodified image buffer. `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.

View File

@ -1,12 +1,12 @@
{ {
"name": "node-red-node-exif", "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.", "description": "A Node-RED node that extracts Exif information from JPEG image buffers.",
"dependencies": { "dependencies": {
"exif": "^0.6.0" "exifreader": "^4.20.0"
}, },
"bundledDependencies": [ "bundledDependencies": [
"exif" "exifreader"
], ],
"repository": { "repository": {
"type": "git", "type": "git",