npm.io
3.1.0 • Published 2d ago

@enslo/sd-metadata

Licence
MIT
Version
3.1.0
Deps
0
Size
683 kB
Vulns
0
Weekly
0
Stars
2

@enslo/sd-metadata

npm version npm downloads license

日本語版はこちら

Live Demo

A TypeScript library to read and write metadata embedded in AI-generated images.

Features

  • Multi-format Support: PNG (tEXt / iTXt), JPEG (COM / Exif), WebP (Exif)
  • Simple API: read(), write(), embed(), stringify() — four functions cover all use cases
  • TypeScript Native: Written in TypeScript with full type definitions included
  • Zero Dependencies: Works in Node.js and browsers without any external dependencies
  • Format Conversion: Seamlessly convert metadata between PNG, JPEG, and WebP
  • Metadata Preservation: Preserves original metadata structure when converting formats (e.g., PNG → JPEG → PNG maintains all original data)
  • AI Provenance Detection: identifies images carrying C2PA Content Credentials (OpenAI ChatGPT, Google Gemini) — detection only, no signature verification

Installation

npm install @enslo/sd-metadata

Quick Start

import { read } from '@enslo/sd-metadata';

// `imageBytes` is a Uint8Array or ArrayBuffer (from fs, fetch, a file input, ...)
const result = read(imageBytes);
if (result.status === 'success') {
  console.log('Tool:', result.metadata.software); // 'novelai', 'comfyui', ...
  console.log('Prompt:', result.metadata.prompt);
  console.log('Size:', result.metadata.width, 'x', result.metadata.height);
}

See Usage for Node.js, browser, and userscript entry points, and Advanced Examples for the other result statuses (c2pa, unrecognized, empty, invalid).

Tool Support

Tool PNG JPEG WebP
NovelAI *
ComfyUI *
Stable Diffusion WebUI
Forge
Forge Classic
Forge Neo
reForge
EasyReforge
SD.Next
InvokeAI
SwarmUI *
Civitai
TensorArt
Stability Matrix
HuggingFace Space
Fooocus
Ruined Fooocus
Easy Diffusion
Draw Things

Legend:

  • Fully Supported - Formats natively supported by the tool, verified with sample files
  • Extended Support - Formats not natively supported by the tool, but sd-metadata enables read/write through custom format conversion. Supports round-trip conversion back to native formats.
  • Experimental - Implemented by analyzing reference code or documentation, not verified with actual sample files. May not extract all metadata fields correctly.
Extended-support examples
  • Stability Matrix (native: PNG only) → sd-metadata enables JPEG/WebP support
  • NovelAI (native: PNG, WebP) → sd-metadata enables JPEG support

When you convert from a native format to an extended format and back (e.g., PNG → JPEG → PNG), all metadata is preserved.

* Tools with format-specific behaviors — see below.

Help us expand tool support! We're actively collecting sample images from experimental tools (Easy Diffusion, Fooocus) and unsupported tools. If you have sample images generated by these or other AI tools, please consider contributing them! See CONTRIBUTING.md for details.

Format-Specific Behaviors

Some tools have specific behaviors when converting between formats:

  • ComfyUI JPEG/WebP: Reading supports multiple node formats (e.g., the built-in Save Animated WEBP and save-image-extended). Writing always matches the built-in Save Animated WEBP node's Make/Model EXIF tags byte-for-byte — the layout ComfyUI itself reads natively for WebP drag-and-drop — dropping any PNG chunk other than prompt/workflow (e.g. parameters from third-party save nodes).
  • NovelAI WebP: Automatically corrects corrupted UTF-8 in the Description field. WebP → PNG → WebP round-trip produces valid, readable metadata but with minor text corrections.
  • SwarmUI PNG→JPEG/WebP: Native SwarmUI JPEG/WebP files do not include node information. When converting from PNG, this library preserves the ComfyUI workflow in the Make field for complete metadata retention (extended support).

Import

ESM (TypeScript / Modern JavaScript):

import { read } from '@enslo/sd-metadata';

CommonJS (Node.js):

const { read } = require('@enslo/sd-metadata');

