imagemagick.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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\ndate:modify=%[date:modify]\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. if (key === 'date:modify') {
  41. value = new Date(value);
  42. key = "date_modify";
  43. }
  44. var p = key.indexOf(':');
  45. if (p !== -1)
  46. key = key.substr(p+1);
  47. key = key.charAt(0).toLowerCase() + key.slice(1);
  48. meta[key] = value;
  49. }
  50. ok(meta);
  51. } catch (err) {
  52. console.error("readMeta", path, "from Imagemagick:", err);
  53. ko(err);
  54. }
  55. });
  56. }
  57. function exifDate(value) {
  58. if (!value)
  59. return undefined;
  60. value = value.split(/ /);
  61. return new Date(value[0].replace(/:/g, '-')+' '+value[1]+' +0000');
  62. }
  63. function exifSlash(value) {
  64. if (!value)
  65. return undefined;
  66. if (!(/[0-9]+\/[0-9]+/).test(value))
  67. return value;
  68. return eval(value);
  69. }
  70. function readTags(data) {
  71. let result = [];
  72. if (data['winXP-Keywords']) {
  73. result = result.concat(Array.from(data["winXP-Keywords"]).filter((a, b) => !(b%2)).slice(0, -1).join("").split(";"));
  74. }
  75. return result;
  76. }
  77. module.exports.parse = async (fileObj, data) => {
  78. if (!fileObj.mimeType.startsWith('image/') || data["exifParsed"])
  79. return {};
  80. let result = new MetaStruct();
  81. try {
  82. let imdata = await readMeta(fileObj.path);
  83. if (!imdata)
  84. return {};
  85. result.artist = imdata.artist || undefined;
  86. result.setExposureProgram(Number.parseInt(imdata.exposureProgram));
  87. result.exposureTime = exifSlash(imdata.exposureTime);
  88. result.exposureTimeStr = imdata.exposureTime || undefined;
  89. result.dateTime = exifDate(imdata.dateTimeDigitized || imdata.dateTimeOriginal) || imdata.date_modify;
  90. result.fNumber = exifSlash(imdata.fNumber);
  91. result.focal = exifSlash(imdata.focalLength);
  92. result.lensModel = imdata.lensModel || undefined;
  93. result.camera = ((imdata.model || "") + (imdata.model && imdata.make ? " " : "") + (imdata.make || "")) || "";
  94. result.software = imdata.software || undefined;
  95. result.iso = Number.parseInt(imdata.photographicSensitivity) || undefined;
  96. result.width = imdata.width || undefined;
  97. result.height = imdata.height || undefined;
  98. result.compression = imdata.compression || undefined;
  99. result.orientation = imdata.orientation || undefined;
  100. try {
  101. result.setGPSInfo(imdata.gPSLatitude.split(',').map(eval), data.gPSLatitudeRef,
  102. imdata.gPSLongitude.split(',').map(eval), data.gPSLongitudeRef);
  103. }
  104. catch (err) {}
  105. result.tags = readTags(imdata);
  106. }
  107. catch (err) {
  108. result.imException = err.toString();
  109. }
  110. for (let i of Object.keys(result))
  111. if (result[i] === undefined || result[i].length === 0)
  112. delete result[i];
  113. return { value: result };
  114. }
  115. module.exports.createThumbnail = async (fileObj, width, height, quality, keepMeta) => {
  116. if (!fileObj?.meta?.width || !fileObj?.meta?.height)
  117. return null;
  118. if (!width && !height)
  119. width = height = 420;
  120. let ratio = Math.min((width / fileObj.meta.width) || 1, (height / fileObj.meta.height) || 1, 1);
  121. const output = tmp.fileSync({ discardDescriptor: true });
  122. try {
  123. let args = [ fileObj.path ];
  124. if (keepMeta !== true)
  125. args.push('-strip');
  126. await im.convert([...args,
  127. '-interlace', 'Plane',
  128. '-auto-orient',
  129. '-resize', (Math.floor(fileObj.meta.width * ratio)),
  130. '-quality', '' + (Math.floor(Math.max(0, Math.min(100, (quality *10))))) + '%',
  131. '-sampling-factor', '4:2:0',
  132. `JPG:${output.name}`,
  133. ]);
  134. }
  135. catch (err) {
  136. console.error("Imagemagick createThumbnail error: ", err);
  137. throw err;
  138. }
  139. return output;
  140. };