feat: implement editMetadata operation in image-engine

This commit is contained in:
Siddharth Kumar Sah
2026-04-06 19:21:15 +08:00
parent 7fba05e1dc
commit 939bb04941
4 changed files with 182 additions and 0 deletions
@@ -19,6 +19,7 @@ import {
contrast,
convert,
crop,
editMetadata,
flip,
getImageInfo,
grayscale,
@@ -1470,3 +1471,89 @@ describe("parseXmp", () => {
expect(result).toEqual({});
});
});
// ---------------------------------------------------------------------------
// editMetadata
// ---------------------------------------------------------------------------
describe("editMetadata", () => {
it("writes common fields readable via exif-reader", async () => {
const image = sharp(jpgWithExif);
const result = await editMetadata(image, {
artist: "New Artist",
copyright: "New Copyright",
});
const buf = await result.jpeg().toBuffer();
const meta = await sharp(buf).metadata();
expect(meta.exif).toBeTruthy();
const parsed = exifReader(meta.exif!);
expect(parsed.Image?.Artist).toBe("New Artist");
expect(parsed.Image?.Copyright).toBe("New Copyright");
// Original fields should be preserved via withExifMerge
expect(parsed.Image?.Software).toBe("Stirling-Image Test");
});
it("clears GPS while preserving other EXIF", async () => {
// First write GPS to the image
const withGps = sharp(jpgWithExif).withExif({
IFD0: { Artist: "GPS Test" },
IFD3: { GPSLatitudeRef: "N" },
});
const gpsBuf = await withGps.jpeg().toBuffer();
const image = sharp(gpsBuf);
const result = await editMetadata(image, { clearGps: true });
const buf = await result.jpeg().toBuffer();
const meta = await sharp(buf).metadata();
const parsed = exifReader(meta.exif!);
// GPS should be gone
expect(parsed.GPSInfo).toBeUndefined();
// Other EXIF should still be present
expect(parsed.Image?.Artist).toBe("GPS Test");
});
it("removes specific fields via fieldsToRemove", async () => {
const image = sharp(jpgWithExif);
const result = await editMetadata(image, {
fieldsToRemove: ["Software"],
});
const buf = await result.jpeg().toBuffer();
const meta = await sharp(buf).metadata();
const parsed = exifReader(meta.exif!);
expect(parsed.Image?.Software).toBeUndefined();
// Other fields preserved
expect(parsed.Image?.Artist).toBe("Test Artist");
});
it("preserves metadata with no options", async () => {
const image = sharp(jpgWithExif);
const result = await editMetadata(image, {});
const buf = await result.jpeg().toBuffer();
const meta = await sharp(buf).metadata();
expect(meta.exif).toBeTruthy();
const parsed = exifReader(meta.exif!);
expect(parsed.Image?.Artist).toBe("Test Artist");
});
it("edit wins over remove for same field", async () => {
const image = sharp(jpgWithExif);
const result = await editMetadata(image, {
artist: "Override Artist",
fieldsToRemove: ["Artist"],
});
const buf = await result.jpeg().toBuffer();
const meta = await sharp(buf).metadata();
const parsed = exifReader(meta.exif!);
expect(parsed.Image?.Artist).toBe("Override Artist");
});
it("writes fresh EXIF to image without existing metadata", async () => {
const image = sharp(png1x1);
const result = await editMetadata(image, {
artist: "Fresh Artist",
copyright: "Fresh Copyright",
});
const buf = await result.png().toBuffer();
// The operation should not throw
expect(buf.length).toBeGreaterThan(0);
});
});