All examples below use ESM syntax. CommonJS users can replace import with require.

Usage

Node.js Usage
import { read, stringify } from '@enslo/sd-metadata';
import { readFileSync } from 'fs';

const imageData = readFileSync('image.png');
const result = read(imageData);

if (result.status === 'success') {
  console.log('Tool:', result.metadata.software);       // 'novelai', 'comfyui', etc.
  console.log('Prompt:', result.metadata.prompt);
  console.log('Model:', result.metadata.model?.name);
  console.log('Size:', result.metadata.width, 'x', result.metadata.height);
}

// Format as human-readable text (works with any status)
const text = stringify(result);
if (text) {
  console.log(text);
}
Browser Usage
import { read, softwareLabels } from '@enslo/sd-metadata';

// Handle file input
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  if (!file) return;

  const arrayBuffer = await file.arrayBuffer();
  const result = read(arrayBuffer);

  if (result.status === 'success') {
    document.getElementById('tool').textContent = softwareLabels[result.metadata.software];
    document.getElementById('prompt').textContent = result.metadata.prompt;
    document.getElementById('model').textContent = result.metadata.model?.name || 'N/A';
  }
});
Userscript Usage

For userscripts (Tampermonkey, Violentmonkey, etc.), load the IIFE build via @require:

// ==UserScript==
// @name        My Script
// @namespace   https://example.com
// @require     https://cdn.jsdelivr.net/npm/@enslo/sd-metadata@3.1.0/dist/index.global.js
// ==/UserScript==

const response = await fetch(imageUrl);
const arrayBuffer = await response.arrayBuffer();
const result = sdMetadata.read(arrayBuffer);

if (result.status === 'success') {
  console.log('Tool:', result.metadata.software);
  console.log('Prompt:', result.metadata.prompt);
}

Always pin to a specific version in @require for stability.

Advanced Examples
Format Conversion

Convert metadata between different image formats:

import { read, write } from '@enslo/sd-metadata';

// Read metadata from PNG
const pngData = readFileSync('comfyui-output.png');
const parseResult = read(pngData);

if (parseResult.status === 'success') {
  // Convert PNG to JPEG (using your preferred image processing library)
  const jpegImageData = convertToJpeg(pngData); // Pseudo-code: use sharp, canvas, etc.
  
  // Embed the metadata into the JPEG
  const result = write(jpegImageData, parseResult);
  
  if (result.ok) {
    writeFileSync('output.jpg', result.value);
    console.log('Image converted to JPEG with metadata preserved');
  }
}

Tip: This library handles metadata read/write only. For actual image format conversion (decoding/encoding pixels), use image processing libraries like sharp, jimp, or browser Canvas API.

Handling Different Result Types
import { read } from '@enslo/sd-metadata';

const result = read(imageData);

switch (result.status) {
  case 'success':
    // Metadata successfully parsed
    console.log(`Generated by ${result.metadata.software}`);
    console.log(`Prompt: ${result.metadata.prompt}`);
    break;

  case 'c2pa':
    // C2PA Content Credentials detected (e.g. OpenAI ChatGPT, Google Gemini).
    // Detection only — the signature is NOT verified, and there are no
    // generation parameters (prompt/seed/model) to read.
    console.log(`AI provenance: ${result.c2pa.vendor} (unverified)`);
    console.log(`Claim generator: ${result.c2pa.claimGenerator ?? 'unknown'}`);
    break;

  case 'unrecognized':
    // Metadata exists but format is not recognized
    console.log('Unknown metadata format');
    // You can still access raw metadata for debugging:
    console.log('Raw chunks:', result.raw);
    break;

  case 'empty':
    // No metadata found
    console.log('No metadata in this image');
    break;

  case 'invalid':
    // Corrupted or invalid image data
    console.log('Error:', result.message);
    break;
}
Handling Unrecognized Metadata

When working with metadata from unsupported tools:

import { read, write } from '@enslo/sd-metadata';

const source = read(unknownImage);
// source.status === 'unrecognized'

