From 6f4ce5b0078104a05b8cb0a4ed4d84b13dbb4cbc Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Sat, 21 Jun 2025 15:11:14 +0100 Subject: [PATCH] WIP: Provide XMP as a string, as well as a Buffer, where possible --- docs/src/content/docs/changelog.md | 2 ++ lib/index.d.ts | 2 ++ lib/input.js | 1 + src/metadata.cc | 4 ++++ test/unit/metadata.js | 3 +++ 5 files changed, 12 insertions(+) diff --git a/docs/src/content/docs/changelog.md b/docs/src/content/docs/changelog.md index 0a248750..66e33f3b 100644 --- a/docs/src/content/docs/changelog.md +++ b/docs/src/content/docs/changelog.md @@ -18,6 +18,8 @@ Requires libvips v8.17.0 * Expose `keepDuplicateFrames` GIF output parameter. +* Provide XMP metadata as a string, as well as a Buffer, where possible. + * Add `pageHeight` option to `create` and `raw` input for animated images. [#3236](https://github.com/lovell/sharp/issues/3236) diff --git a/lib/index.d.ts b/lib/index.d.ts index 395ec977..98e7d4f8 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1240,6 +1240,8 @@ declare namespace sharp { iptc?: Buffer | undefined; /** Buffer containing raw XMP data, if present */ xmp?: Buffer | undefined; + /** String containing XMP data, if valid UTF-8 */ + xmpAsString?: string | undefined; /** Buffer containing raw TIFFTAG_PHOTOSHOP data, if present */ tifftagPhotoshop?: Buffer | undefined; /** The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC) */ diff --git a/lib/input.js b/lib/input.js index ceb6d854..aca0b16c 100644 --- a/lib/input.js +++ b/lib/input.js @@ -605,6 +605,7 @@ function _isStreamInput () { * - `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 + * - `xmpAsString`: String containing XMP data, if valid UTF-8. * - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present * - `formatMagick`: String containing format for images loaded via *magick * - `comments`: Array of keyword/text pairs representing PNG text blocks, if present. diff --git a/src/metadata.cc b/src/metadata.cc index 9991d104..70319910 100644 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -262,6 +262,10 @@ class MetadataWorker : public Napi::AsyncWorker { } if (baton->xmpLength > 0) { info.Set("xmp", Napi::Buffer::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback)); + if (g_utf8_validate(static_cast(baton->xmp), baton->xmpLength, nullptr)) { + info.Set("xmpAsString", + Napi::String::New(env, static_cast(baton->xmp), baton->xmpLength)); + } } if (baton->tifftagPhotoshopLength > 0) { info.Set("tifftagPhotoshop", diff --git a/test/unit/metadata.js b/test/unit/metadata.js index b5bb5e59..c5d0edbd 100644 --- a/test/unit/metadata.js +++ b/test/unit/metadata.js @@ -82,6 +82,7 @@ describe('Image metadata', function () { assert.strictEqual(true, metadata.xmp instanceof Buffer); assert.strictEqual(12466, metadata.xmp.byteLength); assert.strictEqual(metadata.xmp.indexOf(Buffer.from('')); done(); }); }); @@ -106,6 +107,8 @@ describe('Image metadata', function () { assert.strictEqual(3248, metadata.autoOrient.height); assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.icc); + assert.strictEqual('undefined', typeof metadata.xmp); + assert.strictEqual('undefined', typeof metadata.xmpAsString); assert.strictEqual('inch', metadata.resolutionUnit); done(); });