mirror of
https://github.com/lovell/sharp.git
synced 2025-12-19 07:15:08 +01:00
Add keepXmp and withXmp for control over output XMP metadata #4416
This commit is contained in:
committed by
Lovell Fuller
parent
df5454e7dc
commit
4e3f3792ad
@@ -692,6 +692,8 @@ sharp(input)
|
||||
k2: 'v2'
|
||||
}
|
||||
})
|
||||
.keepXmp()
|
||||
.withXmp('test')
|
||||
.keepIccProfile()
|
||||
.withIccProfile('filename')
|
||||
.withIccProfile('filename', { attach: false });
|
||||
|
||||
@@ -1100,6 +1100,170 @@ describe('Image metadata', function () {
|
||||
assert.strictEqual(exif2.Image.Software, 'sharp');
|
||||
});
|
||||
|
||||
describe('XMP metadata tests', function () {
|
||||
it('withMetadata preserves existing XMP metadata from input', async () => {
|
||||
const data = await sharp(fixtures.inputJpgWithIptcAndXmp)
|
||||
.resize(320, 240)
|
||||
.withMetadata()
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
assert.strictEqual('object', typeof metadata.xmp);
|
||||
assert.strictEqual(true, metadata.xmp instanceof Buffer);
|
||||
assert.strictEqual(true, metadata.xmp.length > 0);
|
||||
// Check that XMP starts with the expected XML declaration
|
||||
assert.strictEqual(metadata.xmp.indexOf(Buffer.from('<?xpacket begin="')), 0);
|
||||
});
|
||||
|
||||
it('keepXmp preserves existing XMP metadata from input', async () => {
|
||||
const data = await sharp(fixtures.inputJpgWithIptcAndXmp)
|
||||
.resize(320, 240)
|
||||
.keepXmp()
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
assert.strictEqual('object', typeof metadata.xmp);
|
||||
assert.strictEqual(true, metadata.xmp instanceof Buffer);
|
||||
assert.strictEqual(true, metadata.xmp.length > 0);
|
||||
// Check that XMP starts with the expected XML declaration
|
||||
assert.strictEqual(metadata.xmp.indexOf(Buffer.from('<?xpacket begin="')), 0);
|
||||
});
|
||||
|
||||
it('withXmp with custom XMP replaces existing XMP', async () => {
|
||||
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:creator><rdf:Seq><rdf:li>Test Creator</rdf:li></rdf:Seq></dc:creator><dc:title><rdf:Alt><rdf:li xml:lang="x-default">Test Title</rdf:li></rdf:Alt></dc:title></rdf:Description></rdf:RDF></x:xmpmeta>';
|
||||
|
||||
const data = await sharp(fixtures.inputJpgWithIptcAndXmp)
|
||||
.resize(320, 240)
|
||||
.withXmp(customXmp)
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
assert.strictEqual('object', typeof metadata.xmp);
|
||||
assert.strictEqual(true, metadata.xmp instanceof Buffer);
|
||||
|
||||
// Check that the XMP contains our custom content
|
||||
const xmpString = metadata.xmp.toString();
|
||||
assert.strictEqual(true, xmpString.includes('Test Creator'));
|
||||
assert.strictEqual(true, xmpString.includes('Test Title'));
|
||||
});
|
||||
|
||||
it('withXmp with custom XMP buffer on image without existing XMP', async () => {
|
||||
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:description><rdf:Alt><rdf:li xml:lang="x-default">Added via Sharp</rdf:li></rdf:Alt></dc:description></rdf:Description></rdf:RDF></x:xmpmeta>';
|
||||
|
||||
const data = await sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.withXmp(customXmp)
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
assert.strictEqual('object', typeof metadata.xmp);
|
||||
assert.strictEqual(true, metadata.xmp instanceof Buffer);
|
||||
|
||||
// Check that the XMP contains our custom content
|
||||
const xmpString = metadata.xmp.toString();
|
||||
assert.strictEqual(true, xmpString.includes('Added via Sharp'));
|
||||
});
|
||||
|
||||
it('withXmp with valid XMP metadata for different image formats', async () => {
|
||||
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:subject><rdf:Bag><rdf:li>test</rdf:li><rdf:li>metadata</rdf:li></rdf:Bag></dc:subject></rdf:Description></rdf:RDF></x:xmpmeta>';
|
||||
|
||||
// Test with JPEG output
|
||||
const jpegData = await sharp(fixtures.inputJpg)
|
||||
.resize(100, 100)
|
||||
.jpeg()
|
||||
.withXmp(customXmp)
|
||||
.toBuffer();
|
||||
|
||||
const jpegMetadata = await sharp(jpegData).metadata();
|
||||
assert.strictEqual('object', typeof jpegMetadata.xmp);
|
||||
assert.strictEqual(true, jpegMetadata.xmp instanceof Buffer);
|
||||
assert.strictEqual(true, jpegMetadata.xmp.toString().includes('test'));
|
||||
|
||||
// Test with PNG output (PNG should also support XMP metadata)
|
||||
const pngData = await sharp(fixtures.inputJpg)
|
||||
.resize(100, 100)
|
||||
.png()
|
||||
.withXmp(customXmp)
|
||||
.toBuffer();
|
||||
|
||||
const pngMetadata = await sharp(pngData).metadata();
|
||||
// PNG format should preserve XMP metadata when using withXmp
|
||||
assert.strictEqual('object', typeof pngMetadata.xmp);
|
||||
assert.strictEqual(true, pngMetadata.xmp instanceof Buffer);
|
||||
assert.strictEqual(true, pngMetadata.xmp.toString().includes('test'));
|
||||
|
||||
// Test with WebP output (WebP should also support XMP metadata)
|
||||
const webpData = await sharp(fixtures.inputJpg)
|
||||
.resize(100, 100)
|
||||
.webp()
|
||||
.withXmp(customXmp)
|
||||
.toBuffer();
|
||||
|
||||
const webpMetadata = await sharp(webpData).metadata();
|
||||
// WebP format should preserve XMP metadata when using withXmp
|
||||
assert.strictEqual('object', typeof webpMetadata.xmp);
|
||||
assert.strictEqual(true, webpMetadata.xmp instanceof Buffer);
|
||||
assert.strictEqual(true, webpMetadata.xmp.toString().includes('test'));
|
||||
});
|
||||
|
||||
it('XMP metadata persists through multiple operations', async () => {
|
||||
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:identifier>persistent-test</dc:identifier></rdf:Description></rdf:RDF></x:xmpmeta>';
|
||||
|
||||
const data = await sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.withXmp(customXmp)
|
||||
.rotate(90)
|
||||
.blur(1)
|
||||
.sharpen()
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
assert.strictEqual('object', typeof metadata.xmp);
|
||||
assert.strictEqual(true, metadata.xmp instanceof Buffer);
|
||||
assert.strictEqual(true, metadata.xmp.toString().includes('persistent-test'));
|
||||
});
|
||||
|
||||
it('withXmp XMP works with WebP format specifically', async () => {
|
||||
const webpXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:creator><rdf:Seq><rdf:li>WebP Creator</rdf:li></rdf:Seq></dc:creator><dc:format>image/webp</dc:format></rdf:Description></rdf:RDF></x:xmpmeta>';
|
||||
|
||||
const data = await sharp(fixtures.inputJpg)
|
||||
.resize(120, 80)
|
||||
.webp({ quality: 80 })
|
||||
.withXmp(webpXmp)
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
assert.strictEqual('webp', metadata.format);
|
||||
assert.strictEqual('object', typeof metadata.xmp);
|
||||
assert.strictEqual(true, metadata.xmp instanceof Buffer);
|
||||
|
||||
const xmpString = metadata.xmp.toString();
|
||||
assert.strictEqual(true, xmpString.includes('WebP Creator'));
|
||||
assert.strictEqual(true, xmpString.includes('image/webp'));
|
||||
});
|
||||
|
||||
it('withXmp XMP validation - non-string input', function () {
|
||||
assert.throws(
|
||||
() => sharp().withXmp(123),
|
||||
/Expected non-empty string for xmp but received 123 of type number/
|
||||
);
|
||||
});
|
||||
|
||||
it('withXmp XMP validation - null input', function () {
|
||||
assert.throws(
|
||||
() => sharp().withXmp(null),
|
||||
/Expected non-empty string for xmp but received null of type object/
|
||||
);
|
||||
});
|
||||
|
||||
it('withXmp XMP validation - empty string', function () {
|
||||
assert.throws(
|
||||
() => sharp().withXmp(''),
|
||||
/Expected non-empty string for xmp/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid parameters', function () {
|
||||
it('String orientation', function () {
|
||||
assert.throws(function () {
|
||||
|
||||
Reference in New Issue
Block a user