// Write to target image
// - Same format (e.g., PNG → PNG): metadata preserved as-is
// - Cross-format (e.g., PNG → JPEG): metadata dropped with warning
const result = write(targetImage, source);
if (result.ok) {
  saveFile('output.png', result.value);
  if (result.warning) {
    // Metadata was dropped during cross-format conversion
    console.warn('Metadata was dropped:', result.warning.reason);
  }
}
AI Provenance Detection (C2PA Content Credentials)

Some commercial tools (OpenAI ChatGPT, Google Gemini) embed a signed C2PA "Content Credentials" provenance manifest instead of generation parameters. read() returns { status: 'c2pa', c2pa } for these images:

import { read, c2paVendorLabels } from '@enslo/sd-metadata';

const result = read(imageData);

if (result.status === 'c2pa') {
  console.log('Vendor:', c2paVendorLabels[result.c2pa.vendor]);
  console.log('Declared AI-generated:', result.c2pa.aiGenerated);
  console.log('Claim generator:', result.c2pa.claimGenerator ?? 'unknown');
}

Detection only — not proof. The C2PA signature is not verified, so a c2pa result is forgeable and cannot prove authenticity. Content Credentials are also easily stripped (screenshots, re-uploads, re-encoding), so the absence of a c2pa result does not prove an image is not AI-generated.

Removing Metadata

To strip all metadata from an image:

import { write } from '@enslo/sd-metadata';

const result = write(imageData, { status: 'empty' });
if (result.ok) {
  writeFileSync('clean-image.png', result.value);
}
Embedding Custom Metadata

Create and embed custom metadata in A1111 format:

import { embed } from '@enslo/sd-metadata';

const metadata = {
  prompt: 'masterpiece, best quality, 1girl',
  negativePrompt: 'lowres, bad quality',
  width: 512,
  height: 768,
  sampling: {
    steps: 20,
    sampler: 'Euler a',
    cfg: 7,
    seed: 12345,
  },
  model: { name: 'model.safetensors' },
};

// Write to any image format (PNG, JPEG, WebP)
const result = embed(imageData, metadata);
if (result.ok) {
  writeFileSync('output.png', result.value);
}

You can also add arbitrary key-value pairs to the settings line with extras:

const result = embed(imageData, {
  ...metadata,
  extras: { Version: 'v1.10.0', 'Lora hashes': 'abc123' },
});

Tip: If an extras key matches a structured field (e.g., Steps), the extras value overrides the structured value at its original position. New keys are appended at the end.

Since EmbedMetadata is a subset of all GenerationMetadata variants, you can pass parsed metadata directly — including NovelAI with its characterPrompts:

import { read, embed } from '@enslo/sd-metadata';

const result = read(novelaiPng);
if (result.status === 'success') {
  // NovelAI metadata (or any other tool) works as-is
  const output = embed(blankJpeg, result.metadata);
}
Formatting Metadata for Display

Convert a ParseResult to a human-readable string. Automatically selects the best representation based on status:

import { read, stringify } from '@enslo/sd-metadata';

const result = read(imageData);
const text = stringify(result);
if (text) {
  console.log(text);

  // For 'success': outputs in WebUI format:
  // masterpiece, best quality, 1girl
  // Negative prompt: lowres, bad quality
  // Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 12345, Size: 512x768, Model: model.safetensors
  //
  // For 'unrecognized': outputs raw metadata text
  // For 'empty' / 'invalid': returns empty string
}

API Reference

read(input: Uint8Array | ArrayBuffer, options?: ReadOptions): ParseResult

Reads and parses metadata from an image file.

Parameters:

  • input - Image file data (PNG, JPEG, or WebP)
  • options - Optional read options
    • strict?: boolean (default: false) — When true, dimensions (width / height) are taken strictly from metadata only. When false, missing dimensions are extracted from image headers.

Returns:

  • { status: 'success', metadata, raw } - Successfully parsed
    • metadata: Unified metadata object (see GenerationMetadata)
    • raw: Original format-specific data (chunks/segments)
  • { status: 'c2pa', c2pa } - Image carries C2PA Content Credentials (e.g. OpenAI ChatGPT, Google Gemini) but no parseable generation metadata
    • c2pa: Declared (unverified) AI provenance (see C2paMetadata). Detection only — the signature is not verified.
  • { status: 'unrecognized', raw } - Image has metadata but not from a known AI tool
    • raw: Original metadata preserved for conversion
  • { status: 'empty' } - No metadata found in the image
  • { status: 'invalid', message? } - Corrupted or unsupported image format
    • message: Optional error description
