const fs = require('fs'); const tmp = require('tmp'); 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\ndate:modify=%[date:modify]\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) { return new Promise(async (ok, ko) => { try { let stdout = await im.readMeta(path); var meta = {}; for (const line of stdout.split(/\n/)) { var eq_p = line.indexOf('='); if (eq_p === -1) continue; var key = line.substr(0, eq_p).replace('/','-'), value = line.substr(eq_p+1).trim(); if (key === 'date:modify') { value = new Date(value); key = "date_modify"; } var p = key.indexOf(':'); if (p !== -1) key = key.substr(p+1); key = key.charAt(0).toLowerCase() + key.slice(1); meta[key] = value; } ok(meta); } catch (err) { console.error("readMeta", path, "from Imagemagick:", err); ko(err); } }); } function exifDate(value) { if (!value) return undefined; value = value.split(/ /); return new Date(value[0].replace(/:/g, '-')+' '+value[1]+' +0000'); } function exifSlash(value) { if (!value) return undefined; if (!(/[0-9]+\/[0-9]+/).test(value)) return value; return eval(value); } function readTags(data) { let result = []; if (data['winXP-Keywords']) { result = result.concat(Array.from(data["winXP-Keywords"]).filter((a, b) => !(b%2)).slice(0, -1).join("").split(";")); } return result; } module.exports.parse = async (fileObj, data) => { if (!fileObj.mimeType.startsWith('image/') || data["exifParsed"]) return {}; let result = new MetaStruct(); try { let imdata = await readMeta(fileObj.path); if (!imdata) return {}; result.artist = imdata.artist || undefined; result.setExposureProgram(Number.parseInt(imdata.exposureProgram)); result.exposureTime = exifSlash(imdata.exposureTime); result.exposureTimeStr = imdata.exposureTime || undefined; result.dateTime = exifDate(imdata.dateTimeDigitized || imdata.dateTimeOriginal) || imdata.date_modify; result.fNumber = exifSlash(imdata.fNumber); result.focal = exifSlash(imdata.focalLength); result.lensModel = imdata.lensModel || undefined; result.camera = ((imdata.model || "") + (imdata.model && imdata.make ? " " : "") + (imdata.make || "")) || ""; result.software = imdata.software || undefined; result.iso = Number.parseInt(imdata.photographicSensitivity) || undefined; result.width = imdata.width || undefined; result.height = imdata.height || undefined; result.compression = imdata.compression || undefined; 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); } catch (err) { result.imException = err.toString(); } for (let i of Object.keys(result)) if (result[i] === undefined || result[i].length === 0) delete result[i]; return { value: result }; } module.exports.createThumbnail = async (fileObj, width, height, quality, keepMeta) => { if (!fileObj?.meta?.width || !fileObj?.meta?.height) return null; if (!width && !height) width = height = 420; let ratio = Math.min((width / fileObj.meta.width) || 1, (height / fileObj.meta.height) || 1, 1); const output = tmp.fileSync({ discardDescriptor: true }); try { let args = [ fileObj.path ]; if (keepMeta !== true) args.push('-strip'); await im.convert([...args, '-interlace', 'Plane', '-auto-orient', '-resize', (Math.floor(fileObj.meta.width * ratio)), '-quality', '' + (Math.floor(Math.max(0, Math.min(100, (quality *10))))) + '%', '-sampling-factor', '4:2:0', `JPG:${output.name}`, ]); } catch (err) { console.error("Imagemagick createThumbnail error: ", err); throw err; } return output; };