diff --git a/lib/input.js b/lib/input.js index d2311e92..808334ce 100644 --- a/lib/input.js +++ b/lib/input.js @@ -182,6 +182,8 @@ function clone () { * - `orientation`: Number value of the EXIF Orientation header, if present * - `exif`: Buffer containing raw EXIF data, if present * - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present + * - `iptc`: Buffer containing raw IPTC data, if present + * - `xmp`: Buffer containing raw XMP data, if present * * @example * const image = sharp(inputJpg); diff --git a/package.json b/package.json index e966f809..eb12aa57 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "Matt Parrish ", "Matthew McEachen ", "Jarda Kotěšovec ", - "Kenric D'Souza " + "Kenric D'Souza ", + "Oleh Aleinyk " ], "scripts": { "clean": "rm -rf node_modules/ build/ vendor/ coverage/ test/fixtures/output.*", diff --git a/src/metadata.cc b/src/metadata.cc index 3e6ed9b8..81162377 100644 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -81,6 +81,22 @@ class MetadataWorker : public Nan::AsyncWorker { memcpy(baton->icc, icc, iccLength); baton->iccLength = iccLength; } + // IPTC + if (image.get_typeof(VIPS_META_IPCT_NAME) == VIPS_TYPE_BLOB) { + size_t iptcLength; + void const *iptc = image.get_blob(VIPS_META_IPCT_NAME, &iptcLength); + baton->iptc = static_cast(g_malloc(iptcLength)); + memcpy(baton->iptc, iptc, iptcLength); + baton->iptcLength = iptcLength; + } + // XMP + if (image.get_typeof(VIPS_META_XMP_NAME) == VIPS_TYPE_BLOB) { + size_t xmpLength; + void const *xmp = image.get_blob(VIPS_META_XMP_NAME, &xmpLength); + baton->xmp = static_cast(g_malloc(xmpLength)); + memcpy(baton->xmp, xmp, xmpLength); + baton->xmpLength = xmpLength; + } } // Clean up @@ -123,6 +139,16 @@ class MetadataWorker : public Nan::AsyncWorker { New("icc").ToLocalChecked(), Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked()); } + if (baton->iptcLength > 0) { + Set(info, + New("iptc").ToLocalChecked(), + Nan::NewBuffer(baton->iptc, baton->iptcLength, sharp::FreeCallback, nullptr).ToLocalChecked()); + } + if (baton->xmpLength > 0) { + Set(info, + New("xmp").ToLocalChecked(), + Nan::NewBuffer(baton->xmp, baton->xmpLength, sharp::FreeCallback, nullptr).ToLocalChecked()); + } argv[1] = info; } diff --git a/src/metadata.h b/src/metadata.h index 50175639..c57a6b1a 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -38,6 +38,10 @@ struct MetadataBaton { size_t exifLength; char *icc; size_t iccLength; + char *iptc; + size_t iptcLength; + char *xmp; + size_t xmpLength; std::string err; MetadataBaton(): @@ -52,7 +56,11 @@ struct MetadataBaton { exif(nullptr), exifLength(0), icc(nullptr), - iccLength(0) {} + iccLength(0), + iptc(nullptr), + iptcLength(0), + xmp(nullptr), + xmpLength(0) {} }; NAN_METHOD(metadata); diff --git a/test/fixtures/Landscape_9.jpg b/test/fixtures/Landscape_9.jpg new file mode 100644 index 00000000..07eafa79 Binary files /dev/null and b/test/fixtures/Landscape_9.jpg differ diff --git a/test/fixtures/index.js b/test/fixtures/index.js index d8103cfa..bd9aaf4c 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -57,6 +57,7 @@ module.exports = { inputJpg: getPath('2569067123_aca715a2ee_o.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/ inputJpgWithExif: getPath('Landscape_8.jpg'), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg + inputJpgWithIptcAndXmp: getPath('Landscape_9.jpg'), // https://unsplash.com/photos/RWAIyGmgHTQ inputJpgWithExifMirroring: getPath('Landscape_5.jpg'), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_5.jpg inputJpgWithGammaHoliness: getPath('gamma_dalai_lama_gray.jpg'), // http://www.4p8.com/eric.brasseur/gamma.html inputJpgWithCmykProfile: getPath('Channel_digital_image_CMYK_color.jpg'), // http://en.wikipedia.org/wiki/File:Channel_digital_image_CMYK_color.jpg diff --git a/test/unit/metadata.js b/test/unit/metadata.js index cb8db649..92820530 100644 --- a/test/unit/metadata.js +++ b/test/unit/metadata.js @@ -58,6 +58,23 @@ describe('Image metadata', function () { }); }); + it('JPEG with IPTC/XMP', function (done) { + sharp(fixtures.inputJpgWithIptcAndXmp).metadata(function (err, metadata) { + if (err) throw err; + // IPTC + assert.strictEqual('object', typeof metadata.iptc); + assert.strictEqual(true, metadata.iptc instanceof Buffer); + assert.strictEqual(18250, metadata.iptc.byteLength); + assert.strictEqual(metadata.iptc.indexOf(Buffer.from('Photoshop')), 0); + // XMP + assert.strictEqual('object', typeof metadata.xmp); + assert.strictEqual(true, metadata.xmp instanceof Buffer); + assert.strictEqual(12495, metadata.xmp.byteLength); + assert.strictEqual(metadata.xmp.indexOf(Buffer.from('http://ns.adobe.com/xap/1.0')), 0); + done(); + }); + }); + it('TIFF', function (done) { sharp(fixtures.inputTiff).metadata(function (err, metadata) { if (err) throw err;