🚀 Socket Launch Week Day 4:Socket MCP Adds Org Alerts, Threat Feed Review, and Package Inspection.Learn more
Sign In

aeo-image

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aeo-image - npm Package Compare versions

Comparing version
1.1.1
to
1.2.0
+2
-2
dist/index.d.ts

@@ -1,6 +0,6 @@

import { type ImageMetadata, type ImageFormat, UnsupportedFormatError } from "./types.ts";
import { type ImageMetadata, type ImageFormat, UnsupportedFormatError, DIGITAL_SOURCE_TYPE } from "./types.ts";
import { serializeXmp } from "./xmp/serialize.ts";
import { parseXmp } from "./xmp/parse.ts";
export type { ImageMetadata, ImageFormat };
export { UnsupportedFormatError };
export { UnsupportedFormatError, DIGITAL_SOURCE_TYPE };
export { serializeXmp, parseXmp };

@@ -7,0 +7,0 @@ /** Detect the container format from magic bytes. */

@@ -1,2 +0,2 @@

import { UnsupportedFormatError, } from "./types.js";
import { UnsupportedFormatError, DIGITAL_SOURCE_TYPE, } from "./types.js";
import { isWebp, readWebpMetadata, writeWebpMetadata, removeWebpMetadata, } from "./formats/webp.js";

@@ -8,3 +8,3 @@ import { readAvifMetadata, writeAvifMetadata, removeAvifMetadata, } from "./formats/avif.js";

import { parseXmp } from "./xmp/parse.js";
export { UnsupportedFormatError };
export { UnsupportedFormatError, DIGITAL_SOURCE_TYPE };
export { serializeXmp, parseXmp };

