|
@@ -1,9 +1,35 @@
|
|
|
|
|
|
|
|
const fs = require('fs');
|
|
const fs = require('fs');
|
|
|
const tmp = require('tmp');
|
|
const tmp = require('tmp');
|
|
|
-const im = require('./../imagemagickWrapper.js');
|
|
|
|
|
-const geokit = require('geokit');
|
|
|
|
|
-const geocoder = require('offline-geocoder')({ database: 'static/db.sqlite'});
|
|
|
|
|
|
|
+const imLib = require('imagemagick');
|
|
|
|
|
+const ThreadPool = require('../threadPool.js');
|
|
|
|
|
+const MetaStruct = require('./metaStruct.js').MetaStruct;
|
|
|
|
|
+
|
|
|
|
|
+class ImagemagickWrapper {
|
|
|
|
|
+ #threadPool = new ThreadPool(5);
|
|
|
|
|
+
|
|
|
|
|
+ readMeta(path) {
|
|
|
|
|
+ return this.#threadPool.pushTask(() => new Promise((ok, ko) => {
|
|
|
|
|
+ imLib.identify(['-format', '%[EXIF:*]Compression=%[compression]\nWidth=%w\nHeight=%h\n', path], (err, stdout) => {
|
|
|
|
|
+ if (err)
|
|
|
|
|
+ return ko(err);
|
|
|
|
|
+ ok(stdout);
|
|
|
|
|
+ });
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ convert(args) {
|
|
|
|
|
+ return this.#threadPool.pushTask(() => new Promise((ok, ko) => {
|
|
|
|
|
+ imLib.convert(args, (err, stdout) => {
|
|
|
|
|
+ if (err)
|
|
|
|
|
+ return ko(err);
|
|
|
|
|
+ ok(stdout);
|
|
|
|
|
+ });
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const im = new ImagemagickWrapper();
|
|
|
|
|
|
|
|
function readMeta(path) {
|
|
function readMeta(path) {
|
|
|
return new Promise(async (ok, ko) => {
|
|
return new Promise(async (ok, ko) => {
|
|
@@ -46,63 +72,6 @@ function exifSlash(value) {
|
|
|
return eval(value);
|
|
return eval(value);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function ExifGps(data) {
|
|
|
|
|
- /*
|
|
|
|
|
- * gPSLatitude: '46/1,54/1,3500/100',
|
|
|
|
|
- gPSLatitudeRef: 'N',
|
|
|
|
|
- gPSLongitude: '23/1,48/1,3614/100',
|
|
|
|
|
- gPSLongitudeRef: 'E',
|
|
|
|
|
- */
|
|
|
|
|
- this.data = null;
|
|
|
|
|
- if (!data.gPSLatitude || !data.gPSLongitude || !(/^[0-9\-,\/]+$/).test(data.gPSLatitude) || !(/^[0-9\-,\/]+$/).test(data.gPSLongitude))
|
|
|
|
|
- return;
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- this.data = {
|
|
|
|
|
- lat: data.gPSLatitude.split(',').map(eval),
|
|
|
|
|
- lon: data.gPSLongitude.split(',').map(eval)
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
- catch(err)
|
|
|
|
|
- {
|
|
|
|
|
- this.data = null;
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- if (data.gPSLatitudeRef && data.gPSLatitudeRef !== 'N' && this.data.lat[0] >= 0)
|
|
|
|
|
- this.data.lat[0] *= -1;
|
|
|
|
|
- if (data.gPSLongitudeRef && data.gPSLongitudeRef !== 'E' && this.data.lon[0] >= 0)
|
|
|
|
|
- this.data.lon[0] *= -1;
|
|
|
|
|
- this.data.lat = this.data.lat[0] + this.data.lat[1] / 60 + this.data.lat[2]/3600;
|
|
|
|
|
- this.data.lon = this.data.lon[0] + this.data.lon[1] / 60 + this.data.lon[2]/3600;
|
|
|
|
|
- if (!this.data.lat || isNaN(this.data.lat) || !this.data.lon || isNaN(this.data.lon))
|
|
|
|
|
- this.data = null;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-ExifGps.prototype.toGps = function() {
|
|
|
|
|
- if (!this.data)
|
|
|
|
|
- return undefined;
|
|
|
|
|
- return JSON.stringify([this.data.lat, this.data.lon]);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-ExifGps.prototype.toGeoHash = function() {
|
|
|
|
|
- if (!this.data)
|
|
|
|
|
- return undefined;
|
|
|
|
|
- return geokit.hash({lat: this.data.lat, lng: this.data.lon});
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-ExifGps.prototype.toAddress = function() {
|
|
|
|
|
- return new Promise(async (ok, ko) => {
|
|
|
|
|
- if (!this.data)
|
|
|
|
|
- return ok(undefined);
|
|
|
|
|
- const data = await geocoder.reverse(this.data.lat, this.data.lon);
|
|
|
|
|
- ok({
|
|
|
|
|
- city: data?.name,
|
|
|
|
|
- admin: data?.admin1?.name,
|
|
|
|
|
- country: data?.country?.name
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
function readTags(data) {
|
|
function readTags(data) {
|
|
|
let result = [];
|
|
let result = [];
|
|
|
if (data['winXP-Keywords']) {
|
|
if (data['winXP-Keywords']) {
|
|
@@ -111,23 +80,16 @@ function readTags(data) {
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function exposureProgram(programId) {
|
|
|
|
|
- if (isNaN(programId) || !programId)
|
|
|
|
|
- return;
|
|
|
|
|
- return [ "Manual", "Normal program", "Aperture priority", "Shutter priority",
|
|
|
|
|
- "Creative program", "Action program", "Portrait mode", "Landscape mode"][programId];
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-module.exports.parse = async (fileObj) => {
|
|
|
|
|
- if (!fileObj.mimeType.startsWith('image/'))
|
|
|
|
|
|
|
+module.exports.parse = async (fileObj, data) => {
|
|
|
|
|
+ if (!fileObj.mimeType.startsWith('image/') || data["exifParsed"])
|
|
|
return {};
|
|
return {};
|
|
|
- let result = {};
|
|
|
|
|
|
|
+ let result = new MetaStruct();
|
|
|
try {
|
|
try {
|
|
|
let imdata = await readMeta(fileObj.path);
|
|
let imdata = await readMeta(fileObj.path);
|
|
|
if (!imdata)
|
|
if (!imdata)
|
|
|
return {};
|
|
return {};
|
|
|
result.artist = imdata.artist || undefined;
|
|
result.artist = imdata.artist || undefined;
|
|
|
- result.exposureProgram = exposureProgram(Number.parseInt(imdata.exposureProgram));
|
|
|
|
|
|
|
+ result.setExposureProgram(Number.parseInt(imdata.exposureProgram));
|
|
|
result.exposureTime = exifSlash(imdata.exposureTime);
|
|
result.exposureTime = exifSlash(imdata.exposureTime);
|
|
|
result.exposureTimeStr = imdata.exposureTime || undefined;
|
|
result.exposureTimeStr = imdata.exposureTime || undefined;
|
|
|
result.dateTime = exifDate(imdata.dateTimeDigitized || imdata.dateTimeOriginal);
|
|
result.dateTime = exifDate(imdata.dateTimeDigitized || imdata.dateTimeOriginal);
|
|
@@ -140,13 +102,12 @@ module.exports.parse = async (fileObj) => {
|
|
|
result.width = imdata.width || undefined;
|
|
result.width = imdata.width || undefined;
|
|
|
result.height = imdata.height || undefined;
|
|
result.height = imdata.height || undefined;
|
|
|
result.compression = imdata.compression || undefined;
|
|
result.compression = imdata.compression || undefined;
|
|
|
- const gpsData = new ExifGps(imdata);
|
|
|
|
|
- result.gpsLocation = gpsData.toGps();
|
|
|
|
|
- result.geoHash = gpsData.toGeoHash();
|
|
|
|
|
- const address = await gpsData.toAddress();
|
|
|
|
|
- result.geoCountry = address?.country;
|
|
|
|
|
- result.geoAdmin = address?.admin;
|
|
|
|
|
- result.geoCity = address?.city;
|
|
|
|
|
|
|
+ result.orientation = imdata.orientation || undefined;
|
|
|
|
|
+ try {
|
|
|
|
|
+ result.setGPSInfo(imdata.gPSLatitude.split(',').map(eval), data.gPSLatitudeRef,
|
|
|
|
|
+ imdata.gPSLongitude.split(',').map(eval), data.gPSLongitudeRef);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (err) {}
|
|
|
result.tags = readTags(imdata);
|
|
result.tags = readTags(imdata);
|
|
|
}
|
|
}
|
|
|
catch (err) {
|
|
catch (err) {
|
|
@@ -155,7 +116,7 @@ module.exports.parse = async (fileObj) => {
|
|
|
for (let i of Object.keys(result))
|
|
for (let i of Object.keys(result))
|
|
|
if (result[i] === undefined || result[i].length === 0)
|
|
if (result[i] === undefined || result[i].length === 0)
|
|
|
delete result[i];
|
|
delete result[i];
|
|
|
- return result;
|
|
|
|
|
|
|
+ return { value: result };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
module.exports.createThumbnail = async (fileObj, width, height, quality) => {
|
|
module.exports.createThumbnail = async (fileObj, width, height, quality) => {
|