write(input: Uint8Array | ArrayBuffer, metadata: ParseResult): WriteResult

Writes metadata to an image file.

Parameters:

  • input - Target image file data (PNG, JPEG, or WebP)
  • metadata - ParseResult from read()
    • status: 'success' or 'empty' - Can write directly
    • status: 'unrecognized' - Same format: writes as-is; Cross-format: drops metadata with warning

Returns:

  • { ok: true, value: Uint8Array, warning?: WriteWarning } - Successfully written
    • warning is set when metadata was intentionally dropped (e.g., unrecognized cross-format)
  • { ok: false, error: { type, message? } } - Failed. type is one of:
    • 'unsupportedFormat': Target image is not PNG, JPEG, or WebP
    • 'conversionFailed': Metadata conversion failed (e.g., incompatible format)
    • 'writeFailed': Failed to embed metadata into the image, or the input cannot be written back
embed(input: Uint8Array | ArrayBuffer, metadata: EmbedMetadata | GenerationMetadata): WriteResult

Embeds custom metadata into an image in SD WebUI (A1111) format.

Parameters:

  • input - Target image file data (PNG, JPEG, or WebP)
  • metadata - EmbedMetadata or GenerationMetadata to embed (use extras field for custom settings)

Returns:

  • { ok: true, value: Uint8Array } - Successfully written (returns new image data)
  • { ok: false, error: { type, message? } } - Failed. type is one of:
    • 'unsupportedFormat': Target image is not PNG, JPEG, or WebP
    • 'writeFailed': Failed to embed metadata into the image

Use cases:

  • Creating custom metadata for programmatically generated images
  • Converting metadata from other tools to WebUI-compatible format
  • Building applications that output WebUI-readable metadata
stringify(input: ParseResult | EmbedMetadata | GenerationMetadata): string

Converts metadata to a human-readable string.

Parameters:

  • input - One of:
    • ParseResult from read() — selects best representation based on status
    • EmbedMetadata or GenerationMetadata — formats as A1111 text directly

Returns:

  • ParseResult:
    • success → Human-readable text in WebUI format
    • c2pa → The claim generator name (claimGenerator ?? '')
    • unrecognized → Raw metadata as plain text
    • empty / invalid → Empty string
  • EmbedMetadata / GenerationMetadata → Human-readable text in WebUI format

Use cases:

  • Displaying generation parameters in image viewers or galleries
  • Copying metadata to clipboard as readable text
  • Logging or debugging parsed metadata
  • Previewing EmbedMetadata before embedding
softwareLabels: Record<GenerationSoftware, string>

A read-only mapping from GenerationSoftware identifiers to their human-readable display names.

import { softwareLabels } from '@enslo/sd-metadata';

const result = read(imageData);
if (result.status === 'success') {
  console.log(softwareLabels[result.metadata.software]);
  // => "NovelAI", "ComfyUI", "Stable Diffusion WebUI", etc.
}
c2paVendorLabels: Record<C2paVendor, string>

A read-only mapping from C2paVendor identifiers to their human-readable display names.

import { c2paVendorLabels } from '@enslo/sd-metadata';

const result = read(imageData);
if (result.status === 'c2pa') {
  console.log(c2paVendorLabels[result.c2pa.vendor]);
  // => "OpenAI (ChatGPT)", "Google (Gemini)", "AI-generated (Content Credentials)"
}

Type Reference

This section provides an overview of the main types. For complete type definitions, see Type Documentation.

ParseResult

The result of the read() function. It uses a discriminated union with a status field.

type ParseResult =
  | { status: 'success'; metadata: GenerationMetadata; raw: RawMetadata }
  | { status: 'c2pa'; c2pa: C2paMetadata }
  | { status: 'unrecognized'; raw: RawMetadata }
  | { status: 'empty' }
  | { status: 'invalid'; message?: string };