@@ -11,0 +11,0 @@ function dataU32(buf, o) {

@@ -7,3 +7,3 @@ /**

* library maps them onto the correct XMP namespaces (dc:, photoshop:,
* Iptc4xmpCore:, xmpRights:) under the hood.
* Iptc4xmpCore:, Iptc4xmpExt:, xmpRights:) under the hood.
*/

@@ -41,3 +41,49 @@ export interface ImageMetadata {

copyrightNotice?: string;
/**
* How the image came to be — an IRI from the IPTC Digital Source Type
* vocabulary (https://cv.iptc.org/newscodes/digitalsourcetype/). Maps to
* Iptc4xmpExt:DigitalSourceType. This is the field ecosystems key off to
* label an image as AI-generated: set it to
* `DIGITAL_SOURCE_TYPE.trainedAlgorithmicMedia` alongside the `ai` fields.
*/
digitalSourceType?: string;
/**
* AI-generation provenance (IPTC Photo Metadata Standard 2025.1, Extension
* schema). Per IPTC guidance, also set `digitalSourceType` and leave
* `creator` empty for fully AI-generated images — the prompt writer is
* explicitly not the image creator.
*/
ai?: {
/**
* Prompt(s) given to the generative AI service, including negative
* prompts and model parameters if you wish. Maps to
* Iptc4xmpExt:AIPromptInformation.
*/
prompt?: string;
/** Name of the person who wrote the prompt. Maps to Iptc4xmpExt:AIPromptWriterName. */
promptWriter?: string;
/**
* The AI engine and/or model used, e.g. "DALL-E via Bing Image Creator".
* Maps to Iptc4xmpExt:AISystemUsed.
*/
system?: string;
/** Version of the AI system, if known. Maps to Iptc4xmpExt:AISystemVersionUsed. */
systemVersion?: string;
};
}
/**
* Common IRIs from the IPTC "Digital Source Type" NewsCodes vocabulary, for
* use as `ImageMetadata.digitalSourceType`. Not exhaustive — any IRI from
* https://cv.iptc.org/newscodes/digitalsourcetype/ is valid.
*/
export declare const DIGITAL_SOURCE_TYPE: {
/** Generated purely by an AI model from prompts — the standard "AI-generated" disclosure. */
readonly trainedAlgorithmicMedia: "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia";
/** Composite that includes AI-generated elements. */
readonly compositeWithTrainedAlgorithmicMedia: "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia";
/** Composite of captured and synthetic elements. */
readonly compositeSynthetic: "http://cv.iptc.org/newscodes/digitalsourcetype/compositeSynthetic";
/** Original photograph from a digital camera. */
readonly digitalCapture: "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture";
};
export type ImageFormat = "webp" | "jpeg" | "png" | "avif" | "heic" | "unknown";

@@ -44,0 +90,0 @@ export declare class UnsupportedFormatError extends Error {

@@ -0,1 +1,16 @@

/**
* Common IRIs from the IPTC "Digital Source Type" NewsCodes vocabulary, for
* use as `ImageMetadata.digitalSourceType`. Not exhaustive — any IRI from
* https://cv.iptc.org/newscodes/digitalsourcetype/ is valid.
*/
export const DIGITAL_SOURCE_TYPE = {
/** Generated purely by an AI model from prompts — the standard "AI-generated" disclosure. */
trainedAlgorithmicMedia: "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
/** Composite that includes AI-generated elements. */
compositeWithTrainedAlgorithmicMedia: "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia",
/** Composite of captured and synthetic elements. */
compositeSynthetic: "http://cv.iptc.org/newscodes/digitalsourcetype/compositeSynthetic",
/** Original photograph from a digital camera. */
digitalCapture: "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture",
};
export class UnsupportedFormatError extends Error {

@@ -2,0 +17,0 @@ format;

@@ -84,3 +84,22 @@ /**

}
const digitalSourceType = simple(xmp, "Iptc4xmpExt:DigitalSourceType");
if (digitalSourceType)
meta.digitalSourceType = digitalSourceType;
// IPTC 2025.1 AI-generation provenance — only attach `ai` if any field is present.
const aiPrompt = simple(xmp, "Iptc4xmpExt:AIPromptInformation");
const aiPromptWriter = simple(xmp, "Iptc4xmpExt:AIPromptWriterName");
const aiSystem = simple(xmp, "Iptc4xmpExt:AISystemUsed");
const aiSystemVersion = simple(xmp, "Iptc4xmpExt:AISystemVersionUsed");
if (aiPrompt || aiPromptWriter || aiSystem || aiSystemVersion) {
meta.ai = {};
if (aiPrompt)
meta.ai.prompt = aiPrompt;
if (aiPromptWriter)
meta.ai.promptWriter = aiPromptWriter;
if (aiSystem)
meta.ai.system = aiSystem;
if (aiSystemVersion)
meta.ai.systemVersion = aiSystemVersion;
}
return meta;
}

@@ -15,2 +15,3 @@ /**

Iptc4xmpCore: "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/",
Iptc4xmpExt: "http://iptc.org/std/Iptc4xmpExt/2008-02-29/",
plus: "http://ns.useplus.org/ldf/xmp/1.0/",

@@ -33,2 +34,6 @@ };

}
/** A simple text/URI property (photoshop:Credit, the Iptc4xmpExt AI fields). */
function simpleEl(tag, value) {
return ` <${tag}>${esc(value)}</${tag}>\n`;
}
/** An unordered array property (dc:subject / keywords). */

@@ -81,9 +86,21 @@ function bag(tag, values) {

if (meta.credit)
props.push(` <photoshop:Credit>${esc(meta.credit)}</photoshop:Credit>\n`);
props.push(simpleEl("photoshop:Credit", meta.credit));
if (meta.copyrightNotice)
props.push(` <photoshop:Copyright>${esc(meta.copyrightNotice)}</photoshop:Copyright>\n`);
props.push(simpleEl("photoshop:Copyright", meta.copyrightNotice));
if (meta.licenseUrl)
props.push(` <xmpRights:WebStatement>${esc(meta.licenseUrl)}</xmpRights:WebStatement>\n`);
props.push(simpleEl("xmpRights:WebStatement", meta.licenseUrl));
if (meta.licensor?.url)
props.push(licensorSeq(meta.licensor.url, meta.licensor.name));
// IPTC Extension: digital source type + 2025.1 AI-generation provenance.
// All five are plain single-valued text/URI properties per the spec.
if (meta.digitalSourceType)
props.push(simpleEl("Iptc4xmpExt:DigitalSourceType", meta.digitalSourceType));
if (meta.ai?.prompt)
props.push(simpleEl("Iptc4xmpExt:AIPromptInformation", meta.ai.prompt));
if (meta.ai?.promptWriter)
props.push(simpleEl("Iptc4xmpExt:AIPromptWriterName", meta.ai.promptWriter));
if (meta.ai?.system)
props.push(simpleEl("Iptc4xmpExt:AISystemUsed", meta.ai.system));
if (meta.ai?.systemVersion)
props.push(simpleEl("Iptc4xmpExt:AISystemVersionUsed", meta.ai.systemVersion));
const xmlns = Object.entries(NS)

@@ -90,0 +107,0 @@ .map(([prefix, uri]) => ` xmlns:${prefix}="${uri}"`)

{
"name": "aeo-image",
"version": "1.1.1",
"version": "1.2.0",
"description": "Write descriptive + rights metadata (captions, keywords, alt text, creator, license) into WebP, AVIF, HEIC, JPEG & PNG — self-describing images that Google Images reads and recommends embedding, built for the AI-search era. The only pure-JS, zero-dependency library that writes XMP to AVIF/HEIC. Byte-preserving; runs on Node, Bun, Deno & edge.",

@@ -5,0 +5,0 @@ "type": "module",

@@ -50,3 +50,3 @@ # aeo-image

- 🖼️ **Byte-preserving** — splices metadata only; compressed image data is copied verbatim, never re-encoded.
- 🧠 **Semantic, AEO-oriented API** — you write `description`/`keywords`/`altText`, not raw tag IDs. We map them onto the correct XMP namespaces (`dc:`, `photoshop:`, `Iptc4xmpCore:`, `xmpRights:`).
- 🧠 **Semantic, AEO-oriented API** — you write `description`/`keywords`/`altText`, not raw tag IDs. We map them onto the correct XMP namespaces (`dc:`, `photoshop:`, `Iptc4xmpCore:`, `Iptc4xmpExt:`, `xmpRights:`).
- ☁️ **Runs anywhere** — Node, Deno, Bun, Cloudflare Workers, Vercel/Netlify/Lambda edge functions. No `fs` required; operates on buffers.

@@ -82,3 +82,3 @@ - 🔒 **Privacy-friendly** — `removeMetadata()` strips XMP/EXIF in one call (keeps ICC colour profile).

Fields conform to the **[IPTC Photo Metadata Standard 2025.1](https://iptc.org/standards/photo-metadata/iptc-standard/)** (the current revision), specifically the descriptive, accessibility, and rights/licensing subset, across these namespaces:
Fields conform to the **[IPTC Photo Metadata Standard 2025.1](https://iptc.org/standards/photo-metadata/iptc-standard/)** (the current revision), specifically the descriptive, accessibility, rights/licensing, and AI-provenance subset, across these namespaces:

@@ -89,2 +89,3 @@ | Namespace | Prefix | Used for |

| IPTC Core | `Iptc4xmpCore:` | `AltTextAccessibility` (IPTC **2021.1**+) |
| IPTC Extension | `Iptc4xmpExt:` | digital source type + AI-generation provenance (IPTC **2025.1**) |
| Adobe Photoshop | `photoshop:` | credit, copyright |

@@ -94,3 +95,3 @@ | XMP Rights | `xmpRights:` | web statement (license URL) |

**Not yet implemented:** IPTC 2025.1's AI-generation provenance properties (AI Prompt Information, AI System Used, …) — tracked in the roadmap.
This includes IPTC 2025.1's four **AI-generation provenance** properties (`AIPromptInformation`, `AIPromptWriterName`, `AISystemUsed`, `AISystemVersionUsed`) plus `DigitalSourceType` — see [Label AI-generated images](#label-ai-generated-images). Readable by exiftool (named tags from **13.40**). C2PA / Content Credentials (cryptographically signed manifests) are a separate standard and out of scope.

@@ -136,2 +137,25 @@ ## Install

### Label AI-generated images
IPTC Photo Metadata 2025.1 added four AI-provenance fields, which pair with
`DigitalSourceType` — the field ecosystems read to label an image as
AI-generated:
```ts
import { writeMetadata, DIGITAL_SOURCE_TYPE } from "aeo-image";
const output = writeMetadata(input, {
description: "A neon-lit street market at night in the rain",
digitalSourceType: DIGITAL_SOURCE_TYPE.trainedAlgorithmicMedia,
ai: {
prompt: "neon street market, rain reflections, cinematic 35mm",
promptWriter: "Jane Doe",
system: "DALL-E via Bing Image Creator",
systemVersion: "3",
},
// Per IPTC guidance, leave `creator` empty for fully AI-generated images —
// the prompt writer is explicitly not the image creator.
});
```
### Strip (privacy)

@@ -176,2 +200,4 @@

| `licensor` | `{ url, name? }` | IPTC PLUS `plus:Licensor` — *Google "Get this image" link* |
| `digitalSourceType` | `string` (IRI) | `Iptc4xmpExt:DigitalSourceType` — *AI-generated disclosure*; use `DIGITAL_SOURCE_TYPE.*` |
| `ai` | `{ prompt?, promptWriter?, system?, systemVersion? }` | IPTC 2025.1 `Iptc4xmpExt:AIPromptInformation` / `AIPromptWriterName` / `AISystemUsed` / `AISystemVersionUsed` |

@@ -178,0 +204,0 @@ The last three implement the fields Google Images reads for the **Licensable** badge and license link. All functions return a **new** buffer and never mutate the input. See [`docs/xmp-fields.md`](docs/xmp-fields.md) for the complete field/namespace reference and AEO rationale.

@@ -5,2 +5,3 @@ import {

UnsupportedFormatError,
DIGITAL_SOURCE_TYPE,
} from "./types.ts";

@@ -32,3 +33,3 @@ import {

export type { ImageMetadata, ImageFormat };
export { UnsupportedFormatError };
export { UnsupportedFormatError, DIGITAL_SOURCE_TYPE };
export { serializeXmp, parseXmp };

@@ -35,0 +36,0 @@

@@ -7,3 +7,3 @@ /**

* library maps them onto the correct XMP namespaces (dc:, photoshop:,
* Iptc4xmpCore:, xmpRights:) under the hood.
* Iptc4xmpCore:, Iptc4xmpExt:, xmpRights:) under the hood.
*/

@@ -38,4 +38,55 @@ export interface ImageMetadata {

copyrightNotice?: string;
/**
* How the image came to be — an IRI from the IPTC Digital Source Type
* vocabulary (https://cv.iptc.org/newscodes/digitalsourcetype/). Maps to
* Iptc4xmpExt:DigitalSourceType. This is the field ecosystems key off to
* label an image as AI-generated: set it to
* `DIGITAL_SOURCE_TYPE.trainedAlgorithmicMedia` alongside the `ai` fields.
*/
digitalSourceType?: string;
/**
* AI-generation provenance (IPTC Photo Metadata Standard 2025.1, Extension
* schema). Per IPTC guidance, also set `digitalSourceType` and leave
* `creator` empty for fully AI-generated images — the prompt writer is
* explicitly not the image creator.
*/
ai?: {
/**
* Prompt(s) given to the generative AI service, including negative
* prompts and model parameters if you wish. Maps to
* Iptc4xmpExt:AIPromptInformation.
*/
prompt?: string;
/** Name of the person who wrote the prompt. Maps to Iptc4xmpExt:AIPromptWriterName. */
promptWriter?: string;
/**
* The AI engine and/or model used, e.g. "DALL-E via Bing Image Creator".
* Maps to Iptc4xmpExt:AISystemUsed.
*/
system?: string;
/** Version of the AI system, if known. Maps to Iptc4xmpExt:AISystemVersionUsed. */
systemVersion?: string;
};
}
/**
* Common IRIs from the IPTC "Digital Source Type" NewsCodes vocabulary, for
* use as `ImageMetadata.digitalSourceType`. Not exhaustive — any IRI from
* https://cv.iptc.org/newscodes/digitalsourcetype/ is valid.
*/
export const DIGITAL_SOURCE_TYPE = {
/** Generated purely by an AI model from prompts — the standard "AI-generated" disclosure. */
trainedAlgorithmicMedia:
"http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
/** Composite that includes AI-generated elements. */
compositeWithTrainedAlgorithmicMedia:
"http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia",
/** Composite of captured and synthetic elements. */
compositeSynthetic:
"http://cv.iptc.org/newscodes/digitalsourcetype/compositeSynthetic",
/** Original photograph from a digital camera. */
digitalCapture:
"http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture",
} as const;
export type ImageFormat =

@@ -42,0 +93,0 @@ | "webp"

@@ -93,3 +93,19 @@ import type { ImageMetadata } from "../types.ts";

const digitalSourceType = simple(xmp, "Iptc4xmpExt:DigitalSourceType");
if (digitalSourceType) meta.digitalSourceType = digitalSourceType;
// IPTC 2025.1 AI-generation provenance — only attach `ai` if any field is present.
const aiPrompt = simple(xmp, "Iptc4xmpExt:AIPromptInformation");
const aiPromptWriter = simple(xmp, "Iptc4xmpExt:AIPromptWriterName");
const aiSystem = simple(xmp, "Iptc4xmpExt:AISystemUsed");
const aiSystemVersion = simple(xmp, "Iptc4xmpExt:AISystemVersionUsed");
if (aiPrompt || aiPromptWriter || aiSystem || aiSystemVersion) {
meta.ai = {};
if (aiPrompt) meta.ai.prompt = aiPrompt;
if (aiPromptWriter) meta.ai.promptWriter = aiPromptWriter;
if (aiSystem) meta.ai.system = aiSystem;
if (aiSystemVersion) meta.ai.systemVersion = aiSystemVersion;
}
return meta;
}

@@ -19,2 +19,3 @@ import type { ImageMetadata } from "../types.ts";

Iptc4xmpCore: "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/",
Iptc4xmpExt: "http://iptc.org/std/Iptc4xmpExt/2008-02-29/",
plus: "http://ns.useplus.org/ldf/xmp/1.0/",

@@ -42,2 +43,7 @@ } as const;

/** A simple text/URI property (photoshop:Credit, the Iptc4xmpExt AI fields). */
function simpleEl(tag: string, value: string): string {
return ` <${tag}>${esc(value)}</${tag}>\n`;
}
/** An unordered array property (dc:subject / keywords). */

@@ -90,14 +96,21 @@ function bag(tag: string, values: string[]): string {

props.push(altLang("Iptc4xmpCore:AltTextAccessibility", meta.altText));
if (meta.credit)
props.push(` <photoshop:Credit>${esc(meta.credit)}</photoshop:Credit>\n`);
if (meta.credit) props.push(simpleEl("photoshop:Credit", meta.credit));
if (meta.copyrightNotice)
props.push(
` <photoshop:Copyright>${esc(meta.copyrightNotice)}</photoshop:Copyright>\n`,
);
props.push(simpleEl("photoshop:Copyright", meta.copyrightNotice));
if (meta.licenseUrl)
props.push(
` <xmpRights:WebStatement>${esc(meta.licenseUrl)}</xmpRights:WebStatement>\n`,
);
props.push(simpleEl("xmpRights:WebStatement", meta.licenseUrl));
if (meta.licensor?.url)
props.push(licensorSeq(meta.licensor.url, meta.licensor.name));
// IPTC Extension: digital source type + 2025.1 AI-generation provenance.
// All five are plain single-valued text/URI properties per the spec.
if (meta.digitalSourceType)
props.push(simpleEl("Iptc4xmpExt:DigitalSourceType", meta.digitalSourceType));
if (meta.ai?.prompt)
props.push(simpleEl("Iptc4xmpExt:AIPromptInformation", meta.ai.prompt));
if (meta.ai?.promptWriter)
props.push(simpleEl("Iptc4xmpExt:AIPromptWriterName", meta.ai.promptWriter));
if (meta.ai?.system)
props.push(simpleEl("Iptc4xmpExt:AISystemUsed", meta.ai.system));
if (meta.ai?.systemVersion)
props.push(simpleEl("Iptc4xmpExt:AISystemVersionUsed", meta.ai.systemVersion));

@@ -104,0 +117,0 @@ const xmlns = Object.entries(NS)