@enslo/sd-metadata
v2.2.0
Published
Read and write AI-generated image metadata
Maintainers
Readme
@enslo/sd-metadata
🌐 日本語版はこちら
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)
Installation
npm install @enslo/sd-metadataTool 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.
[!NOTE] * Tools with format-specific behaviors. See Format-Specific Behaviors for details.
[!TIP] 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 custom node formats (e.g.,
save-image-extended), but writing always uses thecomfyui-saveimage-plusformat for best information preservation and compatibility with ComfyUI's native drag-and-drop workflow loading. - 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
Makefield 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');[!NOTE] All examples below use ESM syntax. CommonJS users can replace
importwithrequire.
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/[email protected]/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);
}[!TIP] Always pin to a specific version in
@requirefor stability.
Advanced Examples
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.
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 '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;
}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);
}
}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);
}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);
}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 (see Type Documentation for details)
Returns:
{ status: 'success', metadata, raw }- Successfully parsedmetadata: Unified metadata object (seeGenerationMetadata)raw: Original format-specific data (chunks/segments)
{ status: 'unrecognized', raw }- Image has metadata but not from a known AI toolraw: Original metadata preserved for conversion
{ status: 'empty' }- No metadata found in the image{ status: 'invalid', message? }- Corrupted or unsupported image formatmessage: 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-ParseResultfromread()status: 'success'or'empty'- Can write directlystatus: 'unrecognized'- Same format: writes as-is; Cross-format: drops metadata with warning
Returns:
{ ok: true, value: Uint8Array, warning?: WriteWarning }- Successfully writtenwarningis set when metadata was intentionally dropped (e.g., unrecognized cross-format)
{ ok: false, error: { type, message? } }- Failed.typeis 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
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-EmbedMetadataorGenerationMetadatato embed (useextrasfield for custom settings)
Returns:
{ ok: true, value: Uint8Array }- Successfully written (returns new image data){ ok: false, error: { type, message? } }- Failed.typeis 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:ParseResultfromread()— selects best representation based on statusEmbedMetadataorGenerationMetadata— formats as A1111 text directly
Returns:
ParseResultwithsuccess→ Human-readable text in WebUI formatParseResultwithunrecognized→ Raw metadata as plain textParseResultwithempty/invalid→ Empty stringEmbedMetadata/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
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.
}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: '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 positionsuseCoords?: boolean- Use character coordinates for placementuseOrder?: 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);
// Type-specific handling using discriminated union
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'
) {
// TypeScript knows this is BasicComfyUIMetadata (nodes always present)
console.log('Node count:', Object.keys(metadata.nodes).length);
} else if (metadata.software === 'swarmui') {
// TypeScript knows this is SwarmUIMetadata (nodes optional)
if (metadata.nodes) {
console.log('Workflow included');
}
}
}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