BaseMetadata

Common fields shared by all metadata types. This interface is also the foundation of EmbedMetadata.

interface BaseMetadata {
  prompt: string;
  negativePrompt: string;
  width: number;
  height: number;
  model?: ModelSettings;
  sampling?: SamplingSettings;
  hires?: HiresSettings;
  upscale?: UpscaleSettings;
}
GenerationMetadata

Unified metadata structure returned by the read() function. This is a discriminated union of 3 specific metadata types, distinguished by the software field. All types extend BaseMetadata.

Metadata Type Variants:

  • NovelAIMetadata (software: 'novelai')
    Includes NovelAI-specific fields for V4 character placement:

    • characterPrompts?: CharacterPrompt[] - Per-character prompts with positions
    • useCoords?: boolean - Use character coordinates for placement
    • useOrder?: boolean - Use character order
  • ComfyUIMetadata (software: 'comfyui' | 'tensorart' | 'stability-matrix' | 'swarmui')
    Includes ComfyUI workflow graph:

    • nodes: ComfyNodeGraph (required for comfyui/tensorart/stability-matrix)
    • nodes?: ComfyNodeGraph (optional for swarmui - only in PNG format)
  • StandardMetadata (software: 'sd-webui' | 'forge' | 'forge-classic' | 'reforge' | 'invokeai' | ...) Baseline metadata without tool-specific extensions. Used by most SD WebUI-based tools.

Type Definition:

type GenerationMetadata =
  | NovelAIMetadata
  | ComfyUIMetadata
  | StandardMetadata;

Usage Example:

const result = read(imageData);

if (result.status === 'success') {
  const metadata = result.metadata;
  
  // Access common fields
  console.log('Prompt:', metadata.prompt);
  console.log('Model:', metadata.model?.name);
  console.log('Seed:', metadata.sampling?.seed);
  
  // Narrow on the `software` discriminator for tool-specific fields
  if (metadata.software === 'novelai') {
    // TypeScript knows this is NovelAIMetadata
    console.log('Character prompts:', metadata.characterPrompts);
  } else if (
    metadata.software === 'comfyui' ||
    metadata.software === 'tensorart' ||
    metadata.software === 'stability-matrix' ||
    metadata.software === 'swarmui'
  ) {
    // TypeScript knows this is ComfyUIMetadata.
    // `nodes` is always present except for native SwarmUI JPEG/WebP, so guard it.
    if (metadata.nodes) {
      console.log('Node count:', Object.keys(metadata.nodes).length);
    }
  }
}

See Type Documentation for detailed interface definitions of each metadata type.

GenerationSoftware

String literal union of all supported software identifiers. Used as the key type for softwareLabels.

type GenerationSoftware =
  | 'novelai' | 'comfyui' | 'swarmui' | 'tensorart' | 'stability-matrix'
  | 'sd-webui' | 'forge' | 'forge-classic' | 'forge-neo' 
  | 'reforge'| 'easy-reforge' | 'sd-next' | 'civitai' | 'hf-space'
  | 'invokeai' | 'easydiffusion' | 'fooocus' | 'ruined-fooocus'
  | 'draw-things';
EmbedMetadata

User-created custom metadata for the embed() and stringify() functions. While GenerationMetadata represents parsed output from a known AI tool, EmbedMetadata is designed for composing metadata from scratch. Extends BaseMetadata with optional character prompts and extras.

type EmbedMetadata = BaseMetadata &
  Pick<NovelAIMetadata, 'characterPrompts'> & {
    extras?: Record<string, string | number>;
  };
RawMetadata

Preserves the original metadata structure for round-trip conversions.

type RawMetadata =
  | { format: 'png'; chunks: PngTextChunk[] }
  | { format: 'jpeg'; segments: MetadataSegment[] }
  | { format: 'webp'; segments: MetadataSegment[] };

For detailed documentation of all exported types including ModelSettings, SamplingSettings, and format-specific types, see the Type Documentation.

Development

See the Contributing Guide for development setup and guidelines.

License

MIT

Keywords