imagemagick.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. const fs = require('fs');
  2. const tmp = require('tmp');
  3. const imLib = require('imagemagick');
  4. const ThreadPool = require('../threadPool.js');
  5. const MetaStruct = require('./metaStruct.js').MetaStruct;
  6. class ImagemagickWrapper {
  7. #threadPool = new ThreadPool(5);
  8. readMeta(path) {
  9. return this.#threadPool.pushTask(() => new Promise((ok, ko) => {
  10. imLib.identify(['-format', '%[EXIF:*]Compression=%[compression]\nWidth=%w\nHeight=%h\n', path], (err, stdout) => {
  11. if (err)
  12. return ko(err);
  13. ok(stdout);
  14. });
  15. }));
  16. }
  17. convert(args) {
  18. return this.#threadPool.pushTask(() => new Promise((ok, ko) => {
  19. imLib.convert(args, (err, stdout) => {
  20. if (err)
  21. return ko(err);
  22. ok(stdout);
  23. });
  24. }));
  25. }
  26. }
  27. const im = new ImagemagickWrapper();
  28. function readMeta(path) {
  29. return new Promise(async (ok, ko) => {
  30. try {
  31. let stdout = await im.readMeta(path);
  32. var meta = {};
  33. for (const line of stdout.split(/\n/))
  34. {
  35. var eq_p = line.indexOf('=');
  36. if (eq_p === -1)
  37. continue;
  38. var key = line.substr(0, eq_p).replace('/','-'),
  39. value = line.substr(eq_p+1).trim();
  40. var p = key.indexOf(':');
  41. if (p !== -1)
  42. key = key.substr(p+1);
  43. key = key.charAt(0).toLowerCase() + key.slice(1);
  44. meta[key] = value;
  45. }
  46. ok(meta);
  47. } catch (err) {
  48. console.error("readMeta from Imagemagick: ", err);
  49. ko(err);
  50. }
  51. });
  52. }
  53. function exifDate(value) {
  54. if (!value)
  55. return undefined;
  56. value = value.split(/ /);
  57. return new Date(value[0].replace(/:/g, '-')+' '+value[1]+' +0000');
  58. }
  59. function exifSlash(value) {
  60. if (!value)
  61. return undefined;
  62. if (!(/[0-9]+\/[0-9]+/).test(value))
  63. return value;
  64. return eval(value);
  65. }
  66. function readTags(data) {
  67. let result = [];
  68. if (data['winXP-Keywords']) {
  69. result = result.concat(Array.from(data["winXP-Keywords"]).filter((a, b) => !(b%2)).slice(0, -1).join("").split(";"));
  70. }
  71. return result;
  72. }
  73. module.exports.parse = async (fileObj, data) => {
  74. if (!fileObj.mimeType.startsWith('image/') || data["exifParsed"])
  75. return {};
  76. let result = new MetaStruct();
  77. try {
  78. let imdata = await readMeta(fileObj.path);
  79. if (!imdata)
  80. return {};
  81. result.artist = imdata.artist || undefined;
  82. result.setExposureProgram(Number.parseInt(imdata.exposureProgram));
  83. result.exposureTime = exifSlash(imdata.exposureTime);
  84. result.exposureTimeStr = imdata.exposureTime || undefined;
  85. result.dateTime = exifDate(imdata.dateTimeDigitized || imdata.dateTimeOriginal);
  86. result.fNumber = exifSlash(imdata.fNumber);
  87. result.focal = exifSlash(imdata.focalLength);
  88. result.lensModel = imdata.lensModel || undefined;
  89. result.camera = ((imdata.model || "") + (imdata.model && imdata.make ? " " : "") + (imdata.make || "")) || "";
  90. result.software = imdata.software || undefined;
  91. result.iso = Number.parseInt(imdata.photographicSensitivity) || undefined;
  92. result.width = imdata.width || undefined;
  93. result.height = imdata.height || undefined;
  94. result.compression = imdata.compression || undefined;
  95. result.orientation = imdata.orientation || undefined;
  96. try {
  97. result.setGPSInfo(imdata.gPSLatitude.split(',').map(eval), data.gPSLatitudeRef,
  98. imdata.gPSLongitude.split(',').map(eval), data.gPSLongitudeRef);
  99. }
  100. catch (err) {}
  101. result.tags = readTags(imdata);
  102. }
  103. catch (err) {
  104. result.imException = err;
  105. }
  106. for (let i of Object.keys(result))
  107. if (result[i] === undefined || result[i].length === 0)
  108. delete result[i];
  109. return { value: result };
  110. }
  111. module.exports.createThumbnail = async (fileObj, width, height, quality) => {
  112. if (!fileObj?.meta?.width || !fileObj?.meta?.height)
  113. return null;
  114. if (!width && !height)
  115. width = height = 420;
  116. let ratio = Math.min((width / fileObj.meta.width) || 1, (height / fileObj.meta.height) || 1, 1);
  117. const output = tmp.fileSync({ discardDescriptor: true });
  118. try {
  119. await im.convert([
  120. fileObj.path,
  121. '-strip',
  122. '-interlace', 'Plane',
  123. '-resize', (Math.floor(fileObj.meta.width * ratio)),
  124. '-quality', '' + (Math.floor(Math.max(0, Math.min(100, (quality *10))))) + '%',
  125. '-sampling-factor', '4:2:0',
  126. `JPG:${output.name}`,
  127. ]);
  128. }
  129. catch (err) {
  130. console.error("Imagemagick createThumbnail error: ", err);
  131. throw err;
  132. }
  133. return output;
  134. };