@enslo/sd-metadata 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +40 -50
- package/README.md +40 -49
- package/dist/index.global.js +11 -0
- package/dist/index.js +61 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/converters/utils.ts","../src/converters/chunk-encoding.ts","../src/types.ts","../src/utils/binary.ts","../src/utils/exif-constants.ts","../src/writers/exif.ts","../src/writers/jpeg.ts","../src/writers/png.ts","../src/writers/webp.ts","../src/api/stringify.ts","../src/api/embed.ts","../src/utils/object.ts","../src/parsers/a1111.ts","../src/utils/json.ts","../src/parsers/comfyui-nodes.ts","../src/parsers/comfyui-civitai.ts","../src/parsers/comfyui.ts","../src/parsers/detect.ts","../src/parsers/easydiffusion.ts","../src/parsers/fooocus.ts","../src/parsers/hf-space.ts","../src/utils/entries.ts","../src/parsers/invokeai.ts","../src/parsers/novelai.ts","../src/parsers/ruined-fooocus.ts","../src/parsers/stability-matrix.ts","../src/parsers/swarmui.ts","../src/parsers/tensorart.ts","../src/parsers/index.ts","../src/readers/dimensions.ts","../src/readers/exif.ts","../src/readers/jpeg.ts","../src/readers/png.ts","../src/readers/webp.ts","../src/utils/convert.ts","../src/api/read.ts","../src/converters/a1111.ts","../src/converters/civitai.ts","../src/converters/base-json.ts","../src/converters/comfyui.ts","../src/converters/easydiffusion.ts","../src/converters/invokeai.ts","../src/converters/novelai.ts","../src/converters/simple-chunk.ts","../src/converters/stability-matrix.ts","../src/converters/swarmui.ts","../src/converters/tensorart.ts","../src/converters/index.ts","../src/api/write.ts","../src/constants.ts"],"sourcesContent":["/**\n * Shared utilities for metadata converters\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\n\n/**\n * Create a tEXt chunk, returns empty array if text is undefined\n *\n * @param keyword - Chunk keyword\n * @param text - Chunk text, if undefined returns empty array\n * @returns Array with one chunk or empty array\n */\nexport const createTextChunk = (\n keyword: string,\n text: string | undefined,\n): PngTextChunk[] =>\n text !== undefined ? [{ type: 'tEXt', keyword, text }] : [];\n\n/**\n * Create an iTXt chunk, returns empty array if text is undefined\n *\n * @param keyword - Chunk keyword\n * @param text - Chunk text, if undefined returns empty array\n * @returns Array with one chunk or empty array\n */\nexport const createITxtChunk = (\n keyword: string,\n text: string | undefined,\n): PngTextChunk[] =>\n text !== undefined\n ? [\n {\n type: 'iTXt',\n keyword,\n compressionFlag: 0,\n compressionMethod: 0,\n languageTag: '',\n translatedKeyword: '',\n text,\n },\n ]\n : [];\n\n/**\n * Find a segment by source type\n *\n * @param segments - Array of metadata segments\n * @param type - Source type to find\n * @returns Matching segment or undefined\n */\nexport const findSegment = (\n segments: MetadataSegment[],\n type: string,\n): MetadataSegment | undefined => segments.find((s) => s.source.type === type);\n\n/**\n * Stringify value, returns undefined if value is undefined\n *\n * @param value - Value to stringify\n * @returns Stringified value or undefined\n */\nexport const stringify = (value: unknown): string | undefined => {\n if (value === undefined) return undefined;\n return typeof value === 'string' ? value : JSON.stringify(value);\n};\n","/**\n * Unified chunk encoding strategy for PNG converters\n *\n * Handles three different encoding strategies:\n * 1. dynamic: Choose tEXt/iTXt based on content (for tools like A1111, InvokeAI)\n * 2. text-unicode-escape: tEXt with Unicode escaping (for ComfyUI, SwarmUI)\n * 3. text-utf8-raw: tEXt with raw UTF-8 (for specific chunks in Stability Matrix, TensorArt)\n */\n\nimport type { PngTextChunk } from '../types';\nimport { createITxtChunk, createTextChunk } from './utils';\n\n/**\n * Chunk encoding strategy for PNG converters\n */\nexport type ChunkEncodingStrategy =\n | 'dynamic' // Choose tEXt/iTXt based on content\n | 'text-unicode-escape' // tEXt with Unicode escape (spec-compliant)\n | 'text-utf8-raw'; // tEXt with raw UTF-8 (non-compliant but compatible)\n\n/**\n * Map of chunk keywords to their encoding strategies\n *\n * Used when different chunks within the same software require different\n * encoding strategies (e.g., Stability Matrix, TensorArt).\n *\n * The 'default' key is required and used for keywords not in the map.\n */\nexport type ChunkEncodingMap = {\n [keyword: string]: ChunkEncodingStrategy;\n default: ChunkEncodingStrategy;\n};\n\n/**\n * Escape Unicode characters beyond Latin-1 for tEXt chunk\n *\n * Converts characters beyond Latin-1 to Unicode escape sequences.\n * Latin-1 range (0x00-0xFF) is left as-is since tEXt supports it.\n * Example: テスト → \\u30c6\\u30b9\\u30c8\n *\n * @param text - Text to escape\n * @returns Text with non-Latin-1 characters escaped\n */\nexport function escapeUnicode(text: string): string {\n return text.replace(/[\\u0100-\\uffff]/g, (char) => {\n const code = char.charCodeAt(0).toString(16).padStart(4, '0');\n return `\\\\u${code}`;\n });\n}\n\n/**\n * Unescape Unicode escape sequences back to actual characters\n *\n * Converts Unicode escape sequences back to actual Unicode characters.\n * Example: \\\\u30c6\\\\u30b9\\\\u30c8 → テスト\n *\n * @param text - Text with Unicode escape sequences\n * @returns Text with escape sequences converted to actual characters\n */\nexport function unescapeUnicode(text: string): string {\n return text.replace(/\\\\u([0-9a-fA-F]{4})/g, (_, hex) =>\n String.fromCharCode(Number.parseInt(hex, 16)),\n );\n}\n\n/**\n * Check if text contains characters beyond Latin-1 range\n *\n * PNG tEXt chunks support Latin-1 (ISO 8859-1) encoding (0x00-0xFF).\n * Characters beyond this range require iTXt chunks for UTF-8 support.\n *\n * @param text - Text to check\n * @returns True if text contains characters outside Latin-1 range (>= 0x100)\n */\nfunction hasNonLatin1(text: string): boolean {\n // biome-ignore lint/suspicious/noControlCharactersInRegex: checking for non-Latin-1 characters\n return /[^\\x00-\\xFF]/.test(text);\n}\n\n/**\n * Create PNG chunk with appropriate encoding strategy\n *\n * @param keyword - Chunk keyword\n * @param text - Chunk text (undefined returns empty array)\n * @param strategy - Encoding strategy to use\n * @returns Array of PNG text chunks (empty if text is undefined)\n */\nexport function createEncodedChunk(\n keyword: string,\n text: string | undefined,\n strategy: ChunkEncodingStrategy,\n): PngTextChunk[] {\n if (text === undefined) return [];\n\n switch (strategy) {\n case 'dynamic': {\n // Choose based on content: tEXt for Latin-1, iTXt for beyond\n const chunkType = hasNonLatin1(text) ? 'iTXt' : 'tEXt';\n return chunkType === 'iTXt'\n ? createITxtChunk(keyword, text)\n : createTextChunk(keyword, text);\n }\n\n case 'text-unicode-escape': {\n // tEXt with Unicode escaping (spec-compliant)\n const escaped = escapeUnicode(text);\n return createTextChunk(keyword, escaped);\n }\n\n case 'text-utf8-raw': {\n // tEXt with raw UTF-8 (non-compliant but compatible)\n return createTextChunk(keyword, text);\n }\n }\n}\n\n/**\n * Create multiple PNG chunks with per-keyword encoding strategy\n *\n * Uses a ChunkEncodingMap to determine the encoding strategy for each chunk.\n * If a keyword is not in the map, the 'default' strategy is used.\n *\n * @param entries - Array of [keyword, text] tuples\n * @param encodingMap - Map of keywords to encoding strategies\n * @returns Array of PNG text chunks\n */\nexport function createEncodedChunks(\n entries: [string, string | undefined][],\n encodingMap: ChunkEncodingMap,\n): PngTextChunk[] {\n return entries.flatMap(([keyword, text]) => {\n const strategy = encodingMap[keyword] ?? encodingMap.default;\n return createEncodedChunk(keyword, text, strategy);\n });\n}\n","/**\n * Result type for explicit error handling\n */\nexport type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };\n\n/**\n * Helper functions for Result type\n */\nexport const Result = {\n ok: <T, E>(value: T): Result<T, E> => ({ ok: true, value }),\n error: <T, E>(error: E): Result<T, E> => ({ ok: false, error }),\n};\n\n// ============================================================================\n// PNG Text Chunk Types\n// ============================================================================\n\n/**\n * PNG text chunk (tEXt or iTXt)\n */\nexport type PngTextChunk = TExtChunk | ITXtChunk;\n\n/**\n * tEXt chunk (Latin-1 encoded text)\n */\nexport interface TExtChunk {\n type: 'tEXt';\n /** Chunk keyword (e.g., 'parameters', 'Comment') */\n keyword: string;\n /** Text content */\n text: string;\n}\n\n/**\n * iTXt chunk (UTF-8 encoded international text)\n */\nexport interface ITXtChunk {\n type: 'iTXt';\n /** Chunk keyword */\n keyword: string;\n /** Compression flag (0=uncompressed, 1=compressed) */\n compressionFlag: number;\n /** Compression method (0=zlib/deflate) */\n compressionMethod: number;\n /** Language tag (BCP 47) */\n languageTag: string;\n /** Translated keyword */\n translatedKeyword: string;\n /** Text content */\n text: string;\n}\n\n// ============================================================================\n// Exif Metadata Types (shared between JPEG/WebP)\n// ============================================================================\n\n/**\n * Source location of a metadata segment.\n * Used for round-tripping: reading and writing back to the correct location.\n */\nexport type MetadataSegmentSource =\n | { type: 'exifUserComment' }\n | { type: 'exifImageDescription'; prefix?: string }\n | { type: 'exifMake'; prefix?: string }\n | { type: 'jpegCom' };\n\n/**\n * A single metadata segment with source tracking\n */\nexport interface MetadataSegment {\n /** Source location of this segment */\n source: MetadataSegmentSource;\n /** Raw data string */\n data: string;\n}\n\n/**\n * Raw metadata for write-back (preserves original format)\n */\nexport type RawMetadata =\n | { format: 'png'; chunks: PngTextChunk[] }\n | { format: 'jpeg'; segments: MetadataSegment[] }\n | { format: 'webp'; segments: MetadataSegment[] };\n\n// ============================================================================\n// Generation Software\n// ============================================================================\n\n/**\n * Known AI image generation software\n */\nexport type GenerationSoftware =\n | 'novelai'\n | 'comfyui'\n | 'swarmui'\n | 'tensorart'\n | 'stability-matrix'\n | 'invokeai'\n | 'forge'\n | 'forge-classic'\n | 'forge-neo'\n | 'reforge'\n | 'easy-reforge'\n | 'sd-webui'\n | 'sd-next'\n | 'civitai'\n | 'hf-space'\n | 'easydiffusion'\n | 'fooocus'\n | 'ruined-fooocus';\n\n// ============================================================================\n// Unified Metadata Types\n// ============================================================================\n\n/**\n * Base metadata fields shared by all tools\n */\nexport interface BaseMetadata {\n /** Positive prompt */\n prompt: string;\n /** Negative prompt */\n negativePrompt: string;\n /** Model settings */\n model?: ModelSettings;\n /** Sampling settings */\n sampling?: SamplingSettings;\n /** Hires.fix settings (if applied) */\n hires?: HiresSettings;\n /** Upscale settings (if applied) */\n upscale?: UpscaleSettings;\n /** Image width */\n width: number;\n /** Image height */\n height: number;\n}\n\n/**\n * NovelAI-specific metadata\n */\nexport interface NovelAIMetadata extends BaseMetadata {\n software: 'novelai';\n /** V4 character prompts (when using character placement) */\n characterPrompts?: CharacterPrompt[];\n /** Use character coordinates for placement */\n useCoords?: boolean;\n /** Use character order */\n useOrder?: boolean;\n}\n\n/**\n * Character prompt with position (NovelAI V4)\n */\nexport interface CharacterPrompt {\n /** Character-specific prompt */\n prompt: string;\n /** Character position (normalized 0-1) */\n center?: { x: number; y: number };\n}\n\n/**\n * ComfyUI-format metadata (ComfyUI, TensorArt, Stability Matrix)\n *\n * These tools use ComfyUI-compatible workflow format.\n */\n\n/**\n * ComfyUI node reference (for node outputs)\n *\n * Format: [nodeId, outputIndex]\n * Example: [\"CheckpointLoader_Base\", 0]\n */\nexport type ComfyNodeReference = [nodeId: string, outputIndex: number];\n\n/**\n * ComfyUI node input value\n */\nexport type ComfyNodeInputValue =\n | string\n | number\n | boolean\n | ComfyNodeReference\n | ComfyNodeInputValue[];\n\n/**\n * ComfyUI node structure\n */\nexport interface ComfyNode {\n /** Node class type (e.g., \"CheckpointLoaderSimple\", \"KSampler\") */\n class_type: string;\n /** Node inputs */\n inputs: Record<string, ComfyNodeInputValue>;\n /** Node metadata (ComfyUI only) */\n _meta?: {\n /** Node title for display */\n title?: string;\n };\n /** Change detection hash (rare, for caching) */\n is_changed?: string[] | null;\n}\n\n/**\n * ComfyUI node graph\n *\n * Maps node IDs to their corresponding node data.\n */\nexport type ComfyNodeGraph = Record<string, ComfyNode>;\n\n/**\n * ComfyUI-format metadata (ComfyUI, TensorArt, Stability Matrix)\n *\n * These tools always have nodes in all formats.\n */\nexport interface BasicComfyUIMetadata extends BaseMetadata {\n software: 'comfyui' | 'tensorart' | 'stability-matrix';\n /**\n * ComfyUI node graph (required)\n *\n * Always present in all image formats (PNG, JPEG, WebP).\n * Structure: Record<nodeId, ComfyNode> where ComfyNode contains inputs and class_type.\n */\n nodes: ComfyNodeGraph;\n}\n\n/**\n * SwarmUI-specific metadata\n *\n * SwarmUI uses ComfyUI workflow format but nodes are only present in PNG.\n */\nexport interface SwarmUIMetadata extends BaseMetadata {\n software: 'swarmui';\n /**\n * ComfyUI node graph (optional for SwarmUI)\n *\n * Only present in PNG format. JPEG/WebP contain SwarmUI parameters only.\n * Structure: Record<nodeId, ComfyNode> where ComfyNode contains inputs and class_type.\n */\n nodes?: ComfyNodeGraph;\n}\n\n/**\n * ComfyUI-format metadata (union of BasicComfyUI and SwarmUI)\n *\n * This is a union type to handle different node graph requirements:\n * - ComfyUI/TensorArt/Stability Matrix: nodes are always present\n * - SwarmUI: nodes are only present in PNG format\n */\nexport type ComfyUIMetadata = BasicComfyUIMetadata | SwarmUIMetadata;\n\n/**\n * Standard metadata (SD WebUI, Forge, InvokeAI, and others)\n *\n * Baseline generation metadata without tool-specific extensions.\n * Used by most SD tools that don't require special features like\n * NovelAI's character prompts or ComfyUI's node graphs.\n */\nexport interface StandardMetadata extends BaseMetadata {\n software:\n | 'sd-webui'\n | 'sd-next'\n | 'forge'\n | 'forge-classic'\n | 'forge-neo'\n | 'reforge'\n | 'easy-reforge'\n | 'invokeai'\n | 'civitai'\n | 'hf-space'\n | 'easydiffusion'\n | 'fooocus'\n | 'ruined-fooocus';\n}\n\n/**\n * Unified generation metadata (discriminated union)\n *\n * Use `metadata.software` to narrow by specific tool:\n * ```typescript\n * if (metadata.software === 'comfyui' ||\n * metadata.software === 'tensorart' ||\n * metadata.software === 'stability-matrix' ||\n * metadata.software === 'swarmui') {\n * // TypeScript knows metadata is ComfyUIMetadata\n * if (metadata.nodes) {\n * // Access workflow graph\n * }\n * }\n * ```\n */\nexport type GenerationMetadata =\n | NovelAIMetadata\n | ComfyUIMetadata\n | StandardMetadata;\n\n/**\n * User-created custom metadata for the embed() and stringify() APIs\n *\n * While {@link GenerationMetadata} represents parsed output from a known AI tool,\n * EmbedMetadata is designed for users to compose their own metadata from\n * scratch. Includes all base generation fields plus optional character\n * prompts and extras for the settings line.\n */\nexport type EmbedMetadata = BaseMetadata &\n Pick<NovelAIMetadata, 'characterPrompts'> & {\n /** Additional key-value pairs for the settings line */\n extras?: Record<string, string | number>;\n };\n\n// ============================================================================\n// Settings Types\n// ============================================================================\n\n/**\n * Model settings\n */\nexport interface ModelSettings {\n /** Model name */\n name?: string;\n /** Model hash */\n hash?: string;\n /** VAE name */\n vae?: string;\n}\n\n/**\n * Sampling settings\n */\nexport interface SamplingSettings {\n /** Sampler name */\n sampler?: string;\n /** Scheduler (sometimes included in sampler, sometimes separate) */\n scheduler?: string;\n /** Sampling steps */\n steps?: number;\n /** CFG scale */\n cfg?: number;\n /** Random seed */\n seed?: number;\n /** CLIP skip layers */\n clipSkip?: number;\n /** Denoising strength (ComfyUI only, typically 1.0 for txt2img) */\n denoise?: number;\n}\n\n/**\n * Hires.fix settings\n */\nexport interface HiresSettings {\n /** Upscale factor */\n scale?: number;\n /** Upscaler name */\n upscaler?: string;\n /** Hires steps */\n steps?: number;\n /** Hires denoising strength */\n denoise?: number;\n}\n\n/**\n * Upscale settings (post-generation)\n */\nexport interface UpscaleSettings {\n /** Upscaler name */\n upscaler?: string;\n /** Scale factor */\n scale?: number;\n}\n\n// ============================================================================\n// Parse Result\n// ============================================================================\n\n/**\n * Parse result with 4-status design\n *\n * - `success`: Parsing succeeded, metadata and raw data available\n * - `empty`: No metadata found in the file\n * - `unrecognized`: Metadata exists but format is not recognized\n * - `invalid`: File is corrupted or not a valid image\n */\nexport type ParseResult =\n | { status: 'success'; metadata: GenerationMetadata; raw: RawMetadata }\n | { status: 'empty' }\n | { status: 'unrecognized'; raw: RawMetadata }\n | { status: 'invalid'; message?: string };\n\n// ============================================================================\n// API Types\n// ============================================================================\n\n/**\n * Options for the read function\n */\nexport interface ReadOptions {\n /**\n * When true, dimensions are taken strictly from metadata only.\n * When false (default), missing dimensions are extracted from image headers.\n * @default false\n */\n strict?: boolean;\n}\n\n/**\n * Warning types for write operations\n */\nexport type WriteWarning = {\n type: 'metadataDropped';\n reason: 'unrecognizedCrossFormat';\n};\n\n/**\n * Error types for write operations\n */\ntype WriteError =\n | { type: 'unsupportedFormat' }\n | { type: 'conversionFailed'; message: string }\n | { type: 'writeFailed'; message: string };\n\n/**\n * Result of the write operation\n *\n * Success case may include a warning when metadata was intentionally dropped.\n */\nexport type WriteResult =\n | { ok: true; value: Uint8Array; warning?: WriteWarning }\n | { ok: false; error: WriteError };\n","/**\n * Binary data utilities for reading/writing multi-byte integers\n */\n\n/**\n * Convert input to Uint8Array\n *\n * @param input - Image data as Uint8Array or ArrayBuffer\n * @returns Uint8Array view of the data\n */\nexport function toUint8Array(input: Uint8Array | ArrayBuffer): Uint8Array {\n return input instanceof ArrayBuffer ? new Uint8Array(input) : input;\n}\n\n/**\n * Read 3-byte little-endian unsigned integer\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @returns 24-bit unsigned integer\n */\nexport function readUint24LE(data: Uint8Array, offset: number): number {\n return (\n (data[offset] ?? 0) |\n ((data[offset + 1] ?? 0) << 8) |\n ((data[offset + 2] ?? 0) << 16)\n );\n}\n\n/**\n * Read 4-byte big-endian unsigned integer\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @returns 32-bit unsigned integer\n */\nexport function readUint32BE(data: Uint8Array, offset: number): number {\n return (\n ((data[offset] ?? 0) << 24) |\n ((data[offset + 1] ?? 0) << 16) |\n ((data[offset + 2] ?? 0) << 8) |\n (data[offset + 3] ?? 0)\n );\n}\n\n/**\n * Read 4-byte little-endian unsigned integer\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @returns 32-bit unsigned integer\n */\nexport function readUint32LE(data: Uint8Array, offset: number): number {\n return (\n (data[offset] ?? 0) |\n ((data[offset + 1] ?? 0) << 8) |\n ((data[offset + 2] ?? 0) << 16) |\n ((data[offset + 3] ?? 0) << 24)\n );\n}\n\n/**\n * Write 4-byte big-endian unsigned integer\n *\n * @param data - Byte array to write to\n * @param offset - Offset to start writing at\n * @param value - 32-bit unsigned integer value\n */\nexport function writeUint32BE(\n data: Uint8Array,\n offset: number,\n value: number,\n): void {\n data[offset] = (value >>> 24) & 0xff;\n data[offset + 1] = (value >>> 16) & 0xff;\n data[offset + 2] = (value >>> 8) & 0xff;\n data[offset + 3] = value & 0xff;\n}\n\n/**\n * Read 4-byte chunk type as ASCII string\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @returns 4-character ASCII string\n */\nexport function readChunkType(data: Uint8Array, offset: number): string {\n return String.fromCharCode(\n data[offset] ?? 0,\n data[offset + 1] ?? 0,\n data[offset + 2] ?? 0,\n data[offset + 3] ?? 0,\n );\n}\n\n/**\n * Read 2-byte unsigned integer with endianness support\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @param isLittleEndian - If true, read as little-endian\n * @returns 16-bit unsigned integer\n */\nexport function readUint16(\n data: Uint8Array,\n offset: number,\n isLittleEndian: boolean,\n): number {\n if (isLittleEndian) {\n return (data[offset] ?? 0) | ((data[offset + 1] ?? 0) << 8);\n }\n return ((data[offset] ?? 0) << 8) | (data[offset + 1] ?? 0);\n}\n\n/**\n * Read 4-byte unsigned integer with endianness support\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @param isLittleEndian - If true, read as little-endian\n * @returns 32-bit unsigned integer\n */\nexport function readUint32(\n data: Uint8Array,\n offset: number,\n isLittleEndian: boolean,\n): number {\n if (isLittleEndian) {\n return (\n (data[offset] ?? 0) |\n ((data[offset + 1] ?? 0) << 8) |\n ((data[offset + 2] ?? 0) << 16) |\n ((data[offset + 3] ?? 0) << 24)\n );\n }\n return (\n ((data[offset] ?? 0) << 24) |\n ((data[offset + 1] ?? 0) << 16) |\n ((data[offset + 2] ?? 0) << 8) |\n (data[offset + 3] ?? 0)\n );\n}\n\n/**\n * Compare two Uint8Arrays for equality\n *\n * @param a - First array\n * @param b - Second array\n * @returns true if arrays have same length and all elements match\n */\nexport function arraysEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * Write 2-byte unsigned integer with endianness support\n *\n * @param data - Byte array to write to\n * @param offset - Offset to start writing at\n * @param value - 16-bit unsigned integer value\n * @param isLittleEndian - If true, write as little-endian\n */\nexport function writeUint16(\n data: Uint8Array,\n offset: number,\n value: number,\n isLittleEndian: boolean,\n): void {\n if (isLittleEndian) {\n data[offset] = value & 0xff;\n data[offset + 1] = (value >>> 8) & 0xff;\n } else {\n data[offset] = (value >>> 8) & 0xff;\n data[offset + 1] = value & 0xff;\n }\n}\n\n/**\n * Write 4-byte unsigned integer with endianness support\n *\n * @param data - Byte array to write to\n * @param offset - Offset to start writing at\n * @param value - 32-bit unsigned integer value\n * @param isLittleEndian - If true, write as little-endian\n */\nexport function writeUint32(\n data: Uint8Array,\n offset: number,\n value: number,\n isLittleEndian: boolean,\n): void {\n if (isLittleEndian) {\n data[offset] = value & 0xff;\n data[offset + 1] = (value >>> 8) & 0xff;\n data[offset + 2] = (value >>> 16) & 0xff;\n data[offset + 3] = (value >>> 24) & 0xff;\n } else {\n data[offset] = (value >>> 24) & 0xff;\n data[offset + 1] = (value >>> 16) & 0xff;\n data[offset + 2] = (value >>> 8) & 0xff;\n data[offset + 3] = value & 0xff;\n }\n}\n\n/**\n * Write 4-byte little-endian unsigned integer\n *\n * @param data - Byte array to write to\n * @param offset - Offset to start writing at\n * @param value - 32-bit unsigned integer value\n */\nexport function writeUint32LE(\n data: Uint8Array,\n offset: number,\n value: number,\n): void {\n data[offset] = value & 0xff;\n data[offset + 1] = (value >>> 8) & 0xff;\n data[offset + 2] = (value >>> 16) & 0xff;\n data[offset + 3] = (value >>> 24) & 0xff;\n}\n\n/**\n * Supported image formats\n */\nexport type ImageFormat = 'png' | 'jpeg' | 'webp';\n\n/**\n * Validates if data starts with PNG signature\n */\nexport function isPng(data: Uint8Array): boolean {\n if (data.length < 8) return false;\n return (\n data[0] === 0x89 &&\n data[1] === 0x50 &&\n data[2] === 0x4e &&\n data[3] === 0x47 &&\n data[4] === 0x0d &&\n data[5] === 0x0a &&\n data[6] === 0x1a &&\n data[7] === 0x0a\n );\n}\n\n/**\n * Validates if data starts with JPEG signature\n */\nexport function isJpeg(data: Uint8Array): boolean {\n if (data.length < 2) return false;\n return data[0] === 0xff && data[1] === 0xd8;\n}\n\n/**\n * Validates if data starts with WebP signature\n */\nexport function isWebp(data: Uint8Array): boolean {\n if (data.length < 12) return false;\n return (\n data[0] === 0x52 && // R\n data[1] === 0x49 && // I\n data[2] === 0x46 && // F\n data[3] === 0x46 && // F\n data[8] === 0x57 && // W\n data[9] === 0x45 && // E\n data[10] === 0x42 && // B\n data[11] === 0x50 // P\n );\n}\n\n/**\n * Detect image format from magic bytes\n */\nexport function detectFormat(data: Uint8Array): ImageFormat | null {\n if (isPng(data)) return 'png';\n if (isJpeg(data)) return 'jpeg';\n if (isWebp(data)) return 'webp';\n return null;\n}\n","/**\n * Exif/TIFF tag constants\n *\n * Shared between readers and writers for consistent tag handling.\n */\n\n/** UserComment tag ID in Exif */\nexport const USER_COMMENT_TAG = 0x9286;\n\n/** ImageDescription tag ID */\nexport const IMAGE_DESCRIPTION_TAG = 0x010e;\n\n/** Make tag ID */\nexport const MAKE_TAG = 0x010f;\n\n/** Exif IFD pointer tag */\nexport const EXIF_IFD_POINTER_TAG = 0x8769;\n","/**\n * Exif writing utilities\n *\n * Functions for building Exif/TIFF structures from metadata segments.\n */\n\nimport type { MetadataSegment } from '../types';\nimport { writeUint16, writeUint32 } from '../utils/binary';\nimport {\n EXIF_IFD_POINTER_TAG,\n IMAGE_DESCRIPTION_TAG,\n MAKE_TAG,\n USER_COMMENT_TAG,\n} from '../utils/exif-constants';\n\n/**\n * Build Exif TIFF data from MetadataSegments\n *\n * Creates a complete TIFF structure with IFD0, Exif IFD, and all tag data.\n * Uses little-endian (Intel) byte order for maximum compatibility.\n *\n * @param segments - Metadata segments to encode\n * @returns TIFF data (starts with \"II\" byte order marker)\n */\nexport function buildExifTiffData(segments: MetadataSegment[]): Uint8Array {\n // Separate segments by destination IFD\n const ifd0Segments = segments.filter(\n (s) =>\n s.source.type === 'exifImageDescription' || s.source.type === 'exifMake',\n );\n const exifIfdSegments = segments.filter(\n (s) => s.source.type === 'exifUserComment',\n );\n\n // No Exif-type segments\n if (ifd0Segments.length === 0 && exifIfdSegments.length === 0) {\n return new Uint8Array(0);\n }\n\n const isLittleEndian = true;\n\n // Build tag data for each segment\n const ifd0Tags: Array<{ tag: number; type: number; data: Uint8Array }> = [];\n const exifTags: Array<{ tag: number; type: number; data: Uint8Array }> = [];\n\n for (const seg of ifd0Segments) {\n if (seg.source.type === 'exifImageDescription') {\n const data = encodeAsciiTag(seg.data, seg.source.prefix);\n ifd0Tags.push({ tag: IMAGE_DESCRIPTION_TAG, type: 2, data });\n } else if (seg.source.type === 'exifMake') {\n const data = encodeAsciiTag(seg.data, seg.source.prefix);\n ifd0Tags.push({ tag: MAKE_TAG, type: 2, data });\n }\n }\n\n for (const seg of exifIfdSegments) {\n if (seg.source.type === 'exifUserComment') {\n const data = encodeUserComment(seg.data);\n exifTags.push({ tag: USER_COMMENT_TAG, type: 7, data });\n }\n }\n\n const hasExifIfd = exifTags.length > 0;\n if (hasExifIfd) {\n ifd0Tags.push({\n tag: EXIF_IFD_POINTER_TAG,\n type: 4,\n data: new Uint8Array(4),\n });\n }\n\n // Sort tags by tag number (required by TIFF spec)\n ifd0Tags.sort((a, b) => a.tag - b.tag);\n exifTags.sort((a, b) => a.tag - b.tag);\n\n // Calculate sizes and offsets\n const headerSize = 8;\n const ifd0EntryCount = ifd0Tags.length;\n const ifd0Size = 2 + 12 * ifd0EntryCount + 4;\n const exifEntryCount = exifTags.length;\n const exifIfdSize = hasExifIfd ? 2 + 12 * exifEntryCount + 4 : 0;\n\n const ifd0Offset = headerSize;\n const exifIfdOffset = ifd0Offset + ifd0Size;\n let dataOffset = exifIfdOffset + exifIfdSize;\n\n // Update Exif IFD pointer in IFD0\n if (hasExifIfd) {\n const exifPtrTag = ifd0Tags.find((t) => t.tag === EXIF_IFD_POINTER_TAG);\n if (exifPtrTag) {\n writeUint32(exifPtrTag.data, 0, exifIfdOffset, isLittleEndian);\n }\n }\n\n // Assign data offsets for each tag\n const tagDataOffsets = new Map<\n { tag: number; type: number; data: Uint8Array },\n number\n >();\n\n for (const tag of [...ifd0Tags, ...exifTags]) {\n if (tag.data.length > 4) {\n tagDataOffsets.set(tag, dataOffset);\n dataOffset += tag.data.length;\n if (tag.data.length % 2 !== 0) {\n dataOffset += 1;\n }\n }\n }\n\n // Build result\n const totalSize = dataOffset;\n const result = new Uint8Array(totalSize);\n\n // Write TIFF header\n result[0] = 0x49; // I\n result[1] = 0x49; // I (little-endian)\n writeUint16(result, 2, 42, isLittleEndian);\n writeUint32(result, 4, ifd0Offset, isLittleEndian);\n\n // Write IFD0\n let offset = ifd0Offset;\n writeUint16(result, offset, ifd0EntryCount, isLittleEndian);\n offset += 2;\n\n for (const tag of ifd0Tags) {\n writeIfdEntry(result, offset, tag, tagDataOffsets.get(tag), isLittleEndian);\n offset += 12;\n }\n\n writeUint32(result, offset, 0, isLittleEndian);\n offset += 4;\n\n // Write Exif IFD\n if (hasExifIfd) {\n writeUint16(result, offset, exifEntryCount, isLittleEndian);\n offset += 2;\n\n for (const tag of exifTags) {\n writeIfdEntry(\n result,\n offset,\n tag,\n tagDataOffsets.get(tag),\n isLittleEndian,\n );\n offset += 12;\n }\n\n writeUint32(result, offset, 0, isLittleEndian);\n }\n\n // Write tag data values\n for (const [tag, dataOff] of tagDataOffsets) {\n result.set(tag.data, dataOff);\n }\n\n return result;\n}\n\n/**\n * Write an IFD entry\n */\nfunction writeIfdEntry(\n data: Uint8Array,\n offset: number,\n tag: { tag: number; type: number; data: Uint8Array },\n dataOffset: number | undefined,\n isLittleEndian: boolean,\n): void {\n writeUint16(data, offset, tag.tag, isLittleEndian);\n writeUint16(data, offset + 2, tag.type, isLittleEndian);\n writeUint32(data, offset + 4, tag.data.length, isLittleEndian);\n\n if (tag.data.length <= 4) {\n data.set(tag.data, offset + 8);\n } else {\n writeUint32(data, offset + 8, dataOffset ?? 0, isLittleEndian);\n }\n}\n\n/**\n * Encode string as UserComment with UTF-16LE encoding\n *\n * Uses UNICODE prefix followed by UTF-16LE encoded text.\n *\n * @param text - Text to encode\n * @returns Encoded UserComment data (8-byte prefix + UTF-16LE text)\n */\nfunction encodeUserComment(text: string): Uint8Array {\n const utf16Data: number[] = [];\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n utf16Data.push(code & 0xff);\n utf16Data.push((code >> 8) & 0xff);\n }\n\n const result = new Uint8Array(8 + utf16Data.length);\n\n // UNICODE encoding prefix\n result[0] = 0x55; // U\n result[1] = 0x4e; // N\n result[2] = 0x49; // I\n result[3] = 0x43; // C\n result[4] = 0x4f; // O\n result[5] = 0x44; // D\n result[6] = 0x45; // E\n result[7] = 0x00; // NULL\n\n result.set(new Uint8Array(utf16Data), 8);\n return result;\n}\n\n/**\n * Encode ASCII tag data with optional prefix\n *\n * @param text - Text content\n * @param prefix - Optional prefix (e.g., \"Workflow\")\n * @returns Null-terminated ASCII bytes\n */\nfunction encodeAsciiTag(text: string, prefix?: string): Uint8Array {\n const fullText = prefix ? `${prefix}: ${text}` : text;\n const textBytes = new TextEncoder().encode(fullText);\n const result = new Uint8Array(textBytes.length + 1);\n result.set(textBytes, 0);\n result[textBytes.length] = 0;\n return result;\n}\n","import type { MetadataSegment } from '../types';\nimport { Result } from '../types';\nimport { isJpeg } from '../utils/binary';\nimport { buildExifTiffData } from './exif';\n\n// Internal types (co-located with writer)\ntype JpegWriteError =\n | { type: 'invalidSignature' }\n | { type: 'corruptedStructure'; message: string };\n\ntype JpegWriteResult = Result<Uint8Array, JpegWriteError>;\n\n/** APP1 marker */\nconst APP1_MARKER = 0xe1;\n\n/** COM (Comment) marker */\nconst COM_MARKER = 0xfe;\n\n/** SOS (Start of Scan) marker */\nconst SOS_MARKER = 0xda;\n\n/** EOI (End of Image) marker */\nconst EOI_MARKER = 0xd9;\n\n/** Exif header: \"Exif\\0\\0\" */\nconst EXIF_HEADER = new Uint8Array([0x45, 0x78, 0x69, 0x66, 0x00, 0x00]);\n\n/**\n * Write JPEG metadata to binary data\n *\n * Replaces existing metadata segments with the provided segments.\n * Each segment is written to its original location based on source type:\n * - jpegCom -> COM segment (before SOS)\n * - exifUserComment/exifImageDescription/exifMake -> APP1 Exif segment (after SOI)\n *\n * @param data - Original JPEG file data as Uint8Array\n * @param segments - Metadata segments to embed\n * @returns Result containing new JPEG data with embedded metadata\n */\nexport function writeJpegMetadata(\n data: Uint8Array,\n segments: MetadataSegment[],\n): JpegWriteResult {\n // Validate JPEG signature\n if (!isJpeg(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n // Separate segments by destination\n const comSegments = segments.filter((s) => s.source.type === 'jpegCom');\n const exifSegments = segments.filter(\n (s) =>\n s.source.type === 'exifUserComment' ||\n s.source.type === 'exifImageDescription' ||\n s.source.type === 'exifMake',\n );\n\n // Collect non-metadata segments from original JPEG\n const collectResult = collectNonMetadataSegments(data);\n if (!collectResult.ok) {\n return collectResult;\n }\n\n const { beforeSos, scanData } = collectResult.value;\n\n // Build new APP1 Exif segment\n const app1Segment =\n exifSegments.length > 0 ? buildApp1Segment(exifSegments) : null;\n\n // Build new COM segments\n const comSegmentData = comSegments.map((s) => buildComSegment(s.data));\n\n // Calculate total size\n let totalSize = 2; // SOI\n if (app1Segment) {\n totalSize += app1Segment.length;\n }\n for (const seg of beforeSos) {\n totalSize += seg.length;\n }\n for (const com of comSegmentData) {\n totalSize += com.length;\n }\n totalSize += scanData.length;\n\n // Build output\n const output = new Uint8Array(totalSize);\n let offset = 0;\n\n // Write SOI\n output[offset++] = 0xff;\n output[offset++] = 0xd8;\n\n // Write APP1 Exif (immediately after SOI)\n if (app1Segment) {\n output.set(app1Segment, offset);\n offset += app1Segment.length;\n }\n\n // Write original non-metadata segments\n for (const seg of beforeSos) {\n output.set(seg, offset);\n offset += seg.length;\n }\n\n // Write COM segments (before SOS)\n for (const com of comSegmentData) {\n output.set(com, offset);\n offset += com.length;\n }\n\n // Write scan data (SOS to EOI)\n output.set(scanData, offset);\n\n return Result.ok(output);\n}\n\n/**\n * Collect non-metadata segments from JPEG\n *\n * Returns segments that are not APP1 Exif or COM, plus the scan data (SOS to EOI)\n */\nfunction collectNonMetadataSegments(\n data: Uint8Array,\n): Result<\n { beforeSos: Uint8Array[]; scanData: Uint8Array },\n { type: 'corruptedStructure'; message: string }\n> {\n const beforeSos: Uint8Array[] = [];\n let offset = 2; // Skip SOI\n\n while (offset < data.length - 1) {\n // Check for marker\n if (data[offset] !== 0xff) {\n return Result.error({\n type: 'corruptedStructure',\n message: `Expected marker at offset ${offset}`,\n });\n }\n\n // Skip padding bytes\n while (data[offset] === 0xff && offset < data.length - 1) {\n offset++;\n }\n\n const marker = data[offset];\n offset++;\n\n // Check for SOS - everything after this is scan data\n if (marker === SOS_MARKER) {\n // Include SOS marker in scan data\n const scanData = data.slice(offset - 2);\n return Result.ok({ beforeSos, scanData });\n }\n\n // Check for EOI (shouldn't happen before SOS but handle it)\n if (marker === EOI_MARKER) {\n return Result.ok({ beforeSos, scanData: new Uint8Array([0xff, 0xd9]) });\n }\n\n // Get segment length (big-endian, includes length bytes)\n if (offset + 2 > data.length) {\n return Result.error({\n type: 'corruptedStructure',\n message: 'Unexpected end of file',\n });\n }\n\n const length = ((data[offset] ?? 0) << 8) | (data[offset + 1] ?? 0);\n const segmentStart = offset - 2; // Include marker\n const segmentEnd = offset + length;\n\n if (segmentEnd > data.length) {\n return Result.error({\n type: 'corruptedStructure',\n message: 'Segment extends beyond file',\n });\n }\n\n // Check if this is a metadata segment we want to strip\n const isExifApp1 =\n marker === APP1_MARKER &&\n offset + 2 + 6 <= data.length &&\n data[offset + 2] === 0x45 && // E\n data[offset + 3] === 0x78 && // x\n data[offset + 4] === 0x69 && // i\n data[offset + 5] === 0x66 && // f\n data[offset + 6] === 0x00 && // NULL\n data[offset + 7] === 0x00; // NULL\n\n const isCom = marker === COM_MARKER;\n\n // Keep non-metadata segments\n if (!isExifApp1 && !isCom) {\n beforeSos.push(data.slice(segmentStart, segmentEnd));\n }\n\n offset = segmentEnd;\n }\n\n // If we reach here without finding SOS, the JPEG is malformed\n return Result.error({\n type: 'corruptedStructure',\n message: 'No SOS marker found',\n });\n}\n\n/**\n * Build APP1 Exif segment from metadata segments\n */\nfunction buildApp1Segment(segments: MetadataSegment[]): Uint8Array {\n const tiffData = buildExifTiffData(segments);\n\n if (tiffData.length === 0) {\n return new Uint8Array(0);\n }\n\n // APP1 segment: marker (2) + length (2) + Exif header (6) + TIFF data\n const segmentLength = 2 + EXIF_HEADER.length + tiffData.length;\n const segment = new Uint8Array(2 + segmentLength);\n\n segment[0] = 0xff;\n segment[1] = APP1_MARKER;\n segment[2] = (segmentLength >> 8) & 0xff;\n segment[3] = segmentLength & 0xff;\n segment.set(EXIF_HEADER, 4);\n segment.set(tiffData, 4 + EXIF_HEADER.length);\n\n return segment;\n}\n\n/**\n * Build COM segment from text\n */\nfunction buildComSegment(text: string): Uint8Array {\n const textBytes = new TextEncoder().encode(text);\n const segmentLength = 2 + textBytes.length; // length field includes itself\n\n const segment = new Uint8Array(2 + segmentLength);\n segment[0] = 0xff;\n segment[1] = COM_MARKER;\n segment[2] = (segmentLength >> 8) & 0xff;\n segment[3] = segmentLength & 0xff;\n segment.set(textBytes, 4);\n\n return segment;\n}\n","import type { ITXtChunk, PngTextChunk, TExtChunk } from '../types';\nimport { Result } from '../types';\nimport {\n isPng,\n readChunkType,\n readUint32BE,\n writeUint32BE,\n} from '../utils/binary';\n\n// Internal types (co-located with writer)\ntype PngWriteError = { type: 'invalidSignature' } | { type: 'noIhdrChunk' };\n\ntype PngWriteResult = Result<Uint8Array, PngWriteError>;\n\n/** PNG file signature (magic bytes) */\nconst PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);\n\n/**\n * Write PNG metadata to binary data\n *\n * Replaces all existing tEXt and iTXt chunks with the provided chunks.\n * Chunks are inserted immediately after the IHDR chunk (PNG spec recommended).\n *\n * @param data - Original PNG file data as Uint8Array\n * @param chunks - Text chunks to embed\n * @returns Result containing new PNG data with embedded metadata\n */\nexport function writePngMetadata(\n data: Uint8Array,\n chunks: PngTextChunk[],\n): PngWriteResult {\n // Validate PNG signature\n if (!isPng(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n // Find IHDR chunk end position\n const ihdrEnd = findIhdrChunkEnd(data);\n if (ihdrEnd === -1) {\n return Result.error({ type: 'noIhdrChunk' });\n }\n\n // Collect non-text chunks from original data\n const originalChunks = collectNonTextChunks(data);\n\n // Serialize new text chunks\n const serializedTextChunks = chunks.map((chunk) =>\n chunk.type === 'tEXt'\n ? serializeTExtChunk(chunk)\n : serializeITXtChunk(chunk),\n );\n\n // Calculate total output size\n const totalSize =\n PNG_SIGNATURE.length +\n originalChunks.ihdr.length +\n serializedTextChunks.reduce((sum, chunk) => sum + chunk.length, 0) +\n originalChunks.others.reduce((sum, chunk) => sum + chunk.length, 0);\n\n // Build output\n const output = new Uint8Array(totalSize);\n let offset = 0;\n\n // Write signature\n output.set(PNG_SIGNATURE, offset);\n offset += PNG_SIGNATURE.length;\n\n // Write IHDR\n output.set(originalChunks.ihdr, offset);\n offset += originalChunks.ihdr.length;\n\n // Write text chunks (immediately after IHDR)\n for (const chunk of serializedTextChunks) {\n output.set(chunk, offset);\n offset += chunk.length;\n }\n\n // Write other chunks\n for (const chunk of originalChunks.others) {\n output.set(chunk, offset);\n offset += chunk.length;\n }\n\n return Result.ok(output);\n}\n\n/**\n * Find the end position of IHDR chunk (including CRC)\n * @returns End offset or -1 if not found\n */\nfunction findIhdrChunkEnd(data: Uint8Array): number {\n const offset = PNG_SIGNATURE.length;\n\n if (offset + 8 > data.length) {\n return -1;\n }\n\n const length = readUint32BE(data, offset);\n const chunkType = readChunkType(data, offset + 4);\n\n if (chunkType !== 'IHDR') {\n return -1;\n }\n\n // Return end position: length(4) + type(4) + data(length) + crc(4)\n return offset + 4 + 4 + length + 4;\n}\n\n/**\n * Collect chunks from PNG, separating IHDR and non-text chunks\n */\nfunction collectNonTextChunks(data: Uint8Array): {\n ihdr: Uint8Array;\n others: Uint8Array[];\n} {\n const others: Uint8Array[] = [];\n let offset = PNG_SIGNATURE.length;\n let ihdr: Uint8Array = new Uint8Array(0);\n\n while (offset < data.length) {\n const chunkStart = offset;\n\n // Read chunk length\n if (offset + 4 > data.length) break;\n const length = readUint32BE(data, offset);\n offset += 4;\n\n // Read chunk type\n if (offset + 4 > data.length) break;\n const chunkType = readChunkType(data, offset);\n offset += 4;\n\n // Skip chunk data\n offset += length;\n\n // Skip CRC\n offset += 4;\n\n const chunkEnd = offset;\n const chunkData = data.slice(chunkStart, chunkEnd);\n\n if (chunkType === 'IHDR') {\n ihdr = chunkData;\n } else if (chunkType !== 'tEXt' && chunkType !== 'iTXt') {\n others.push(chunkData);\n }\n\n if (chunkType === 'IEND') {\n break;\n }\n }\n\n return { ihdr, others };\n}\n\n/**\n * Serialize a tEXt chunk to binary\n *\n * Per PNG spec, tEXt uses Latin-1 encoding. However, to maintain round-trip\n * compatibility with tools that incorrectly write UTF-8 (e.g., TensorArt),\n * we encode the text as UTF-8 bytes. This allows non-ASCII characters to\n * survive the read-write cycle.\n */\nfunction serializeTExtChunk(chunk: TExtChunk): Uint8Array {\n // Encode keyword as Latin-1 (keywords are ASCII-safe)\n const keyword = latin1Encode(chunk.keyword);\n // Encode text as UTF-8 (for round-trip compatibility with non-compliant tools)\n const text = utf8Encode(chunk.text);\n\n // Data: keyword + null + text\n const chunkData = new Uint8Array(keyword.length + 1 + text.length);\n chunkData.set(keyword, 0);\n chunkData[keyword.length] = 0; // null separator\n chunkData.set(text, keyword.length + 1);\n\n return buildChunk('tEXt', chunkData);\n}\n\n/**\n * Serialize an iTXt chunk to binary\n */\nfunction serializeITXtChunk(chunk: ITXtChunk): Uint8Array {\n // Encode strings\n const keyword = utf8Encode(chunk.keyword);\n const languageTag = utf8Encode(chunk.languageTag);\n const translatedKeyword = utf8Encode(chunk.translatedKeyword);\n const text = utf8Encode(chunk.text);\n\n // Calculate data size\n const dataSize =\n keyword.length +\n 1 + // null\n 1 + // compression flag\n 1 + // compression method\n languageTag.length +\n 1 + // null\n translatedKeyword.length +\n 1 + // null\n text.length;\n\n const chunkData = new Uint8Array(dataSize);\n let offset = 0;\n\n // Write keyword\n chunkData.set(keyword, offset);\n offset += keyword.length;\n chunkData[offset++] = 0; // null\n\n // Write compression flag and method\n chunkData[offset++] = chunk.compressionFlag;\n chunkData[offset++] = chunk.compressionMethod;\n\n // Write language tag\n chunkData.set(languageTag, offset);\n offset += languageTag.length;\n chunkData[offset++] = 0; // null\n\n // Write translated keyword\n chunkData.set(translatedKeyword, offset);\n offset += translatedKeyword.length;\n chunkData[offset++] = 0; // null\n\n // Write text\n chunkData.set(text, offset);\n\n return buildChunk('iTXt', chunkData);\n}\n\n/**\n * Build a complete PNG chunk with length, type, data, and CRC\n */\nfunction buildChunk(type: string, data: Uint8Array): Uint8Array {\n const chunk = new Uint8Array(4 + 4 + data.length + 4);\n\n // Write length (4 bytes, big-endian)\n writeUint32BE(chunk, 0, data.length);\n\n // Write type (4 bytes)\n for (let i = 0; i < 4; i++) {\n chunk[4 + i] = type.charCodeAt(i);\n }\n\n // Write data\n chunk.set(data, 8);\n\n // Calculate and write CRC (over type + data)\n const crcData = chunk.slice(4, 8 + data.length);\n const crc = calculateCrc32(crcData);\n writeUint32BE(chunk, 8 + data.length, crc);\n\n return chunk;\n}\n\n/**\n * Encode string as Latin-1 bytes\n */\nfunction latin1Encode(str: string): Uint8Array {\n const bytes = new Uint8Array(str.length);\n for (let i = 0; i < str.length; i++) {\n bytes[i] = str.charCodeAt(i) & 0xff;\n }\n return bytes;\n}\n\n/**\n * Encode string as UTF-8 bytes\n */\nfunction utf8Encode(str: string): Uint8Array {\n return new TextEncoder().encode(str);\n}\n\n// ============================================================================\n// CRC-32 Implementation (IEEE polynomial)\n// ============================================================================\n\n/** CRC-32 lookup table */\nconst CRC_TABLE = makeCrcTable();\n\n/**\n * Generate CRC-32 lookup table\n */\nfunction makeCrcTable(): Uint32Array {\n const table = new Uint32Array(256);\n for (let n = 0; n < 256; n++) {\n let c = n;\n for (let k = 0; k < 8; k++) {\n if (c & 1) {\n c = 0xedb88320 ^ (c >>> 1);\n } else {\n c = c >>> 1;\n }\n }\n table[n] = c >>> 0;\n }\n return table;\n}\n\n/**\n * Calculate CRC-32 checksum\n */\nfunction calculateCrc32(data: Uint8Array): number {\n let crc = 0xffffffff;\n for (let i = 0; i < data.length; i++) {\n crc = (CRC_TABLE[(crc ^ (data[i] ?? 0)) & 0xff] ?? 0) ^ (crc >>> 8);\n }\n return (crc ^ 0xffffffff) >>> 0;\n}\n","import type { MetadataSegment } from '../types';\nimport { Result } from '../types';\nimport { arraysEqual, isWebp, writeUint32LE } from '../utils/binary';\nimport { buildExifTiffData } from './exif';\n\n// Internal types (co-located with writer)\ntype WebpWriteError =\n | { type: 'invalidSignature' }\n | { type: 'invalidRiffStructure'; message: string };\n\ntype WebpWriteResult = Result<Uint8Array, WebpWriteError>;\n\n/** WebP file signature: \"RIFF\" */\nconst RIFF_SIGNATURE = new Uint8Array([0x52, 0x49, 0x46, 0x46]);\n\n/** WebP format marker: \"WEBP\" */\nconst WEBP_MARKER = new Uint8Array([0x57, 0x45, 0x42, 0x50]);\n\n/** EXIF chunk type */\nconst EXIF_CHUNK_TYPE = new Uint8Array([0x45, 0x58, 0x49, 0x46]);\n\n/**\n * Write WebP metadata to binary data\n *\n * Replaces existing EXIF chunk with new metadata.\n * All segments are written to the EXIF chunk based on their source type.\n *\n * @param data - Original WebP file data as Uint8Array\n * @param segments - Metadata segments to embed\n * @returns Result containing new WebP data with embedded metadata\n */\nexport function writeWebpMetadata(\n data: Uint8Array,\n segments: MetadataSegment[],\n): WebpWriteResult {\n // Validate WebP signature\n if (!isWebp(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n // Collect all chunks except EXIF\n const collectResult = collectNonExifChunks(data);\n if (!collectResult.ok) {\n return collectResult;\n }\n\n const { chunks } = collectResult.value;\n\n // Build new EXIF chunk from segments\n const exifChunk = buildExifChunk(segments);\n\n // Calculate new file size (excluding RIFF header)\n let newFileSize = 4; // \"WEBP\"\n for (const chunk of chunks) {\n newFileSize += chunk.length;\n }\n if (exifChunk) {\n newFileSize += exifChunk.length;\n }\n\n // Build output\n const output = new Uint8Array(8 + newFileSize);\n let offset = 0;\n\n // Write RIFF header\n output.set(RIFF_SIGNATURE, offset);\n offset += 4;\n writeUint32LE(output, offset, newFileSize);\n offset += 4;\n\n // Write WEBP marker\n output.set(WEBP_MARKER, offset);\n offset += 4;\n\n // Write EXIF chunk first if we have one (after VP8/VP8L/VP8X)\n // EXIF should come after the image chunk for best compatibility\n let exifWritten = false;\n\n for (const chunk of chunks) {\n // Write chunks in original order\n output.set(chunk, offset);\n offset += chunk.length;\n\n // Write EXIF after first image-related chunk (VP8, VP8L, VP8X)\n if (!exifWritten && exifChunk && isImageChunk(chunk)) {\n output.set(exifChunk, offset);\n offset += exifChunk.length;\n exifWritten = true;\n }\n }\n\n // If EXIF wasn't written yet (no VP8* chunk found), append it\n if (!exifWritten && exifChunk) {\n output.set(exifChunk, offset);\n }\n\n return Result.ok(output);\n}\n\n/**\n * Check if chunk is an image-related chunk (VP8, VP8L, VP8X)\n */\nfunction isImageChunk(chunk: Uint8Array): boolean {\n if (chunk.length < 4) return false;\n const type = String.fromCharCode(\n chunk[0] ?? 0,\n chunk[1] ?? 0,\n chunk[2] ?? 0,\n chunk[3] ?? 0,\n );\n return type === 'VP8 ' || type === 'VP8L' || type === 'VP8X';\n}\n\n/**\n * Collect all chunks except EXIF\n */\nfunction collectNonExifChunks(\n data: Uint8Array,\n): Result<\n { chunks: Uint8Array[]; firstChunkType: string },\n { type: 'invalidRiffStructure'; message: string }\n> {\n const chunks: Uint8Array[] = [];\n let firstChunkType = '';\n\n // Start after RIFF header (12 bytes: \"RIFF\" + size + \"WEBP\")\n let offset = 12;\n\n while (offset < data.length - 8) {\n // Read chunk type (4 bytes)\n const chunkType = data.slice(offset, offset + 4);\n const typeStr = String.fromCharCode(\n chunkType[0] ?? 0,\n chunkType[1] ?? 0,\n chunkType[2] ?? 0,\n chunkType[3] ?? 0,\n );\n\n if (!firstChunkType) {\n firstChunkType = typeStr;\n }\n\n // Read chunk size (4 bytes, little-endian)\n const chunkSize =\n (data[offset + 4] ?? 0) |\n ((data[offset + 5] ?? 0) << 8) |\n ((data[offset + 6] ?? 0) << 16) |\n ((data[offset + 7] ?? 0) << 24);\n\n // Validate chunk\n if (offset + 8 + chunkSize > data.length) {\n return Result.error({\n type: 'invalidRiffStructure',\n message: `Chunk extends beyond file at offset ${offset}`,\n });\n }\n\n // Keep all chunks except EXIF\n if (!arraysEqual(chunkType, EXIF_CHUNK_TYPE)) {\n // Include type + size + data (+ padding if odd)\n const paddedSize = chunkSize + (chunkSize % 2);\n const chunkData = data.slice(offset, offset + 8 + paddedSize);\n chunks.push(chunkData);\n }\n\n // Move to next chunk (chunk size + type + size fields)\n // RIFF chunks are padded to even byte boundaries\n const paddedSize = chunkSize + (chunkSize % 2);\n offset += 8 + paddedSize;\n }\n\n return Result.ok({ chunks, firstChunkType });\n}\n\n/**\n * Build EXIF chunk from metadata segments\n */\nfunction buildExifChunk(segments: MetadataSegment[]): Uint8Array | null {\n // Filter Exif-compatible segments\n const exifSegments = segments.filter(\n (s) =>\n s.source.type === 'exifUserComment' ||\n s.source.type === 'exifImageDescription' ||\n s.source.type === 'exifMake',\n );\n\n if (exifSegments.length === 0) {\n return null;\n }\n\n const tiffData = buildExifTiffData(exifSegments);\n\n if (tiffData.length === 0) {\n return null;\n }\n\n // Build EXIF chunk: type (4) + size (4) + TIFF data\n const chunkSize = tiffData.length;\n const paddedSize = chunkSize + (chunkSize % 2);\n const chunk = new Uint8Array(8 + paddedSize);\n\n chunk.set(EXIF_CHUNK_TYPE, 0);\n writeUint32LE(chunk, 4, chunkSize);\n chunk.set(tiffData, 8);\n\n return chunk;\n}\n","/**\n * Unified metadata stringification\n *\n * Builds A1111-format plain text from metadata and provides the stringify()\n * public API for converting any metadata type to a human-readable string.\n */\n\nimport type {\n BaseMetadata,\n CharacterPrompt,\n EmbedMetadata,\n GenerationMetadata,\n HiresSettings,\n ParseResult,\n RawMetadata,\n} from '../types';\n\n// ============================================================================\n// Internal Helpers\n// ============================================================================\n\n/**\n * Normalize line endings to LF (\\n)\n */\nfunction normalizeLineEndings(text: string): string {\n return text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n}\n\n/**\n * Build character prompts section\n *\n * Format: # Character N [x, y]:\\n[prompt]\n */\nfunction buildCharacterPromptsSection(\n characterPrompts: CharacterPrompt[],\n): string[] {\n return characterPrompts.flatMap((cp, index) => {\n const coords = cp.center ? ` [${cp.center.x}, ${cp.center.y}]` : '';\n return [\n `# Character ${index + 1}${coords}:`,\n normalizeLineEndings(cp.prompt),\n ];\n });\n}\n\n/**\n * Build settings line from BaseMetadata and optional extras\n *\n * Structured fields are output in A1111-standard order. Extras can override\n * structured fields (taking their position) or append as new entries at the end.\n */\nfunction buildSettingsLine(\n metadata: BaseMetadata,\n extras?: Record<string, string | number>,\n): string {\n const hires: HiresSettings | undefined = metadata.hires ?? metadata.upscale;\n\n const fields: Record<string, string | number | undefined> = {\n Steps: metadata.sampling?.steps,\n Sampler: metadata.sampling?.sampler,\n 'Schedule type': metadata.sampling?.scheduler,\n 'CFG scale': metadata.sampling?.cfg,\n Seed: metadata.sampling?.seed,\n Size:\n metadata.width > 0 && metadata.height > 0\n ? `${metadata.width}x${metadata.height}`\n : undefined,\n 'Model hash': metadata.model?.hash,\n Model: metadata.model?.name,\n 'Clip skip': metadata.sampling?.clipSkip,\n 'Denoising strength': hires?.denoise,\n 'Hires upscale': hires?.scale,\n 'Hires steps': hires?.steps,\n 'Hires upscaler': hires?.upscaler,\n };\n\n return Object.entries({ ...fields, ...extras })\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${k}: ${v}`)\n .join(', ');\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Build A1111-format text from EmbedMetadata\n *\n * Output structure:\n * 1. Positive prompt (line-ending normalized)\n * 2. Character prompts (if present)\n * 3. Negative prompt (if non-empty)\n * 4. Settings line (structured fields + extras)\n *\n * @param metadata - Embed metadata (extras included via `metadata.extras`)\n * @returns A1111-format plain text\n */\nexport function buildEmbedText(metadata: EmbedMetadata): string {\n return [\n normalizeLineEndings(metadata.prompt),\n metadata.characterPrompts?.length\n ? buildCharacterPromptsSection(metadata.characterPrompts).join('\\n')\n : undefined,\n metadata.negativePrompt\n ? `Negative prompt: ${normalizeLineEndings(metadata.negativePrompt)}`\n : undefined,\n buildSettingsLine(metadata, metadata.extras) || undefined,\n ]\n .filter((s): s is string => s !== undefined)\n .join('\\n');\n}\n\n/**\n * Format raw metadata as plain text\n *\n * Extracts text content from RawMetadata and returns it as a simple string.\n * Multiple entries are separated by double newlines.\n *\n * @param raw - Raw metadata from ParseResult\n * @returns Plain text content from the metadata\n */\nexport function formatRaw(raw: RawMetadata): string {\n switch (raw.format) {\n case 'png':\n return raw.chunks.map((chunk) => chunk.text).join('\\n\\n');\n\n case 'jpeg':\n case 'webp':\n return raw.segments.map((segment) => segment.data).join('\\n\\n');\n }\n}\n\n/**\n * Convert metadata to a human-readable string\n *\n * Accepts multiple input types:\n * - `ParseResult`: Automatically selects the best representation based on status\n * - `GenerationMetadata`: Formats as A1111 text (parsed metadata from any tool)\n * - `EmbedMetadata`: Formats as A1111 text (user-created custom metadata)\n *\n * @param input - Parse result, generation metadata, or embed metadata\n * @returns Human-readable text representation, or empty string if no data\n *\n * @example\n * ```typescript\n * import { read, stringify } from '@enslo/sd-metadata';\n *\n * // From parse result\n * const result = read(imageData);\n * const text = stringify(result);\n *\n * // From GenerationMetadata (e.g. after parsing)\n * if (result.status === 'success') {\n * const text3 = stringify(result.metadata);\n * }\n *\n * // From EmbedMetadata (e.g. user-created)\n * const text2 = stringify({\n * prompt: 'masterpiece, 1girl',\n * negativePrompt: '',\n * width: 512,\n * height: 768,\n * sampling: { steps: 20, sampler: 'Euler a', cfg: 7, seed: 12345 },\n * extras: { Version: 'v1.10.0' },\n * });\n * ```\n */\nexport function stringify(\n input: ParseResult | EmbedMetadata | GenerationMetadata,\n): string {\n if ('status' in input) {\n switch (input.status) {\n case 'success':\n return buildEmbedText(input.metadata);\n case 'unrecognized':\n return formatRaw(input.raw);\n case 'empty':\n case 'invalid':\n return '';\n }\n }\n return buildEmbedText(input);\n}\n","/**\n * Embed API for sd-metadata\n *\n * Write user-created or parsed metadata in A1111 format to PNG, JPEG,\n * or WebP images. Accepts both EmbedMetadata (custom metadata composed\n * by the user) and GenerationMetadata (parsed output from AI tools).\n */\n\nimport { createEncodedChunk } from '../converters/chunk-encoding';\nimport type { EmbedMetadata, GenerationMetadata, WriteResult } from '../types';\nimport { Result } from '../types';\nimport type { ImageFormat } from '../utils/binary';\nimport { detectFormat, toUint8Array } from '../utils/binary';\nimport { writeJpegMetadata } from '../writers/jpeg';\nimport { writePngMetadata } from '../writers/png';\nimport { writeWebpMetadata } from '../writers/webp';\nimport { buildEmbedText } from './stringify';\n\n/**\n * Embed metadata into an image\n *\n * Converts the provided metadata to A1111 plain text format and embeds it\n * into the image. Accepts {@link EmbedMetadata} for user-created custom metadata, or\n * {@link GenerationMetadata} for re-embedding parsed output from any AI tool.\n *\n * Extras (`metadata.extras`) allow adding arbitrary key-value pairs to the\n * settings line. If an extras key matches a structured field (e.g., \"Steps\"),\n * the extras value overrides the structured value at its original position.\n *\n * The metadata is stored differently based on image format:\n * - PNG: `parameters` tEXt/iTXt chunk (encoding auto-selected based on content)\n * - JPEG/WebP: Exif UserComment field\n *\n * @param input - Target image file data (Uint8Array or ArrayBuffer)\n * @param metadata - Metadata to embed (EmbedMetadata or GenerationMetadata)\n * @returns New image data with embedded metadata, or error\n *\n * @example\n * ```typescript\n * import { embed } from '@enslo/sd-metadata';\n *\n * const metadata = {\n * prompt: 'masterpiece, 1girl',\n * negativePrompt: 'lowres, bad quality',\n * width: 512,\n * height: 768,\n * sampling: { steps: 20, sampler: 'Euler a', cfg: 7, seed: 12345 },\n * model: { name: 'model.safetensors' },\n * };\n *\n * // Embed with custom extras\n * const result = embed(imageData, {\n * ...metadata,\n * extras: { Version: 'v1.10.0', 'Lora hashes': 'abc123' },\n * });\n *\n * if (result.ok) {\n * writeFileSync('output.png', result.value);\n * }\n * ```\n */\nexport function embed(\n input: Uint8Array | ArrayBuffer,\n metadata: EmbedMetadata | GenerationMetadata,\n): WriteResult {\n const data = toUint8Array(input);\n\n // Detect image format\n const format = detectFormat(data);\n if (!format) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Build A1111-format text\n const text = buildEmbedText(metadata);\n\n // Write format-specific metadata\n const writeResult = writeForFormat(format, data, text);\n\n if (!writeResult.ok) {\n return Result.error({\n type: 'writeFailed',\n message: writeResult.error.type,\n });\n }\n\n return Result.ok(writeResult.value);\n}\n\n/**\n * Write metadata text to image in format-specific way\n */\nfunction writeForFormat(format: ImageFormat, data: Uint8Array, text: string) {\n switch (format) {\n case 'png':\n return writePngMetadata(\n data,\n createEncodedChunk('parameters', text, 'dynamic'),\n );\n case 'jpeg':\n return writeJpegMetadata(data, [\n { source: { type: 'exifUserComment' as const }, data: text },\n ]);\n case 'webp':\n return writeWebpMetadata(data, [\n { source: { type: 'exifUserComment' as const }, data: text },\n ]);\n }\n}\n","/**\n * Object utility functions\n */\n\n/**\n * Remove undefined values from an object\n *\n * Creates a new object with only defined (non-undefined) values.\n * Returns undefined if all values are undefined (empty result).\n * Useful for building metadata objects where undefined fields should be omitted.\n *\n * @example\n * trimObject({ a: 1, b: undefined, c: 'hello' })\n * // => { a: 1, c: 'hello' }\n *\n * trimObject({ a: undefined, b: undefined })\n * // => undefined\n *\n * @param obj - Object with potentially undefined values\n * @returns New object with undefined values removed, or undefined if empty\n */\nexport function trimObject<T extends Record<string, unknown>>(\n obj: T,\n): Partial<T> | undefined {\n const result = Object.fromEntries(\n Object.entries(obj).filter(([, value]) => value !== undefined),\n ) as Partial<T>;\n return Object.keys(result).length === 0 ? undefined : result;\n}\n","import type { GenerationSoftware } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/** Software identifiers that use A1111-compatible text format */\ntype A1111Software = Extract<\n GenerationSoftware,\n | 'sd-webui'\n | 'sd-next'\n | 'forge'\n | 'forge-classic'\n | 'forge-neo'\n | 'reforge'\n | 'easy-reforge'\n | 'civitai'\n | 'fooocus'\n>;\n\n/**\n * Parse A1111-format metadata from entries\n *\n * A1111 format is used by:\n * - Stable Diffusion WebUI (AUTOMATIC1111)\n * - Forge family (Forge, Forge Classic, Forge Neo, reForge, EasyReforge)\n * - SD.Next\n * - Civitai (A1111 text fallback)\n *\n * Format:\n * ```\n * positive prompt\n * Negative prompt: negative prompt\n * Steps: 20, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, ...\n * ```\n *\n * @param entries - Metadata entries\n * @param software - Pre-detected software identifier from detectSoftware()\n * @returns Parsed metadata or error\n */\nexport function parseA1111(\n entries: EntryRecord,\n software: A1111Software,\n): InternalParseResult {\n // Find parameters entry (PNG uses 'parameters', JPEG/WebP uses 'UserComment')\n const text = entries.parameters ?? entries.UserComment;\n if (!text) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse the text into sections\n const { prompt, negativePrompt, settings } = parseParametersText(text);\n\n // Parse settings key-value pairs\n const settingsMap = parseSettings(settings);\n\n // Extract dimensions (optional, defaults to \"0x0\" like SD Prompt Reader)\n const size = settingsMap.get('Size') ?? '0x0';\n const [width, height] = parseSize(size);\n\n // Extract model settings\n const modelName = settingsMap.get('Model');\n const modelHash = settingsMap.get('Model hash');\n\n // Extract sampling settings\n const sampler = settingsMap.get('Sampler');\n const scheduler = settingsMap.get('Schedule type');\n const steps = parseNumber(settingsMap.get('Steps'));\n const cfg = parseNumber(\n settingsMap.get('CFG scale') ?? settingsMap.get('CFG Scale'),\n );\n const seed = parseNumber(settingsMap.get('Seed'));\n const clipSkip = parseNumber(settingsMap.get('Clip skip'));\n\n // Extract hires settings\n const hiresScale = parseNumber(settingsMap.get('Hires upscale'));\n const upscaler = settingsMap.get('Hires upscaler');\n const hiresSteps = parseNumber(settingsMap.get('Hires steps'));\n const denoise = parseNumber(settingsMap.get('Denoising strength'));\n const hiresSize = settingsMap.get('Hires size');\n const [hiresWidth] = parseSize(hiresSize ?? '');\n const scale = hiresScale ?? (hiresWidth > 0 ? hiresWidth / width : undefined);\n\n return Result.ok({\n software,\n prompt,\n negativePrompt,\n width,\n height,\n model: trimObject({ name: modelName, hash: modelHash }),\n sampling: trimObject({\n sampler,\n scheduler,\n steps,\n cfg,\n seed,\n clipSkip,\n }),\n hires: trimObject({ scale, upscaler, steps: hiresSteps, denoise }),\n });\n}\n\n/**\n * Parse parameters text into prompt, negative prompt, and settings\n */\nfunction parseParametersText(text: string): {\n prompt: string;\n negativePrompt: string;\n settings: string;\n} {\n // Find \"Negative prompt:\" marker\n const negativeIndex = text.indexOf('Negative prompt:');\n\n // Find the settings line (starts after the last newline before \"Steps:\")\n const stepsIndex = text.indexOf('Steps:');\n\n if (negativeIndex === -1 && stepsIndex === -1) {\n // No negative prompt, no settings - just prompt\n return { prompt: text.trim(), negativePrompt: '', settings: '' };\n }\n\n if (negativeIndex === -1) {\n // No negative prompt\n const settingsStart = text.lastIndexOf('\\n', stepsIndex);\n return {\n prompt: text.slice(0, settingsStart).trim(),\n negativePrompt: '',\n settings: text.slice(settingsStart).trim(),\n };\n }\n\n if (stepsIndex === -1) {\n // No settings (unusual)\n return {\n prompt: text.slice(0, negativeIndex).trim(),\n negativePrompt: text.slice(negativeIndex + 16).trim(),\n settings: '',\n };\n }\n\n // Both exist: find where negative prompt ends and settings begin\n const settingsStart = text.lastIndexOf('\\n', stepsIndex);\n\n return {\n prompt: text.slice(0, negativeIndex).trim(),\n negativePrompt: text.slice(negativeIndex + 16, settingsStart).trim(),\n settings: text.slice(settingsStart).trim(),\n };\n}\n\n/**\n * Parse settings line into key-value map\n *\n * Format: \"Key1: value1, Key2: value2, ...\"\n * Note: Values may contain commas (e.g., model names), so we parse carefully\n */\nfunction parseSettings(settings: string): Map<string, string> {\n const result = new Map<string, string>();\n if (!settings) return result;\n\n // Match \"Key: value\" pairs\n // Key is word characters (including spaces before colon)\n // Value continues until next \"Key:\" pattern or end\n const regex =\n /([A-Za-z][A-Za-z0-9 ]*?):\\s*([^,]+?)(?=,\\s*[A-Za-z][A-Za-z0-9 ]*?:|$)/g;\n\n for (const match of settings.matchAll(regex)) {\n const key = (match[1] ?? '').trim();\n const value = (match[2] ?? '').trim();\n result.set(key, value);\n }\n\n return result;\n}\n\n/**\n * Parse \"WxH\" size string\n */\nfunction parseSize(size: string): [number, number] {\n const match = size.match(/(\\d+)x(\\d+)/);\n if (!match) return [0, 0];\n return [\n Number.parseInt(match[1] ?? '0', 10),\n Number.parseInt(match[2] ?? '0', 10),\n ];\n}\n\n/**\n * Parse number from string, returning undefined if invalid\n */\nfunction parseNumber(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const num = Number.parseFloat(value);\n return Number.isNaN(num) ? undefined : num;\n}\n","/**\n * Type-safe JSON parsing utilities\n */\n\n/**\n * Discriminated union for JSON parse results.\n * Each variant contains the actual runtime type for proper narrowing.\n *\n * @typeParam T - Type for object values (default: Record<string, unknown>)\n */\nexport type JsonParseResult<T = Record<string, unknown>> =\n | { ok: true; type: 'object'; value: T }\n | { ok: true; type: 'array'; value: unknown[] }\n | { ok: true; type: 'string'; value: string }\n | { ok: true; type: 'number'; value: number }\n | { ok: true; type: 'boolean'; value: boolean }\n | { ok: true; type: 'null'; value: null }\n | { ok: false; error: { type: 'parseError'; message: string } };\n\n/**\n * Type-safe JSON parse with discriminated union result.\n *\n * Returns a result that includes the runtime type of the parsed value,\n * enabling proper TypeScript narrowing without unsafe type assertions.\n *\n * @typeParam T - Expected object type (used when type === 'object')\n * @param text - JSON string to parse\n * @returns Discriminated union with parsed value and its type, or parse error\n *\n * @example\n * // Without type parameter - value is Record<string, unknown>\n * const parsed = parseJson(text);\n * if (!parsed.ok || parsed.type !== 'object') return null;\n * const name = parsed.value.name; // unknown\n *\n * @example\n * // With type parameter - value is the specified type\n * const parsed = parseJson<MyInterface>(text);\n * if (!parsed.ok || parsed.type !== 'object') return null;\n * const name = parsed.value.name; // typed!\n */\nexport function parseJson<T = Record<string, unknown>>(\n text: string,\n): JsonParseResult<T> {\n try {\n const value: unknown = JSON.parse(text);\n return toJsonResult<T>(value);\n } catch {\n return {\n ok: false,\n error: { type: 'parseError', message: 'Invalid JSON' },\n };\n }\n}\n\n/**\n * Convert a parsed JSON value to a discriminated result\n */\nfunction toJsonResult<T>(value: unknown): JsonParseResult<T> {\n if (value === null) {\n return { ok: true, type: 'null', value: null };\n }\n if (Array.isArray(value)) {\n return { ok: true, type: 'array', value };\n }\n switch (typeof value) {\n case 'object':\n return { ok: true, type: 'object', value: value as T };\n case 'string':\n return { ok: true, type: 'string', value };\n case 'number':\n return { ok: true, type: 'number', value };\n case 'boolean':\n return { ok: true, type: 'boolean', value };\n default:\n // Should never happen with valid JSON\n return {\n ok: false,\n error: { type: 'parseError', message: 'Unexpected JSON type' },\n };\n }\n}\n","/**\n * ComfyUI node graph utilities\n *\n * Functions for searching and extracting data from ComfyUI node graphs.\n * Node graphs use class_type to identify node types and [nodeId, outputIndex]\n * arrays to reference connections between nodes.\n */\n\nimport type {\n ComfyNode,\n ComfyNodeGraph,\n ModelSettings,\n SamplingSettings,\n} from '../types';\n\n// =============================================================================\n// Node Class Type Constants\n// =============================================================================\n\nconst SAMPLER_TYPES = ['KSampler', 'KSamplerAdvanced', 'SamplerCustomAdvanced'];\nconst LATENT_IMAGE_TYPES = ['EmptyLatentImage'];\nconst LATENT_IMAGE_RGTHREE_TYPES = ['SDXL Empty Latent Image (rgthree)'];\nconst CHECKPOINT_TYPES = ['CheckpointLoaderSimple', 'CheckpointLoader'];\nconst UNET_LOADER_TYPES = ['UNETLoader'];\nconst CLIP_SET_LAST_LAYER_TYPES = ['CLIPSetLastLayer'];\nconst HIRES_MODEL_UPSCALE_TYPES = ['UpscaleModelLoader'];\nconst HIRES_IMAGE_SCALE_TYPES = ['ImageScale', 'ImageScaleBy'];\nconst LATENT_UPSCALE_TYPES = ['LatentUpscale', 'LatentUpscaleBy'];\nconst VAE_ENCODE_TYPES = ['VAEEncode', 'VAEEncodeTiled'];\n\n// =============================================================================\n// Node Classification (Single-Pass)\n// =============================================================================\n\n/**\n * Pre-classified ComfyUI nodes by role\n *\n * Built by a single pass over the node graph.\n */\nexport interface ClassifiedNodes {\n sampler?: ComfyNode;\n latentImage?: ComfyNode;\n latentImageRgthree?: ComfyNode;\n checkpoint?: ComfyNode;\n unetLoader?: ComfyNode;\n clipSetLastLayer?: ComfyNode;\n hiresModelUpscale?: ComfyNode;\n hiresImageScale?: ComfyNode;\n latentUpscale?: ComfyNode;\n vaeEncode?: ComfyNode;\n}\n\n/**\n * Classify all nodes in a single pass over the node graph\n *\n * Replaces multiple findNode() calls with one iteration.\n * Stores the first matching node for each category.\n */\nexport function classifyNodes(nodes: ComfyNodeGraph): ClassifiedNodes {\n const result: ClassifiedNodes = {};\n\n for (const node of Object.values(nodes)) {\n const ct = node.class_type;\n\n if (!result.sampler && SAMPLER_TYPES.includes(ct)) {\n result.sampler = node;\n } else if (!result.latentImage && LATENT_IMAGE_TYPES.includes(ct)) {\n result.latentImage = node;\n } else if (\n !result.latentImageRgthree &&\n LATENT_IMAGE_RGTHREE_TYPES.includes(ct)\n ) {\n result.latentImageRgthree = node;\n } else if (!result.checkpoint && CHECKPOINT_TYPES.includes(ct)) {\n result.checkpoint = node;\n } else if (!result.unetLoader && UNET_LOADER_TYPES.includes(ct)) {\n result.unetLoader = node;\n } else if (\n !result.clipSetLastLayer &&\n CLIP_SET_LAST_LAYER_TYPES.includes(ct)\n ) {\n result.clipSetLastLayer = node;\n } else if (\n !result.hiresModelUpscale &&\n HIRES_MODEL_UPSCALE_TYPES.includes(ct)\n ) {\n result.hiresModelUpscale = node;\n } else if (\n !result.hiresImageScale &&\n HIRES_IMAGE_SCALE_TYPES.includes(ct)\n ) {\n result.hiresImageScale = node;\n } else if (!result.latentUpscale && LATENT_UPSCALE_TYPES.includes(ct)) {\n result.latentUpscale = node;\n } else if (!result.vaeEncode && VAE_ENCODE_TYPES.includes(ct)) {\n result.vaeEncode = node;\n }\n }\n\n return result;\n}\n\n// =============================================================================\n// Node Reference Utilities\n// =============================================================================\n\n/**\n * Resolve a node reference to the target node\n */\nfunction resolveNode(\n nodes: ComfyNodeGraph,\n ref: unknown,\n): ComfyNode | undefined {\n if (!isNodeReference(ref)) return undefined;\n return nodes[String(ref[0])];\n}\n\n/**\n * Check if a value is a node reference [nodeId, outputIndex]\n */\nexport function isNodeReference(value: unknown): value is [string, number] {\n return (\n Array.isArray(value) &&\n value.length === 2 &&\n (typeof value[0] === 'string' || typeof value[0] === 'number') &&\n typeof value[1] === 'number'\n );\n}\n\n/**\n * Extract text from a node, following references if needed\n *\n * Handles various text input patterns:\n * - CLIPTextEncode: inputs.text\n * - Power Prompt (rgthree): inputs.prompt\n * - DF_Text_Box (ComfyRoll): inputs.Text\n * - Reference chains: [nodeId, outputIndex] -> trace to source\n */\nexport function extractText(\n nodes: ComfyNodeGraph,\n nodeId: string,\n maxDepth = 10,\n): string {\n if (maxDepth <= 0) return '';\n\n const node = nodes[nodeId];\n if (!node) return '';\n\n // Try common text input names\n const textValue = node.inputs.text ?? node.inputs.prompt ?? node.inputs.Text;\n\n if (typeof textValue === 'string') {\n return textValue;\n }\n\n // If text is a reference, follow it\n if (isNodeReference(textValue)) {\n return extractText(nodes, String(textValue[0]), maxDepth - 1);\n }\n\n return '';\n}\n\n// =============================================================================\n// Prompt Extraction\n// =============================================================================\n\n/**\n * Resolve the node that holds positive/negative conditioning references\n *\n * KSampler/KSamplerAdvanced have positive/negative directly.\n * SamplerCustomAdvanced routes through a guider node (e.g. CFGGuider).\n */\nfunction resolveConditioningSource(\n nodes: ComfyNodeGraph,\n sampler: ComfyNode,\n): ComfyNode {\n const guiderNode = resolveNode(nodes, sampler.inputs.guider);\n if (guiderNode) return guiderNode;\n return sampler;\n}\n\n/**\n * Extract prompt texts by tracing from sampler's positive/negative inputs\n *\n * @param nodes - Full node graph (needed for reference following)\n * @param sampler - Pre-classified sampler node\n */\nexport function extractPromptTexts(\n nodes: ComfyNodeGraph,\n sampler: ComfyNode | undefined,\n): {\n promptText: string;\n negativeText: string;\n} {\n if (!sampler) {\n return { promptText: '', negativeText: '' };\n }\n\n // Resolve the node that holds positive/negative conditioning references.\n // KSampler/KSamplerAdvanced: directly on sampler inputs\n // SamplerCustomAdvanced: on the CFGGuider node via guider input\n const conditioningSource = resolveConditioningSource(nodes, sampler);\n\n const positiveRef = conditioningSource.inputs.positive;\n const negativeRef = conditioningSource.inputs.negative;\n\n return {\n promptText: isNodeReference(positiveRef)\n ? extractText(nodes, String(positiveRef[0]))\n : '',\n negativeText: isNodeReference(negativeRef)\n ? extractText(nodes, String(negativeRef[0]))\n : '',\n };\n}\n\n// =============================================================================\n// Dimension Extraction\n// =============================================================================\n\n/**\n * Extract dimensions from pre-classified LatentImage nodes\n *\n * Handles two node types separately:\n * - EmptyLatentImage: inputs.width and inputs.height as numbers\n * - SDXL Empty Latent Image (rgthree): inputs.dimensions as string like \"1024 x 1024 (square)\"\n *\n * @param latentImage - Standard EmptyLatentImage node\n * @param latentImageRgthree - rgthree SDXL latent image node\n */\nexport function extractDimensions(\n latentImage: ComfyNode | undefined,\n latentImageRgthree: ComfyNode | undefined,\n): {\n width: number;\n height: number;\n} {\n // Try standard EmptyLatentImage first (width/height properties)\n if (latentImage) {\n const width = Number(latentImage.inputs.width) || 0;\n const height = Number(latentImage.inputs.height) || 0;\n if (width > 0 && height > 0) return { width, height };\n }\n\n // Try rgthree latent image (dimensions string like \"1024 x 1024 (square)\")\n if (\n latentImageRgthree &&\n typeof latentImageRgthree.inputs.dimensions === 'string'\n ) {\n const match =\n latentImageRgthree.inputs.dimensions.match(/^(\\d+)\\s*x\\s*(\\d+)/);\n if (match?.[1] && match[2]) {\n return {\n width: Number.parseInt(match[1], 10),\n height: Number.parseInt(match[2], 10),\n };\n }\n }\n\n return { width: 0, height: 0 };\n}\n\n// =============================================================================\n// Sampling & Model Extraction\n// =============================================================================\n\n/**\n * Extract sampling settings from pre-classified sampler node\n *\n * @param nodes - Full node graph (needed for seed reference following)\n * @param sampler - Pre-classified sampler node\n */\nexport function extractSampling(\n nodes: ComfyNodeGraph,\n sampler: ComfyNode | undefined,\n): SamplingSettings | undefined {\n if (!sampler) return undefined;\n\n if (sampler.class_type === 'SamplerCustomAdvanced') {\n return extractAdvancedSampling(nodes, sampler);\n }\n\n // Handle seed which may be a reference or direct value\n let seed = sampler.inputs.seed;\n if (isNodeReference(seed)) {\n // Seed is from another node (e.g., CR Seed), try to extract it\n const seedNode = nodes[String(seed[0])];\n seed = seedNode?.inputs.seed;\n }\n\n // Extract denoise only if explicitly set and less than 1.0\n // (denoise = 1.0 is the txt2img default, not meaningful to store)\n const rawDenoise = sampler.inputs.denoise;\n const denoise =\n typeof rawDenoise === 'number' && rawDenoise < 1 ? rawDenoise : undefined;\n\n return {\n seed: seed as number,\n steps: sampler.inputs.steps as number,\n cfg: sampler.inputs.cfg as number,\n sampler: sampler.inputs.sampler_name as string,\n scheduler: sampler.inputs.scheduler as string,\n denoise,\n };\n}\n\n/**\n * Extract sampling settings from SamplerCustomAdvanced\n *\n * Traces distributed inputs:\n * - noise → RandomNoise → noise_seed\n * - guider → CFGGuider → cfg\n * - sampler → KSamplerSelect → sampler_name\n * - sigmas → BasicScheduler → scheduler, steps, denoise\n */\nfunction extractAdvancedSampling(\n nodes: ComfyNodeGraph,\n sampler: ComfyNode,\n): SamplingSettings {\n const noiseNode = resolveNode(nodes, sampler.inputs.noise);\n const guiderNode = resolveNode(nodes, sampler.inputs.guider);\n const samplerSelectNode = resolveNode(nodes, sampler.inputs.sampler);\n const schedulerNode = resolveNode(nodes, sampler.inputs.sigmas);\n\n const rawDenoise = schedulerNode?.inputs.denoise;\n const denoise =\n typeof rawDenoise === 'number' && rawDenoise < 1 ? rawDenoise : undefined;\n\n return {\n seed: noiseNode?.inputs.noise_seed as number,\n steps: schedulerNode?.inputs.steps as number,\n cfg: guiderNode?.inputs.cfg as number,\n sampler: samplerSelectNode?.inputs.sampler_name as string,\n scheduler: schedulerNode?.inputs.scheduler as string,\n denoise,\n };\n}\n\n/**\n * Extract model name from pre-classified model loader node\n *\n * Checks CheckpointLoader first, then falls back to UNETLoader.\n *\n * @param checkpoint - Pre-classified checkpoint loader node\n * @param unetLoader - Pre-classified UNET loader node (fallback)\n */\nexport function extractModel(\n checkpoint: ComfyNode | undefined,\n unetLoader?: ComfyNode | undefined,\n): ModelSettings | undefined {\n if (checkpoint?.inputs?.ckpt_name) {\n return { name: String(checkpoint.inputs.ckpt_name) };\n }\n if (unetLoader?.inputs?.unet_name) {\n return { name: String(unetLoader.inputs.unet_name) };\n }\n return undefined;\n}\n\n/**\n * Extract CLIP skip value from CLIPSetLastLayer node\n *\n * stop_at_clip_layer is negative (-2 = clip skip 2).\n * Returns undefined if no node is present.\n *\n * @param clipSetLastLayer - Pre-classified CLIPSetLastLayer node\n */\nexport function extractClipSkip(\n clipSetLastLayer: ComfyNode | undefined,\n): number | undefined {\n if (!clipSetLastLayer) return undefined;\n const stopAt = clipSetLastLayer.inputs.stop_at_clip_layer;\n if (typeof stopAt !== 'number') return undefined;\n return -stopAt;\n}\n\n// =============================================================================\n// Scale Calculation\n// =============================================================================\n\n/**\n * Calculate scale factor rounded to 2 decimal places\n *\n * @param targetWidth - Target width after scaling\n * @param baseWidth - Original base width\n * @returns Scale factor or undefined if invalid inputs\n */\nexport function calculateScale(\n targetWidth: number,\n baseWidth: number,\n): number | undefined {\n if (baseWidth <= 0 || targetWidth <= 0) return undefined;\n return Math.round((targetWidth / baseWidth) * 100) / 100;\n}\n\n// =============================================================================\n// Hires Detection\n// =============================================================================\n\n/**\n * Check if a sampler is a hires sampler by tracing its latent_image input\n *\n * Hires fix workflows have two patterns:\n * 1. Image space: KSampler.latent_image -> VAE Encode -> Upscale Image\n * 2. Latent space: KSampler.latent_image -> LatentUpscale\n *\n * @param nodes - ComfyUI node graph\n * @param sampler - Sampler node to check\n * @returns true if this sampler is connected to an upscaled pipeline\n */\nfunction isHiresSampler(nodes: ComfyNodeGraph, sampler: ComfyNode): boolean {\n const latentImageRef = sampler.inputs.latent_image;\n if (!isNodeReference(latentImageRef)) return false;\n\n const inputNode = nodes[String(latentImageRef[0])];\n if (!inputNode) return false;\n\n // Pattern 1: Latent space upscale (LatentUpscale -> KSampler)\n if (LATENT_UPSCALE_TYPES.includes(inputNode.class_type)) {\n return true;\n }\n\n // Pattern 2: Image space upscale (ImageScale -> VAEEncode -> KSampler)\n if (!VAE_ENCODE_TYPES.includes(inputNode.class_type)) return false;\n\n const pixelsRef = inputNode.inputs.pixels;\n if (!isNodeReference(pixelsRef)) return false;\n\n const upscaleNode = nodes[String(pixelsRef[0])];\n if (!upscaleNode) return false;\n\n return HIRES_IMAGE_SCALE_TYPES.includes(upscaleNode.class_type);\n}\n\n/**\n * Find hires sampler node by connection pattern\n *\n * Detects hires fix by tracing: KSampler.latent_image -> VAE Encode -> Upscale Image\n * This is more reliable than checking denoise < 1, which can be used in normal generation.\n */\nexport function findHiresSampler(nodes: ComfyNodeGraph): ComfyNode | undefined {\n return Object.values(nodes).find(\n (node) =>\n SAMPLER_TYPES.includes(node.class_type) && isHiresSampler(nodes, node),\n );\n}\n","/**\n * CivitAI extraMetadata handling for ComfyUI parser\n *\n * CivitAI stores original generation parameters in an extraMetadata field\n * when images are upscaled through their orchestration service.\n */\n\nimport type {\n ComfyNodeGraph,\n HiresSettings,\n ModelSettings,\n SamplingSettings,\n UpscaleSettings,\n} from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport { calculateScale } from './comfyui-nodes';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Civitai extraMetadata structure (nested JSON in prompt)\n */\nexport interface CivitaiExtraMetadata {\n prompt?: string;\n negativePrompt?: string;\n cfgScale?: number;\n sampler?: string;\n clipSkip?: number;\n steps?: number;\n seed?: number;\n width?: number;\n height?: number;\n baseModel?: string;\n transformations?: Array<{\n type?: string;\n upscaleWidth?: number;\n upscaleHeight?: number;\n }>;\n}\n\n/**\n * Partial metadata extracted from CivitAI extraMetadata\n */\nexport interface CivitaiPartialMetadata {\n prompt?: string;\n negativePrompt?: string;\n width?: number;\n height?: number;\n model?: ModelSettings;\n sampling?: SamplingSettings;\n hires?: HiresSettings;\n upscale?: UpscaleSettings;\n}\n\n// =============================================================================\n// Extraction\n// =============================================================================\n\n/**\n * Extract extraMetadata from ComfyUI prompt or entryRecord\n *\n * Civitai upscale workflows embed original generation params in extraMetadata field.\n * This can be:\n * 1. Inside the prompt JSON (JPEG format: single JSON with all data)\n * 2. As a separate entry (PNG format: extraMetadata as separate chunk)\n */\nexport function extractExtraMetadata(\n prompt: ComfyNodeGraph,\n entryRecord?: EntryRecord,\n): CivitaiExtraMetadata | undefined {\n // First try to find extraMetadata inside the prompt (JPEG format)\n const extraMetaField = prompt.extraMetadata;\n if (typeof extraMetaField === 'string') {\n const parsed = parseJson<CivitaiExtraMetadata>(extraMetaField);\n if (parsed.ok && parsed.type === 'object') return parsed.value;\n }\n\n // Fall back to separate entry (PNG format)\n if (entryRecord?.extraMetadata) {\n const parsed = parseJson<CivitaiExtraMetadata>(entryRecord.extraMetadata);\n if (parsed.ok && parsed.type === 'object') return parsed.value;\n }\n\n return undefined;\n}\n\n/**\n * Extract metadata from CivitAI extraMetadata (fallback source)\n *\n * Used when ComfyUI nodes don't contain the expected data\n * (e.g., upscale-only workflows from Civitai).\n *\n * @param extraMeta - CivitAI extraMetadata\n * @returns Partial metadata from CivitAI extraMetadata\n */\nexport function extractCivitaiMetadata(\n extraMeta: CivitaiExtraMetadata | undefined,\n): CivitaiPartialMetadata | undefined {\n if (!extraMeta) return undefined;\n\n const upscale = buildCivitaiUpscale(extraMeta);\n const sampling = buildCivitaiSampling(extraMeta);\n\n return trimObject({\n prompt: extraMeta.prompt,\n negativePrompt: extraMeta.negativePrompt,\n width: extraMeta.width,\n height: extraMeta.height,\n model: extraMeta.baseModel ? { name: extraMeta.baseModel } : undefined,\n ...sampling,\n ...upscale,\n });\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/**\n * Build upscale settings from CivitAI transformations\n *\n * CivitAI stores upscale information in extraMetadata.transformations array.\n * This extracts the upscale transformation and calculates the scale factor.\n */\nfunction buildCivitaiUpscale(\n extraMeta: CivitaiExtraMetadata,\n): Pick<CivitaiPartialMetadata, 'upscale'> {\n if (!extraMeta.transformations) return {};\n\n const upscaleTransform = extraMeta.transformations.find(\n (t) => t.type === 'upscale',\n );\n if (!upscaleTransform?.upscaleWidth) return {};\n\n const scale = calculateScale(\n upscaleTransform.upscaleWidth,\n extraMeta.width ?? 0,\n );\n if (scale === undefined) return {};\n\n return {\n upscale: { scale },\n };\n}\n\n/**\n * Build sampling settings from CivitAI extraMetadata\n *\n * Only creates sampling object if at least one field is defined.\n */\nfunction buildCivitaiSampling(\n extraMeta: CivitaiExtraMetadata,\n): Pick<CivitaiPartialMetadata, 'sampling'> {\n if (\n extraMeta.seed === undefined &&\n extraMeta.steps === undefined &&\n extraMeta.cfgScale === undefined &&\n extraMeta.sampler === undefined\n ) {\n return {};\n }\n\n return {\n sampling: {\n seed: extraMeta.seed,\n steps: extraMeta.steps,\n cfg: extraMeta.cfgScale,\n sampler: extraMeta.sampler,\n },\n };\n}\n","/**\n * ComfyUI metadata parser\n *\n * Parses ComfyUI-format prompt data from node graphs.\n * Also handles Civitai extraMetadata fallbacks for upscale workflows.\n */\n\nimport type {\n ComfyNodeGraph,\n HiresSettings,\n ModelSettings,\n SamplingSettings,\n UpscaleSettings,\n} from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport {\n extractCivitaiMetadata,\n extractExtraMetadata,\n} from './comfyui-civitai';\nimport {\n type ClassifiedNodes,\n calculateScale,\n classifyNodes,\n extractClipSkip,\n extractDimensions,\n extractModel,\n extractPromptTexts,\n extractSampling,\n findHiresSampler,\n isNodeReference,\n} from './comfyui-nodes';\nimport type { InternalParseResult } from './types';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/**\n * CivitAI extension keys that are not ComfyUI nodes\n *\n * These keys are stored alongside nodes in JPEG format but should be\n * excluded from the nodes object to maintain type safety.\n */\nconst CIVITAI_EXTENSION_KEYS = ['extra', 'extraMetadata', 'resource-stack'];\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Partial metadata extracted from a single source\n *\n * All fields optional - will be merged later with other sources.\n */\ninterface PartialMetadata {\n prompt?: string;\n negativePrompt?: string;\n width?: number;\n height?: number;\n model?: ModelSettings;\n sampling?: SamplingSettings;\n hires?: HiresSettings;\n upscale?: UpscaleSettings;\n}\n\n/**\n * Merged metadata with required base fields\n *\n * Result of merging ComfyUI and CivitAI metadata sources.\n */\ninterface MergedMetadata {\n prompt: string;\n negativePrompt: string;\n width: number;\n height: number;\n model?: ModelSettings;\n sampling?: SamplingSettings;\n hires?: HiresSettings;\n upscale?: UpscaleSettings;\n}\n\n// =============================================================================\n// Main Parser\n// =============================================================================\n\n/**\n * Parse ComfyUI metadata from entries\n *\n * ComfyUI stores metadata with:\n * - prompt: JSON containing node graph with inputs\n * - workflow: JSON containing the full workflow (stored in raw, not parsed)\n *\n * This parser extracts metadata from ComfyUI nodes and merges with\n * CivitAI extraMetadata fallbacks when available.\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseComfyUI(entries: EntryRecord): InternalParseResult {\n // Find and parse prompt JSON\n const promptText = findPromptJson(entries);\n if (!promptText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n const parsed = parseJson<ComfyNodeGraph>(promptText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in prompt entry',\n });\n }\n const prompt = parsed.value;\n\n // Extract metadata from both sources\n const comfyMetadata = extractComfyUIMetadata(prompt);\n const civitaiMetadata = extractCivitaiMetadata(\n extractExtraMetadata(prompt, entries),\n );\n\n // Merge with ComfyUI taking priority\n const merged = mergeMetadata(civitaiMetadata, comfyMetadata);\n\n return Result.ok({\n software: 'comfyui',\n // Build pure ComfyUI nodes (exclude CivitAI extensions)\n nodes: Object.fromEntries(\n Object.entries(prompt).filter(\n ([key]) => !CIVITAI_EXTENSION_KEYS.includes(key),\n ),\n ),\n ...merged,\n });\n}\n\n// =============================================================================\n// JSON Utilities\n// =============================================================================\n\n/**\n * Clean JSON string for parsing\n *\n * Handles common issues in ComfyUI JSON:\n * - Remove null terminators that some tools append\n * - Replace NaN with null (NaN is not valid in JSON spec)\n */\nfunction cleanJsonString(json: string): string {\n return json.replace(/\\0+$/, '').replace(/:\\s*NaN\\b/g, ': null');\n}\n\n// =============================================================================\n// Prompt Finding\n// =============================================================================\n\n/**\n * Find ComfyUI prompt JSON from entry record\n *\n * PNG uses 'prompt', JPEG/WebP may use Comment, Description, or Make.\n */\nfunction findPromptJson(entryRecord: EntryRecord): string | undefined {\n // PNG format: prompt entry\n if (entryRecord.prompt) {\n return cleanJsonString(entryRecord.prompt);\n }\n\n // JPEG/WebP format: may be in various entries\n const candidates = [\n entryRecord.UserComment,\n entryRecord.ImageDescription,\n entryRecord.Make,\n entryRecord.Prompt, // save-image-extended uses this\n entryRecord.Workflow, // Not a prompt, but may contain nodes info\n ];\n\n for (const candidate of candidates) {\n if (!candidate) continue;\n\n // Check if it's JSON that looks like ComfyUI prompt\n if (candidate.startsWith('{')) {\n const cleaned = cleanJsonString(candidate);\n const parsed = parseJson(cleaned);\n if (!parsed.ok || parsed.type !== 'object') continue;\n\n // Check if it's wrapped in {\"prompt\": {...}} format\n if (parsed.value.prompt && typeof parsed.value.prompt === 'object') {\n return JSON.stringify(parsed.value.prompt);\n }\n // Check for nodes with class_type\n const values = Object.values(parsed.value);\n if (values.some((v) => v && typeof v === 'object' && 'class_type' in v)) {\n return cleaned; // Return cleaned JSON, not original candidate\n }\n }\n }\n\n return undefined;\n}\n\n// =============================================================================\n// ComfyUI Metadata Extraction\n// =============================================================================\n\n/**\n * Extract metadata from ComfyUI nodes\n *\n * Extracts prompt, dimensions, model, sampling, and hires settings\n * from standard ComfyUI node structure using class_type-based lookup.\n *\n * @param nodes - Parsed ComfyUI prompt (node graph)\n * @returns Partial metadata from ComfyUI nodes\n */\nexport function extractComfyUIMetadata(\n nodes: ComfyNodeGraph,\n): PartialMetadata | undefined {\n // Classify all nodes in a single pass\n const c = classifyNodes(nodes);\n\n // Extract from pre-classified nodes\n const { promptText, negativeText } = extractPromptTexts(nodes, c.sampler);\n const { width, height } = extractDimensions(\n c.latentImage,\n c.latentImageRgthree,\n );\n\n // Find hires sampler and resolve its sampling parameters\n const hiresSamplerNode = findHiresSampler(nodes);\n const hiresSampling = hiresSamplerNode\n ? extractSampling(nodes, hiresSamplerNode)\n : undefined;\n\n // Resolve hires scale from available sources\n const hiresScale = resolveHiresScale(nodes, c, width);\n\n const upscalerName = c.hiresModelUpscale?.inputs.model_name as\n | string\n | undefined;\n\n const rawSampling = extractSampling(nodes, c.sampler);\n const clipSkip = extractClipSkip(c.clipSetLastLayer);\n\n return trimObject({\n prompt: promptText || undefined,\n negativePrompt: negativeText || undefined,\n width: width > 0 ? width : undefined,\n height: height > 0 ? height : undefined,\n model: extractModel(c.checkpoint, c.unetLoader),\n sampling: trimObject({ ...rawSampling, clipSkip }),\n ...buildHiresOrUpscale(upscalerName, hiresScale, hiresSampling),\n });\n}\n\n/**\n * Resolve hires scale factor from available node sources\n *\n * Priority:\n * 1. LatentUpscaleBy.scale_by (direct value)\n * 2. ImageScale.width as node reference → source node's clip_scale (rgthree)\n * 3. ImageScale.width as number → calculate ratio against base width\n */\nfunction resolveHiresScale(\n nodes: ComfyNodeGraph,\n c: ClassifiedNodes,\n baseWidth: number,\n): number | undefined {\n const latentUpscale = c.latentUpscale?.inputs;\n if (latentUpscale?.scale_by !== undefined) {\n return latentUpscale.scale_by as number;\n }\n\n const widthInput = c.hiresImageScale?.inputs.width;\n if (widthInput === undefined) return undefined;\n\n // Width from a node reference (e.g. rgthree provides scaled dimensions)\n if (isNodeReference(widthInput)) {\n const sourceNode = nodes[String(widthInput[0])];\n if (typeof sourceNode?.inputs.clip_scale === 'number') {\n return sourceNode.inputs.clip_scale;\n }\n return undefined;\n }\n\n // Width as direct number value\n if (typeof widthInput === 'number') {\n return calculateScale(widthInput, baseWidth);\n }\n\n return undefined;\n}\n\n/**\n * Build hires or upscale settings from resolved values\n *\n * @param upscalerName - Upscale model name (from UpscaleModelLoader)\n * @param scale - Pre-resolved scale factor\n * @param hiresSampling - Resolved sampling settings for hires sampler\n * @returns Hires or upscale settings\n */\nfunction buildHiresOrUpscale(\n upscalerName: string | undefined,\n scale: number | undefined,\n hiresSampling: SamplingSettings | undefined,\n): Pick<PartialMetadata, 'hires' | 'upscale'> {\n if (!upscalerName && scale === undefined && !hiresSampling) return {};\n\n if (hiresSampling) {\n return {\n hires: {\n upscaler: upscalerName,\n scale,\n steps: hiresSampling.steps,\n denoise: hiresSampling.denoise,\n },\n };\n }\n\n // Pure upscale without sampler requires upscaler model\n if (!upscalerName) return {};\n\n return {\n upscale: {\n upscaler: upscalerName,\n scale,\n },\n };\n}\n\n// =============================================================================\n// Metadata Merging\n// =============================================================================\n\n/**\n * Shallow merge two objects, skipping undefined values\n *\n * Unlike spread, undefined in override does NOT overwrite defined base values.\n * Returns undefined if result is empty.\n */\nfunction mergeObjects<T extends object>(\n base: T | undefined,\n override: T | undefined,\n): T | undefined {\n if (!base && !override) return undefined;\n const merged: Record<string, unknown> = {};\n for (const obj of [base, override]) {\n if (!obj) continue;\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined) merged[k] = v;\n }\n }\n return Object.keys(merged).length > 0 ? (merged as T) : undefined;\n}\n\n/**\n * Merge two partial metadata objects\n *\n * ComfyUI metadata takes priority over CivitAI fallback.\n * Handles:\n * - Required fields: defaults to empty/zero if both undefined\n * - Optional fields: omitted if undefined (not set to undefined)\n * - Nested objects (upscale, hires): deep merge preserving defined values from both\n *\n * @param base - Base metadata (lower priority, e.g., CivitAI fallback)\n * @param override - Override metadata (higher priority, e.g., ComfyUI nodes)\n * @returns Merged metadata with required fields and optional fields\n */\nfunction mergeMetadata(\n base: PartialMetadata | undefined,\n override: PartialMetadata | undefined,\n): MergedMetadata {\n // Deep merge for nested objects (upscale, hires)\n // mergeObjects excludes undefined values, preserving defined values from both sources\n const upscale = mergeObjects(base?.upscale, override?.upscale);\n const hires = mergeObjects(base?.hires, override?.hires);\n\n return {\n // Required fields with defaults (override takes priority)\n prompt: override?.prompt ?? base?.prompt ?? '',\n negativePrompt: override?.negativePrompt ?? base?.negativePrompt ?? '',\n width: override?.width ?? base?.width ?? 0,\n height: override?.height ?? base?.height ?? 0,\n // Optional fields - only include if defined\n ...trimObject({\n model: override?.model ?? base?.model,\n sampling: override?.sampling ?? base?.sampling,\n hires,\n upscale,\n }),\n };\n}\n","import type { GenerationSoftware } from '../types';\nimport type { EntryRecord } from '../utils/entries';\n\n// =============================================================================\n// Detection Marker Strings\n// =============================================================================\n\n// Unique chunk keywords\nconst M_INVOKEAI = 'invokeai_metadata';\nconst M_TENSORART = 'generation_data';\nconst M_STABILITY_MATRIX = 'smproj';\nconst M_CIVITAI_EXTRA = 'extraMetadata';\n\n// Content patterns\nconst M_SWARMUI = 'sui_image_params';\nconst M_SWARM_VERSION = 'swarm_version';\nconst M_COMFYUI_NODE = 'class_type';\nconst M_NOVELAI_SCHEDULE = 'noise_schedule';\nconst M_NOVELAI_V4 = 'v4_prompt';\nconst M_NOVELAI_UNCOND = 'uncond_scale';\nconst M_CIVITAI_NS = 'civitai:';\nconst M_CIVITAI_RESOURCES = 'Civitai resources:';\nconst M_RUINED_FOOOCUS = 'RuinedFooocus';\nconst M_EASYDIFFUSION = 'use_stable_diffusion_model';\nconst M_HF_MODEL = '\"Model\"';\nconst M_HF_RESOLUTION = '\"resolution\"';\nconst M_FOOOCUS_BASE = '\"base_model\"';\n\n/**\n * Detect generation software from metadata entries\n *\n * Analyzes entry keywords and content to identify the software that\n * generated the image. This centralized detection allows parsers to\n * focus on extracting structured data.\n *\n * @param entries - Metadata entries to analyze\n * @returns Detected software or null if unknown\n */\nexport function detectSoftware(\n entries: EntryRecord,\n): GenerationSoftware | null {\n // Tier 1: Fastest - unique keywords\n const uniqueResult = detectUniqueKeywords(entries);\n if (uniqueResult) return uniqueResult;\n\n // Tier 2: Format-specific structured detection\n const comfyResult = detectComfyUIEntries(entries);\n if (comfyResult) return comfyResult;\n\n // Tier 3: Content analysis\n const text = entries.parameters ?? entries.UserComment ?? entries.Comment;\n if (text) {\n return detectFromTextContent(text);\n }\n\n return null;\n}\n\n/**\n * Detect software from unique chunk keywords\n *\n * Checks for presence of keys that uniquely identify software.\n * Used for both direct entry records (PNG) and parsed JSON (JPEG/WebP conversions).\n */\nfunction detectByUniqueKey(\n record: Record<string, unknown>,\n): GenerationSoftware | null {\n if (M_INVOKEAI in record) return 'invokeai';\n if (M_TENSORART in record) return 'tensorart';\n if (M_STABILITY_MATRIX in record) return 'stability-matrix';\n if (M_CIVITAI_EXTRA in record) return 'civitai';\n return null;\n}\n\n/**\n * Detect software from unique keywords (Tier 1)\n *\n * Fast path: checks for presence of specific keywords that uniquely\n * identify each software. These are the most reliable indicators.\n *\n * Includes:\n * - Unique PNG chunk keywords\n * - Unique content patterns in parameters\n * - JPEG/WebP Comment JSON parsing (conversion cases)\n */\nfunction detectUniqueKeywords(\n entryRecord: EntryRecord,\n): GenerationSoftware | null {\n // ========================================\n // PNG Chunk Keywords\n // ========================================\n\n // NovelAI: Uses \"Software\" chunk with \"NovelAI\" value\n if (entryRecord.Software?.startsWith('NovelAI')) {\n return 'novelai';\n }\n\n // Unique key detection (InvokeAI, TensorArt, Stability Matrix, CivitAI)\n const keyResult = detectByUniqueKey(entryRecord);\n if (keyResult) return keyResult;\n\n // Fooocus: Has \"fooocus_scheme\" chunk (PNG only)\n if ('fooocus_scheme' in entryRecord) {\n return 'fooocus';\n }\n\n // Easy Diffusion: Has \"use_stable_diffusion_model\" keyword (unique to Easy Diffusion)\n if ('use_stable_diffusion_model' in entryRecord) {\n return 'easydiffusion';\n }\n\n // ========================================\n // Parameters Content Patterns\n // ========================================\n\n // SwarmUI: Check parameters for \"sui_image_params\"\n // MUST check here to catch it before ComfyUI detection\n const parameters = entryRecord.parameters;\n if (parameters?.includes(M_SWARMUI)) {\n return 'swarmui';\n }\n\n // ========================================\n // JPEG/WebP UserComment/Comment JSON\n // ========================================\n\n // Check UserComment (Exif) first, then Comment (JPEG COM)\n const comment = entryRecord.UserComment ?? entryRecord.Comment;\n if (comment?.startsWith('{')) {\n return detectFromCommentJson(comment);\n }\n\n return null;\n}\n\n/**\n * Detect software from Comment JSON (conversion cases)\n *\n * Handles PNG→JPEG/WebP conversions where chunks become JSON.\n */\nfunction detectFromCommentJson(comment: string): GenerationSoftware | null {\n try {\n const parsed = JSON.parse(comment) as Record<string, unknown>;\n\n // Unique key detection (InvokeAI, TensorArt, Stability Matrix, CivitAI)\n const keyResult = detectByUniqueKey(parsed);\n if (keyResult) return keyResult;\n\n // ComfyUI: Has both prompt and workflow in JSON\n if ('prompt' in parsed && 'workflow' in parsed) {\n const workflow = parsed.workflow;\n const prompt = parsed.prompt;\n\n const isObject =\n typeof workflow === 'object' || typeof prompt === 'object';\n const isJsonString =\n (typeof workflow === 'string' && workflow.startsWith('{')) ||\n (typeof prompt === 'string' && prompt.startsWith('{'));\n\n if (isObject || isJsonString) {\n return 'comfyui';\n }\n }\n\n // SwarmUI: Same as parameters check, but from Comment JSON\n if (M_SWARMUI in parsed) {\n return 'swarmui';\n }\n\n // SwarmUI alternative format\n if ('prompt' in parsed && 'parameters' in parsed) {\n const params = String(parsed.parameters || '');\n if (params.includes(M_SWARMUI) || params.includes(M_SWARM_VERSION)) {\n return 'swarmui';\n }\n }\n } catch {\n // Invalid JSON\n }\n\n return null;\n}\n\n/**\n * Detect ComfyUI from specific entry combinations (Tier 2)\n *\n * ComfyUI has unique entry combinations that can be detected\n * before analyzing text content.\n */\nfunction detectComfyUIEntries(\n entryRecord: EntryRecord,\n): GenerationSoftware | null {\n // ComfyUI: Both prompt AND workflow chunks exist\n // Checks both lowercase (PNG) and capitalized (EXIF prefix) keys\n if (\n ('prompt' in entryRecord && 'workflow' in entryRecord) ||\n ('Prompt' in entryRecord && 'Workflow' in entryRecord)\n ) {\n return 'comfyui';\n }\n\n // ComfyUI: Workflow chunk only (rare, but valid)\n if ('workflow' in entryRecord || 'Workflow' in entryRecord) {\n return 'comfyui';\n }\n\n // ComfyUI: Prompt chunk with workflow JSON data\n // IMPORTANT: Check SwarmUI and CivitAI FIRST\n const promptText = entryRecord.prompt ?? entryRecord.Prompt;\n if (promptText?.startsWith('{')) {\n // SwarmUI: Must check FIRST\n if (promptText.includes(M_SWARMUI)) {\n return 'swarmui';\n }\n\n // CivitAI: Has extraMetadata key in prompt JSON\n // This detects CivitAI Orchestration format where all data is in single prompt chunk\n if (promptText.includes(`\"${M_CIVITAI_EXTRA}\"`)) {\n return 'civitai';\n }\n\n // ComfyUI: Has class_type in prompt JSON\n if (promptText.includes(M_COMFYUI_NODE)) {\n return 'comfyui';\n }\n }\n\n return null;\n}\n\n/**\n * Detect software from text content (Tier 3)\n *\n * Analyzes text content which can be either JSON format or A1111 text format.\n * This is the slowest but most thorough detection path.\n */\nfunction detectFromTextContent(text: string): GenerationSoftware | null {\n // JSON format detection\n if (text.startsWith('{')) {\n return detectFromJsonFormat(text);\n }\n\n // A1111-style text format detection\n return detectFromA1111Format(text);\n}\n\n/**\n * Detect software from JSON-formatted metadata\n *\n * Priority order:\n * 1. Unique string patterns (most specific)\n * 2. Multi-field combinations (moderately specific)\n * 3. Generic patterns (least specific, fallback)\n */\nfunction detectFromJsonFormat(json: string): GenerationSoftware | null {\n // ========================================\n // Tier 1: Unique String Identifiers\n // ========================================\n\n // SwarmUI: Has \"sui_image_params\" (unique identifier)\n if (json.includes(M_SWARMUI)) {\n return 'swarmui';\n }\n\n // Ruined Fooocus: Has explicit software field\n if (\n json.includes(`\"software\":\"${M_RUINED_FOOOCUS}\"`) ||\n json.includes(`\"software\": \"${M_RUINED_FOOOCUS}\"`)\n ) {\n return 'ruined-fooocus';\n }\n\n // Easy Diffusion: Has unique field name\n if (json.includes(`\"${M_EASYDIFFUSION}\"`)) {\n return 'easydiffusion';\n }\n\n // CivitAI: Has \"civitai:\" namespace prefix OR \"extraMetadata\" key\n if (json.includes(M_CIVITAI_NS) || json.includes(`\"${M_CIVITAI_EXTRA}\"`)) {\n return 'civitai';\n }\n\n // ========================================\n // Tier 2: Multi-Field Combinations\n // ========================================\n\n // NovelAI: Has distinctive v4_prompt or noise_schedule fields\n if (\n json.includes(`\"${M_NOVELAI_V4}\"`) ||\n json.includes(`\"${M_NOVELAI_SCHEDULE}\"`) ||\n json.includes(`\"${M_NOVELAI_UNCOND}\"`) ||\n json.includes('\"Software\":\"NovelAI\"') ||\n json.includes(`\\\\\"${M_NOVELAI_SCHEDULE}\\\\\"`) ||\n json.includes(`\\\\\"${M_NOVELAI_V4}\\\\\"`)\n ) {\n return 'novelai';\n }\n\n // HuggingFace Space: Combination of Model + resolution\n if (json.includes(M_HF_MODEL) && json.includes(M_HF_RESOLUTION)) {\n return 'hf-space';\n }\n\n // Fooocus: Has prompt + base_model combination\n if (json.includes('\"prompt\"') && json.includes(M_FOOOCUS_BASE)) {\n return 'fooocus';\n }\n\n // ========================================\n // Tier 3: Generic Fallback Patterns\n // ========================================\n\n // ComfyUI: Has \"prompt\" or \"nodes\" (very generic, last resort)\n if (json.includes('\"prompt\"') || json.includes('\"nodes\"')) {\n return 'comfyui';\n }\n\n return null;\n}\n\n/**\n * Detect software from A1111-style text format\n *\n * Priority order:\n * 1. SwarmUI indicators (check first as it has unique markers)\n * 2. Version field analysis (sd-webui, forge family, comfyui)\n * 3. App field (SD.Next)\n * 4. Resource markers (Civitai)\n * 5. Default A1111 format (steps + sampler, fallback for embed-created data)\n */\nfunction detectFromA1111Format(text: string): GenerationSoftware | null {\n // ========================================\n // Tier 1: SwarmUI Detection\n // ========================================\n\n // SwarmUI: Has sui_image_params or swarm_version\n if (text.includes(M_SWARMUI) || text.includes(M_SWARM_VERSION)) {\n return 'swarmui';\n }\n\n // ========================================\n // Tier 2: Version Field Analysis\n // ========================================\n\n const versionMatch = text.match(/Version:\\s*([^\\s,]+)/);\n if (versionMatch) {\n const version = versionMatch[1];\n\n // SD WebUI: Version starts with \"v\" + digit (e.g., \"v1.10.1\")\n if (version && /^v\\d/.test(version)) {\n return 'sd-webui';\n }\n\n // Forge Classic: Version is literal \"classic\"\n if (version === 'classic') {\n return 'forge-classic';\n }\n\n // Forge Neo: Version starts with \"neo\"\n if (version === 'neo' || version?.startsWith('neo')) {\n return 'forge-neo';\n }\n\n // Forge family: Version starts with \"f\" followed by a digit\n if (version?.startsWith('f') && /^f\\d/.test(version)) {\n // EasyReforge: f{semver}-v pattern (dash directly after forge semver)\n if (/^f\\d+\\.\\d+(\\.\\d+)?-v/.test(version)) return 'easy-reforge';\n // reForge: f{semver}v{N}-v pattern (version number then dash-v)\n if (/^f\\d+\\.\\d+(\\.\\d+)?v\\d+-v/.test(version)) return 'reforge';\n // Forge: remaining f{digit} prefix (default for current Forge)\n return 'forge';\n }\n\n // Fooocus: Version starts with \"Fooocus\"\n if (version?.startsWith('Fooocus')) {\n return 'fooocus';\n }\n\n // ComfyUI: Version explicitly says \"ComfyUI\"\n if (version === 'ComfyUI') {\n return 'comfyui';\n }\n }\n\n // ========================================\n // Tier 3: Other Unique Text Markers\n // ========================================\n\n // SD.Next: Has App field with SD.Next value\n if (text.includes('App: SD.Next') || text.includes('App:SD.Next')) {\n return 'sd-next';\n }\n\n // Civitai: Has resource list marker\n if (text.includes(M_CIVITAI_RESOURCES)) {\n return 'civitai';\n }\n\n // ========================================\n // Tier 4: Default A1111 Format (fallback)\n // ========================================\n\n // SD-WebUI (fallback): Has typical A1111 parameters but no Version field.\n // Catches embed()-created data and legacy images without version info.\n if (\n text.includes('Steps:') ||\n text.includes('Sampler:') ||\n text.includes('Negative prompt:')\n ) {\n return 'sd-webui';\n }\n\n return null;\n}\n","import type { StandardMetadata } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * Easy Diffusion metadata parser\n *\n * ⚠️ UNVERIFIED: This parser has not been verified with actual Easy Diffusion samples.\n * The implementation is based on source code analysis of Easy Diffusion\n * (github.com/easydiffusion/easydiffusion). Please report any issues if you encounter\n * problems with Easy Diffusion metadata parsing.\n *\n * Easy Diffusion stores metadata using snake_case keys:\n * - PNG: Each field as a separate tEXt chunk (e.g., `prompt`, `seed`, `sampler_name`)\n * - JPEG/WebP: All fields as JSON in EXIF UserComment (0x9286)\n */\n\n/**\n * Extract model name from path\n *\n * Easy Diffusion stores full path like \"path/to/model.safetensors\"\n */\nfunction extractModelName(path: string | undefined): string | undefined {\n if (!path) return undefined;\n // Handle both Windows and POSIX paths\n const parts = path.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1];\n}\n\n/**\n * Parse Easy Diffusion metadata from entries\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseEasyDiffusion(entries: EntryRecord): InternalParseResult {\n // PNG: each field is a separate tEXt chunk\n if ('use_stable_diffusion_model' in entries) {\n return buildMetadata(entries);\n }\n\n // JPEG/WebP: all fields as JSON in UserComment (or parameters for conversion cases)\n const jsonText = entries.UserComment?.startsWith('{')\n ? entries.UserComment\n : entries.parameters?.startsWith('{')\n ? entries.parameters\n : undefined;\n\n if (!jsonText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n const parsed = parseJson<Record<string, unknown>>(jsonText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in Easy Diffusion metadata',\n });\n }\n\n return buildMetadata(parsed.value);\n}\n\n/**\n * Build metadata from a key-value record\n *\n * Handles both PNG entries (string values) and parsed JSON (typed values).\n * All keys use snake_case format matching Easy Diffusion's internal naming.\n */\nfunction buildMetadata(data: Record<string, unknown>): InternalParseResult {\n const str = (key: string): string | undefined => {\n const v = data[key];\n return typeof v === 'string' ? v : undefined;\n };\n const num = (key: string): number | undefined => {\n const v = Number(data[key]);\n return v || undefined;\n };\n\n const prompt = (str('prompt') ?? '').trim();\n const negativePrompt = (str('negative_prompt') ?? '').trim();\n const modelPath = str('use_stable_diffusion_model');\n\n // Upscale settings (post-processing, only present when upscaler is used)\n const upscale = trimObject({\n upscaler: str('use_upscale'),\n scale: num('upscale_amount'),\n });\n\n const metadata: StandardMetadata = {\n software: 'easydiffusion',\n prompt,\n negativePrompt,\n width: num('width') ?? 0,\n height: num('height') ?? 0,\n model: trimObject({\n name: extractModelName(modelPath),\n vae: str('use_vae_model'),\n }),\n sampling: trimObject({\n sampler: str('sampler_name'),\n steps: num('num_inference_steps'),\n cfg: num('guidance_scale'),\n seed: num('seed'),\n clipSkip: num('clip_skip'),\n denoise: num('prompt_strength'),\n }),\n ...(upscale ? { upscale } : {}),\n };\n\n return Result.ok(metadata);\n}\n","import type { StandardMetadata } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * Fooocus JSON metadata structure\n *\n * ⚠️ UNVERIFIED: This parser has not been verified with actual Fooocus samples.\n * The implementation is based on source code analysis of Fooocus\n * (github.com/lllyasviel/Fooocus). Please report any issues if you encounter\n * problems with Fooocus metadata parsing.\n *\n * Fooocus uses the \"fooocus\" metadata scheme which embeds JSON in:\n * - PNG: `parameters` tEXt chunk\n * - JPEG/WebP: EXIF UserComment (0x9286)\n */\ninterface FooocusJsonMetadata {\n prompt?: string;\n negative_prompt?: string;\n resolution?: string;\n base_model?: string;\n base_model_hash?: string;\n vae?: string;\n sampler?: string;\n scheduler?: string;\n seed?: number;\n guidance_scale?: number;\n steps?: number;\n clip_skip?: number;\n}\n\n/**\n * Parse resolution string \"(W, H)\" into width and height\n *\n * Fooocus stores resolution as a Python tuple string, e.g., \"(1024, 1024)\".\n *\n * @param resolution - Resolution string in format \"(width, height)\"\n * @returns Parsed dimensions or { width: 0, height: 0 } on failure\n */\nfunction parseResolution(resolution: string | undefined): {\n width: number;\n height: number;\n} {\n if (!resolution) return { width: 0, height: 0 };\n\n const match = resolution.match(/\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)/);\n if (!match?.[1] || !match[2]) return { width: 0, height: 0 };\n\n return {\n width: Number(match[1]),\n height: Number(match[2]),\n };\n}\n\n/**\n * Parse Fooocus JSON metadata from entries\n *\n * Reads from `parameters` (PNG) or `UserComment` (JPEG/WebP).\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseFooocus(entries: EntryRecord): InternalParseResult {\n const jsonText = entries.parameters ?? entries.UserComment;\n\n if (!jsonText || !jsonText.startsWith('{')) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n const parsed = parseJson<FooocusJsonMetadata>(jsonText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in Fooocus metadata',\n });\n }\n const json = parsed.value;\n\n const { width, height } = parseResolution(json.resolution);\n\n const metadata: StandardMetadata = {\n software: 'fooocus',\n prompt: json.prompt?.trim() ?? '',\n negativePrompt: json.negative_prompt?.trim() ?? '',\n width,\n height,\n model: trimObject({\n name: json.base_model,\n hash: json.base_model_hash,\n vae: json.vae,\n }),\n sampling: trimObject({\n sampler: json.sampler,\n scheduler: json.scheduler,\n steps: json.steps,\n cfg: json.guidance_scale,\n seed: json.seed,\n clipSkip: json.clip_skip,\n }),\n };\n\n return Result.ok(metadata);\n}\n","import type { StandardMetadata } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * HuggingFace Space upscaler settings\n *\n * Present when image was upscaled during generation.\n */\ninterface HfSpaceUpscaler {\n upscale_method?: string;\n upscaler_strength?: number;\n upscale_by?: number;\n upscale_steps?: number;\n new_resolution?: string;\n}\n\n/**\n * HuggingFace Space JSON metadata structure\n */\ninterface HfSpaceJsonMetadata {\n prompt?: string;\n negative_prompt?: string;\n resolution?: string;\n guidance_scale?: number;\n num_inference_steps?: number;\n style_preset?: string;\n seed?: number;\n sampler?: string;\n Model?: string;\n 'Model hash'?: string;\n use_upscaler?: HfSpaceUpscaler | null;\n}\n\n/**\n * Parse HuggingFace Space metadata from entries\n *\n * HuggingFace Spaces using Gradio + Diffusers store metadata as JSON\n * in the parameters chunk.\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseHfSpace(entries: EntryRecord): InternalParseResult {\n // Find parameters entry\n // PNG: stored in 'parameters' chunk\n // JPEG/WebP (after conversion): stored in 'UserComment' (from exifUserComment)\n const parametersText = entries.parameters ?? entries.UserComment;\n if (!parametersText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse JSON\n const parsed = parseJson<HfSpaceJsonMetadata>(parametersText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in parameters entry',\n });\n }\n const json = parsed.value;\n\n // Parse resolution (format: \"832 x 1216\")\n const parseResolution = (res?: string) => {\n const match = res?.match(/(\\d+)\\s*x\\s*(\\d+)/);\n return match?.[1] && match?.[2]\n ? {\n width: Number.parseInt(match[1], 10),\n height: Number.parseInt(match[2], 10),\n }\n : { width: 0, height: 0 };\n };\n const { width, height } = parseResolution(json.resolution);\n\n // Build metadata\n const metadata: Omit<StandardMetadata, 'raw'> = {\n software: 'hf-space',\n prompt: json.prompt ?? '',\n negativePrompt: json.negative_prompt ?? '',\n width,\n height,\n model: trimObject({\n name: json.Model,\n hash: json['Model hash'],\n }),\n sampling: trimObject({\n sampler: json.sampler,\n steps: json.num_inference_steps,\n cfg: json.guidance_scale,\n seed: json.seed,\n }),\n hires: json.use_upscaler\n ? trimObject({\n upscaler: json.use_upscaler.upscale_method,\n denoise: json.use_upscaler.upscaler_strength,\n scale: json.use_upscaler.upscale_by,\n steps: json.use_upscaler.upscale_steps,\n })\n : undefined,\n };\n\n return Result.ok(metadata);\n}\n","import { parseJson } from './json';\n\n/**\n * Entry record type (readonly for immutability)\n *\n * Maps metadata keywords to their text values for O(1) lookup.\n */\nexport type EntryRecord = Readonly<Record<string, string>>;\n\n/**\n * Extract a field from UserComment JSON\n *\n * Many parsers store metadata as JSON in the UserComment entry.\n * This utility extracts a specific field, handling both string and object values.\n *\n * @param entryRecord - Entry record from buildEntryRecord\n * @param key - JSON key to extract\n * @returns Extracted value as string, or undefined if not found\n *\n * @example\n * ```typescript\n * // UserComment: {\"generation_data\": \"{...}\", \"other\": {...}}\n * const data = extractFromCommentJson(entryRecord, 'generation_data');\n * // Returns the string value or JSON.stringify of object value\n * ```\n */\nexport function extractFromCommentJson(\n entryRecord: EntryRecord,\n key: string,\n): string | undefined {\n if (!entryRecord.UserComment?.startsWith('{')) return undefined;\n\n const parsed = parseJson(entryRecord.UserComment);\n if (!parsed.ok || parsed.type !== 'object') return undefined;\n\n const value = parsed.value[key];\n if (typeof value === 'string') return value;\n if (typeof value === 'object' && value !== null) return JSON.stringify(value);\n return undefined;\n}\n","import { Result } from '../types';\nimport { type EntryRecord, extractFromCommentJson } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * InvokeAI metadata JSON structure\n */\ninterface InvokeAIMetadataJson {\n positive_prompt?: string;\n negative_prompt?: string;\n width?: number;\n height?: number;\n seed?: number;\n steps?: number;\n cfg_scale?: number;\n scheduler?: string;\n model?: {\n name?: string;\n hash?: string;\n };\n upscale_model?: {\n name?: string;\n };\n upscale_scale?: number;\n}\n\n/**\n * Extract InvokeAI metadata from entry record\n *\n * Checks direct 'invokeai_metadata' entry first, then tries to extract from Comment JSON\n */\nfunction extractInvokeAIMetadata(entryRecord: EntryRecord): string | undefined {\n return (\n entryRecord.invokeai_metadata ??\n extractFromCommentJson(entryRecord, 'invokeai_metadata')\n );\n}\n\n/**\n * Parse InvokeAI metadata from entries\n *\n * InvokeAI stores metadata with:\n * - invokeai_metadata: JSON containing generation parameters\n * - invokeai_graph: JSON containing the full node graph (not parsed here)\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseInvokeAI(entries: EntryRecord): InternalParseResult {\n // Find invokeai_metadata entry\n // For PNG: direct keyword\n // For JPEG/WebP: inside Comment JSON\n const metadataText = extractInvokeAIMetadata(entries);\n\n if (!metadataText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse metadata JSON\n const parsed = parseJson<InvokeAIMetadataJson>(metadataText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in invokeai_metadata entry',\n });\n }\n const data = parsed.value;\n\n // Extract dimensions (fallback to 0 for IHDR extraction)\n const width = data.width ?? 0;\n const height = data.height ?? 0;\n\n // Build upscale settings if present\n const upscale = buildUpscale(data);\n\n return Result.ok({\n software: 'invokeai',\n prompt: data.positive_prompt ?? '',\n negativePrompt: data.negative_prompt ?? '',\n width,\n height,\n model: trimObject({\n name: data.model?.name,\n hash: data.model?.hash,\n }),\n sampling: trimObject({\n seed: data.seed,\n steps: data.steps,\n cfg: data.cfg_scale,\n sampler: data.scheduler,\n }),\n ...upscale,\n });\n}\n\n/**\n * Build upscale settings from InvokeAI metadata\n * - upscale_model.name: The upscaler model name\n * - upscale_scale: The scale factor (e.g., 2 for 2x upscale)\n */\nfunction buildUpscale(\n data: InvokeAIMetadataJson,\n): { upscale: { upscaler?: string; scale?: number } } | Record<string, never> {\n if (!data.upscale_model?.name && data.upscale_scale === undefined) {\n return {};\n }\n\n const upscale = trimObject({\n upscaler: data.upscale_model?.name,\n scale: data.upscale_scale,\n });\n\n return upscale ? { upscale } : {};\n}\n","import type { CharacterPrompt } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * NovelAI Comment JSON structure\n */\ninterface NovelAIComment {\n prompt: string;\n uc?: string;\n steps?: number;\n height?: number;\n width?: number;\n scale?: number;\n seed?: number;\n noise_schedule?: string;\n sampler?: string;\n /** V4 prompt structure */\n v4_prompt?: V4Prompt;\n /** V4 negative prompt structure */\n v4_negative_prompt?: V4Prompt;\n}\n\n/**\n * NovelAI V4 prompt structure\n */\ninterface V4Prompt {\n caption?: {\n base_caption?: string;\n char_captions?: Array<{\n char_caption?: string;\n centers?: Array<{ x: number; y: number }>;\n }>;\n };\n use_coords?: boolean;\n use_order?: boolean;\n}\n\n/**\n * Parse NovelAI metadata from entries\n *\n * NovelAI stores metadata with:\n * - Software: \"NovelAI\"\n * - Comment: JSON containing generation parameters\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseNovelAI(entries: EntryRecord): InternalParseResult {\n // Parse Comment JSON\n // NovelAI natively supports PNG and WebP, using Exif UserComment for JPEG/WebP.\n // COM segment (→ Comment) is a fallback for non-standard converted images.\n const commentText = entries.UserComment ?? entries.Comment;\n if (!commentText) {\n return Result.error({\n type: 'parseError',\n message: 'Missing Comment/UserComment entry',\n });\n }\n\n const parsed = parseJson<NovelAIComment>(commentText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in Comment entry',\n });\n }\n const comment = parsed.value;\n\n // Extract dimensions (fallback to 0 for IHDR extraction)\n const width = comment.width ?? 0;\n const height = comment.height ?? 0;\n\n // Extract prompt - prefer V4 base_caption if available\n const prompt =\n comment.v4_prompt?.caption?.base_caption ?? comment.prompt ?? '';\n const negativePrompt =\n comment.v4_negative_prompt?.caption?.base_caption ?? comment.uc ?? '';\n\n // Extract V4 character prompts\n const charCaptions = comment.v4_prompt?.caption?.char_captions;\n const characterPrompts =\n charCaptions && charCaptions.length > 0\n ? charCaptions\n .map((cc): CharacterPrompt | null => {\n if (!cc.char_caption) return null;\n return {\n prompt: cc.char_caption,\n center: cc.centers?.[0],\n };\n })\n .filter((cp): cp is CharacterPrompt => cp !== null)\n : undefined;\n\n return Result.ok({\n software: 'novelai',\n prompt,\n negativePrompt,\n width,\n height,\n sampling: trimObject({\n steps: comment.steps,\n cfg: comment.scale,\n seed: comment.seed,\n sampler: comment.sampler,\n scheduler: comment.noise_schedule,\n }),\n characterPrompts,\n useCoords: characterPrompts ? comment.v4_prompt?.use_coords : undefined,\n useOrder: characterPrompts ? comment.v4_prompt?.use_order : undefined,\n });\n}\n","import type { StandardMetadata } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport type { InternalParseResult } from './types';\n\n/**\n * Ruined Fooocus JSON metadata structure\n *\n * Ruined Fooocus stores metadata as JSON in the `parameters` chunk.\n * It has a `software` field set to \"RuinedFooocus\" for identification.\n */\ninterface RuinedFooocusJsonMetadata {\n Prompt?: string;\n Negative?: string;\n steps?: number;\n cfg?: number;\n width?: number;\n height?: number;\n seed?: number;\n sampler_name?: string;\n scheduler?: string;\n base_model_name?: string;\n base_model_hash?: string;\n loras?: Array<{ name: string; weight: number }>;\n clip_skip?: number;\n software?: string;\n}\n\n/**\n * Parse Ruined Fooocus metadata from entries\n *\n * Ruined Fooocus stores metadata as JSON in the `parameters` chunk,\n * with a `software` field set to \"RuinedFooocus\".\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseRuinedFooocus(entries: EntryRecord): InternalParseResult {\n // Find JSON in parameters entry\n // PNG: stored in 'parameters' chunk\n // JPEG/WebP (after conversion): stored in 'UserComment' (from exifUserComment)\n const jsonText = entries.parameters ?? entries.UserComment;\n\n if (!jsonText || !jsonText.startsWith('{')) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse JSON\n const parsed = parseJson<RuinedFooocusJsonMetadata>(jsonText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in Ruined Fooocus metadata',\n });\n }\n const json = parsed.value;\n\n const metadata: Omit<StandardMetadata, 'raw'> = {\n software: 'ruined-fooocus',\n prompt: json.Prompt?.trim() ?? '',\n negativePrompt: json.Negative?.trim() ?? '',\n width: json.width ?? 0,\n height: json.height ?? 0,\n model: {\n name: json.base_model_name,\n hash: json.base_model_hash,\n },\n sampling: {\n sampler: json.sampler_name,\n scheduler: json.scheduler,\n steps: json.steps,\n cfg: json.cfg,\n seed: json.seed,\n clipSkip: json.clip_skip,\n },\n };\n\n return Result.ok(metadata);\n}\n","import { Result } from '../types';\nimport { type EntryRecord, extractFromCommentJson } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { parseComfyUI } from './comfyui';\nimport type { InternalParseResult } from './types';\n\n/**\n * Stability Matrix parameters-json structure\n */\ninterface StabilityMatrixJson {\n PositivePrompt?: string;\n NegativePrompt?: string;\n Width?: number;\n Height?: number;\n Seed?: number;\n Steps?: number;\n CfgScale?: number;\n Sampler?: string;\n ModelName?: string;\n ModelHash?: string;\n}\n\n/**\n * Parse Stability Matrix metadata from entries\n *\n * Stability Matrix stores metadata with:\n * - prompt: ComfyUI-compatible workflow JSON (primary source)\n * - parameters-json: JSON containing generation parameters\n * - Used to override prompts (more complete than workflow)\n * - parameters: A1111-style text (fallback)\n * - smproj: Project data (not parsed here)\n *\n * Strategy:\n * 1. Parse as ComfyUI workflow (workflow, model, sampling, etc.)\n * 2. Override prompts from parameters-json (more complete)\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseStabilityMatrix(\n entries: EntryRecord,\n): InternalParseResult {\n // First, parse as ComfyUI workflow to get base metadata\n const comfyResult = parseComfyUI(entries);\n if (!comfyResult.ok || comfyResult.value.software !== 'comfyui') {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Find parameters-json entry for prompt/model override\n // PNG: stored in 'parameters-json' chunk\n // JPEG/WebP (after conversion): stored in 'Comment' as {\"parameters-json\": ..., ...}\n const jsonText =\n entries['parameters-json'] ??\n extractFromCommentJson(entries, 'parameters-json');\n const parsed = jsonText\n ? parseJson<StabilityMatrixJson>(jsonText)\n : undefined;\n const data =\n parsed?.ok && parsed.type === 'object' ? parsed.value : undefined;\n\n return Result.ok({\n ...comfyResult.value,\n software: 'stability-matrix',\n // Override prompts from parameters-json (more complete than workflow)\n prompt: data?.PositivePrompt ?? comfyResult.value.prompt,\n negativePrompt: data?.NegativePrompt ?? comfyResult.value.negativePrompt,\n // Override model if either name or hash is provided\n model:\n data?.ModelName !== undefined || data?.ModelHash !== undefined\n ? { name: data?.ModelName, hash: data?.ModelHash }\n : comfyResult.value.model,\n });\n}\n","import type { ComfyNodeGraph } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * SwarmUI parameters JSON structure\n */\ninterface SwarmUIParameters {\n sui_image_params?: {\n prompt?: string;\n negativeprompt?: string;\n model?: string;\n seed?: number;\n steps?: number;\n cfgscale?: number;\n width?: number;\n height?: number;\n sampler?: string;\n scheduler?: string;\n // Refiner/Upscale settings\n refinerupscale?: number;\n refinerupscalemethod?: string;\n refinercontrolpercentage?: number;\n };\n}\n\n/**\n * Extract SwarmUI parameters from entry record\n *\n * Checks direct 'parameters' entry first, then tries to extract from Comment JSON.\n * After converter fix, Comment JSON contains direct sui_image_params (native WebP format).\n */\nfunction extractSwarmUIParameters(\n entryRecord: Record<string, string | undefined>,\n): string | undefined {\n // Direct parameters entry (PNG format)\n if (entryRecord.parameters) {\n return entryRecord.parameters;\n }\n\n // Try to extract from UserComment JSON (JPEG/WebP format)\n if (!entryRecord.UserComment) {\n return undefined;\n }\n\n const commentParsed = parseJson(entryRecord.UserComment);\n if (!commentParsed.ok || commentParsed.type !== 'object') {\n return undefined;\n }\n\n // Native WebP format: direct sui_image_params\n if ('sui_image_params' in commentParsed.value) {\n return entryRecord.UserComment; // Return as-is to preserve full structure\n }\n\n return undefined;\n}\n\n/**\n * Parse SwarmUI metadata from entries\n *\n * SwarmUI stores metadata with:\n * - parameters: JSON containing sui_image_params\n * - prompt: ComfyUI-style node graph (fallback)\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseSwarmUI(entries: EntryRecord): InternalParseResult {\n // Find parameters entry\n // For PNG: direct keyword 'parameters'\n // For JPEG/WebP: inside Comment JSON\n const parametersText = extractSwarmUIParameters(entries);\n\n if (!parametersText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse parameters JSON\n const parsed = parseJson<SwarmUIParameters>(parametersText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in parameters entry',\n });\n }\n\n // Verify SwarmUI format (has sui_image_params)\n const params = parsed.value.sui_image_params;\n if (!params) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Extract dimensions (fallback to 0 for IHDR extraction)\n const width = params.width ?? 0;\n const height = params.height ?? 0;\n\n // Parse nodes from prompt chunk (PNG format) or Make field (JPEG/WebP extended format)\n const promptSource = entries.prompt || entries.Make;\n const promptParsed = promptSource\n ? parseJson<ComfyNodeGraph>(promptSource)\n : undefined;\n const nodes =\n promptParsed?.ok && promptParsed.type === 'object'\n ? promptParsed.value\n : undefined;\n\n return Result.ok({\n software: 'swarmui',\n prompt: params.prompt ?? '',\n negativePrompt: params.negativeprompt ?? '',\n width,\n height,\n nodes,\n model: trimObject({ name: params.model }),\n sampling: trimObject({\n seed: params.seed,\n steps: params.steps,\n cfg: params.cfgscale,\n sampler: params.sampler,\n scheduler: params.scheduler,\n }),\n hires: trimObject({\n scale: params.refinerupscale,\n upscaler: params.refinerupscalemethod,\n denoise: params.refinercontrolpercentage,\n }),\n });\n}\n","import type { ComfyNodeGraph } from '../types';\nimport { Result } from '../types';\nimport { type EntryRecord, extractFromCommentJson } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport { extractComfyUIMetadata } from './comfyui';\nimport type { InternalParseResult } from './types';\n\n/**\n * TensorArt generation_data JSON structure\n *\n * Only prompt, negativePrompt, and baseModel are used from generation_data.\n * All other parameters (sampling, dimensions, etc.) are extracted from the\n * ComfyUI node graph, which provides more complete information.\n */\ninterface TensorArtGenerationData {\n prompt?: string;\n negativePrompt?: string;\n baseModel?: {\n modelFileName?: string;\n hash?: string;\n };\n}\n\n/**\n * Parse TensorArt metadata from entries\n *\n * TensorArt stores metadata with:\n * - generation_data: JSON containing generation parameters\n * - prompt: ComfyUI-style node graph (workflow)\n *\n * Strategy: Delegate node graph parsing to the ComfyUI parser for sampling,\n * dimensions, hires, and upscale. Only prompt text and model info are taken\n * from generation_data, since TensorArt uses a custom checkpoint loader\n * (ECHOCheckpointLoaderSimple) that the standard ComfyUI parser cannot detect.\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseTensorArt(entries: EntryRecord): InternalParseResult {\n // Find generation_data entry\n // PNG: stored in 'generation_data' chunk\n // JPEG/WebP (after conversion): stored in 'Comment' as {\"generation_data\": ..., \"prompt\": ...}\n const dataText =\n entries.generation_data ??\n extractFromCommentJson(entries, 'generation_data');\n const promptChunk =\n entries.prompt ?? extractFromCommentJson(entries, 'prompt');\n\n if (!dataText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse JSON (TensorArt appends NUL characters)\n const cleanedText = dataText.replace(/\\0+$/, '');\n const parsed = parseJson<TensorArtGenerationData>(cleanedText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in generation_data entry',\n });\n }\n const data = parsed.value;\n\n // Parse nodes from prompt chunk (required for TensorArt)\n if (!promptChunk) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n const promptParsed = parseJson<ComfyNodeGraph>(promptChunk);\n if (!promptParsed.ok || promptParsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in prompt chunk',\n });\n }\n const nodes = promptParsed.value;\n\n // Delegate node parsing to ComfyUI parser\n const comfy = extractComfyUIMetadata(nodes);\n\n // Model from generation_data (ECHOCheckpointLoaderSimple is not detected by ComfyUI parser)\n const model = trimObject({\n name: data.baseModel?.modelFileName,\n hash: data.baseModel?.hash,\n });\n\n return Result.ok({\n software: 'tensorart',\n nodes,\n // Prompt from generation_data, fallback to nodes\n prompt: data.prompt ?? comfy?.prompt ?? '',\n negativePrompt: data.negativePrompt ?? comfy?.negativePrompt ?? '',\n // Dimensions from nodes, fallback to generation_data\n width: comfy?.width ?? 0,\n height: comfy?.height ?? 0,\n // Model from generation_data, fallback to nodes\n model: model ?? comfy?.model,\n // Sampling, hires, upscale entirely from nodes\n sampling: comfy?.sampling,\n hires: comfy?.hires,\n upscale: comfy?.upscale,\n });\n}\n","import { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseA1111 } from './a1111';\nimport { parseComfyUI } from './comfyui';\nimport { detectSoftware } from './detect';\nimport { parseEasyDiffusion } from './easydiffusion';\nimport { parseFooocus } from './fooocus';\nimport { parseHfSpace } from './hf-space';\nimport { parseInvokeAI } from './invokeai';\nimport { parseNovelAI } from './novelai';\nimport { parseRuinedFooocus } from './ruined-fooocus';\nimport { parseStabilityMatrix } from './stability-matrix';\nimport { parseSwarmUI } from './swarmui';\nimport { parseTensorArt } from './tensorart';\nimport type { InternalParseResult } from './types';\n\n/**\n * Parse metadata entries to unified format\n *\n * Automatically detects the generation software and applies the appropriate parser.\n * This function returns metadata WITHOUT the `raw` field; callers should attach it.\n *\n * @param entries - Format-agnostic metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseMetadata(entries: EntryRecord): InternalParseResult {\n // Detect software from entries\n const software = detectSoftware(entries);\n\n // Route to appropriate parser based on detected software\n switch (software) {\n case 'novelai':\n return parseNovelAI(entries);\n\n case 'sd-webui':\n case 'forge':\n case 'forge-classic':\n case 'forge-neo':\n case 'reforge':\n case 'easy-reforge':\n case 'sd-next':\n return parseA1111(entries, software);\n\n case 'hf-space':\n return parseHfSpace(entries);\n\n case 'civitai': {\n // Civitai uses ComfyUI JSON format with custom structure\n const comfyResult = parseComfyUI(entries);\n if (comfyResult.ok) {\n // Override software to preserve CivitAI detection\n comfyResult.value.software = 'civitai';\n return comfyResult;\n }\n // Fallback to A1111 format\n return parseA1111(entries, 'civitai');\n }\n\n case 'comfyui': {\n // ComfyUI can use either JSON or A1111 text format (e.g., comfy-image-saver)\n const comfyResult = parseComfyUI(entries);\n if (comfyResult.ok) return comfyResult;\n // Fallback to A1111 text format (treat as sd-webui since Version is absent)\n return parseA1111(entries, 'sd-webui');\n }\n\n case 'invokeai':\n return parseInvokeAI(entries);\n\n case 'swarmui':\n return parseSwarmUI(entries);\n\n case 'tensorart':\n return parseTensorArt(entries);\n\n case 'stability-matrix':\n return parseStabilityMatrix(entries);\n\n case 'easydiffusion':\n return parseEasyDiffusion(entries);\n\n case 'fooocus': {\n // Fooocus supports two user-selectable metadata schemes:\n // - 'fooocus': JSON format (default)\n // - 'a1111': A1111-compatible text format\n const scheme = entries.fooocus_scheme;\n const content = entries.parameters ?? entries.UserComment;\n if (scheme === 'a1111' || (content && !content.startsWith('{'))) {\n return parseA1111(entries, 'fooocus');\n }\n return parseFooocus(entries);\n }\n\n case 'ruined-fooocus':\n return parseRuinedFooocus(entries);\n\n default:\n return Result.error({ type: 'unsupportedFormat' });\n }\n}\n","/**\n * Image dimension extraction from binary headers\n *\n * Reads width and height from image format headers without parsing metadata.\n * Supports PNG (IHDR), JPEG (SOF markers), and WebP (VP8X/VP8/VP8L).\n */\n\nimport type { ImageFormat } from '../utils/binary';\nimport {\n readChunkType,\n readUint24LE,\n readUint32BE,\n readUint32LE,\n} from '../utils/binary';\n\nexport interface ImageDimensions {\n width: number;\n height: number;\n}\n\n/**\n * Read image dimensions from binary data based on format\n */\nexport function readImageDimensions(\n data: Uint8Array,\n format: ImageFormat,\n): ImageDimensions | null {\n if (format === 'png') return readPngDimensions(data);\n if (format === 'jpeg') return readJpegDimensions(data);\n return readWebpDimensions(data);\n}\n\n/**\n * Read width and height from PNG IHDR chunk\n */\nfunction readPngDimensions(data: Uint8Array): ImageDimensions | null {\n const PNG_SIGNATURE_LENGTH = 8;\n if (data.length < 24) return null;\n // IHDR data starts at offset 16 (8 sig + 4 len + 4 type)\n // We assume valid PNG if detectFormat passed, and IHDR is always first.\n return {\n width: readUint32BE(data, PNG_SIGNATURE_LENGTH + 8),\n height: readUint32BE(data, PNG_SIGNATURE_LENGTH + 12),\n };\n}\n\n/**\n * Read width and height from JPEG SOF markers\n */\nfunction readJpegDimensions(data: Uint8Array): ImageDimensions | null {\n let offset = 2;\n while (offset < data.length - 4) {\n if (data[offset] !== 0xff) {\n offset++;\n continue;\n }\n\n const marker = data[offset + 1] ?? 0;\n if (marker === 0xff) {\n offset++;\n continue; // Padding\n }\n\n // Read length (16-bit BE)\n const length = ((data[offset + 2] ?? 0) << 8) | (data[offset + 3] ?? 0);\n\n // SOF0 (C0) ... SOF15 (CF), except C4 (DHT), C8 (JPG), CC (DAC)\n if (\n marker >= 0xc0 &&\n marker <= 0xcf &&\n marker !== 0xc4 &&\n marker !== 0xc8 &&\n marker !== 0xcc\n ) {\n // Structure: Precision(1), Height(2), Width(2)\n // Offset: Marker(2) + Length(2) + Precision(1) = 5\n const height = ((data[offset + 5] ?? 0) << 8) | (data[offset + 6] ?? 0);\n const width = ((data[offset + 7] ?? 0) << 8) | (data[offset + 8] ?? 0);\n return { width, height };\n }\n\n offset += 2 + length;\n if (marker === 0xda) break; // SOS\n }\n return null;\n}\n\n/**\n * Read width and height from WebP chunks (VP8X, VP8, VP8L)\n */\nfunction readWebpDimensions(data: Uint8Array): ImageDimensions | null {\n // RIFF(4) + Size(4) + WEBP(4) = 12 bytes\n let offset = 12;\n\n while (offset < data.length) {\n if (offset + 8 > data.length) break;\n\n const chunkType = readChunkType(data, offset);\n const chunkSize = readUint32LE(data, offset + 4);\n const paddedSize = chunkSize + (chunkSize % 2);\n\n if (chunkType === 'VP8X') {\n // VP8X: Width (3 bytes @ offset 12) + Height (3 bytes @ offset 15)\n // Both are 1-based (stored value is width-1)\n const wMinus1 = readUint24LE(data, offset + 12);\n const hMinus1 = readUint24LE(data, offset + 15);\n return { width: wMinus1 + 1, height: hMinus1 + 1 };\n }\n\n if (chunkType === 'VP8 ') {\n // VP8 (lossy): Check keyframe\n const start = offset + 8;\n const tag =\n (data[start] ?? 0) |\n ((data[start + 1] ?? 0) << 8) |\n ((data[start + 2] ?? 0) << 16);\n const keyFrame = !(tag & 1);\n\n if (keyFrame) {\n // Validation code: 0x9d 0x01 0x2a bytes @ start+3\n if (\n data[start + 3] === 0x9d &&\n data[start + 4] === 0x01 &&\n data[start + 5] === 0x2a\n ) {\n const wRaw = (data[start + 6] ?? 0) | ((data[start + 7] ?? 0) << 8);\n const hRaw = (data[start + 8] ?? 0) | ((data[start + 9] ?? 0) << 8);\n return { width: wRaw & 0x3fff, height: hRaw & 0x3fff };\n }\n }\n }\n\n if (chunkType === 'VP8L') {\n // VP8L (lossless)\n if (data[offset + 8] === 0x2f) {\n const bits = readUint32LE(data, offset + 9);\n const width = (bits & 0x3fff) + 1;\n const height = ((bits >> 14) & 0x3fff) + 1;\n return { width, height };\n }\n }\n\n offset += 8 + paddedSize;\n }\n return null;\n}\n","/**\n * Exif reading utilities\n *\n * Functions for parsing Exif/TIFF structures and extracting metadata segments.\n */\n\nimport type { MetadataSegment } from '../types';\nimport { readUint16, readUint32 } from '../utils/binary';\nimport {\n EXIF_IFD_POINTER_TAG,\n IMAGE_DESCRIPTION_TAG,\n MAKE_TAG,\n USER_COMMENT_TAG,\n} from '../utils/exif-constants';\n\n/**\n * Parse Exif TIFF structure and extract all metadata segments\n *\n * Extracts metadata from:\n * - ImageDescription (0x010E) - Used by ComfyUI Save Image Extended (with \"Workflow:\" prefix)\n * - Make (0x010F) - Used by ComfyUI Save Image Extended (with \"Prompt:\" prefix)\n * - UserComment (0x9286) - Used by most tools\n *\n * @param exifData - TIFF data (starting with II/MM byte order marker)\n * @returns Array of metadata segments found\n */\nexport function parseExifMetadataSegments(\n exifData: Uint8Array,\n): MetadataSegment[] {\n if (exifData.length < 8) return [];\n\n // Check TIFF byte order\n const isLittleEndian = exifData[0] === 0x49 && exifData[1] === 0x49; // \"II\"\n const isBigEndian = exifData[0] === 0x4d && exifData[1] === 0x4d; // \"MM\"\n\n if (!isLittleEndian && !isBigEndian) return [];\n\n // Verify TIFF magic number (42)\n const magic = readUint16(exifData, 2, isLittleEndian);\n if (magic !== 42) return [];\n\n // Get IFD0 offset\n const ifd0Offset = readUint32(exifData, 4, isLittleEndian);\n\n // Extract all tags from IFD0\n const ifd0Segments = extractTagsFromIfd(exifData, ifd0Offset, isLittleEndian);\n\n // Find Exif IFD and extract UserComment from there\n const exifIfdOffset = findExifIfdOffset(exifData, ifd0Offset, isLittleEndian);\n const exifIfdSegments =\n exifIfdOffset !== null\n ? extractTagsFromIfd(exifData, exifIfdOffset, isLittleEndian)\n : [];\n\n return [...ifd0Segments, ...exifIfdSegments];\n}\n\n/**\n * Extract metadata tags from an IFD\n */\nfunction extractTagsFromIfd(\n data: Uint8Array,\n ifdOffset: number,\n isLittleEndian: boolean,\n): MetadataSegment[] {\n const segments: MetadataSegment[] = [];\n\n if (ifdOffset + 2 > data.length) return segments;\n\n const entryCount = readUint16(data, ifdOffset, isLittleEndian);\n let offset = ifdOffset + 2;\n\n for (let i = 0; i < entryCount; i++) {\n if (offset + 12 > data.length) return segments;\n\n const tag = readUint16(data, offset, isLittleEndian);\n const type = readUint16(data, offset + 2, isLittleEndian);\n const count = readUint32(data, offset + 4, isLittleEndian);\n\n // Calculate data size based on type\n const typeSize = getTypeSize(type);\n const dataSize = count * typeSize;\n\n let valueOffset: number;\n if (dataSize <= 4) {\n valueOffset = offset + 8;\n } else {\n valueOffset = readUint32(data, offset + 8, isLittleEndian);\n }\n\n if (valueOffset + dataSize > data.length) {\n offset += 12;\n continue;\n }\n\n const tagData = data.slice(valueOffset, valueOffset + dataSize);\n\n // Process known tags\n if (tag === IMAGE_DESCRIPTION_TAG) {\n const text = decodeAsciiString(tagData);\n if (text) {\n const prefix = extractPrefix(text);\n segments.push({\n source: { type: 'exifImageDescription', prefix: prefix ?? undefined },\n data: prefix ? text.slice(prefix.length + 2) : text,\n });\n }\n } else if (tag === MAKE_TAG) {\n const text = decodeAsciiString(tagData);\n if (text) {\n const prefix = extractPrefix(text);\n segments.push({\n source: { type: 'exifMake', prefix: prefix ?? undefined },\n data: prefix ? text.slice(prefix.length + 2) : text,\n });\n }\n } else if (tag === USER_COMMENT_TAG) {\n const text = decodeUserComment(tagData);\n if (text) {\n segments.push({\n source: { type: 'exifUserComment' },\n data: text,\n });\n }\n }\n\n offset += 12;\n }\n\n return segments;\n}\n\n/**\n * Extract prefix from text like \"Workflow: {...}\" -> \"Workflow\"\n */\nfunction extractPrefix(text: string): string | null {\n const match = text.match(/^([A-Za-z]+):\\s/);\n return match?.[1] ?? null;\n}\n\n/**\n * Get size in bytes for TIFF data type\n */\nfunction getTypeSize(type: number): number {\n switch (type) {\n case 1:\n return 1; // BYTE\n case 2:\n return 1; // ASCII\n case 3:\n return 2; // SHORT\n case 4:\n return 4; // LONG\n case 5:\n return 8; // RATIONAL\n case 7:\n return 1; // UNDEFINED\n default:\n return 1;\n }\n}\n\n/**\n * Decode ASCII/UTF-8 string from tag data\n */\nfunction decodeAsciiString(data: Uint8Array): string | null {\n try {\n const decoder = new TextDecoder('utf-8', { fatal: false });\n let text = decoder.decode(data);\n // Remove null terminator if present\n if (text.endsWith('\\0')) {\n text = text.slice(0, -1);\n }\n return text.trim() || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Find Exif IFD offset from IFD0\n */\nfunction findExifIfdOffset(\n data: Uint8Array,\n ifdOffset: number,\n isLittleEndian: boolean,\n): number | null {\n if (ifdOffset + 2 > data.length) return null;\n\n const entryCount = readUint16(data, ifdOffset, isLittleEndian);\n let offset = ifdOffset + 2;\n\n for (let i = 0; i < entryCount; i++) {\n if (offset + 12 > data.length) return null;\n\n const tag = readUint16(data, offset, isLittleEndian);\n\n if (tag === EXIF_IFD_POINTER_TAG) {\n // Exif IFD pointer found\n return readUint32(data, offset + 8, isLittleEndian);\n }\n\n offset += 12;\n }\n\n return null;\n}\n\n/**\n * Decode UserComment based on encoding prefix\n *\n * @param data - UserComment data including encoding prefix\n * @returns Decoded string\n */\nexport function decodeUserComment(data: Uint8Array): string | null {\n if (data.length < 8) return null;\n\n // Check for UNICODE prefix\n if (\n data[0] === 0x55 && // U\n data[1] === 0x4e && // N\n data[2] === 0x49 && // I\n data[3] === 0x43 && // C\n data[4] === 0x4f && // O\n data[5] === 0x44 && // D\n data[6] === 0x45 && // E\n data[7] === 0x00 // NULL\n ) {\n // UTF-16 encoded - detect byte order by looking at first character\n const textData = data.slice(8);\n if (textData.length >= 2) {\n const isLikelyLE = textData[0] !== 0x00 && textData[1] === 0x00;\n return isLikelyLE ? decodeUtf16LE(textData) : decodeUtf16BE(textData);\n }\n return decodeUtf16BE(textData);\n }\n\n // Check for ASCII prefix\n if (\n data[0] === 0x41 && // A\n data[1] === 0x53 && // S\n data[2] === 0x43 && // C\n data[3] === 0x49 && // I\n data[4] === 0x49 && // I\n data[5] === 0x00 && // NULL\n data[6] === 0x00 && // NULL\n data[7] === 0x00 // NULL\n ) {\n // ASCII encoded\n return decodeAscii(data.slice(8));\n }\n\n // Try UTF-8 (for ComfyUI JSON format without prefix)\n try {\n const decoder = new TextDecoder('utf-8', { fatal: true });\n let result = decoder.decode(data);\n // Strip null terminator if present\n if (result.endsWith('\\0')) {\n result = result.slice(0, -1);\n }\n return result;\n } catch {\n return null;\n }\n}\n\n/**\n * Decode UTF-16BE string\n */\nfunction decodeUtf16BE(data: Uint8Array): string {\n const chars: string[] = [];\n\n for (let i = 0; i < data.length - 1; i += 2) {\n const code = ((data[i] ?? 0) << 8) | (data[i + 1] ?? 0);\n if (code === 0) break;\n chars.push(String.fromCharCode(code));\n }\n\n return chars.join('');\n}\n\n/**\n * Decode UTF-16LE string\n */\nfunction decodeUtf16LE(data: Uint8Array): string {\n const chars: string[] = [];\n\n for (let i = 0; i < data.length - 1; i += 2) {\n const code = (data[i] ?? 0) | ((data[i + 1] ?? 0) << 8);\n if (code === 0) break;\n chars.push(String.fromCharCode(code));\n }\n\n return chars.join('');\n}\n\n/**\n * Decode ASCII string\n */\nfunction decodeAscii(data: Uint8Array): string {\n const chars: string[] = [];\n\n for (let i = 0; i < data.length; i++) {\n if (data[i] === 0) break;\n chars.push(String.fromCharCode(data[i] ?? 0));\n }\n\n return chars.join('');\n}\n","import type { MetadataSegment } from '../types';\nimport { Result } from '../types';\nimport { arraysEqual, isJpeg } from '../utils/binary';\nimport { parseExifMetadataSegments } from './exif';\n\n// Internal types (co-located with reader)\ntype JpegReadError = { type: 'invalidSignature' };\n\ntype JpegMetadataResult = Result<MetadataSegment[], JpegReadError>;\n\n/** APP1 marker */\nconst APP1_MARKER = 0xe1;\n\n/** COM (Comment) marker */\nconst COM_MARKER = 0xfe;\n\n/** Exif header: \"Exif\\0\\0\" */\nconst EXIF_HEADER = new Uint8Array([0x45, 0x78, 0x69, 0x66, 0x00, 0x00]);\n\n/**\n * Read JPEG metadata from binary data\n *\n * Collects metadata from multiple sources:\n * - Exif tags (APP1 segment): UserComment, ImageDescription, Make\n * - COM segment - Used by NovelAI\n *\n * @param data - JPEG file data as Uint8Array\n * @returns Result containing all metadata segments or error\n */\nexport function readJpegMetadata(data: Uint8Array): JpegMetadataResult {\n if (!isJpeg(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n const segments: MetadataSegment[] = [];\n\n // Extract all Exif metadata (UserComment, ImageDescription, Make)\n const app1 = findApp1Segment(data);\n if (app1) {\n const exifData = data.slice(app1.offset, app1.offset + app1.length);\n const exifSegments = parseExifMetadataSegments(exifData);\n segments.push(...exifSegments);\n }\n\n // Try COM segment (NovelAI uses this)\n const comSegment = findComSegment(data);\n if (comSegment) {\n const comData = data.slice(\n comSegment.offset,\n comSegment.offset + comSegment.length,\n );\n const comText = decodeComSegment(comData);\n\n if (comText !== null) {\n segments.push({\n source: { type: 'jpegCom' },\n data: comText,\n });\n }\n }\n\n return Result.ok(segments);\n}\n\n/**\n * Find APP1 segment containing Exif data\n *\n * @param data - JPEG file data\n * @returns Offset and length of APP1 segment data, or null if not found\n */\nfunction findApp1Segment(\n data: Uint8Array,\n): { offset: number; length: number } | null {\n let offset = 2; // Skip SOI marker\n\n while (offset < data.length - 4) {\n // Check for marker\n if (data[offset] !== 0xff) {\n offset++;\n continue;\n }\n\n const marker = data[offset + 1];\n\n // Skip padding bytes\n if (marker === 0xff) {\n offset++;\n continue;\n }\n\n // Get segment length (big-endian, includes length bytes)\n const length = ((data[offset + 2] ?? 0) << 8) | (data[offset + 3] ?? 0);\n\n // Check for APP1 marker\n if (marker === APP1_MARKER) {\n // Verify Exif header\n const headerStart = offset + 4;\n if (headerStart + 6 <= data.length) {\n const header = data.slice(headerStart, headerStart + 6);\n if (arraysEqual(header, EXIF_HEADER)) {\n // Return offset to TIFF data (after Exif header)\n return {\n offset: headerStart + 6,\n length: length - 8, // Subtract length bytes and Exif header\n };\n }\n }\n }\n\n // Move to next segment\n offset += 2 + length;\n\n // Stop at SOS (Start of Scan) or EOI\n if (marker === 0xda || marker === 0xd9) {\n break;\n }\n }\n\n return null;\n}\n\n/**\n * Find COM (Comment) segment\n *\n * COM segments are used by NovelAI to store metadata as UTF-8 JSON.\n *\n * @param data - JPEG file data\n * @returns Offset and length of COM segment data, or null if not found\n */\nfunction findComSegment(\n data: Uint8Array,\n): { offset: number; length: number } | null {\n let offset = 2; // Skip SOI marker\n\n while (offset < data.length - 4) {\n // Check for marker\n if (data[offset] !== 0xff) {\n offset++;\n continue;\n }\n\n const marker = data[offset + 1];\n\n // Skip padding bytes\n if (marker === 0xff) {\n offset++;\n continue;\n }\n\n // Get segment length (big-endian, includes length bytes)\n const length = ((data[offset + 2] ?? 0) << 8) | (data[offset + 3] ?? 0);\n\n // Check for COM marker\n if (marker === COM_MARKER) {\n // Return offset to comment data (after marker and length)\n return {\n offset: offset + 4,\n length: length - 2, // Subtract length bytes only\n };\n }\n\n // Move to next segment\n offset += 2 + length;\n\n // Stop at SOS (Start of Scan) or EOI\n if (marker === 0xda || marker === 0xd9) {\n break;\n }\n }\n\n return null;\n}\n\n/**\n * Decode COM segment data as UTF-8 string\n *\n * @param data - COM segment data\n * @returns Decoded string or null if invalid\n */\nfunction decodeComSegment(data: Uint8Array): string | null {\n try {\n const decoder = new TextDecoder('utf-8', { fatal: true });\n return decoder.decode(data);\n } catch {\n return null;\n }\n}\n","import type { ITXtChunk, PngTextChunk, TExtChunk } from '../types';\nimport { Result } from '../types';\nimport { isPng, readChunkType, readUint32BE } from '../utils/binary';\n\n// Internal types (co-located with reader)\ntype PngReadError =\n | { type: 'invalidSignature' }\n | { type: 'corruptedChunk'; message: string };\n\ntype PngMetadataResult = Result<PngTextChunk[], PngReadError>;\n\n/**\n * Read PNG metadata from binary data\n * @param data - PNG file data as Uint8Array\n * @returns Result containing metadata or error\n */\nexport function readPngMetadata(data: Uint8Array): PngMetadataResult {\n // Validate PNG signature\n if (!isPng(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n // Extract text chunks\n const chunksResult = extractTextChunks(data);\n if (!chunksResult.ok) {\n return chunksResult;\n }\n\n return Result.ok(chunksResult.value);\n}\n\n/**\n * Extract tEXt and iTXt chunks from PNG data\n */\n// 8 bytes for PNG signature\nconst PNG_SIGNATURE_LENGTH = 8;\n\n/**\n * Extract tEXt and iTXt chunks from PNG data\n */\nfunction extractTextChunks(\n data: Uint8Array,\n): Result<PngTextChunk[], PngReadError> {\n const chunks: PngTextChunk[] = [];\n let offset = PNG_SIGNATURE_LENGTH;\n\n while (offset < data.length) {\n // Read chunk length (4 bytes, big-endian)\n if (offset + 4 > data.length) {\n return Result.error({\n type: 'corruptedChunk',\n message: 'Unexpected end of file while reading chunk length',\n });\n }\n const length = readUint32BE(data, offset);\n offset += 4;\n\n // Read chunk type (4 bytes)\n if (offset + 4 > data.length) {\n return Result.error({\n type: 'corruptedChunk',\n message: 'Unexpected end of file while reading chunk type',\n });\n }\n const chunkType = readChunkType(data, offset);\n offset += 4;\n\n // Read chunk data\n if (offset + length > data.length) {\n return Result.error({\n type: 'corruptedChunk',\n message: `Unexpected end of file while reading chunk data (${chunkType})`,\n });\n }\n const chunkData = data.slice(offset, offset + length);\n offset += length;\n\n // Skip CRC (4 bytes)\n offset += 4;\n\n // Parse text chunks\n if (chunkType === 'tEXt') {\n const parsed = parseTExtChunk(chunkData);\n if (parsed) {\n chunks.push(parsed);\n }\n } else if (chunkType === 'iTXt') {\n const parsed = parseITXtChunk(chunkData);\n if (parsed) {\n chunks.push(parsed);\n }\n }\n\n // Stop at IEND\n if (chunkType === 'IEND') {\n break;\n }\n }\n\n return Result.ok(chunks);\n}\n\n/**\n * Parse tEXt chunk data\n *\n * Per PNG specification, tEXt chunks use Latin-1 (ISO-8859-1) encoding.\n * However, some tools (notably TensorArt) incorrectly write UTF-8 bytes\n * directly into tEXt chunks. To handle these non-compliant tools, we\n * attempt UTF-8 decoding first and fall back to Latin-1 if that fails.\n */\nfunction parseTExtChunk(data: Uint8Array): TExtChunk | null {\n // Find null separator\n const nullIndex = data.indexOf(0);\n if (nullIndex === -1) {\n return null;\n }\n\n // Keyword is Latin-1 encoded (per spec, keywords are ASCII-safe)\n const keyword = latin1Decode(data.slice(0, nullIndex));\n\n // Text: Try UTF-8 first (for non-compliant tools), fallback to Latin-1\n const textData = data.slice(nullIndex + 1);\n const text = tryUtf8Decode(textData) ?? latin1Decode(textData);\n\n return { type: 'tEXt', keyword, text };\n}\n\n/**\n * Try to decode data as UTF-8, return null if invalid\n */\nfunction tryUtf8Decode(data: Uint8Array): string | null {\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(data);\n } catch {\n return null;\n }\n}\n\n/**\n * Parse iTXt chunk data\n */\nfunction parseITXtChunk(data: Uint8Array): ITXtChunk | null {\n let offset = 0;\n\n // Read keyword (null-terminated)\n const keywordEnd = findNull(data, offset);\n if (keywordEnd === -1) return null;\n const keyword = utf8Decode(data.slice(offset, keywordEnd));\n offset = keywordEnd + 1;\n\n // Read compression flag (1 byte)\n if (offset >= data.length) return null;\n const compressionFlag = data[offset] ?? 0;\n offset += 1;\n\n // Read compression method (1 byte)\n if (offset >= data.length) return null;\n const compressionMethod = data[offset] ?? 0;\n offset += 1;\n\n // Read language tag (null-terminated)\n const langEnd = findNull(data, offset);\n if (langEnd === -1) return null;\n const languageTag = utf8Decode(data.slice(offset, langEnd));\n offset = langEnd + 1;\n\n // Read translated keyword (null-terminated)\n const transEnd = findNull(data, offset);\n if (transEnd === -1) return null;\n const translatedKeyword = utf8Decode(data.slice(offset, transEnd));\n offset = transEnd + 1;\n\n // Read text (rest of data)\n let text: string;\n if (compressionFlag === 1) {\n // Compressed with zlib\n const decompressed = decompressZlib(data.slice(offset));\n if (!decompressed) return null;\n text = utf8Decode(decompressed);\n } else {\n text = utf8Decode(data.slice(offset));\n }\n\n return {\n type: 'iTXt',\n keyword,\n compressionFlag,\n compressionMethod,\n languageTag,\n translatedKeyword,\n text,\n };\n}\n\n/**\n * Find null byte in data starting from offset\n */\nfunction findNull(data: Uint8Array, offset: number): number {\n for (let i = offset; i < data.length; i++) {\n if (data[i] === 0) {\n return i;\n }\n }\n return -1;\n}\n\n/**\n * Decode Latin-1 (ISO-8859-1) bytes to string\n */\nfunction latin1Decode(data: Uint8Array): string {\n let result = '';\n for (let i = 0; i < data.length; i++) {\n result += String.fromCharCode(data[i] ?? 0);\n }\n return result;\n}\n\n/**\n * Decode UTF-8 bytes to string\n */\nfunction utf8Decode(data: Uint8Array): string {\n return new TextDecoder('utf-8').decode(data);\n}\n\n/**\n * Decompress zlib-compressed data\n *\n * Currently unimplemented: All surveyed sample images use uncompressed iTXt.\n * When a sample with compressed iTXt is found, implement using pako library.\n *\n * @see https://www.npmjs.com/package/pako\n */\nfunction decompressZlib(_data: Uint8Array): Uint8Array | null {\n // Not yet implemented - no compressed iTXt samples encountered\n return null;\n}\n","import type { MetadataSegment } from '../types';\nimport { Result } from '../types';\nimport { arraysEqual, isWebp, readUint32LE } from '../utils/binary';\nimport { parseExifMetadataSegments } from './exif';\n\n// Internal types (co-located with reader)\ntype WebpReadError = { type: 'invalidSignature' };\n\ntype WebpMetadataResult = Result<MetadataSegment[], WebpReadError>;\n\n/** EXIF chunk type */\nconst EXIF_CHUNK_TYPE = new Uint8Array([0x45, 0x58, 0x49, 0x46]);\n\n/**\n * Read WebP metadata from binary data\n *\n * Extracts metadata from EXIF chunk in WebP files.\n * The EXIF chunk contains TIFF-formatted data identical to JPEG Exif.\n *\n * @param data - WebP file data as Uint8Array\n * @returns Result containing all metadata segments or error\n */\nexport function readWebpMetadata(data: Uint8Array): WebpMetadataResult {\n if (!isWebp(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n const exifChunk = findExifChunk(data);\n if (!exifChunk) {\n return Result.ok([]);\n }\n\n const exifData = data.slice(\n exifChunk.offset,\n exifChunk.offset + exifChunk.length,\n );\n\n // Parse all EXIF metadata segments (UserComment, ImageDescription, Make)\n const segments = parseExifMetadataSegments(exifData);\n\n return Result.ok(segments);\n}\n\n/**\n * Find EXIF chunk in WebP file\n *\n * WebP uses RIFF container format with named chunks.\n * EXIF chunk contains TIFF data starting with \"II\" or \"MM\" byte order marker.\n *\n * @param data - WebP file data\n * @returns Offset and length of EXIF chunk data, or null if not found\n */\nexport function findExifChunk(\n data: Uint8Array,\n): { offset: number; length: number } | null {\n // Start after RIFF header (12 bytes: \"RIFF\" + size + \"WEBP\")\n let offset = 12;\n\n while (offset < data.length - 8) {\n // Read chunk type (4 bytes)\n const chunkType = data.slice(offset, offset + 4);\n\n // Read chunk size (4 bytes, little-endian)\n const chunkSize = readUint32LE(data, offset + 4);\n\n // Check for EXIF chunk\n if (arraysEqual(chunkType, EXIF_CHUNK_TYPE)) {\n // EXIF chunk data starts after type and size\n return {\n offset: offset + 8,\n length: chunkSize,\n };\n }\n\n // Move to next chunk (chunk size + type + size fields)\n // RIFF chunks are padded to even byte boundaries\n const paddedSize = chunkSize + (chunkSize % 2);\n offset += 8 + paddedSize;\n }\n\n return null;\n}\n","import type {\n MetadataSegment,\n MetadataSegmentSource,\n PngTextChunk,\n} from '../types';\nimport type { EntryRecord } from './entries';\nimport { parseJson } from './json';\n\n/**\n * Convert PNG text chunks to entry record\n *\n * @param chunks - PNG tEXt/iTXt chunks\n * @returns Entry record for keyword lookup\n */\nexport function pngChunksToRecord(chunks: PngTextChunk[]): EntryRecord {\n return Object.fromEntries(chunks.map((c) => [c.keyword, c.text]));\n}\n\n/**\n * Convert JPEG/WebP metadata segments to entry record\n *\n * Maps segment sources to conventional keywords:\n * - jpegCom → 'Comment'\n * - exifUserComment → 'UserComment' (or expand if NovelAI WebP format)\n * - exifImageDescription → prefix or 'ImageDescription'\n * - exifMake → prefix or 'Make'\n *\n * Special handling for NovelAI WebP format where metadata is stored as:\n * {\"Comment\": \"{...inner JSON...}\", \"Software\": \"NovelAI\", ...}\n *\n * @param segments - Metadata segments from JPEG/WebP reader\n * @returns Entry record for keyword lookup\n */\nexport function segmentsToRecord(segments: MetadataSegment[]): EntryRecord {\n const record: Record<string, string> = {};\n\n for (const segment of segments) {\n const keyword = sourceToKeyword(segment.source);\n const text = segment.data;\n\n // Try to detect and expand NovelAI WebP format\n // Format: {\"Comment\": \"{...}\", \"Software\": \"NovelAI\", ...}\n if (segment.source.type === 'exifUserComment' && text.startsWith('{')) {\n const expanded = tryExpandNovelAIWebpFormat(text);\n if (expanded) {\n Object.assign(record, expanded);\n continue;\n }\n }\n\n record[keyword] = text;\n }\n\n return record;\n}\n\n/**\n * Try to expand NovelAI WebP format metadata\n *\n * NovelAI WebP stores metadata as outer JSON with:\n * - Software: \"NovelAI\"\n * - Comment: inner JSON string with actual parameters\n *\n * @param text - JSON text to try to expand\n * @returns Entry record if NovelAI format, null otherwise\n */\nfunction tryExpandNovelAIWebpFormat(text: string): EntryRecord | null {\n const outerParsed = parseJson(text);\n if (!outerParsed.ok || outerParsed.type !== 'object') {\n return null;\n }\n\n const outer = outerParsed.value;\n\n // Check if this is NovelAI WebP format\n if (\n (typeof outer.Software === 'string' &&\n !outer.Software.startsWith('NovelAI')) ||\n typeof outer.Comment !== 'string'\n ) {\n return null;\n }\n\n // Parse and add inner Comment as Comment entry\n const innerParsed = parseJson(outer.Comment);\n\n return {\n Software: typeof outer.Software === 'string' ? outer.Software : 'NovelAI',\n Comment: innerParsed.ok ? JSON.stringify(innerParsed.value) : outer.Comment,\n };\n}\n\n/**\n * Map metadata segment source to keyword\n */\nfunction sourceToKeyword(source: MetadataSegmentSource): string {\n switch (source.type) {\n case 'jpegCom':\n return 'Comment';\n case 'exifUserComment':\n return 'UserComment';\n case 'exifImageDescription':\n return source.prefix ?? 'ImageDescription';\n case 'exifMake':\n return source.prefix ?? 'Make';\n }\n}\n","/**\n * Read API for sd-metadata\n *\n * Handles reading and parsing metadata from images.\n * Automatically detects image format and extracts embedded generation metadata.\n */\n\nimport { parseMetadata } from '../parsers';\nimport { readImageDimensions } from '../readers/dimensions';\nimport { readJpegMetadata } from '../readers/jpeg';\nimport { readPngMetadata } from '../readers/png';\nimport { readWebpMetadata } from '../readers/webp';\nimport type {\n MetadataSegment,\n ParseResult,\n PngTextChunk,\n RawMetadata,\n ReadOptions,\n} from '../types';\nimport { detectFormat, type ImageFormat, toUint8Array } from '../utils/binary';\nimport { pngChunksToRecord, segmentsToRecord } from '../utils/convert';\n\n/**\n * Read and parse metadata from an image\n *\n * Automatically detects the image format (PNG, JPEG, WebP) and parses\n * any embedded generation metadata.\n *\n * @param input - Image file data (Uint8Array or ArrayBuffer)\n * @param options - Read options\n * @returns Parse result containing metadata and raw data\n */\nexport function read(\n input: Uint8Array | ArrayBuffer,\n options?: ReadOptions,\n): ParseResult {\n const data = toUint8Array(input);\n const format = detectFormat(data);\n\n if (!format) {\n return { status: 'invalid', message: 'Unknown image format' };\n }\n\n // 1. Read raw metadata based on format\n const rawResult = readRawMetadata(data, format);\n if (rawResult.status !== 'success') {\n return rawResult;\n }\n const raw = rawResult.raw;\n\n // 2. Convert to entry record\n const entries =\n raw.format === 'png'\n ? pngChunksToRecord(raw.chunks)\n : segmentsToRecord(raw.segments);\n\n // 3. Parse metadata\n const parseResult = parseMetadata(entries);\n if (!parseResult.ok) {\n return { status: 'unrecognized', raw };\n }\n\n const metadata = parseResult.value;\n\n // 4. Fallback for dimensions if missing (unless strict mode)\n if (!options?.strict && (metadata.width === 0 || metadata.height === 0)) {\n const dims = readImageDimensions(data, format);\n\n if (dims) {\n metadata.width = metadata.width || dims.width;\n metadata.height = metadata.height || dims.height;\n }\n }\n\n return { status: 'success', metadata, raw };\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Result type for readRawMetadata */\ntype RawReadResult =\n | { status: 'success'; raw: RawMetadata }\n | { status: 'empty' }\n | { status: 'invalid'; message: string };\n\n/**\n * Read raw metadata from image data\n */\nfunction readRawMetadata(data: Uint8Array, format: ImageFormat): RawReadResult {\n const result =\n format === 'png'\n ? readPngMetadata(data)\n : format === 'jpeg'\n ? readJpegMetadata(data)\n : readWebpMetadata(data);\n\n if (!result.ok) {\n const message =\n result.error.type === 'invalidSignature'\n ? `Invalid ${format.toUpperCase()} signature`\n : result.error.message;\n return { status: 'invalid', message };\n }\n\n if (result.value.length === 0) return { status: 'empty' };\n\n // PNG uses PngTextChunk[], JPEG/WebP use MetadataSegment[]\n if (format === 'png') {\n return {\n status: 'success',\n raw: { format: 'png', chunks: result.value as PngTextChunk[] },\n };\n }\n return {\n status: 'success',\n raw: { format, segments: result.value as MetadataSegment[] },\n };\n}\n","/**\n * A1111-format metadata conversion utilities\n *\n * Handles conversion for sd-webui, forge-classic, forge-neo, and civitai.\n * A1111 format stores metadata as plain text in:\n * - PNG: `parameters` tEXt/iTXt chunk (dynamic selection)\n * - JPEG/WebP: Exif UserComment\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { createEncodedChunk } from './chunk-encoding';\n\n/**\n * Convert A1111-format PNG chunks to JPEG/WebP segments\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertA1111PngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n // Find parameters chunk\n const parameters = chunks.find((c) => c.keyword === 'parameters');\n if (!parameters) {\n return [];\n }\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: parameters.text,\n },\n ];\n}\n\n/**\n * Convert JPEG/WebP segments to A1111-format PNG chunks\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertA1111SegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = segments.find((s) => s.source.type === 'exifUserComment');\n if (!userComment) {\n return [];\n }\n\n // Use dynamic selection (tEXt for ASCII, iTXt for non-ASCII)\n return createEncodedChunk('parameters', userComment.data, 'dynamic');\n}\n","/**\n * CivitAI metadata conversion utilities\n *\n * CivitAI has TWO formats:\n *\n * 1. CivitAI Orchestration (ComfyUI-like JSON):\n * - Node IDs as keys instead of standard \"prompt\"/\"workflow\" chunks\n * - \"resource-stack\" for model/checkpoint info\n * - \"extra\" and \"extraMetadata\" for CivitAI-specific data\n *\n * 2. CivitAI A1111 (plain text):\n * - Standard A1111 format with \"Civitai resources:\" marker\n * - Should be converted like regular A1111\n *\n * This converter handles BOTH formats by detecting the data type.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport { convertA1111PngToSegments, convertA1111SegmentsToPng } from './a1111';\nimport { createEncodedChunk } from './chunk-encoding';\nimport { findSegment } from './utils';\n\n/**\n * Convert CivitAI PNG chunks to JPEG/WebP segments\n *\n * Handles BOTH CivitAI formats:\n * - Orchestration (prompt chunk with JSON): Combine into single JSON object\n * - A1111 (parameters chunk with text): Preserve as A1111 format\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertCivitaiPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n // Check if this is A1111-style (has parameters chunk with non-JSON text)\n const parametersChunk = chunks.find((c) => c.keyword === 'parameters');\n if (parametersChunk && !parametersChunk.text.trimStart().startsWith('{')) {\n // A1111-style: use A1111 converter\n return convertA1111PngToSegments(chunks);\n }\n\n // Orchestration-style: combine chunks into JSON\n const data: Record<string, unknown> = {};\n\n for (const chunk of chunks) {\n if (chunk.keyword === 'prompt') {\n // Expand prompt chunk contents as top-level keys\n const parsed = parseJson(chunk.text);\n if (parsed.ok && parsed.type === 'object') {\n Object.assign(data, parsed.value);\n }\n } else if (chunk.keyword === 'extraMetadata') {\n // Keep extraMetadata as string (matches original JPEG format)\n // The parser expects a JSON string, not a parsed object\n data[chunk.keyword] = chunk.text;\n } else {\n // Other chunks: parse JSON if possible\n const parsed = parseJson(chunk.text);\n data[chunk.keyword] = parsed.ok ? parsed.value : chunk.text;\n }\n }\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(data),\n },\n ];\n}\n\n/**\n * Convert JPEG/WebP segments to CivitAI PNG chunks\n *\n * Handles BOTH CivitAI formats:\n * - Orchestration (JSON): Store entire JSON in single \"prompt\" chunk\n * - A1111 (text): Fall back to A1111 converter\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertCivitaiSegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n if (!userComment) return [];\n\n // Check if data is JSON (CivitAI Orchestration) or text (CivitAI A1111)\n const isJson = userComment.data.trimStart().startsWith('{');\n\n if (!isJson) {\n // Fall back to A1111 conversion for text format\n return convertA1111SegmentsToPng(segments);\n }\n\n // Orchestration format: store entire JSON as single \"prompt\" chunk\n // This preserves the original structure for round-trip compatibility\n return createEncodedChunk('prompt', userComment.data, 'text-unicode-escape');\n}\n","/**\n * Shared logic for JSON-based metadata converters\n *\n * Many formats (ComfyUI, InvokeAI) roughly follow a \"Key-Value\" pattern:\n * - PNG: Multiple chunks where Keyword=Key, Text=Value (Value might be JSON string)\n * - JPEG/WebP: A single UserComment containing a JSON object { Key: Value, ... }\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport {\n type ChunkEncodingStrategy,\n createEncodedChunk,\n unescapeUnicode,\n} from './chunk-encoding';\nimport { findSegment, stringify } from './utils';\n\n/**\n * Convert KV-style PNG chunks to a unified JSON object in a segment\n *\n * @param chunks - Raw PNG chunks\n * @returns Array containing a single UserComment segment with JSON data\n */\nexport function convertKvPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n const data: Record<string, unknown> = {};\n\n for (const chunk of chunks) {\n const parsed = parseJson(chunk.text);\n if (parsed.ok) {\n data[chunk.keyword] = parsed.value;\n } else {\n data[chunk.keyword] = chunk.text;\n }\n }\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(data),\n },\n ];\n}\n\n/**\n * Convert a unified JSON object in a segment back to KV-style PNG chunks\n *\n * @param segments - Metadata segments\n * @param encodingStrategy - Encoding strategy to use for chunks\n * @returns Array of PNG chunks\n */\nexport function convertKvSegmentsToPng(\n segments: MetadataSegment[],\n encodingStrategy: ChunkEncodingStrategy,\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n if (!userComment) {\n return [];\n }\n\n const parsed = parseJson(userComment.data);\n if (!parsed.ok || parsed.type !== 'object') {\n // If not JSON object, we can't blindly map keys.\n // Return empty here, letting specific converters handle fallback if needed.\n return [];\n }\n\n // Map each key back to a chunk\n // Value is stringified if it's an object/array\n // Unescape Unicode sequences that may have been escaped during PNG→JPEG conversion\n return Object.entries(parsed.value).flatMap(([keyword, value]) => {\n const text = stringify(value);\n return createEncodedChunk(\n keyword,\n text !== undefined ? unescapeUnicode(text) : undefined,\n encodingStrategy,\n );\n });\n}\n","/**\n * ComfyUI metadata conversion utilities\n *\n * ComfyUI stores metadata as:\n * - PNG: `prompt` + `workflow` tEXt chunks (both JSON)\n * - JPEG/WebP: exifUserComment with {\"prompt\": {...}, \"workflow\": {...}} (saveimage-plus format)\n *\n * Also handles: tensorart, stability-matrix (same format)\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { convertKvPngToSegments, convertKvSegmentsToPng } from './base-json';\nimport { createEncodedChunk } from './chunk-encoding';\nimport { findSegment } from './utils';\n\n/**\n * Convert ComfyUI PNG chunks to JPEG/WebP segments\n *\n * Uses saveimage-plus format: stores chunk keywords as JSON keys.\n * For chunks that contain JSON strings (prompt, workflow), parse them\n * and store as objects to match saveimage-plus format.\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertComfyUIPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n return convertKvPngToSegments(chunks);\n}\n\n/**\n * Try save-image-extended format (exifImageDescription + exifMake)\n *\n * @returns PNG chunks if format matches, null otherwise\n */\nconst tryParseExtendedFormat = (\n segments: MetadataSegment[],\n): PngTextChunk[] | null => {\n const imageDescription = findSegment(segments, 'exifImageDescription');\n const make = findSegment(segments, 'exifMake');\n\n if (!imageDescription && !make) {\n return null;\n }\n\n return [\n ...createEncodedChunk('prompt', make?.data, 'text-unicode-escape'),\n ...createEncodedChunk(\n 'workflow',\n imageDescription?.data,\n 'text-unicode-escape',\n ),\n ];\n};\n\n/**\n * Try saveimage-plus format (exifUserComment with JSON)\n *\n * @returns PNG chunks if format matches, null otherwise\n */\nconst tryParseSaveImagePlusFormat = (\n segments: MetadataSegment[],\n): PngTextChunk[] | null => {\n const chunks = convertKvSegmentsToPng(segments, 'text-unicode-escape');\n return chunks.length > 0 ? chunks : null;\n};\n\n/**\n * Convert JPEG/WebP segments to ComfyUI PNG chunks\n *\n * Supports:\n * - save-image-extended format: exifImageDescription (workflow) + exifMake (prompt)\n * - saveimage-plus format: exifUserComment with {\"prompt\": {...}, \"workflow\": {...}}\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertComfyUISegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n // Try each format in order of priority\n return (\n tryParseExtendedFormat(segments) ??\n tryParseSaveImagePlusFormat(segments) ??\n []\n );\n}\n","/**\n * Easy Diffusion metadata conversion utilities\n *\n * Easy Diffusion stores metadata using snake_case keys:\n * - PNG: Each field as a separate tEXt/iTXt chunk (e.g., `prompt`, `seed`, `sampler_name`)\n * - JPEG/WebP: All fields as JSON in EXIF UserComment\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport { createEncodedChunk } from './chunk-encoding';\nimport { findSegment, stringify } from './utils';\n\n/**\n * Convert Easy Diffusion PNG chunks to JPEG/WebP segments\n *\n * Easy Diffusion PNG stores metadata as individual chunks.\n * We combine them into a JSON object for JPEG/WebP storage.\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertEasyDiffusionPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n const json = Object.fromEntries(\n chunks.map((chunk) => [chunk.keyword, chunk.text]),\n );\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(json),\n },\n ];\n}\n\n/**\n * Convert JPEG/WebP segments to Easy Diffusion PNG chunks\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertEasyDiffusionSegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n if (!userComment) {\n return [];\n }\n\n const parsed = parseJson(userComment.data);\n if (!parsed.ok || parsed.type !== 'object') {\n return [];\n }\n\n // Convert each key-value pair to a chunk with dynamic selection\n return Object.entries(parsed.value).flatMap(([keyword, value]) =>\n createEncodedChunk(keyword, stringify(value), 'dynamic'),\n );\n}\n","/**\n * InvokeAI metadata conversion utilities\n *\n * InvokeAI stores metadata as:\n * - PNG: `invokeai_metadata` + `invokeai_graph` iTXt/tEXt chunks (both JSON, dynamic selection)\n * - JPEG/WebP: Not officially supported by InvokeAI\n *\n * For conversion, we use a JSON format similar to ComfyUI saveimage-plus:\n * {\"invokeai_metadata\": {...}, \"invokeai_graph\": {...}}\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { convertKvPngToSegments, convertKvSegmentsToPng } from './base-json';\n\n/**\n * Convert InvokeAI PNG chunks to JPEG/WebP segments\n *\n * Parses JSON chunks and stores them as objects.\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertInvokeAIPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n // Use generic KV converter\n return convertKvPngToSegments(chunks);\n}\n\n/**\n * Convert JPEG/WebP segments to InvokeAI PNG chunks\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertInvokeAISegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n // Use generic KV converter with dynamic encoding strategy\n return convertKvSegmentsToPng(segments, 'dynamic');\n}\n","/**\n * NovelAI metadata conversion utilities\n *\n * Converts NovelAI metadata between PNG chunks and JPEG/WebP segments.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport { createEncodedChunk } from './chunk-encoding';\nimport { createTextChunk, findSegment, stringify } from './utils';\n\n/** Fixed values for NovelAI PNG chunks */\nconst NOVELAI_TITLE = 'NovelAI generated image';\nconst NOVELAI_SOFTWARE = 'NovelAI';\n\n/**\n * Convert NovelAI PNG chunks to JPEG/WebP segments\n *\n * PNG structure:\n * - Title: \\\"NovelAI generated image\\\"\n * - Description: short prompt\n * - Software: \\\"NovelAI\\\"\n * - Source: version info\n * - Generation time: time\n * - Comment: full JSON parameters\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertNovelaiPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n // Build segments array declaratively\n const description = chunks.find((c) => c.keyword === 'Description');\n const descriptionSegment: MetadataSegment | undefined = description && {\n source: { type: 'exifImageDescription' },\n data: `\\0\\0\\0\\0${description.text}`,\n };\n\n const data = buildUserCommentJson(chunks);\n const userCommentSegment: MetadataSegment = {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(data),\n };\n\n return [descriptionSegment, userCommentSegment].filter(\n (segment) => segment !== undefined,\n );\n}\n\n/**\n * Build UserComment JSON from PNG chunks in NovelAI's standard key order\n */\nfunction buildUserCommentJson(chunks: PngTextChunk[]): Record<string, string> {\n return NOVELAI_KEY_ORDER.map((key) => {\n const chunk = chunks.find((c) => c.keyword === key);\n return chunk ? { [key]: chunk.text } : null;\n })\n .filter((entry): entry is Record<string, string> => entry !== null)\n .reduce(\n (acc, entry) => Object.assign(acc, entry),\n {} as Record<string, string>,\n );\n}\n\n/**\n * NovelAI standard key order for UserComment JSON\n */\nconst NOVELAI_KEY_ORDER = [\n 'Comment',\n 'Description',\n 'Generation time',\n 'Software',\n 'Source',\n 'Title',\n] as const;\n\n/**\n * Convert JPEG/WebP segments to NovelAI PNG chunks\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertNovelaiSegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userCommentSeg = findSegment(segments, 'exifUserComment');\n const descriptionSeg = findSegment(segments, 'exifImageDescription');\n\n return parseSegments(userCommentSeg, descriptionSeg);\n}\n\n/**\n * Parse UserComment JSON and convert to PNG chunks\n */\nfunction parseSegments(\n userCommentSeg: MetadataSegment | undefined,\n descriptionSeg: MetadataSegment | undefined,\n): PngTextChunk[] {\n if (!userCommentSeg || !descriptionSeg) {\n return [];\n }\n\n const parsed = parseJson(userCommentSeg.data);\n if (!parsed.ok || parsed.type !== 'object') {\n // If parsing fails or not an object, treat the whole thing as Comment\n return createTextChunk('Comment', userCommentSeg.data);\n }\n\n const jsonData = parsed.value;\n\n // Extract Description text (prefer exifImageDescription over corrupted JSON)\n const descriptionText = extractDescriptionText(\n descriptionSeg,\n stringify(jsonData.Description),\n );\n\n return [\n // Title (required, use default if missing)\n createTextChunk('Title', stringify(jsonData.Title) ?? NOVELAI_TITLE),\n // Description (optional, prefer exifImageDescription over JSON)\n createEncodedChunk('Description', descriptionText, 'dynamic'),\n // Software (required, use default if missing)\n createTextChunk(\n 'Software',\n stringify(jsonData.Software) ?? NOVELAI_SOFTWARE,\n ),\n // Source (optional)\n createTextChunk('Source', stringify(jsonData.Source)),\n // Generation time (optional)\n createTextChunk('Generation time', stringify(jsonData['Generation time'])),\n // Comment (optional)\n createTextChunk('Comment', stringify(jsonData.Comment)),\n ].flat();\n}\n\n/**\n * Extract Description text from exifImageDescription or UserComment JSON\n *\n * NovelAI WebP has corrupted UTF-8 in UserComment JSON Description,\n * so we prefer the clean exifImageDescription segment when available.\n */\nfunction extractDescriptionText(\n descriptionSeg: MetadataSegment | undefined,\n jsonDescription: string | undefined,\n): string | undefined {\n // First, try exifImageDescription segment (strip 4-byte null prefix)\n if (descriptionSeg?.data) {\n const data = descriptionSeg.data;\n // NovelAI WebP format has 4-byte null prefix before ImageDescription\n return data.startsWith('\\0\\0\\0\\0') ? data.slice(4) : data;\n }\n\n // Fallback: use JSON value (for non-NovelAI WebP sources)\n if (jsonDescription) {\n // Strip 4-byte null prefix if present\n return jsonDescription.startsWith('\\0\\0\\0\\0')\n ? jsonDescription.slice(4)\n : jsonDescription;\n }\n\n return undefined;\n}\n","/**\n * Simple chunk converter utilities\n *\n * Factory functions for converters that simply copy a single chunk keyword\n * between PNG and JPEG/WebP formats, with encoding based on tool strategy.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport {\n type ChunkEncodingStrategy,\n createEncodedChunk,\n} from './chunk-encoding';\n\n/**\n * Create a PNG-to-segments converter that extracts a single chunk by keyword\n *\n * @param keyword - The PNG chunk keyword to extract\n * @returns Converter function\n */\nexport function createPngToSegments(\n keyword: string,\n): (chunks: PngTextChunk[]) => MetadataSegment[] {\n return (chunks) => {\n const chunk = chunks.find((c) => c.keyword === keyword);\n return !chunk\n ? []\n : [{ source: { type: 'exifUserComment' }, data: chunk.text }];\n };\n}\n\n/**\n * Create a segments-to-PNG converter that writes to a single chunk keyword\n *\n * @param keyword - The PNG chunk keyword to write\n * @param encodingStrategy - Encoding strategy to use\n * @returns Converter function\n */\nexport function createSegmentsToPng(\n keyword: string,\n encodingStrategy: ChunkEncodingStrategy,\n): (segments: MetadataSegment[]) => PngTextChunk[] {\n return (segments) => {\n const userComment = segments.find(\n (s) => s.source.type === 'exifUserComment',\n );\n if (!userComment) return [];\n\n return createEncodedChunk(keyword, userComment.data, encodingStrategy);\n };\n}\n","/**\n * Stability Matrix metadata conversion utilities\n *\n * Stability Matrix stores metadata with multiple PNG chunks:\n * - parameters: A1111-style text (RAW UTF-8 - non-compliant but matches original)\n * - parameters-json: JSON with generation params (Unicode escape)\n * - smproj: Project data (Unicode escape)\n * - prompt: ComfyUI node graph JSON (Unicode escape)\n *\n * Per-chunk encoding is critical for round-trip compatibility.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport {\n type ChunkEncodingMap,\n createEncodedChunks,\n unescapeUnicode,\n} from './chunk-encoding';\nimport { findSegment, stringify } from './utils';\n\n/**\n * Chunk encoding map for Stability Matrix\n *\n * - parameters: RAW UTF-8 (A1111-style text with Japanese chars)\n * - All others: Unicode escape (JSON should be ASCII-safe)\n */\nconst STABILITY_MATRIX_ENCODING: ChunkEncodingMap = {\n parameters: 'text-utf8-raw',\n default: 'text-unicode-escape',\n};\n\n/**\n * Convert Stability Matrix PNG chunks to JPEG/WebP segments\n *\n * Combines all chunks into a single JSON object stored in exifUserComment.\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertStabilityMatrixPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n const data: Record<string, unknown> = {};\n\n for (const chunk of chunks) {\n const parsed = parseJson(chunk.text);\n data[chunk.keyword] = parsed.ok ? parsed.value : chunk.text;\n }\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(data),\n },\n ];\n}\n\n/**\n * Convert JPEG/WebP segments to Stability Matrix PNG chunks\n *\n * Splits the JSON object back into individual chunks with per-chunk encoding.\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertStabilityMatrixSegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n if (!userComment) return [];\n\n const parsed = parseJson(userComment.data);\n if (!parsed.ok || parsed.type !== 'object') return [];\n\n const value = parsed.value as Record<string, unknown>;\n\n // Build entries from all keys in the object\n const entries: [string, string | undefined][] = Object.entries(value).map(\n ([key, val]) => {\n const text = stringify(val);\n return [key, text !== undefined ? unescapeUnicode(text) : undefined];\n },\n );\n\n return createEncodedChunks(entries, STABILITY_MATRIX_ENCODING);\n}\n","/**\n * SwarmUI metadata conversion utilities\n *\n * SwarmUI stores metadata as:\n * - PNG: `parameters` chunk containing sui_image_params JSON\n * - JPEG/WebP: exifUserComment contains sui_image_params JSON directly\n *\n * The converter extracts/wraps the content appropriately for each format.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport { createEncodedChunk } from './chunk-encoding';\nimport { findSegment } from './utils';\n\n/**\n * Convert SwarmUI PNG chunks to JPEG/WebP segments\n *\n * Extracts the 'parameters' chunk and optionally preserves 'prompt' chunk (ComfyUI workflow).\n * - parameters chunk → exifUserComment (matches native SwarmUI format)\n * - prompt chunk → exifMake (preserves ComfyUI node graph for round-trip)\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertSwarmUIPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n // Find 'parameters' chunk\n const parametersChunk = chunks.find((c) => c.keyword === 'parameters');\n if (!parametersChunk) {\n return [];\n }\n\n // Parse and return the JSON directly (no wrapping in parameters key)\n const parsed = parseJson(parametersChunk.text);\n const segments: MetadataSegment[] = [\n {\n source: { type: 'exifUserComment' },\n data: parsed.ok ? JSON.stringify(parsed.value) : parametersChunk.text,\n },\n ];\n\n // Preserve node graph if present (prompt chunk contains ComfyUI node graph)\n const promptChunk = chunks.find((c) => c.keyword === 'prompt');\n if (promptChunk) {\n segments.push({\n source: { type: 'exifMake' },\n data: promptChunk.text,\n });\n }\n\n return segments;\n}\n\n/**\n * Convert JPEG/WebP segments to SwarmUI PNG chunks\n *\n * Handles both native SwarmUI format and extended format with node graph:\n * - exifUserComment → parameters chunk (always present)\n * - exifMake → prompt chunk (optional, contains ComfyUI node graph)\n *\n * Chunk order matches original SwarmUI format: [prompt, parameters]\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertSwarmUISegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n const make = findSegment(segments, 'exifMake');\n\n const chunks: PngTextChunk[] = [\n // Restore node graph first if present (extended format)\n createEncodedChunk('prompt', make?.data, 'text-unicode-escape'),\n // Add parameters chunk second (always present)\n createEncodedChunk('parameters', userComment?.data, 'text-unicode-escape'),\n ].flat();\n\n return chunks;\n}\n","/**\n * TensorArt metadata conversion utilities\n *\n * TensorArt stores metadata with two PNG chunks:\n * - generation_data: JSON with generation params (RAW UTF-8 - non-compliant but matches original)\n * - prompt: ComfyUI node graph JSON (Unicode escape)\n *\n * Per-chunk encoding is critical for round-trip compatibility.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport {\n type ChunkEncodingMap,\n createEncodedChunks,\n unescapeUnicode,\n} from './chunk-encoding';\nimport { findSegment, stringify } from './utils';\n\n/**\n * Chunk encoding map for TensorArt\n *\n * - generation_data: RAW UTF-8 (contains Japanese chars in prompts)\n * - All others: Unicode escape (ComfyUI node graph)\n */\nconst TENSORART_ENCODING: ChunkEncodingMap = {\n generation_data: 'text-utf8-raw',\n default: 'text-unicode-escape',\n};\n\n/**\n * Convert TensorArt PNG chunks to JPEG/WebP segments\n *\n * Combines all chunks into a single JSON object stored in exifUserComment.\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertTensorArtPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n const data: Record<string, unknown> = {};\n\n for (const chunk of chunks) {\n const parsed = parseJson(chunk.text);\n data[chunk.keyword] = parsed.ok ? parsed.value : chunk.text;\n }\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(data),\n },\n ];\n}\n\n/**\n * Convert JPEG/WebP segments to TensorArt PNG chunks\n *\n * Splits the JSON object back into individual chunks with per-chunk encoding.\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertTensorArtSegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n if (!userComment) return [];\n\n const parsed = parseJson(userComment.data);\n if (!parsed.ok || parsed.type !== 'object') return [];\n\n const value = parsed.value as Record<string, unknown>;\n\n // Build entries from all keys in the object\n const entries: [string, string | undefined][] = Object.entries(value).map(\n ([key, val]) => {\n const text = stringify(val);\n return [key, text !== undefined ? unescapeUnicode(text) : undefined];\n },\n );\n\n return createEncodedChunks(entries, TENSORART_ENCODING);\n}\n","/**\n * Metadata conversion utilities\n *\n * Provides functions to convert metadata between different image formats.\n */\n\nimport type { ParseResult, RawMetadata } from '../types';\nimport { Result } from '../types';\n\nimport { convertA1111PngToSegments, convertA1111SegmentsToPng } from './a1111';\nimport {\n convertCivitaiPngToSegments,\n convertCivitaiSegmentsToPng,\n} from './civitai';\nimport {\n convertComfyUIPngToSegments,\n convertComfyUISegmentsToPng,\n} from './comfyui';\nimport {\n convertEasyDiffusionPngToSegments,\n convertEasyDiffusionSegmentsToPng,\n} from './easydiffusion';\nimport {\n convertInvokeAIPngToSegments,\n convertInvokeAISegmentsToPng,\n} from './invokeai';\nimport {\n convertNovelaiPngToSegments,\n convertNovelaiSegmentsToPng,\n} from './novelai';\nimport { createPngToSegments, createSegmentsToPng } from './simple-chunk';\nimport {\n convertStabilityMatrixPngToSegments,\n convertStabilityMatrixSegmentsToPng,\n} from './stability-matrix';\nimport {\n convertSwarmUIPngToSegments,\n convertSwarmUISegmentsToPng,\n} from './swarmui';\nimport {\n convertTensorArtPngToSegments,\n convertTensorArtSegmentsToPng,\n} from './tensorart';\n\n// Internal types\ntype ConversionTargetFormat = 'png' | 'jpeg' | 'webp';\n\ntype ConversionError =\n | { type: 'missingRawData' }\n | { type: 'invalidParseResult'; status: string }\n | { type: 'unsupportedSoftware'; software: string };\n\ntype ConversionResult = Result<RawMetadata, ConversionError>;\n\n/**\n * Convert metadata from one format to another\n *\n * Takes a ParseResult and converts the raw metadata to the target format.\n * Conversion strategy is determined by the detected software.\n *\n * @param parseResult - Result from parsePng, parseJpeg, or parseWebp\n * @param targetFormat - Target format ('png', 'jpeg', or 'webp')\n * @returns Converted RawMetadata or error\n *\n * @example\n * ```typescript\n * const pngResult = parsePng(pngData);\n * const converted = convertMetadata(pngResult, 'webp');\n * if (converted.ok) {\n * const webpWithMetadata = writeWebpMetadata(webpData, converted.value.segments);\n * }\n * ```\n */\nexport function convertMetadata(\n parseResult: ParseResult,\n targetFormat: ConversionTargetFormat,\n): ConversionResult {\n // Handle non-success statuses\n if (parseResult.status === 'empty') {\n return Result.error({ type: 'missingRawData' });\n }\n\n if (parseResult.status === 'invalid') {\n return Result.error({\n type: 'invalidParseResult',\n status: parseResult.status,\n });\n }\n\n // Handle unrecognized - should not reach here in normal flow\n // since write() handles unrecognized separately\n if (parseResult.status === 'unrecognized') {\n return Result.error({\n type: 'unsupportedSoftware',\n software: 'unknown',\n });\n }\n\n const raw = parseResult.raw;\n\n // If source and target are the same format, return as-is\n if (raw.format === targetFormat) {\n return Result.ok(raw);\n }\n\n const software = parseResult.metadata.software;\n\n // Get converter for detected software\n const converter = softwareConverters[software];\n if (!converter) {\n // This should never happen if software is a valid GenerationSoftware\n return Result.error({\n type: 'unsupportedSoftware',\n software,\n });\n }\n\n return converter(raw, targetFormat);\n}\n\n// Type for converter function\ntype ConverterFn = (\n raw: RawMetadata,\n targetFormat: ConversionTargetFormat,\n) => ConversionResult;\n\n// Type for PNG↔segment conversion functions\ntype PngToSegmentsFn = (\n chunks: import('../types').PngTextChunk[],\n) => import('../types').MetadataSegment[];\ntype SegmentsToPngFn = (\n segments: import('../types').MetadataSegment[],\n) => import('../types').PngTextChunk[];\n\n/**\n * Factory function to create format converters\n *\n * All converters follow the same pattern:\n * - PNG → JPEG/WebP: convert chunks to segments\n * - JPEG/WebP → PNG: convert segments to chunks\n * - Same format: return as-is\n */\nfunction createFormatConverter(\n pngToSegments: PngToSegmentsFn,\n segmentsToPng: SegmentsToPngFn,\n): ConverterFn {\n return (raw, targetFormat) => {\n if (raw.format === 'png') {\n // PNG → same format: return as-is\n if (targetFormat === 'png') {\n return Result.ok(raw);\n }\n // PNG → JPEG/WebP\n const segments = pngToSegments(raw.chunks);\n return Result.ok({ format: targetFormat, segments });\n }\n\n // JPEG/WebP → JPEG/WebP: just copy segments\n if (targetFormat === 'jpeg' || targetFormat === 'webp') {\n return Result.ok({ format: targetFormat, segments: raw.segments });\n }\n\n // JPEG/WebP → PNG\n const chunks = segmentsToPng(raw.segments);\n return Result.ok({ format: 'png', chunks });\n };\n}\n\n// Create converters using factory\nconst convertNovelai = createFormatConverter(\n convertNovelaiPngToSegments,\n convertNovelaiSegmentsToPng,\n);\n\nconst convertA1111 = createFormatConverter(\n convertA1111PngToSegments,\n convertA1111SegmentsToPng,\n);\n\nconst convertComfyUI = createFormatConverter(\n convertComfyUIPngToSegments,\n convertComfyUISegmentsToPng,\n);\n\nconst convertEasyDiffusion = createFormatConverter(\n convertEasyDiffusionPngToSegments,\n convertEasyDiffusionSegmentsToPng,\n);\n\nconst convertFooocus = createFormatConverter(\n createPngToSegments('parameters'),\n createSegmentsToPng('parameters', 'text-unicode-escape'),\n);\n\nconst convertRuinedFooocus = createFormatConverter(\n createPngToSegments('parameters'),\n createSegmentsToPng('parameters', 'text-unicode-escape'),\n);\n\nconst convertSwarmUI = createFormatConverter(\n convertSwarmUIPngToSegments,\n convertSwarmUISegmentsToPng,\n);\n\nconst convertInvokeAI = createFormatConverter(\n convertInvokeAIPngToSegments,\n convertInvokeAISegmentsToPng,\n);\n\nconst convertHfSpace = createFormatConverter(\n createPngToSegments('parameters'),\n createSegmentsToPng('parameters', 'text-unicode-escape'),\n);\n\nconst convertCivitai = createFormatConverter(\n convertCivitaiPngToSegments,\n convertCivitaiSegmentsToPng,\n);\n\nconst convertStabilityMatrix = createFormatConverter(\n convertStabilityMatrixPngToSegments,\n convertStabilityMatrixSegmentsToPng,\n);\n\nconst convertTensorArt = createFormatConverter(\n convertTensorArtPngToSegments,\n convertTensorArtSegmentsToPng,\n);\n\n/**\n * Lookup table: software name → converter function\n */\nconst softwareConverters = {\n // NovelAI\n novelai: convertNovelai,\n // A1111-format (sd-webui, forge family, sd-next)\n 'sd-webui': convertA1111,\n 'sd-next': convertA1111,\n forge: convertA1111,\n 'forge-classic': convertA1111,\n 'forge-neo': convertA1111,\n reforge: convertA1111,\n 'easy-reforge': convertA1111,\n // CivitAI Orchestration format\n civitai: convertCivitai,\n // ComfyUI-format\n comfyui: convertComfyUI,\n // TensorArt (per-chunk encoding: generation_data uses raw UTF-8)\n tensorart: convertTensorArt,\n // Stability Matrix (per-chunk encoding: parameters uses raw UTF-8)\n 'stability-matrix': convertStabilityMatrix,\n // Easy Diffusion\n easydiffusion: convertEasyDiffusion,\n // Fooocus variants\n fooocus: convertFooocus,\n 'ruined-fooocus': convertRuinedFooocus,\n // SwarmUI\n swarmui: convertSwarmUI,\n // InvokeAI\n invokeai: convertInvokeAI,\n // HuggingFace Space\n 'hf-space': convertHfSpace,\n} as const;\n","/**\n * Write API for sd-metadata\n *\n * Handles writing metadata to images with automatic format conversion.\n * Supports PNG, JPEG, and WebP formats.\n */\n\nimport { convertMetadata } from '../converters';\nimport type { ParseResult, WriteResult } from '../types';\n\nimport type { ImageFormat } from '../utils/binary';\nimport { detectFormat, toUint8Array } from '../utils/binary';\nimport { writeJpegMetadata } from '../writers/jpeg';\nimport { writePngMetadata } from '../writers/png';\nimport { writeWebpMetadata } from '../writers/webp';\n\n/**\n * Write metadata to an image\n *\n * Automatically detects the target image format and converts the metadata\n * if necessary. For unrecognized metadata with cross-format conversion,\n * metadata is dropped and a warning is returned.\n *\n * @param input - Target image file data (Uint8Array or ArrayBuffer)\n * @param metadata - ParseResult from `read()`\n * @returns New image data with embedded metadata (or warning if metadata was dropped)\n */\nexport function write(\n input: Uint8Array | ArrayBuffer,\n metadata: ParseResult,\n): WriteResult {\n const data = toUint8Array(input);\n const targetFormat = detectFormat(data);\n if (!targetFormat) {\n return { ok: false, error: { type: 'unsupportedFormat' } };\n }\n\n // Handle empty metadata: strip all metadata\n if (metadata.status === 'empty') {\n const result = HELPERS[targetFormat].writeEmpty(data, []);\n if (!result.ok) {\n return {\n ok: false,\n error: { type: 'writeFailed', message: result.error.type },\n };\n }\n return { ok: true, value: result.value };\n }\n\n // Handle invalid metadata\n if (metadata.status === 'invalid') {\n return {\n ok: false,\n error: { type: 'writeFailed', message: 'Cannot write invalid metadata' },\n };\n }\n\n // Handle unrecognized metadata\n if (metadata.status === 'unrecognized') {\n const sourceFormat = metadata.raw.format;\n\n // Same format: write as-is\n if (sourceFormat === targetFormat) {\n return writeRaw(data, targetFormat, metadata.raw);\n }\n\n // Cross-format: drop metadata and return with warning\n const result = HELPERS[targetFormat].writeEmpty(data, []);\n if (!result.ok) {\n return {\n ok: false,\n error: { type: 'writeFailed', message: result.error.type },\n };\n }\n return {\n ok: true,\n value: result.value,\n warning: { type: 'metadataDropped', reason: 'unrecognizedCrossFormat' },\n };\n }\n\n // Handle success metadata: convert if needed\n const conversionResult = convertMetadata(metadata, targetFormat);\n\n if (!conversionResult.ok) {\n return {\n ok: false,\n error: {\n type: 'conversionFailed',\n message: `Failed to convert metadata: ${conversionResult.error.type}`,\n },\n };\n }\n\n return writeRaw(data, targetFormat, conversionResult.value);\n}\n\n/**\n * Write raw metadata to image\n */\nfunction writeRaw(\n data: Uint8Array,\n targetFormat: ImageFormat,\n raw: import('../types').RawMetadata,\n): WriteResult {\n if (targetFormat === 'png' && raw.format === 'png') {\n const result = writePngMetadata(data, raw.chunks);\n if (!result.ok) {\n return {\n ok: false,\n error: { type: 'writeFailed', message: result.error.type },\n };\n }\n return { ok: true, value: result.value };\n }\n\n if (targetFormat === 'jpeg' && raw.format === 'jpeg') {\n const result = writeJpegMetadata(data, raw.segments);\n if (!result.ok) {\n return {\n ok: false,\n error: { type: 'writeFailed', message: result.error.type },\n };\n }\n return { ok: true, value: result.value };\n }\n\n if (targetFormat === 'webp' && raw.format === 'webp') {\n const result = writeWebpMetadata(data, raw.segments);\n if (!result.ok) {\n return {\n ok: false,\n error: { type: 'writeFailed', message: result.error.type },\n };\n }\n return { ok: true, value: result.value };\n }\n\n return {\n ok: false,\n error: {\n type: 'writeFailed',\n message: 'Internal error: format mismatch after conversion',\n },\n };\n}\n\n// ============================================================================\n// Format Helpers\n// ============================================================================\n\n/** Format-specific helper functions */\nconst HELPERS = {\n png: {\n writeEmpty: writePngMetadata,\n },\n jpeg: {\n writeEmpty: writeJpegMetadata,\n },\n webp: {\n writeEmpty: writeWebpMetadata,\n },\n} as const satisfies Record<ImageFormat, unknown>;\n","import type { GenerationSoftware } from './types';\n\n/**\n * Human-readable display labels for each generation software identifier.\n *\n * @example\n * ```typescript\n * import { softwareLabels } from '@enslo/sd-metadata';\n *\n * const result = read(imageData);\n * if (result.status === 'success') {\n * console.log(softwareLabels[result.metadata.software]);\n * // => \"NovelAI\", \"ComfyUI\", \"Stable Diffusion WebUI\", etc.\n * }\n * ```\n */\nexport const softwareLabels: Readonly<Record<GenerationSoftware, string>> =\n Object.freeze({\n novelai: 'NovelAI',\n comfyui: 'ComfyUI',\n swarmui: 'SwarmUI',\n tensorart: 'TensorArt',\n 'stability-matrix': 'Stability Matrix',\n invokeai: 'InvokeAI',\n 'sd-webui': 'Stable Diffusion WebUI',\n forge: 'Forge',\n 'forge-classic': 'Forge - Classic',\n 'forge-neo': 'Forge - Neo',\n reforge: 'reForge',\n 'easy-reforge': 'EasyReforge',\n 'sd-next': 'SD.Next',\n civitai: 'Civitai',\n 'hf-space': 'Hugging Face Space',\n easydiffusion: 'Easy Diffusion',\n fooocus: 'Fooocus',\n 'ruined-fooocus': 'Ruined Fooocus',\n });\n"],"mappings":"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","names":["paddedSize","stringify","settingsStart","parseResolution","PNG_SIGNATURE_LENGTH","APP1_MARKER","COM_MARKER","EXIF_HEADER","EXIF_CHUNK_TYPE"]}
|
|
1
|
+
{"version":3,"sources":["../src/converters/utils.ts","../src/converters/chunk-encoding.ts","../src/types.ts","../src/utils/binary.ts","../src/utils/exif-constants.ts","../src/writers/exif.ts","../src/writers/jpeg.ts","../src/writers/png.ts","../src/writers/webp.ts","../src/api/stringify.ts","../src/api/embed.ts","../src/utils/object.ts","../src/parsers/a1111.ts","../src/utils/json.ts","../src/parsers/comfyui-nodes.ts","../src/parsers/comfyui-civitai.ts","../src/parsers/comfyui.ts","../src/parsers/detect.ts","../src/parsers/easydiffusion.ts","../src/parsers/fooocus.ts","../src/parsers/hf-space.ts","../src/utils/entries.ts","../src/parsers/invokeai.ts","../src/parsers/novelai.ts","../src/parsers/ruined-fooocus.ts","../src/parsers/stability-matrix.ts","../src/parsers/swarmui.ts","../src/parsers/tensorart.ts","../src/parsers/index.ts","../src/readers/dimensions.ts","../src/readers/exif.ts","../src/readers/jpeg.ts","../src/readers/png.ts","../src/readers/webp.ts","../src/utils/convert.ts","../src/api/read.ts","../src/converters/a1111.ts","../src/converters/civitai.ts","../src/converters/base-json.ts","../src/converters/comfyui.ts","../src/converters/easydiffusion.ts","../src/converters/invokeai.ts","../src/converters/novelai.ts","../src/converters/simple-chunk.ts","../src/converters/stability-matrix.ts","../src/converters/swarmui.ts","../src/converters/tensorart.ts","../src/converters/index.ts","../src/api/write.ts","../src/constants.ts"],"sourcesContent":["/**\n * Shared utilities for metadata converters\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\n\n/**\n * Create a tEXt chunk, returns empty array if text is undefined\n *\n * @param keyword - Chunk keyword\n * @param text - Chunk text, if undefined returns empty array\n * @returns Array with one chunk or empty array\n */\nexport const createTextChunk = (\n keyword: string,\n text: string | undefined,\n): PngTextChunk[] =>\n text !== undefined ? [{ type: 'tEXt', keyword, text }] : [];\n\n/**\n * Create an iTXt chunk, returns empty array if text is undefined\n *\n * @param keyword - Chunk keyword\n * @param text - Chunk text, if undefined returns empty array\n * @returns Array with one chunk or empty array\n */\nexport const createITxtChunk = (\n keyword: string,\n text: string | undefined,\n): PngTextChunk[] =>\n text !== undefined\n ? [\n {\n type: 'iTXt',\n keyword,\n compressionFlag: 0,\n compressionMethod: 0,\n languageTag: '',\n translatedKeyword: '',\n text,\n },\n ]\n : [];\n\n/**\n * Find a segment by source type\n *\n * @param segments - Array of metadata segments\n * @param type - Source type to find\n * @returns Matching segment or undefined\n */\nexport const findSegment = (\n segments: MetadataSegment[],\n type: string,\n): MetadataSegment | undefined => segments.find((s) => s.source.type === type);\n\n/**\n * Stringify value, returns undefined if value is undefined\n *\n * @param value - Value to stringify\n * @returns Stringified value or undefined\n */\nexport const stringify = (value: unknown): string | undefined => {\n if (value === undefined) return undefined;\n return typeof value === 'string' ? value : JSON.stringify(value);\n};\n","/**\n * Unified chunk encoding strategy for PNG converters\n *\n * Handles three different encoding strategies:\n * 1. dynamic: Choose tEXt/iTXt based on content (for tools like A1111, InvokeAI)\n * 2. text-unicode-escape: tEXt with Unicode escaping (for ComfyUI, SwarmUI)\n * 3. text-utf8-raw: tEXt with raw UTF-8 (for specific chunks in Stability Matrix, TensorArt)\n */\n\nimport type { PngTextChunk } from '../types';\nimport { createITxtChunk, createTextChunk } from './utils';\n\n/**\n * Chunk encoding strategy for PNG converters\n */\nexport type ChunkEncodingStrategy =\n | 'dynamic' // Choose tEXt/iTXt based on content\n | 'text-unicode-escape' // tEXt with Unicode escape (spec-compliant)\n | 'text-utf8-raw'; // tEXt with raw UTF-8 (non-compliant but compatible)\n\n/**\n * Map of chunk keywords to their encoding strategies\n *\n * Used when different chunks within the same software require different\n * encoding strategies (e.g., Stability Matrix, TensorArt).\n *\n * The 'default' key is required and used for keywords not in the map.\n */\nexport type ChunkEncodingMap = {\n [keyword: string]: ChunkEncodingStrategy;\n default: ChunkEncodingStrategy;\n};\n\n/**\n * Escape Unicode characters beyond Latin-1 for tEXt chunk\n *\n * Converts characters beyond Latin-1 to Unicode escape sequences.\n * Latin-1 range (0x00-0xFF) is left as-is since tEXt supports it.\n * Example: テスト → \\u30c6\\u30b9\\u30c8\n *\n * @param text - Text to escape\n * @returns Text with non-Latin-1 characters escaped\n */\nexport function escapeUnicode(text: string): string {\n return text.replace(/[\\u0100-\\uffff]/g, (char) => {\n const code = char.charCodeAt(0).toString(16).padStart(4, '0');\n return `\\\\u${code}`;\n });\n}\n\n/**\n * Unescape Unicode escape sequences back to actual characters\n *\n * Converts Unicode escape sequences back to actual Unicode characters.\n * Example: \\\\u30c6\\\\u30b9\\\\u30c8 → テスト\n *\n * @param text - Text with Unicode escape sequences\n * @returns Text with escape sequences converted to actual characters\n */\nexport function unescapeUnicode(text: string): string {\n return text.replace(/\\\\u([0-9a-fA-F]{4})/g, (_, hex) =>\n String.fromCharCode(Number.parseInt(hex, 16)),\n );\n}\n\n/**\n * Check if text contains characters beyond Latin-1 range\n *\n * PNG tEXt chunks support Latin-1 (ISO 8859-1) encoding (0x00-0xFF).\n * Characters beyond this range require iTXt chunks for UTF-8 support.\n *\n * @param text - Text to check\n * @returns True if text contains characters outside Latin-1 range (>= 0x100)\n */\nfunction hasNonLatin1(text: string): boolean {\n // biome-ignore lint/suspicious/noControlCharactersInRegex: checking for non-Latin-1 characters\n return /[^\\x00-\\xFF]/.test(text);\n}\n\n/**\n * Create PNG chunk with appropriate encoding strategy\n *\n * @param keyword - Chunk keyword\n * @param text - Chunk text (undefined returns empty array)\n * @param strategy - Encoding strategy to use\n * @returns Array of PNG text chunks (empty if text is undefined)\n */\nexport function createEncodedChunk(\n keyword: string,\n text: string | undefined,\n strategy: ChunkEncodingStrategy,\n): PngTextChunk[] {\n if (text === undefined) return [];\n\n switch (strategy) {\n case 'dynamic': {\n // Choose based on content: tEXt for Latin-1, iTXt for beyond\n const chunkType = hasNonLatin1(text) ? 'iTXt' : 'tEXt';\n return chunkType === 'iTXt'\n ? createITxtChunk(keyword, text)\n : createTextChunk(keyword, text);\n }\n\n case 'text-unicode-escape': {\n // tEXt with Unicode escaping (spec-compliant)\n const escaped = escapeUnicode(text);\n return createTextChunk(keyword, escaped);\n }\n\n case 'text-utf8-raw': {\n // tEXt with raw UTF-8 (non-compliant but compatible)\n return createTextChunk(keyword, text);\n }\n }\n}\n\n/**\n * Create multiple PNG chunks with per-keyword encoding strategy\n *\n * Uses a ChunkEncodingMap to determine the encoding strategy for each chunk.\n * If a keyword is not in the map, the 'default' strategy is used.\n *\n * @param entries - Array of [keyword, text] tuples\n * @param encodingMap - Map of keywords to encoding strategies\n * @returns Array of PNG text chunks\n */\nexport function createEncodedChunks(\n entries: [string, string | undefined][],\n encodingMap: ChunkEncodingMap,\n): PngTextChunk[] {\n return entries.flatMap(([keyword, text]) => {\n const strategy = encodingMap[keyword] ?? encodingMap.default;\n return createEncodedChunk(keyword, text, strategy);\n });\n}\n","/**\n * Result type for explicit error handling\n */\nexport type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };\n\n/**\n * Helper functions for Result type\n */\nexport const Result = {\n ok: <T, E>(value: T): Result<T, E> => ({ ok: true, value }),\n error: <T, E>(error: E): Result<T, E> => ({ ok: false, error }),\n};\n\n// ============================================================================\n// PNG Text Chunk Types\n// ============================================================================\n\n/**\n * PNG text chunk (tEXt or iTXt)\n */\nexport type PngTextChunk = TExtChunk | ITXtChunk;\n\n/**\n * tEXt chunk (Latin-1 encoded text)\n */\nexport interface TExtChunk {\n type: 'tEXt';\n /** Chunk keyword (e.g., 'parameters', 'Comment') */\n keyword: string;\n /** Text content */\n text: string;\n}\n\n/**\n * iTXt chunk (UTF-8 encoded international text)\n */\nexport interface ITXtChunk {\n type: 'iTXt';\n /** Chunk keyword */\n keyword: string;\n /** Compression flag (0=uncompressed, 1=compressed) */\n compressionFlag: number;\n /** Compression method (0=zlib/deflate) */\n compressionMethod: number;\n /** Language tag (BCP 47) */\n languageTag: string;\n /** Translated keyword */\n translatedKeyword: string;\n /** Text content */\n text: string;\n}\n\n// ============================================================================\n// Exif Metadata Types (shared between JPEG/WebP)\n// ============================================================================\n\n/**\n * Source location of a metadata segment.\n * Used for round-tripping: reading and writing back to the correct location.\n */\nexport type MetadataSegmentSource =\n | { type: 'exifUserComment' }\n | { type: 'exifImageDescription'; prefix?: string }\n | { type: 'exifMake'; prefix?: string }\n | { type: 'jpegCom' };\n\n/**\n * A single metadata segment with source tracking\n */\nexport interface MetadataSegment {\n /** Source location of this segment */\n source: MetadataSegmentSource;\n /** Raw data string */\n data: string;\n}\n\n/**\n * Raw metadata for write-back (preserves original format)\n */\nexport type RawMetadata =\n | { format: 'png'; chunks: PngTextChunk[] }\n | { format: 'jpeg'; segments: MetadataSegment[] }\n | { format: 'webp'; segments: MetadataSegment[] };\n\n// ============================================================================\n// Generation Software\n// ============================================================================\n\n/**\n * Known AI image generation software\n */\nexport type GenerationSoftware =\n | 'novelai'\n | 'comfyui'\n | 'swarmui'\n | 'tensorart'\n | 'stability-matrix'\n | 'invokeai'\n | 'forge'\n | 'forge-classic'\n | 'forge-neo'\n | 'reforge'\n | 'easy-reforge'\n | 'sd-webui'\n | 'sd-next'\n | 'civitai'\n | 'hf-space'\n | 'easydiffusion'\n | 'fooocus'\n | 'ruined-fooocus';\n\n// ============================================================================\n// Unified Metadata Types\n// ============================================================================\n\n/**\n * Base metadata fields shared by all tools\n */\nexport interface BaseMetadata {\n /** Positive prompt */\n prompt: string;\n /** Negative prompt */\n negativePrompt: string;\n /** Model settings */\n model?: ModelSettings;\n /** Sampling settings */\n sampling?: SamplingSettings;\n /** Hires.fix settings (if applied) */\n hires?: HiresSettings;\n /** Upscale settings (if applied) */\n upscale?: UpscaleSettings;\n /** Image width */\n width: number;\n /** Image height */\n height: number;\n}\n\n/**\n * NovelAI-specific metadata\n */\nexport interface NovelAIMetadata extends BaseMetadata {\n software: 'novelai';\n /** V4 character prompts (when using character placement) */\n characterPrompts?: CharacterPrompt[];\n /** Use character coordinates for placement */\n useCoords?: boolean;\n /** Use character order */\n useOrder?: boolean;\n}\n\n/**\n * Character prompt with position (NovelAI V4)\n */\nexport interface CharacterPrompt {\n /** Character-specific prompt */\n prompt: string;\n /** Character position (normalized 0-1) */\n center?: { x: number; y: number };\n}\n\n/**\n * ComfyUI-format metadata (ComfyUI, TensorArt, Stability Matrix)\n *\n * These tools use ComfyUI-compatible workflow format.\n */\n\n/**\n * ComfyUI node reference (for node outputs)\n *\n * Format: [nodeId, outputIndex]\n * Example: [\"CheckpointLoader_Base\", 0]\n */\nexport type ComfyNodeReference = [nodeId: string, outputIndex: number];\n\n/**\n * ComfyUI node input value\n */\nexport type ComfyNodeInputValue =\n | string\n | number\n | boolean\n | ComfyNodeReference\n | ComfyNodeInputValue[];\n\n/**\n * ComfyUI node structure\n */\nexport interface ComfyNode {\n /** Node class type (e.g., \"CheckpointLoaderSimple\", \"KSampler\") */\n class_type: string;\n /** Node inputs */\n inputs: Record<string, ComfyNodeInputValue>;\n /** Node metadata (ComfyUI only) */\n _meta?: {\n /** Node title for display */\n title?: string;\n };\n /** Change detection hash (rare, for caching) */\n is_changed?: string[] | null;\n}\n\n/**\n * ComfyUI node graph\n *\n * Maps node IDs to their corresponding node data.\n */\nexport type ComfyNodeGraph = Record<string, ComfyNode>;\n\n/**\n * ComfyUI-format metadata (ComfyUI, TensorArt, Stability Matrix)\n *\n * These tools always have nodes in all formats.\n */\nexport interface BasicComfyUIMetadata extends BaseMetadata {\n software: 'comfyui' | 'tensorart' | 'stability-matrix';\n /**\n * ComfyUI node graph (required)\n *\n * Always present in all image formats (PNG, JPEG, WebP).\n * Structure: Record<nodeId, ComfyNode> where ComfyNode contains inputs and class_type.\n */\n nodes: ComfyNodeGraph;\n}\n\n/**\n * SwarmUI-specific metadata\n *\n * SwarmUI uses ComfyUI workflow format but nodes are only present in PNG.\n */\nexport interface SwarmUIMetadata extends BaseMetadata {\n software: 'swarmui';\n /**\n * ComfyUI node graph (optional for SwarmUI)\n *\n * Only present in PNG format. JPEG/WebP contain SwarmUI parameters only.\n * Structure: Record<nodeId, ComfyNode> where ComfyNode contains inputs and class_type.\n */\n nodes?: ComfyNodeGraph;\n}\n\n/**\n * ComfyUI-format metadata (union of BasicComfyUI and SwarmUI)\n *\n * This is a union type to handle different node graph requirements:\n * - ComfyUI/TensorArt/Stability Matrix: nodes are always present\n * - SwarmUI: nodes are only present in PNG format\n */\nexport type ComfyUIMetadata = BasicComfyUIMetadata | SwarmUIMetadata;\n\n/**\n * Standard metadata (SD WebUI, Forge, InvokeAI, and others)\n *\n * Baseline generation metadata without tool-specific extensions.\n * Used by most SD tools that don't require special features like\n * NovelAI's character prompts or ComfyUI's node graphs.\n */\nexport interface StandardMetadata extends BaseMetadata {\n software:\n | 'sd-webui'\n | 'sd-next'\n | 'forge'\n | 'forge-classic'\n | 'forge-neo'\n | 'reforge'\n | 'easy-reforge'\n | 'invokeai'\n | 'civitai'\n | 'hf-space'\n | 'easydiffusion'\n | 'fooocus'\n | 'ruined-fooocus';\n}\n\n/**\n * Unified generation metadata (discriminated union)\n *\n * Use `metadata.software` to narrow by specific tool:\n * ```typescript\n * if (metadata.software === 'comfyui' ||\n * metadata.software === 'tensorart' ||\n * metadata.software === 'stability-matrix' ||\n * metadata.software === 'swarmui') {\n * // TypeScript knows metadata is ComfyUIMetadata\n * if (metadata.nodes) {\n * // Access workflow graph\n * }\n * }\n * ```\n */\nexport type GenerationMetadata =\n | NovelAIMetadata\n | ComfyUIMetadata\n | StandardMetadata;\n\n/**\n * User-created custom metadata for the embed() and stringify() APIs\n *\n * While {@link GenerationMetadata} represents parsed output from a known AI tool,\n * EmbedMetadata is designed for users to compose their own metadata from\n * scratch. Includes all base generation fields plus optional character\n * prompts and extras for the settings line.\n */\nexport type EmbedMetadata = BaseMetadata &\n Pick<NovelAIMetadata, 'characterPrompts'> & {\n /** Additional key-value pairs for the settings line */\n extras?: Record<string, string | number>;\n };\n\n// ============================================================================\n// Settings Types\n// ============================================================================\n\n/**\n * Model settings\n */\nexport interface ModelSettings {\n /** Model name */\n name?: string;\n /** Model hash */\n hash?: string;\n /** VAE name */\n vae?: string;\n}\n\n/**\n * Sampling settings\n */\nexport interface SamplingSettings {\n /** Sampler name */\n sampler?: string;\n /** Scheduler (sometimes included in sampler, sometimes separate) */\n scheduler?: string;\n /** Sampling steps */\n steps?: number;\n /** CFG scale */\n cfg?: number;\n /** Random seed */\n seed?: number;\n /** CLIP skip layers */\n clipSkip?: number;\n /** Denoising strength (ComfyUI only, typically 1.0 for txt2img) */\n denoise?: number;\n}\n\n/**\n * Hires.fix settings\n */\nexport interface HiresSettings {\n /** Upscale factor */\n scale?: number;\n /** Upscaler name */\n upscaler?: string;\n /** Hires steps */\n steps?: number;\n /** Hires denoising strength */\n denoise?: number;\n}\n\n/**\n * Upscale settings (post-generation)\n */\nexport interface UpscaleSettings {\n /** Upscaler name */\n upscaler?: string;\n /** Scale factor */\n scale?: number;\n}\n\n// ============================================================================\n// Parse Result\n// ============================================================================\n\n/**\n * Parse result with 4-status design\n *\n * - `success`: Parsing succeeded, metadata and raw data available\n * - `empty`: No metadata found in the file\n * - `unrecognized`: Metadata exists but format is not recognized\n * - `invalid`: File is corrupted or not a valid image\n */\nexport type ParseResult =\n | { status: 'success'; metadata: GenerationMetadata; raw: RawMetadata }\n | { status: 'empty' }\n | { status: 'unrecognized'; raw: RawMetadata }\n | { status: 'invalid'; message?: string };\n\n// ============================================================================\n// API Types\n// ============================================================================\n\n/**\n * Options for the read function\n */\nexport interface ReadOptions {\n /**\n * When true, dimensions are taken strictly from metadata only.\n * When false (default), missing dimensions are extracted from image headers.\n * @default false\n */\n strict?: boolean;\n}\n\n/**\n * Warning types for write operations\n */\nexport type WriteWarning = {\n type: 'metadataDropped';\n reason: 'unrecognizedCrossFormat';\n};\n\n/**\n * Error types for write operations\n */\ntype WriteError =\n | { type: 'unsupportedFormat' }\n | { type: 'conversionFailed'; message: string }\n | { type: 'writeFailed'; message: string };\n\n/**\n * Result of the write operation\n *\n * Success case may include a warning when metadata was intentionally dropped.\n */\nexport type WriteResult =\n | { ok: true; value: Uint8Array; warning?: WriteWarning }\n | { ok: false; error: WriteError };\n","/**\n * Binary data utilities for reading/writing multi-byte integers\n */\n\n/**\n * Convert input to Uint8Array\n *\n * @param input - Image data as Uint8Array or ArrayBuffer\n * @returns Uint8Array view of the data\n */\nexport function toUint8Array(input: Uint8Array | ArrayBuffer): Uint8Array {\n return input instanceof ArrayBuffer ? new Uint8Array(input) : input;\n}\n\n/**\n * Read 3-byte little-endian unsigned integer\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @returns 24-bit unsigned integer\n */\nexport function readUint24LE(data: Uint8Array, offset: number): number {\n return (\n (data[offset] ?? 0) |\n ((data[offset + 1] ?? 0) << 8) |\n ((data[offset + 2] ?? 0) << 16)\n );\n}\n\n/**\n * Read 4-byte big-endian unsigned integer\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @returns 32-bit unsigned integer\n */\nexport function readUint32BE(data: Uint8Array, offset: number): number {\n return (\n ((data[offset] ?? 0) << 24) |\n ((data[offset + 1] ?? 0) << 16) |\n ((data[offset + 2] ?? 0) << 8) |\n (data[offset + 3] ?? 0)\n );\n}\n\n/**\n * Read 4-byte little-endian unsigned integer\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @returns 32-bit unsigned integer\n */\nexport function readUint32LE(data: Uint8Array, offset: number): number {\n return (\n (data[offset] ?? 0) |\n ((data[offset + 1] ?? 0) << 8) |\n ((data[offset + 2] ?? 0) << 16) |\n ((data[offset + 3] ?? 0) << 24)\n );\n}\n\n/**\n * Write 4-byte big-endian unsigned integer\n *\n * @param data - Byte array to write to\n * @param offset - Offset to start writing at\n * @param value - 32-bit unsigned integer value\n */\nexport function writeUint32BE(\n data: Uint8Array,\n offset: number,\n value: number,\n): void {\n data[offset] = (value >>> 24) & 0xff;\n data[offset + 1] = (value >>> 16) & 0xff;\n data[offset + 2] = (value >>> 8) & 0xff;\n data[offset + 3] = value & 0xff;\n}\n\n/**\n * Read 4-byte chunk type as ASCII string\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @returns 4-character ASCII string\n */\nexport function readChunkType(data: Uint8Array, offset: number): string {\n return String.fromCharCode(\n data[offset] ?? 0,\n data[offset + 1] ?? 0,\n data[offset + 2] ?? 0,\n data[offset + 3] ?? 0,\n );\n}\n\n/**\n * Read 2-byte unsigned integer with endianness support\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @param isLittleEndian - If true, read as little-endian\n * @returns 16-bit unsigned integer\n */\nexport function readUint16(\n data: Uint8Array,\n offset: number,\n isLittleEndian: boolean,\n): number {\n if (isLittleEndian) {\n return (data[offset] ?? 0) | ((data[offset + 1] ?? 0) << 8);\n }\n return ((data[offset] ?? 0) << 8) | (data[offset + 1] ?? 0);\n}\n\n/**\n * Read 4-byte unsigned integer with endianness support\n *\n * @param data - Byte array\n * @param offset - Offset to start reading from\n * @param isLittleEndian - If true, read as little-endian\n * @returns 32-bit unsigned integer\n */\nexport function readUint32(\n data: Uint8Array,\n offset: number,\n isLittleEndian: boolean,\n): number {\n if (isLittleEndian) {\n return (\n (data[offset] ?? 0) |\n ((data[offset + 1] ?? 0) << 8) |\n ((data[offset + 2] ?? 0) << 16) |\n ((data[offset + 3] ?? 0) << 24)\n );\n }\n return (\n ((data[offset] ?? 0) << 24) |\n ((data[offset + 1] ?? 0) << 16) |\n ((data[offset + 2] ?? 0) << 8) |\n (data[offset + 3] ?? 0)\n );\n}\n\n/**\n * Compare two Uint8Arrays for equality\n *\n * @param a - First array\n * @param b - Second array\n * @returns true if arrays have same length and all elements match\n */\nexport function arraysEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * Write 2-byte unsigned integer with endianness support\n *\n * @param data - Byte array to write to\n * @param offset - Offset to start writing at\n * @param value - 16-bit unsigned integer value\n * @param isLittleEndian - If true, write as little-endian\n */\nexport function writeUint16(\n data: Uint8Array,\n offset: number,\n value: number,\n isLittleEndian: boolean,\n): void {\n if (isLittleEndian) {\n data[offset] = value & 0xff;\n data[offset + 1] = (value >>> 8) & 0xff;\n } else {\n data[offset] = (value >>> 8) & 0xff;\n data[offset + 1] = value & 0xff;\n }\n}\n\n/**\n * Write 4-byte unsigned integer with endianness support\n *\n * @param data - Byte array to write to\n * @param offset - Offset to start writing at\n * @param value - 32-bit unsigned integer value\n * @param isLittleEndian - If true, write as little-endian\n */\nexport function writeUint32(\n data: Uint8Array,\n offset: number,\n value: number,\n isLittleEndian: boolean,\n): void {\n if (isLittleEndian) {\n data[offset] = value & 0xff;\n data[offset + 1] = (value >>> 8) & 0xff;\n data[offset + 2] = (value >>> 16) & 0xff;\n data[offset + 3] = (value >>> 24) & 0xff;\n } else {\n data[offset] = (value >>> 24) & 0xff;\n data[offset + 1] = (value >>> 16) & 0xff;\n data[offset + 2] = (value >>> 8) & 0xff;\n data[offset + 3] = value & 0xff;\n }\n}\n\n/**\n * Write 4-byte little-endian unsigned integer\n *\n * @param data - Byte array to write to\n * @param offset - Offset to start writing at\n * @param value - 32-bit unsigned integer value\n */\nexport function writeUint32LE(\n data: Uint8Array,\n offset: number,\n value: number,\n): void {\n data[offset] = value & 0xff;\n data[offset + 1] = (value >>> 8) & 0xff;\n data[offset + 2] = (value >>> 16) & 0xff;\n data[offset + 3] = (value >>> 24) & 0xff;\n}\n\n/**\n * Supported image formats\n */\nexport type ImageFormat = 'png' | 'jpeg' | 'webp';\n\n/**\n * Validates if data starts with PNG signature\n */\nexport function isPng(data: Uint8Array): boolean {\n if (data.length < 8) return false;\n return (\n data[0] === 0x89 &&\n data[1] === 0x50 &&\n data[2] === 0x4e &&\n data[3] === 0x47 &&\n data[4] === 0x0d &&\n data[5] === 0x0a &&\n data[6] === 0x1a &&\n data[7] === 0x0a\n );\n}\n\n/**\n * Validates if data starts with JPEG signature\n */\nexport function isJpeg(data: Uint8Array): boolean {\n if (data.length < 2) return false;\n return data[0] === 0xff && data[1] === 0xd8;\n}\n\n/**\n * Validates if data starts with WebP signature\n */\nexport function isWebp(data: Uint8Array): boolean {\n if (data.length < 12) return false;\n return (\n data[0] === 0x52 && // R\n data[1] === 0x49 && // I\n data[2] === 0x46 && // F\n data[3] === 0x46 && // F\n data[8] === 0x57 && // W\n data[9] === 0x45 && // E\n data[10] === 0x42 && // B\n data[11] === 0x50 // P\n );\n}\n\n/**\n * Detect image format from magic bytes\n */\nexport function detectFormat(data: Uint8Array): ImageFormat | null {\n if (isPng(data)) return 'png';\n if (isJpeg(data)) return 'jpeg';\n if (isWebp(data)) return 'webp';\n return null;\n}\n","/**\n * Exif/TIFF tag constants\n *\n * Shared between readers and writers for consistent tag handling.\n */\n\n/** UserComment tag ID in Exif */\nexport const USER_COMMENT_TAG = 0x9286;\n\n/** ImageDescription tag ID */\nexport const IMAGE_DESCRIPTION_TAG = 0x010e;\n\n/** Make tag ID */\nexport const MAKE_TAG = 0x010f;\n\n/** Exif IFD pointer tag */\nexport const EXIF_IFD_POINTER_TAG = 0x8769;\n","/**\n * Exif writing utilities\n *\n * Functions for building Exif/TIFF structures from metadata segments.\n */\n\nimport type { MetadataSegment } from '../types';\nimport { writeUint16, writeUint32 } from '../utils/binary';\nimport {\n EXIF_IFD_POINTER_TAG,\n IMAGE_DESCRIPTION_TAG,\n MAKE_TAG,\n USER_COMMENT_TAG,\n} from '../utils/exif-constants';\n\n/**\n * Build Exif TIFF data from MetadataSegments\n *\n * Creates a complete TIFF structure with IFD0, Exif IFD, and all tag data.\n * Uses little-endian (Intel) byte order for maximum compatibility.\n *\n * @param segments - Metadata segments to encode\n * @returns TIFF data (starts with \"II\" byte order marker)\n */\nexport function buildExifTiffData(segments: MetadataSegment[]): Uint8Array {\n // Separate segments by destination IFD\n const ifd0Segments = segments.filter(\n (s) =>\n s.source.type === 'exifImageDescription' || s.source.type === 'exifMake',\n );\n const exifIfdSegments = segments.filter(\n (s) => s.source.type === 'exifUserComment',\n );\n\n // No Exif-type segments\n if (ifd0Segments.length === 0 && exifIfdSegments.length === 0) {\n return new Uint8Array(0);\n }\n\n const isLittleEndian = true;\n\n // Build tag data for each segment\n const ifd0Tags: Array<{ tag: number; type: number; data: Uint8Array }> = [];\n const exifTags: Array<{ tag: number; type: number; data: Uint8Array }> = [];\n\n for (const seg of ifd0Segments) {\n if (seg.source.type === 'exifImageDescription') {\n const data = encodeAsciiTag(seg.data, seg.source.prefix);\n ifd0Tags.push({ tag: IMAGE_DESCRIPTION_TAG, type: 2, data });\n } else if (seg.source.type === 'exifMake') {\n const data = encodeAsciiTag(seg.data, seg.source.prefix);\n ifd0Tags.push({ tag: MAKE_TAG, type: 2, data });\n }\n }\n\n for (const seg of exifIfdSegments) {\n if (seg.source.type === 'exifUserComment') {\n const data = encodeUserComment(seg.data);\n exifTags.push({ tag: USER_COMMENT_TAG, type: 7, data });\n }\n }\n\n const hasExifIfd = exifTags.length > 0;\n if (hasExifIfd) {\n ifd0Tags.push({\n tag: EXIF_IFD_POINTER_TAG,\n type: 4,\n data: new Uint8Array(4),\n });\n }\n\n // Sort tags by tag number (required by TIFF spec)\n ifd0Tags.sort((a, b) => a.tag - b.tag);\n exifTags.sort((a, b) => a.tag - b.tag);\n\n // Calculate sizes and offsets\n const headerSize = 8;\n const ifd0EntryCount = ifd0Tags.length;\n const ifd0Size = 2 + 12 * ifd0EntryCount + 4;\n const exifEntryCount = exifTags.length;\n const exifIfdSize = hasExifIfd ? 2 + 12 * exifEntryCount + 4 : 0;\n\n const ifd0Offset = headerSize;\n const exifIfdOffset = ifd0Offset + ifd0Size;\n let dataOffset = exifIfdOffset + exifIfdSize;\n\n // Update Exif IFD pointer in IFD0\n if (hasExifIfd) {\n const exifPtrTag = ifd0Tags.find((t) => t.tag === EXIF_IFD_POINTER_TAG);\n if (exifPtrTag) {\n writeUint32(exifPtrTag.data, 0, exifIfdOffset, isLittleEndian);\n }\n }\n\n // Assign data offsets for each tag\n const tagDataOffsets = new Map<\n { tag: number; type: number; data: Uint8Array },\n number\n >();\n\n for (const tag of [...ifd0Tags, ...exifTags]) {\n if (tag.data.length > 4) {\n tagDataOffsets.set(tag, dataOffset);\n dataOffset += tag.data.length;\n if (tag.data.length % 2 !== 0) {\n dataOffset += 1;\n }\n }\n }\n\n // Build result\n const totalSize = dataOffset;\n const result = new Uint8Array(totalSize);\n\n // Write TIFF header\n result[0] = 0x49; // I\n result[1] = 0x49; // I (little-endian)\n writeUint16(result, 2, 42, isLittleEndian);\n writeUint32(result, 4, ifd0Offset, isLittleEndian);\n\n // Write IFD0\n let offset = ifd0Offset;\n writeUint16(result, offset, ifd0EntryCount, isLittleEndian);\n offset += 2;\n\n for (const tag of ifd0Tags) {\n writeIfdEntry(result, offset, tag, tagDataOffsets.get(tag), isLittleEndian);\n offset += 12;\n }\n\n writeUint32(result, offset, 0, isLittleEndian);\n offset += 4;\n\n // Write Exif IFD\n if (hasExifIfd) {\n writeUint16(result, offset, exifEntryCount, isLittleEndian);\n offset += 2;\n\n for (const tag of exifTags) {\n writeIfdEntry(\n result,\n offset,\n tag,\n tagDataOffsets.get(tag),\n isLittleEndian,\n );\n offset += 12;\n }\n\n writeUint32(result, offset, 0, isLittleEndian);\n }\n\n // Write tag data values\n for (const [tag, dataOff] of tagDataOffsets) {\n result.set(tag.data, dataOff);\n }\n\n return result;\n}\n\n/**\n * Write an IFD entry\n */\nfunction writeIfdEntry(\n data: Uint8Array,\n offset: number,\n tag: { tag: number; type: number; data: Uint8Array },\n dataOffset: number | undefined,\n isLittleEndian: boolean,\n): void {\n writeUint16(data, offset, tag.tag, isLittleEndian);\n writeUint16(data, offset + 2, tag.type, isLittleEndian);\n writeUint32(data, offset + 4, tag.data.length, isLittleEndian);\n\n if (tag.data.length <= 4) {\n data.set(tag.data, offset + 8);\n } else {\n writeUint32(data, offset + 8, dataOffset ?? 0, isLittleEndian);\n }\n}\n\n/**\n * Encode string as UserComment with UTF-16LE encoding\n *\n * Uses UNICODE prefix followed by UTF-16LE encoded text.\n *\n * @param text - Text to encode\n * @returns Encoded UserComment data (8-byte prefix + UTF-16LE text)\n */\nfunction encodeUserComment(text: string): Uint8Array {\n const utf16Data: number[] = [];\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n utf16Data.push(code & 0xff);\n utf16Data.push((code >> 8) & 0xff);\n }\n\n const result = new Uint8Array(8 + utf16Data.length);\n\n // UNICODE encoding prefix\n result[0] = 0x55; // U\n result[1] = 0x4e; // N\n result[2] = 0x49; // I\n result[3] = 0x43; // C\n result[4] = 0x4f; // O\n result[5] = 0x44; // D\n result[6] = 0x45; // E\n result[7] = 0x00; // NULL\n\n result.set(new Uint8Array(utf16Data), 8);\n return result;\n}\n\n/**\n * Encode ASCII tag data with optional prefix\n *\n * @param text - Text content\n * @param prefix - Optional prefix (e.g., \"Workflow\")\n * @returns Null-terminated ASCII bytes\n */\nfunction encodeAsciiTag(text: string, prefix?: string): Uint8Array {\n const fullText = prefix ? `${prefix}: ${text}` : text;\n const textBytes = new TextEncoder().encode(fullText);\n const result = new Uint8Array(textBytes.length + 1);\n result.set(textBytes, 0);\n result[textBytes.length] = 0;\n return result;\n}\n","import type { MetadataSegment } from '../types';\nimport { Result } from '../types';\nimport { isJpeg } from '../utils/binary';\nimport { buildExifTiffData } from './exif';\n\n// Internal types (co-located with writer)\ntype JpegWriteError =\n | { type: 'invalidSignature' }\n | { type: 'corruptedStructure'; message: string };\n\ntype JpegWriteResult = Result<Uint8Array, JpegWriteError>;\n\n/** APP1 marker */\nconst APP1_MARKER = 0xe1;\n\n/** COM (Comment) marker */\nconst COM_MARKER = 0xfe;\n\n/** SOS (Start of Scan) marker */\nconst SOS_MARKER = 0xda;\n\n/** EOI (End of Image) marker */\nconst EOI_MARKER = 0xd9;\n\n/** Exif header: \"Exif\\0\\0\" */\nconst EXIF_HEADER = new Uint8Array([0x45, 0x78, 0x69, 0x66, 0x00, 0x00]);\n\n/**\n * Write JPEG metadata to binary data\n *\n * Replaces existing metadata segments with the provided segments.\n * Each segment is written to its original location based on source type:\n * - jpegCom -> COM segment (before SOS)\n * - exifUserComment/exifImageDescription/exifMake -> APP1 Exif segment (after SOI)\n *\n * @param data - Original JPEG file data as Uint8Array\n * @param segments - Metadata segments to embed\n * @returns Result containing new JPEG data with embedded metadata\n */\nexport function writeJpegMetadata(\n data: Uint8Array,\n segments: MetadataSegment[],\n): JpegWriteResult {\n // Validate JPEG signature\n if (!isJpeg(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n // Separate segments by destination\n const comSegments = segments.filter((s) => s.source.type === 'jpegCom');\n const exifSegments = segments.filter(\n (s) =>\n s.source.type === 'exifUserComment' ||\n s.source.type === 'exifImageDescription' ||\n s.source.type === 'exifMake',\n );\n\n // Collect non-metadata segments from original JPEG\n const collectResult = collectNonMetadataSegments(data);\n if (!collectResult.ok) {\n return collectResult;\n }\n\n const { beforeSos, scanData } = collectResult.value;\n\n // Build new APP1 Exif segment\n const app1Segment =\n exifSegments.length > 0 ? buildApp1Segment(exifSegments) : null;\n\n // Build new COM segments\n const comSegmentData = comSegments.map((s) => buildComSegment(s.data));\n\n // Calculate total size\n let totalSize = 2; // SOI\n if (app1Segment) {\n totalSize += app1Segment.length;\n }\n for (const seg of beforeSos) {\n totalSize += seg.length;\n }\n for (const com of comSegmentData) {\n totalSize += com.length;\n }\n totalSize += scanData.length;\n\n // Build output\n const output = new Uint8Array(totalSize);\n let offset = 0;\n\n // Write SOI\n output[offset++] = 0xff;\n output[offset++] = 0xd8;\n\n // Write APP1 Exif (immediately after SOI)\n if (app1Segment) {\n output.set(app1Segment, offset);\n offset += app1Segment.length;\n }\n\n // Write original non-metadata segments\n for (const seg of beforeSos) {\n output.set(seg, offset);\n offset += seg.length;\n }\n\n // Write COM segments (before SOS)\n for (const com of comSegmentData) {\n output.set(com, offset);\n offset += com.length;\n }\n\n // Write scan data (SOS to EOI)\n output.set(scanData, offset);\n\n return Result.ok(output);\n}\n\n/**\n * Collect non-metadata segments from JPEG\n *\n * Returns segments that are not APP1 Exif or COM, plus the scan data (SOS to EOI)\n */\nfunction collectNonMetadataSegments(\n data: Uint8Array,\n): Result<\n { beforeSos: Uint8Array[]; scanData: Uint8Array },\n { type: 'corruptedStructure'; message: string }\n> {\n const beforeSos: Uint8Array[] = [];\n let offset = 2; // Skip SOI\n\n while (offset < data.length - 1) {\n // Check for marker\n if (data[offset] !== 0xff) {\n return Result.error({\n type: 'corruptedStructure',\n message: `Expected marker at offset ${offset}`,\n });\n }\n\n // Skip padding bytes\n while (data[offset] === 0xff && offset < data.length - 1) {\n offset++;\n }\n\n const marker = data[offset];\n offset++;\n\n // Check for SOS - everything after this is scan data\n if (marker === SOS_MARKER) {\n // Include SOS marker in scan data\n const scanData = data.slice(offset - 2);\n return Result.ok({ beforeSos, scanData });\n }\n\n // Check for EOI (shouldn't happen before SOS but handle it)\n if (marker === EOI_MARKER) {\n return Result.ok({ beforeSos, scanData: new Uint8Array([0xff, 0xd9]) });\n }\n\n // Get segment length (big-endian, includes length bytes)\n if (offset + 2 > data.length) {\n return Result.error({\n type: 'corruptedStructure',\n message: 'Unexpected end of file',\n });\n }\n\n const length = ((data[offset] ?? 0) << 8) | (data[offset + 1] ?? 0);\n const segmentStart = offset - 2; // Include marker\n const segmentEnd = offset + length;\n\n if (segmentEnd > data.length) {\n return Result.error({\n type: 'corruptedStructure',\n message: 'Segment extends beyond file',\n });\n }\n\n // Check if this is a metadata segment we want to strip\n const isExifApp1 =\n marker === APP1_MARKER &&\n offset + 2 + 6 <= data.length &&\n data[offset + 2] === 0x45 && // E\n data[offset + 3] === 0x78 && // x\n data[offset + 4] === 0x69 && // i\n data[offset + 5] === 0x66 && // f\n data[offset + 6] === 0x00 && // NULL\n data[offset + 7] === 0x00; // NULL\n\n const isCom = marker === COM_MARKER;\n\n // Keep non-metadata segments\n if (!isExifApp1 && !isCom) {\n beforeSos.push(data.slice(segmentStart, segmentEnd));\n }\n\n offset = segmentEnd;\n }\n\n // If we reach here without finding SOS, the JPEG is malformed\n return Result.error({\n type: 'corruptedStructure',\n message: 'No SOS marker found',\n });\n}\n\n/**\n * Build APP1 Exif segment from metadata segments\n */\nfunction buildApp1Segment(segments: MetadataSegment[]): Uint8Array {\n const tiffData = buildExifTiffData(segments);\n\n if (tiffData.length === 0) {\n return new Uint8Array(0);\n }\n\n // APP1 segment: marker (2) + length (2) + Exif header (6) + TIFF data\n const segmentLength = 2 + EXIF_HEADER.length + tiffData.length;\n const segment = new Uint8Array(2 + segmentLength);\n\n segment[0] = 0xff;\n segment[1] = APP1_MARKER;\n segment[2] = (segmentLength >> 8) & 0xff;\n segment[3] = segmentLength & 0xff;\n segment.set(EXIF_HEADER, 4);\n segment.set(tiffData, 4 + EXIF_HEADER.length);\n\n return segment;\n}\n\n/**\n * Build COM segment from text\n */\nfunction buildComSegment(text: string): Uint8Array {\n const textBytes = new TextEncoder().encode(text);\n const segmentLength = 2 + textBytes.length; // length field includes itself\n\n const segment = new Uint8Array(2 + segmentLength);\n segment[0] = 0xff;\n segment[1] = COM_MARKER;\n segment[2] = (segmentLength >> 8) & 0xff;\n segment[3] = segmentLength & 0xff;\n segment.set(textBytes, 4);\n\n return segment;\n}\n","import type { ITXtChunk, PngTextChunk, TExtChunk } from '../types';\nimport { Result } from '../types';\nimport {\n isPng,\n readChunkType,\n readUint32BE,\n writeUint32BE,\n} from '../utils/binary';\n\n// Internal types (co-located with writer)\ntype PngWriteError = { type: 'invalidSignature' } | { type: 'noIhdrChunk' };\n\ntype PngWriteResult = Result<Uint8Array, PngWriteError>;\n\n/** PNG file signature (magic bytes) */\nconst PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);\n\n/**\n * Write PNG metadata to binary data\n *\n * Replaces all existing tEXt and iTXt chunks with the provided chunks.\n * Chunks are inserted immediately after the IHDR chunk (PNG spec recommended).\n *\n * @param data - Original PNG file data as Uint8Array\n * @param chunks - Text chunks to embed\n * @returns Result containing new PNG data with embedded metadata\n */\nexport function writePngMetadata(\n data: Uint8Array,\n chunks: PngTextChunk[],\n): PngWriteResult {\n // Validate PNG signature\n if (!isPng(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n // Find IHDR chunk end position\n const ihdrEnd = findIhdrChunkEnd(data);\n if (ihdrEnd === -1) {\n return Result.error({ type: 'noIhdrChunk' });\n }\n\n // Collect non-text chunks from original data\n const originalChunks = collectNonTextChunks(data);\n\n // Serialize new text chunks\n const serializedTextChunks = chunks.map((chunk) =>\n chunk.type === 'tEXt'\n ? serializeTExtChunk(chunk)\n : serializeITXtChunk(chunk),\n );\n\n // Calculate total output size\n const totalSize =\n PNG_SIGNATURE.length +\n originalChunks.ihdr.length +\n serializedTextChunks.reduce((sum, chunk) => sum + chunk.length, 0) +\n originalChunks.others.reduce((sum, chunk) => sum + chunk.length, 0);\n\n // Build output\n const output = new Uint8Array(totalSize);\n let offset = 0;\n\n // Write signature\n output.set(PNG_SIGNATURE, offset);\n offset += PNG_SIGNATURE.length;\n\n // Write IHDR\n output.set(originalChunks.ihdr, offset);\n offset += originalChunks.ihdr.length;\n\n // Write text chunks (immediately after IHDR)\n for (const chunk of serializedTextChunks) {\n output.set(chunk, offset);\n offset += chunk.length;\n }\n\n // Write other chunks\n for (const chunk of originalChunks.others) {\n output.set(chunk, offset);\n offset += chunk.length;\n }\n\n return Result.ok(output);\n}\n\n/**\n * Find the end position of IHDR chunk (including CRC)\n * @returns End offset or -1 if not found\n */\nfunction findIhdrChunkEnd(data: Uint8Array): number {\n const offset = PNG_SIGNATURE.length;\n\n if (offset + 8 > data.length) {\n return -1;\n }\n\n const length = readUint32BE(data, offset);\n const chunkType = readChunkType(data, offset + 4);\n\n if (chunkType !== 'IHDR') {\n return -1;\n }\n\n // Return end position: length(4) + type(4) + data(length) + crc(4)\n return offset + 4 + 4 + length + 4;\n}\n\n/**\n * Collect chunks from PNG, separating IHDR and non-text chunks\n */\nfunction collectNonTextChunks(data: Uint8Array): {\n ihdr: Uint8Array;\n others: Uint8Array[];\n} {\n const others: Uint8Array[] = [];\n let offset = PNG_SIGNATURE.length;\n let ihdr: Uint8Array = new Uint8Array(0);\n\n while (offset < data.length) {\n const chunkStart = offset;\n\n // Read chunk length\n if (offset + 4 > data.length) break;\n const length = readUint32BE(data, offset);\n offset += 4;\n\n // Read chunk type\n if (offset + 4 > data.length) break;\n const chunkType = readChunkType(data, offset);\n offset += 4;\n\n // Skip chunk data\n offset += length;\n\n // Skip CRC\n offset += 4;\n\n const chunkEnd = offset;\n const chunkData = data.slice(chunkStart, chunkEnd);\n\n if (chunkType === 'IHDR') {\n ihdr = chunkData;\n } else if (chunkType !== 'tEXt' && chunkType !== 'iTXt') {\n others.push(chunkData);\n }\n\n if (chunkType === 'IEND') {\n break;\n }\n }\n\n return { ihdr, others };\n}\n\n/**\n * Serialize a tEXt chunk to binary\n *\n * Per PNG spec, tEXt uses Latin-1 encoding. However, to maintain round-trip\n * compatibility with tools that incorrectly write UTF-8 (e.g., TensorArt),\n * we encode the text as UTF-8 bytes. This allows non-ASCII characters to\n * survive the read-write cycle.\n */\nfunction serializeTExtChunk(chunk: TExtChunk): Uint8Array {\n // Encode keyword as Latin-1 (keywords are ASCII-safe)\n const keyword = latin1Encode(chunk.keyword);\n // Encode text as UTF-8 (for round-trip compatibility with non-compliant tools)\n const text = utf8Encode(chunk.text);\n\n // Data: keyword + null + text\n const chunkData = new Uint8Array(keyword.length + 1 + text.length);\n chunkData.set(keyword, 0);\n chunkData[keyword.length] = 0; // null separator\n chunkData.set(text, keyword.length + 1);\n\n return buildChunk('tEXt', chunkData);\n}\n\n/**\n * Serialize an iTXt chunk to binary\n */\nfunction serializeITXtChunk(chunk: ITXtChunk): Uint8Array {\n // Encode strings\n const keyword = utf8Encode(chunk.keyword);\n const languageTag = utf8Encode(chunk.languageTag);\n const translatedKeyword = utf8Encode(chunk.translatedKeyword);\n const text = utf8Encode(chunk.text);\n\n // Calculate data size\n const dataSize =\n keyword.length +\n 1 + // null\n 1 + // compression flag\n 1 + // compression method\n languageTag.length +\n 1 + // null\n translatedKeyword.length +\n 1 + // null\n text.length;\n\n const chunkData = new Uint8Array(dataSize);\n let offset = 0;\n\n // Write keyword\n chunkData.set(keyword, offset);\n offset += keyword.length;\n chunkData[offset++] = 0; // null\n\n // Write compression flag and method\n chunkData[offset++] = chunk.compressionFlag;\n chunkData[offset++] = chunk.compressionMethod;\n\n // Write language tag\n chunkData.set(languageTag, offset);\n offset += languageTag.length;\n chunkData[offset++] = 0; // null\n\n // Write translated keyword\n chunkData.set(translatedKeyword, offset);\n offset += translatedKeyword.length;\n chunkData[offset++] = 0; // null\n\n // Write text\n chunkData.set(text, offset);\n\n return buildChunk('iTXt', chunkData);\n}\n\n/**\n * Build a complete PNG chunk with length, type, data, and CRC\n */\nfunction buildChunk(type: string, data: Uint8Array): Uint8Array {\n const chunk = new Uint8Array(4 + 4 + data.length + 4);\n\n // Write length (4 bytes, big-endian)\n writeUint32BE(chunk, 0, data.length);\n\n // Write type (4 bytes)\n for (let i = 0; i < 4; i++) {\n chunk[4 + i] = type.charCodeAt(i);\n }\n\n // Write data\n chunk.set(data, 8);\n\n // Calculate and write CRC (over type + data)\n const crcData = chunk.slice(4, 8 + data.length);\n const crc = calculateCrc32(crcData);\n writeUint32BE(chunk, 8 + data.length, crc);\n\n return chunk;\n}\n\n/**\n * Encode string as Latin-1 bytes\n */\nfunction latin1Encode(str: string): Uint8Array {\n const bytes = new Uint8Array(str.length);\n for (let i = 0; i < str.length; i++) {\n bytes[i] = str.charCodeAt(i) & 0xff;\n }\n return bytes;\n}\n\n/**\n * Encode string as UTF-8 bytes\n */\nfunction utf8Encode(str: string): Uint8Array {\n return new TextEncoder().encode(str);\n}\n\n// ============================================================================\n// CRC-32 Implementation (IEEE polynomial)\n// ============================================================================\n\n/** CRC-32 lookup table */\nconst CRC_TABLE = makeCrcTable();\n\n/**\n * Generate CRC-32 lookup table\n */\nfunction makeCrcTable(): Uint32Array {\n const table = new Uint32Array(256);\n for (let n = 0; n < 256; n++) {\n let c = n;\n for (let k = 0; k < 8; k++) {\n if (c & 1) {\n c = 0xedb88320 ^ (c >>> 1);\n } else {\n c = c >>> 1;\n }\n }\n table[n] = c >>> 0;\n }\n return table;\n}\n\n/**\n * Calculate CRC-32 checksum\n */\nfunction calculateCrc32(data: Uint8Array): number {\n let crc = 0xffffffff;\n for (let i = 0; i < data.length; i++) {\n crc = (CRC_TABLE[(crc ^ (data[i] ?? 0)) & 0xff] ?? 0) ^ (crc >>> 8);\n }\n return (crc ^ 0xffffffff) >>> 0;\n}\n","import type { MetadataSegment } from '../types';\nimport { Result } from '../types';\nimport { arraysEqual, isWebp, writeUint32LE } from '../utils/binary';\nimport { buildExifTiffData } from './exif';\n\n// Internal types (co-located with writer)\ntype WebpWriteError =\n | { type: 'invalidSignature' }\n | { type: 'invalidRiffStructure'; message: string };\n\ntype WebpWriteResult = Result<Uint8Array, WebpWriteError>;\n\n/** WebP file signature: \"RIFF\" */\nconst RIFF_SIGNATURE = new Uint8Array([0x52, 0x49, 0x46, 0x46]);\n\n/** WebP format marker: \"WEBP\" */\nconst WEBP_MARKER = new Uint8Array([0x57, 0x45, 0x42, 0x50]);\n\n/** EXIF chunk type */\nconst EXIF_CHUNK_TYPE = new Uint8Array([0x45, 0x58, 0x49, 0x46]);\n\n/**\n * Write WebP metadata to binary data\n *\n * Replaces existing EXIF chunk with new metadata.\n * All segments are written to the EXIF chunk based on their source type.\n *\n * @param data - Original WebP file data as Uint8Array\n * @param segments - Metadata segments to embed\n * @returns Result containing new WebP data with embedded metadata\n */\nexport function writeWebpMetadata(\n data: Uint8Array,\n segments: MetadataSegment[],\n): WebpWriteResult {\n // Validate WebP signature\n if (!isWebp(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n // Collect all chunks except EXIF\n const collectResult = collectNonExifChunks(data);\n if (!collectResult.ok) {\n return collectResult;\n }\n\n const { chunks } = collectResult.value;\n\n // Build new EXIF chunk from segments\n const exifChunk = buildExifChunk(segments);\n\n // Calculate new file size (excluding RIFF header)\n let newFileSize = 4; // \"WEBP\"\n for (const chunk of chunks) {\n newFileSize += chunk.length;\n }\n if (exifChunk) {\n newFileSize += exifChunk.length;\n }\n\n // Build output\n const output = new Uint8Array(8 + newFileSize);\n let offset = 0;\n\n // Write RIFF header\n output.set(RIFF_SIGNATURE, offset);\n offset += 4;\n writeUint32LE(output, offset, newFileSize);\n offset += 4;\n\n // Write WEBP marker\n output.set(WEBP_MARKER, offset);\n offset += 4;\n\n // Write EXIF chunk first if we have one (after VP8/VP8L/VP8X)\n // EXIF should come after the image chunk for best compatibility\n let exifWritten = false;\n\n for (const chunk of chunks) {\n // Write chunks in original order\n output.set(chunk, offset);\n offset += chunk.length;\n\n // Write EXIF after first image-related chunk (VP8, VP8L, VP8X)\n if (!exifWritten && exifChunk && isImageChunk(chunk)) {\n output.set(exifChunk, offset);\n offset += exifChunk.length;\n exifWritten = true;\n }\n }\n\n // If EXIF wasn't written yet (no VP8* chunk found), append it\n if (!exifWritten && exifChunk) {\n output.set(exifChunk, offset);\n }\n\n return Result.ok(output);\n}\n\n/**\n * Check if chunk is an image-related chunk (VP8, VP8L, VP8X)\n */\nfunction isImageChunk(chunk: Uint8Array): boolean {\n if (chunk.length < 4) return false;\n const type = String.fromCharCode(\n chunk[0] ?? 0,\n chunk[1] ?? 0,\n chunk[2] ?? 0,\n chunk[3] ?? 0,\n );\n return type === 'VP8 ' || type === 'VP8L' || type === 'VP8X';\n}\n\n/**\n * Collect all chunks except EXIF\n */\nfunction collectNonExifChunks(\n data: Uint8Array,\n): Result<\n { chunks: Uint8Array[]; firstChunkType: string },\n { type: 'invalidRiffStructure'; message: string }\n> {\n const chunks: Uint8Array[] = [];\n let firstChunkType = '';\n\n // Start after RIFF header (12 bytes: \"RIFF\" + size + \"WEBP\")\n let offset = 12;\n\n while (offset < data.length - 8) {\n // Read chunk type (4 bytes)\n const chunkType = data.slice(offset, offset + 4);\n const typeStr = String.fromCharCode(\n chunkType[0] ?? 0,\n chunkType[1] ?? 0,\n chunkType[2] ?? 0,\n chunkType[3] ?? 0,\n );\n\n if (!firstChunkType) {\n firstChunkType = typeStr;\n }\n\n // Read chunk size (4 bytes, little-endian)\n const chunkSize =\n (data[offset + 4] ?? 0) |\n ((data[offset + 5] ?? 0) << 8) |\n ((data[offset + 6] ?? 0) << 16) |\n ((data[offset + 7] ?? 0) << 24);\n\n // Validate chunk\n if (offset + 8 + chunkSize > data.length) {\n return Result.error({\n type: 'invalidRiffStructure',\n message: `Chunk extends beyond file at offset ${offset}`,\n });\n }\n\n // Keep all chunks except EXIF\n if (!arraysEqual(chunkType, EXIF_CHUNK_TYPE)) {\n // Include type + size + data (+ padding if odd)\n const paddedSize = chunkSize + (chunkSize % 2);\n const chunkData = data.slice(offset, offset + 8 + paddedSize);\n chunks.push(chunkData);\n }\n\n // Move to next chunk (chunk size + type + size fields)\n // RIFF chunks are padded to even byte boundaries\n const paddedSize = chunkSize + (chunkSize % 2);\n offset += 8 + paddedSize;\n }\n\n return Result.ok({ chunks, firstChunkType });\n}\n\n/**\n * Build EXIF chunk from metadata segments\n */\nfunction buildExifChunk(segments: MetadataSegment[]): Uint8Array | null {\n // Filter Exif-compatible segments\n const exifSegments = segments.filter(\n (s) =>\n s.source.type === 'exifUserComment' ||\n s.source.type === 'exifImageDescription' ||\n s.source.type === 'exifMake',\n );\n\n if (exifSegments.length === 0) {\n return null;\n }\n\n const tiffData = buildExifTiffData(exifSegments);\n\n if (tiffData.length === 0) {\n return null;\n }\n\n // Build EXIF chunk: type (4) + size (4) + TIFF data\n const chunkSize = tiffData.length;\n const paddedSize = chunkSize + (chunkSize % 2);\n const chunk = new Uint8Array(8 + paddedSize);\n\n chunk.set(EXIF_CHUNK_TYPE, 0);\n writeUint32LE(chunk, 4, chunkSize);\n chunk.set(tiffData, 8);\n\n return chunk;\n}\n","/**\n * Unified metadata stringification\n *\n * Builds A1111-format plain text from metadata and provides the stringify()\n * public API for converting any metadata type to a human-readable string.\n */\n\nimport type {\n BaseMetadata,\n CharacterPrompt,\n EmbedMetadata,\n GenerationMetadata,\n HiresSettings,\n ParseResult,\n RawMetadata,\n} from '../types';\n\n// ============================================================================\n// Internal Helpers\n// ============================================================================\n\n/**\n * Normalize line endings to LF (\\n)\n */\nfunction normalizeLineEndings(text: string): string {\n return text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n}\n\n/**\n * Build character prompts section\n *\n * Format: # Character N [x, y]:\\n[prompt]\n */\nfunction buildCharacterPromptsSection(\n characterPrompts: CharacterPrompt[],\n): string[] {\n return characterPrompts.flatMap((cp, index) => {\n const coords = cp.center ? ` [${cp.center.x}, ${cp.center.y}]` : '';\n return [\n `# Character ${index + 1}${coords}:`,\n normalizeLineEndings(cp.prompt),\n ];\n });\n}\n\n/**\n * Build settings line from BaseMetadata and optional extras\n *\n * Structured fields are output in A1111-standard order. Extras can override\n * structured fields (taking their position) or append as new entries at the end.\n */\nfunction buildSettingsLine(\n metadata: BaseMetadata,\n extras?: Record<string, string | number>,\n): string {\n const hires: HiresSettings | undefined = metadata.hires ?? metadata.upscale;\n\n const fields: Record<string, string | number | undefined> = {\n Steps: metadata.sampling?.steps,\n Sampler: metadata.sampling?.sampler,\n 'Schedule type': metadata.sampling?.scheduler,\n 'CFG scale': metadata.sampling?.cfg,\n Seed: metadata.sampling?.seed,\n Size:\n metadata.width > 0 && metadata.height > 0\n ? `${metadata.width}x${metadata.height}`\n : undefined,\n 'Model hash': metadata.model?.hash,\n Model: metadata.model?.name,\n 'Clip skip': metadata.sampling?.clipSkip,\n 'Denoising strength': hires?.denoise,\n 'Hires upscale': hires?.scale,\n 'Hires steps': hires?.steps,\n 'Hires upscaler': hires?.upscaler,\n };\n\n return Object.entries({ ...fields, ...extras })\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${k}: ${v}`)\n .join(', ');\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Build A1111-format text from EmbedMetadata\n *\n * Output structure:\n * 1. Positive prompt (line-ending normalized)\n * 2. Character prompts (if present)\n * 3. Negative prompt (if non-empty)\n * 4. Settings line (structured fields + extras)\n *\n * @param metadata - Embed metadata (extras included via `metadata.extras`)\n * @returns A1111-format plain text\n */\nexport function buildEmbedText(metadata: EmbedMetadata): string {\n return [\n normalizeLineEndings(metadata.prompt),\n metadata.characterPrompts?.length\n ? buildCharacterPromptsSection(metadata.characterPrompts).join('\\n')\n : undefined,\n metadata.negativePrompt\n ? `Negative prompt: ${normalizeLineEndings(metadata.negativePrompt)}`\n : undefined,\n buildSettingsLine(metadata, metadata.extras) || undefined,\n ]\n .filter((s): s is string => s !== undefined)\n .join('\\n');\n}\n\n/**\n * Format raw metadata as plain text\n *\n * Extracts text content from RawMetadata and returns it as a simple string.\n * Multiple entries are separated by double newlines.\n *\n * @param raw - Raw metadata from ParseResult\n * @returns Plain text content from the metadata\n */\nexport function formatRaw(raw: RawMetadata): string {\n switch (raw.format) {\n case 'png':\n return raw.chunks.map((chunk) => chunk.text).join('\\n\\n');\n\n case 'jpeg':\n case 'webp':\n return raw.segments.map((segment) => segment.data).join('\\n\\n');\n }\n}\n\n/**\n * Convert metadata to a human-readable string\n *\n * Accepts multiple input types:\n * - `ParseResult`: Automatically selects the best representation based on status\n * - `GenerationMetadata`: Formats as A1111 text (parsed metadata from any tool)\n * - `EmbedMetadata`: Formats as A1111 text (user-created custom metadata)\n *\n * @param input - Parse result, generation metadata, or embed metadata\n * @returns Human-readable text representation, or empty string if no data\n *\n * @example\n * ```typescript\n * import { read, stringify } from '@enslo/sd-metadata';\n *\n * // From parse result\n * const result = read(imageData);\n * const text = stringify(result);\n *\n * // From GenerationMetadata (e.g. after parsing)\n * if (result.status === 'success') {\n * const text3 = stringify(result.metadata);\n * }\n *\n * // From EmbedMetadata (e.g. user-created)\n * const text2 = stringify({\n * prompt: 'masterpiece, 1girl',\n * negativePrompt: '',\n * width: 512,\n * height: 768,\n * sampling: { steps: 20, sampler: 'Euler a', cfg: 7, seed: 12345 },\n * extras: { Version: 'v1.10.0' },\n * });\n * ```\n */\nexport function stringify(\n input: ParseResult | EmbedMetadata | GenerationMetadata,\n): string {\n if ('status' in input) {\n switch (input.status) {\n case 'success':\n return buildEmbedText(input.metadata);\n case 'unrecognized':\n return formatRaw(input.raw);\n case 'empty':\n case 'invalid':\n return '';\n }\n }\n return buildEmbedText(input);\n}\n","/**\n * Embed API for sd-metadata\n *\n * Write user-created or parsed metadata in A1111 format to PNG, JPEG,\n * or WebP images. Accepts both EmbedMetadata (custom metadata composed\n * by the user) and GenerationMetadata (parsed output from AI tools).\n */\n\nimport { createEncodedChunk } from '../converters/chunk-encoding';\nimport type { EmbedMetadata, GenerationMetadata, WriteResult } from '../types';\nimport { Result } from '../types';\nimport type { ImageFormat } from '../utils/binary';\nimport { detectFormat, toUint8Array } from '../utils/binary';\nimport { writeJpegMetadata } from '../writers/jpeg';\nimport { writePngMetadata } from '../writers/png';\nimport { writeWebpMetadata } from '../writers/webp';\nimport { buildEmbedText } from './stringify';\n\n/**\n * Embed metadata into an image\n *\n * Converts the provided metadata to A1111 plain text format and embeds it\n * into the image. Accepts {@link EmbedMetadata} for user-created custom metadata, or\n * {@link GenerationMetadata} for re-embedding parsed output from any AI tool.\n *\n * Extras (`metadata.extras`) allow adding arbitrary key-value pairs to the\n * settings line. If an extras key matches a structured field (e.g., \"Steps\"),\n * the extras value overrides the structured value at its original position.\n *\n * The metadata is stored differently based on image format:\n * - PNG: `parameters` tEXt/iTXt chunk (encoding auto-selected based on content)\n * - JPEG/WebP: Exif UserComment field\n *\n * @param input - Target image file data (Uint8Array or ArrayBuffer)\n * @param metadata - Metadata to embed (EmbedMetadata or GenerationMetadata)\n * @returns New image data with embedded metadata, or error\n *\n * @example\n * ```typescript\n * import { embed } from '@enslo/sd-metadata';\n *\n * const metadata = {\n * prompt: 'masterpiece, 1girl',\n * negativePrompt: 'lowres, bad quality',\n * width: 512,\n * height: 768,\n * sampling: { steps: 20, sampler: 'Euler a', cfg: 7, seed: 12345 },\n * model: { name: 'model.safetensors' },\n * };\n *\n * // Embed with custom extras\n * const result = embed(imageData, {\n * ...metadata,\n * extras: { Version: 'v1.10.0', 'Lora hashes': 'abc123' },\n * });\n *\n * if (result.ok) {\n * writeFileSync('output.png', result.value);\n * }\n * ```\n */\nexport function embed(\n input: Uint8Array | ArrayBuffer,\n metadata: EmbedMetadata | GenerationMetadata,\n): WriteResult {\n const data = toUint8Array(input);\n\n // Detect image format\n const format = detectFormat(data);\n if (!format) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Build A1111-format text\n const text = buildEmbedText(metadata);\n\n // Write format-specific metadata\n const writeResult = writeForFormat(format, data, text);\n\n if (!writeResult.ok) {\n return Result.error({\n type: 'writeFailed',\n message: writeResult.error.type,\n });\n }\n\n return Result.ok(writeResult.value);\n}\n\n/**\n * Write metadata text to image in format-specific way\n */\nfunction writeForFormat(format: ImageFormat, data: Uint8Array, text: string) {\n switch (format) {\n case 'png':\n return writePngMetadata(\n data,\n createEncodedChunk('parameters', text, 'dynamic'),\n );\n case 'jpeg':\n return writeJpegMetadata(data, [\n { source: { type: 'exifUserComment' as const }, data: text },\n ]);\n case 'webp':\n return writeWebpMetadata(data, [\n { source: { type: 'exifUserComment' as const }, data: text },\n ]);\n }\n}\n","/**\n * Object utility functions\n */\n\n/**\n * Remove undefined values from an object\n *\n * Creates a new object with only defined (non-undefined) values.\n * Returns undefined if all values are undefined (empty result).\n * Useful for building metadata objects where undefined fields should be omitted.\n *\n * @example\n * trimObject({ a: 1, b: undefined, c: 'hello' })\n * // => { a: 1, c: 'hello' }\n *\n * trimObject({ a: undefined, b: undefined })\n * // => undefined\n *\n * @param obj - Object with potentially undefined values\n * @returns New object with undefined values removed, or undefined if empty\n */\nexport function trimObject<T extends Record<string, unknown>>(\n obj: T,\n): Partial<T> | undefined {\n const result = Object.fromEntries(\n Object.entries(obj).filter(([, value]) => value !== undefined),\n ) as Partial<T>;\n return Object.keys(result).length === 0 ? undefined : result;\n}\n","import type { GenerationSoftware } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/** Software identifiers that use A1111-compatible text format */\ntype A1111Software = Extract<\n GenerationSoftware,\n | 'sd-webui'\n | 'sd-next'\n | 'forge'\n | 'forge-classic'\n | 'forge-neo'\n | 'reforge'\n | 'easy-reforge'\n | 'civitai'\n | 'fooocus'\n>;\n\n/**\n * Parse A1111-format metadata from entries\n *\n * A1111 format is used by:\n * - Stable Diffusion WebUI (AUTOMATIC1111)\n * - Forge family (Forge, Forge Classic, Forge Neo, reForge, EasyReforge)\n * - SD.Next\n * - Civitai (A1111 text fallback)\n *\n * Format:\n * ```\n * positive prompt\n * Negative prompt: negative prompt\n * Steps: 20, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, ...\n * ```\n *\n * @param entries - Metadata entries\n * @param software - Pre-detected software identifier from detectSoftware()\n * @returns Parsed metadata or error\n */\nexport function parseA1111(\n entries: EntryRecord,\n software: A1111Software,\n): InternalParseResult {\n // Find parameters entry (PNG uses 'parameters', JPEG/WebP uses 'UserComment')\n const text = entries.parameters ?? entries.UserComment;\n if (!text) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse the text into sections\n const { prompt, negativePrompt, settings } = parseParametersText(text);\n\n // Parse settings key-value pairs\n const settingsMap = parseSettings(settings);\n\n // Extract dimensions (optional, defaults to \"0x0\" like SD Prompt Reader)\n const size = settingsMap.get('Size') ?? '0x0';\n const [width, height] = parseSize(size);\n\n // Extract model settings\n const modelName = settingsMap.get('Model');\n const modelHash = settingsMap.get('Model hash');\n\n // Extract sampling settings\n const sampler = settingsMap.get('Sampler');\n const scheduler = settingsMap.get('Schedule type');\n const steps = parseNumber(settingsMap.get('Steps'));\n const cfg = parseNumber(\n settingsMap.get('CFG scale') ?? settingsMap.get('CFG Scale'),\n );\n const seed = parseNumber(settingsMap.get('Seed'));\n const clipSkip = parseNumber(settingsMap.get('Clip skip'));\n\n // Extract hires settings\n const hiresScale = parseNumber(settingsMap.get('Hires upscale'));\n const upscaler = settingsMap.get('Hires upscaler');\n const hiresSteps = parseNumber(settingsMap.get('Hires steps'));\n const denoise = parseNumber(settingsMap.get('Denoising strength'));\n const hiresSize = settingsMap.get('Hires size');\n const [hiresWidth] = parseSize(hiresSize ?? '');\n const scale = hiresScale ?? (hiresWidth > 0 ? hiresWidth / width : undefined);\n\n return Result.ok({\n software,\n prompt,\n negativePrompt,\n width,\n height,\n model: trimObject({ name: modelName, hash: modelHash }),\n sampling: trimObject({\n sampler,\n scheduler,\n steps,\n cfg,\n seed,\n clipSkip,\n }),\n hires: trimObject({ scale, upscaler, steps: hiresSteps, denoise }),\n });\n}\n\n/**\n * Parse parameters text into prompt, negative prompt, and settings\n */\nfunction parseParametersText(text: string): {\n prompt: string;\n negativePrompt: string;\n settings: string;\n} {\n // Find \"Negative prompt:\" marker\n const negativeIndex = text.indexOf('Negative prompt:');\n\n // Find the settings line (starts after the last newline before \"Steps:\")\n const stepsIndex = text.indexOf('Steps:');\n\n if (negativeIndex === -1 && stepsIndex === -1) {\n // No negative prompt, no settings - just prompt\n return { prompt: text.trim(), negativePrompt: '', settings: '' };\n }\n\n if (negativeIndex === -1) {\n // No negative prompt\n const settingsStart = text.lastIndexOf('\\n', stepsIndex);\n return {\n prompt: text.slice(0, settingsStart).trim(),\n negativePrompt: '',\n settings: text.slice(settingsStart).trim(),\n };\n }\n\n if (stepsIndex === -1) {\n // No settings (unusual)\n return {\n prompt: text.slice(0, negativeIndex).trim(),\n negativePrompt: text.slice(negativeIndex + 16).trim(),\n settings: '',\n };\n }\n\n // Both exist: find where negative prompt ends and settings begin\n const settingsStart = text.lastIndexOf('\\n', stepsIndex);\n\n return {\n prompt: text.slice(0, negativeIndex).trim(),\n negativePrompt: text.slice(negativeIndex + 16, settingsStart).trim(),\n settings: text.slice(settingsStart).trim(),\n };\n}\n\n/**\n * Parse settings line into key-value map\n *\n * Format: \"Key1: value1, Key2: value2, ...\"\n * Note: Values may contain commas (e.g., model names), so we parse carefully\n */\nfunction parseSettings(settings: string): Map<string, string> {\n const result = new Map<string, string>();\n if (!settings) return result;\n\n // Match \"Key: value\" pairs\n // Key is word characters (including spaces before colon)\n // Value continues until next \"Key:\" pattern or end\n const regex =\n /([A-Za-z][A-Za-z0-9 ]*?):\\s*([^,]+?)(?=,\\s*[A-Za-z][A-Za-z0-9 ]*?:|$)/g;\n\n for (const match of settings.matchAll(regex)) {\n const key = (match[1] ?? '').trim();\n const value = (match[2] ?? '').trim();\n result.set(key, value);\n }\n\n return result;\n}\n\n/**\n * Parse \"WxH\" size string\n */\nfunction parseSize(size: string): [number, number] {\n const match = size.match(/(\\d+)x(\\d+)/);\n if (!match) return [0, 0];\n return [\n Number.parseInt(match[1] ?? '0', 10),\n Number.parseInt(match[2] ?? '0', 10),\n ];\n}\n\n/**\n * Parse number from string, returning undefined if invalid\n */\nfunction parseNumber(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const num = Number.parseFloat(value);\n return Number.isNaN(num) ? undefined : num;\n}\n","/**\n * Type-safe JSON parsing utilities\n */\n\n/**\n * Discriminated union for JSON parse results.\n * Each variant contains the actual runtime type for proper narrowing.\n *\n * @typeParam T - Type for object values (default: Record<string, unknown>)\n */\nexport type JsonParseResult<T = Record<string, unknown>> =\n | { ok: true; type: 'object'; value: T }\n | { ok: true; type: 'array'; value: unknown[] }\n | { ok: true; type: 'string'; value: string }\n | { ok: true; type: 'number'; value: number }\n | { ok: true; type: 'boolean'; value: boolean }\n | { ok: true; type: 'null'; value: null }\n | { ok: false; error: { type: 'parseError'; message: string } };\n\n/**\n * Type-safe JSON parse with discriminated union result.\n *\n * Returns a result that includes the runtime type of the parsed value,\n * enabling proper TypeScript narrowing without unsafe type assertions.\n *\n * @typeParam T - Expected object type (used when type === 'object')\n * @param text - JSON string to parse\n * @returns Discriminated union with parsed value and its type, or parse error\n *\n * @example\n * // Without type parameter - value is Record<string, unknown>\n * const parsed = parseJson(text);\n * if (!parsed.ok || parsed.type !== 'object') return null;\n * const name = parsed.value.name; // unknown\n *\n * @example\n * // With type parameter - value is the specified type\n * const parsed = parseJson<MyInterface>(text);\n * if (!parsed.ok || parsed.type !== 'object') return null;\n * const name = parsed.value.name; // typed!\n */\nexport function parseJson<T = Record<string, unknown>>(\n text: string,\n): JsonParseResult<T> {\n try {\n const value: unknown = JSON.parse(text);\n return toJsonResult<T>(value);\n } catch {\n return {\n ok: false,\n error: { type: 'parseError', message: 'Invalid JSON' },\n };\n }\n}\n\n/**\n * Convert a parsed JSON value to a discriminated result\n */\nfunction toJsonResult<T>(value: unknown): JsonParseResult<T> {\n if (value === null) {\n return { ok: true, type: 'null', value: null };\n }\n if (Array.isArray(value)) {\n return { ok: true, type: 'array', value };\n }\n switch (typeof value) {\n case 'object':\n return { ok: true, type: 'object', value: value as T };\n case 'string':\n return { ok: true, type: 'string', value };\n case 'number':\n return { ok: true, type: 'number', value };\n case 'boolean':\n return { ok: true, type: 'boolean', value };\n default:\n // Should never happen with valid JSON\n return {\n ok: false,\n error: { type: 'parseError', message: 'Unexpected JSON type' },\n };\n }\n}\n","/**\n * ComfyUI node graph utilities\n *\n * Functions for searching and extracting data from ComfyUI node graphs.\n * Node graphs use class_type to identify node types and [nodeId, outputIndex]\n * arrays to reference connections between nodes.\n */\n\nimport type {\n ComfyNode,\n ComfyNodeGraph,\n ModelSettings,\n SamplingSettings,\n} from '../types';\n\n// =============================================================================\n// Node Class Type Constants\n// =============================================================================\n\nconst SAMPLER_TYPES = [\n 'KSampler',\n 'KSamplerAdvanced',\n 'SamplerCustomAdvanced',\n 'SamplerCustom',\n 'DetailerForEach',\n];\n\nconst LATENT_IMAGE_TYPES = ['EmptyLatentImage'];\nconst LATENT_IMAGE_RGTHREE_TYPES = ['SDXL Empty Latent Image (rgthree)'];\nconst CHECKPOINT_TYPES = ['CheckpointLoaderSimple', 'CheckpointLoader'];\nconst UNET_LOADER_TYPES = ['UNETLoader'];\nconst CLIP_SET_LAST_LAYER_TYPES = ['CLIPSetLastLayer'];\nconst HIRES_MODEL_UPSCALE_TYPES = ['UpscaleModelLoader'];\nconst HIRES_IMAGE_SCALE_TYPES = ['ImageScale', 'ImageScaleBy'];\nconst LATENT_UPSCALE_TYPES = ['LatentUpscale', 'LatentUpscaleBy'];\nconst VAE_ENCODE_TYPES = ['VAEEncode', 'VAEEncodeTiled'];\n\n// =============================================================================\n// Node Classification (Single-Pass)\n// =============================================================================\n\n/**\n * Pre-classified ComfyUI nodes by role\n *\n * Built by a single pass over the node graph.\n */\nexport interface ClassifiedNodes {\n sampler?: ComfyNode;\n latentImage?: ComfyNode;\n latentImageRgthree?: ComfyNode;\n checkpoint?: ComfyNode;\n unetLoader?: ComfyNode;\n clipSetLastLayer?: ComfyNode;\n hiresModelUpscale?: ComfyNode;\n hiresImageScale?: ComfyNode;\n latentUpscale?: ComfyNode;\n vaeEncode?: ComfyNode;\n}\n\n/**\n * Classify all nodes in a single pass over the node graph\n *\n * Replaces multiple findNode() calls with one iteration.\n * Stores the first matching node for each category.\n */\nexport function classifyNodes(nodes: ComfyNodeGraph): ClassifiedNodes {\n const result: ClassifiedNodes = {};\n\n for (const node of Object.values(nodes)) {\n const ct = node.class_type;\n\n if (!result.sampler && SAMPLER_TYPES.includes(ct)) {\n result.sampler = node;\n } else if (!result.latentImage && LATENT_IMAGE_TYPES.includes(ct)) {\n result.latentImage = node;\n } else if (\n !result.latentImageRgthree &&\n LATENT_IMAGE_RGTHREE_TYPES.includes(ct)\n ) {\n result.latentImageRgthree = node;\n } else if (!result.checkpoint && CHECKPOINT_TYPES.includes(ct)) {\n result.checkpoint = node;\n } else if (!result.unetLoader && UNET_LOADER_TYPES.includes(ct)) {\n result.unetLoader = node;\n } else if (\n !result.clipSetLastLayer &&\n CLIP_SET_LAST_LAYER_TYPES.includes(ct)\n ) {\n result.clipSetLastLayer = node;\n } else if (\n !result.hiresModelUpscale &&\n HIRES_MODEL_UPSCALE_TYPES.includes(ct)\n ) {\n result.hiresModelUpscale = node;\n } else if (\n !result.hiresImageScale &&\n HIRES_IMAGE_SCALE_TYPES.includes(ct)\n ) {\n result.hiresImageScale = node;\n } else if (!result.latentUpscale && LATENT_UPSCALE_TYPES.includes(ct)) {\n result.latentUpscale = node;\n } else if (!result.vaeEncode && VAE_ENCODE_TYPES.includes(ct)) {\n result.vaeEncode = node;\n }\n }\n\n return result;\n}\n\n// =============================================================================\n// Node Reference Utilities\n// =============================================================================\n\n/**\n * Resolve a node reference to the target node\n */\nfunction resolveNode(\n nodes: ComfyNodeGraph,\n ref: unknown,\n): ComfyNode | undefined {\n if (!isNodeReference(ref)) return undefined;\n return nodes[String(ref[0])];\n}\n\n/**\n * Check if a value is a node reference [nodeId, outputIndex]\n */\nexport function isNodeReference(value: unknown): value is [string, number] {\n return (\n Array.isArray(value) &&\n value.length === 2 &&\n (typeof value[0] === 'string' || typeof value[0] === 'number') &&\n typeof value[1] === 'number'\n );\n}\n\n/**\n * Extract text from a node, following references if needed\n *\n * Handles various text input patterns:\n * - CLIPTextEncode: inputs.text\n * - Power Prompt (rgthree): inputs.prompt\n * - DF_Text_Box (ComfyRoll): inputs.Text\n * - Reference chains: [nodeId, outputIndex] -> trace to source\n */\nexport function extractText(\n nodes: ComfyNodeGraph,\n nodeId: string,\n maxDepth = 10,\n): string {\n if (maxDepth <= 0) return '';\n\n const node = nodes[nodeId];\n if (!node) return '';\n\n // Try common text input names\n const textValue = node.inputs.text ?? node.inputs.prompt ?? node.inputs.Text;\n\n if (typeof textValue === 'string') {\n return textValue;\n }\n\n // If text is a reference, follow it\n if (isNodeReference(textValue)) {\n return extractText(nodes, String(textValue[0]), maxDepth - 1);\n }\n\n return '';\n}\n\n// =============================================================================\n// Prompt Extraction\n// =============================================================================\n\n/**\n * Resolve the node that holds positive/negative conditioning references\n *\n * KSampler/KSamplerAdvanced have positive/negative directly.\n * SamplerCustomAdvanced routes through a guider node (e.g. CFGGuider).\n */\nfunction resolveConditioningSource(\n nodes: ComfyNodeGraph,\n sampler: ComfyNode,\n): ComfyNode {\n const guiderNode = resolveNode(nodes, sampler.inputs.guider);\n if (guiderNode) return guiderNode;\n return sampler;\n}\n\n/**\n * Extract prompt texts by tracing from sampler's positive/negative inputs\n *\n * @param nodes - Full node graph (needed for reference following)\n * @param sampler - Pre-classified sampler node\n */\nexport function extractPromptTexts(\n nodes: ComfyNodeGraph,\n sampler: ComfyNode | undefined,\n): {\n promptText: string;\n negativeText: string;\n} {\n if (!sampler) {\n return { promptText: '', negativeText: '' };\n }\n\n // Resolve the node that holds positive/negative conditioning references.\n // KSampler/KSamplerAdvanced: directly on sampler inputs\n // SamplerCustomAdvanced: on the CFGGuider node via guider input\n const conditioningSource = resolveConditioningSource(nodes, sampler);\n\n return {\n promptText: extractTextFromConditioning(\n nodes,\n conditioningSource.inputs.positive,\n 'positive',\n ),\n negativeText: extractTextFromConditioning(\n nodes,\n conditioningSource.inputs.negative,\n 'negative',\n ),\n };\n}\n\n/**\n * Extract text by following a conditioning chain\n *\n * Conditioning may pass through intermediate nodes (e.g. ControlNet apply)\n * before reaching the CLIPTextEncode that holds the actual text.\n * This function first tries extractText on the target node; if no text is\n * found, it follows the same conditioning key (positive/negative) deeper.\n */\nfunction extractTextFromConditioning(\n nodes: ComfyNodeGraph,\n ref: unknown,\n condKey: 'positive' | 'negative',\n maxDepth = 10,\n): string {\n if (maxDepth <= 0 || !isNodeReference(ref)) return '';\n\n const nodeId = String(ref[0]);\n const text = extractText(nodes, nodeId);\n if (text) return text;\n\n // No text found — follow the conditioning chain\n const node = nodes[nodeId];\n if (!node) return '';\n\n const next = node.inputs[condKey];\n return extractTextFromConditioning(nodes, next, condKey, maxDepth - 1);\n}\n\n// =============================================================================\n// Dimension Extraction\n// =============================================================================\n\n/**\n * Extract dimensions from pre-classified LatentImage nodes\n *\n * Handles two node types separately:\n * - EmptyLatentImage: inputs.width and inputs.height as numbers\n * - SDXL Empty Latent Image (rgthree): inputs.dimensions as string like \"1024 x 1024 (square)\"\n *\n * @param latentImage - Standard EmptyLatentImage node\n * @param latentImageRgthree - rgthree SDXL latent image node\n */\nexport function extractDimensions(\n latentImage: ComfyNode | undefined,\n latentImageRgthree: ComfyNode | undefined,\n): {\n width: number;\n height: number;\n} {\n // Try standard EmptyLatentImage first (width/height properties)\n if (latentImage) {\n const width = Number(latentImage.inputs.width) || 0;\n const height = Number(latentImage.inputs.height) || 0;\n if (width > 0 && height > 0) return { width, height };\n }\n\n // Try rgthree latent image (dimensions string like \"1024 x 1024 (square)\")\n if (\n latentImageRgthree &&\n typeof latentImageRgthree.inputs.dimensions === 'string'\n ) {\n const match =\n latentImageRgthree.inputs.dimensions.match(/^(\\d+)\\s*x\\s*(\\d+)/);\n if (match?.[1] && match[2]) {\n return {\n width: Number.parseInt(match[1], 10),\n height: Number.parseInt(match[2], 10),\n };\n }\n }\n\n return { width: 0, height: 0 };\n}\n\n// =============================================================================\n// Sampling & Model Extraction\n// =============================================================================\n\n/**\n * Extract sampling settings from pre-classified sampler node\n *\n * @param nodes - Full node graph (needed for seed reference following)\n * @param sampler - Pre-classified sampler node\n */\nexport function extractSampling(\n nodes: ComfyNodeGraph,\n sampler: ComfyNode | undefined,\n): SamplingSettings | undefined {\n if (!sampler) return undefined;\n\n if (sampler.class_type === 'SamplerCustomAdvanced') {\n return extractAdvancedSampling(nodes, sampler);\n }\n\n if (sampler.class_type === 'SamplerCustom') {\n return extractCustomSampling(nodes, sampler);\n }\n\n // Handle seed which may be a reference or direct value\n let seed = sampler.inputs.seed;\n if (isNodeReference(seed)) {\n // Seed is from another node (e.g., CR Seed), try to extract it\n const seedNode = nodes[String(seed[0])];\n seed = seedNode?.inputs.seed;\n }\n\n return {\n seed: seed as number,\n steps: sampler.inputs.steps as number,\n cfg: sampler.inputs.cfg as number,\n sampler: sampler.inputs.sampler_name as string,\n scheduler: sampler.inputs.scheduler as string,\n denoise: sampler.inputs.denoise as number | undefined,\n };\n}\n\n/**\n * Extract sampling settings from SamplerCustomAdvanced\n *\n * Traces distributed inputs:\n * - noise → RandomNoise → noise_seed\n * - guider → CFGGuider → cfg\n * - sampler → KSamplerSelect → sampler_name\n * - sigmas → BasicScheduler → scheduler, steps, denoise\n */\nfunction extractAdvancedSampling(\n nodes: ComfyNodeGraph,\n sampler: ComfyNode,\n): SamplingSettings {\n const noiseNode = resolveNode(nodes, sampler.inputs.noise);\n const guiderNode = resolveNode(nodes, sampler.inputs.guider);\n const samplerSelectNode = resolveNode(nodes, sampler.inputs.sampler);\n const schedulerNode = resolveNode(nodes, sampler.inputs.sigmas);\n\n return {\n seed: noiseNode?.inputs.noise_seed as number,\n steps: schedulerNode?.inputs.steps as number,\n cfg: guiderNode?.inputs.cfg as number,\n sampler: samplerSelectNode?.inputs.sampler_name as string,\n scheduler: schedulerNode?.inputs.scheduler as string,\n denoise: schedulerNode?.inputs.denoise as number | undefined,\n };\n}\n\n/**\n * Extract sampling settings from SamplerCustom\n *\n * Similar to SamplerCustomAdvanced but cfg and noise_seed are located\n * differently:\n * - cfg: directly on the sampler node (not via guider)\n * - noise_seed: reference to a seed node (not via noise node)\n * - sampler → KSamplerSelect → sampler_name\n * - sigmas → BasicScheduler → scheduler, steps, denoise\n */\nfunction extractCustomSampling(\n nodes: ComfyNodeGraph,\n sampler: ComfyNode,\n): SamplingSettings {\n const samplerSelectNode = resolveNode(nodes, sampler.inputs.sampler);\n const schedulerNode = resolveNode(nodes, sampler.inputs.sigmas);\n\n // noise_seed may be a direct value or a reference to a seed node\n let seed = sampler.inputs.noise_seed;\n if (isNodeReference(seed)) {\n const seedNode = nodes[String(seed[0])];\n seed = seedNode?.inputs.seed;\n }\n\n return {\n seed: seed as number,\n steps: schedulerNode?.inputs.steps as number,\n cfg: sampler.inputs.cfg as number,\n sampler: samplerSelectNode?.inputs.sampler_name as string,\n scheduler: schedulerNode?.inputs.scheduler as string,\n denoise: schedulerNode?.inputs.denoise as number | undefined,\n };\n}\n\n/**\n * Extract model name from pre-classified model loader node\n *\n * Checks CheckpointLoader first, then falls back to UNETLoader.\n *\n * @param checkpoint - Pre-classified checkpoint loader node\n * @param unetLoader - Pre-classified UNET loader node (fallback)\n */\nexport function extractModel(\n checkpoint: ComfyNode | undefined,\n unetLoader?: ComfyNode | undefined,\n): ModelSettings | undefined {\n if (checkpoint?.inputs?.ckpt_name) {\n return { name: String(checkpoint.inputs.ckpt_name) };\n }\n if (unetLoader?.inputs?.unet_name) {\n return { name: String(unetLoader.inputs.unet_name) };\n }\n return undefined;\n}\n\n/**\n * Extract CLIP skip value from CLIPSetLastLayer node\n *\n * stop_at_clip_layer is negative (-2 = clip skip 2).\n * Returns undefined if no node is present.\n *\n * @param clipSetLastLayer - Pre-classified CLIPSetLastLayer node\n */\nexport function extractClipSkip(\n clipSetLastLayer: ComfyNode | undefined,\n): number | undefined {\n if (!clipSetLastLayer) return undefined;\n const stopAt = clipSetLastLayer.inputs.stop_at_clip_layer;\n if (typeof stopAt !== 'number') return undefined;\n return -stopAt;\n}\n\n// =============================================================================\n// Scale Calculation\n// =============================================================================\n\n/**\n * Calculate scale factor rounded to 2 decimal places\n *\n * @param targetWidth - Target width after scaling\n * @param baseWidth - Original base width\n * @returns Scale factor or undefined if invalid inputs\n */\nexport function calculateScale(\n targetWidth: number,\n baseWidth: number,\n): number | undefined {\n if (baseWidth <= 0 || targetWidth <= 0) return undefined;\n return Math.round((targetWidth / baseWidth) * 100) / 100;\n}\n\n// =============================================================================\n// Hires Detection\n// =============================================================================\n\n/**\n * Check if a sampler is a hires sampler by tracing its latent_image input\n *\n * Hires fix workflows have two patterns:\n * 1. Image space: KSampler.latent_image -> VAE Encode -> Upscale Image\n * 2. Latent space: KSampler.latent_image -> LatentUpscale\n *\n * @param nodes - ComfyUI node graph\n * @param sampler - Sampler node to check\n * @returns true if this sampler is connected to an upscaled pipeline\n */\nfunction isHiresSampler(nodes: ComfyNodeGraph, sampler: ComfyNode): boolean {\n const latentImageRef = sampler.inputs.latent_image;\n if (!isNodeReference(latentImageRef)) return false;\n\n const inputNode = nodes[String(latentImageRef[0])];\n if (!inputNode) return false;\n\n // Pattern 1: Latent space upscale (LatentUpscale -> KSampler)\n if (LATENT_UPSCALE_TYPES.includes(inputNode.class_type)) {\n return true;\n }\n\n // Pattern 2: Image space upscale (ImageScale -> VAEEncode -> KSampler)\n if (!VAE_ENCODE_TYPES.includes(inputNode.class_type)) return false;\n\n const pixelsRef = inputNode.inputs.pixels;\n if (!isNodeReference(pixelsRef)) return false;\n\n const upscaleNode = nodes[String(pixelsRef[0])];\n if (!upscaleNode) return false;\n\n return HIRES_IMAGE_SCALE_TYPES.includes(upscaleNode.class_type);\n}\n\n/**\n * Find hires sampler node by connection pattern\n *\n * Detects hires fix by tracing: KSampler.latent_image -> VAE Encode -> Upscale Image\n * This is more reliable than checking denoise < 1, which can be used in normal generation.\n */\nexport function findHiresSampler(nodes: ComfyNodeGraph): ComfyNode | undefined {\n return Object.values(nodes).find(\n (node) =>\n SAMPLER_TYPES.includes(node.class_type) && isHiresSampler(nodes, node),\n );\n}\n","/**\n * CivitAI extraMetadata handling for ComfyUI parser\n *\n * CivitAI stores original generation parameters in an extraMetadata field\n * when images are upscaled through their orchestration service.\n */\n\nimport type {\n ComfyNodeGraph,\n HiresSettings,\n ModelSettings,\n SamplingSettings,\n UpscaleSettings,\n} from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport { calculateScale } from './comfyui-nodes';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Civitai extraMetadata structure (nested JSON in prompt)\n */\nexport interface CivitaiExtraMetadata {\n prompt?: string;\n negativePrompt?: string;\n cfgScale?: number;\n sampler?: string;\n clipSkip?: number;\n steps?: number;\n seed?: number;\n width?: number;\n height?: number;\n baseModel?: string;\n transformations?: Array<{\n type?: string;\n upscaleWidth?: number;\n upscaleHeight?: number;\n }>;\n}\n\n/**\n * Partial metadata extracted from CivitAI extraMetadata\n */\nexport interface CivitaiPartialMetadata {\n prompt?: string;\n negativePrompt?: string;\n width?: number;\n height?: number;\n model?: ModelSettings;\n sampling?: SamplingSettings;\n hires?: HiresSettings;\n upscale?: UpscaleSettings;\n}\n\n// =============================================================================\n// Extraction\n// =============================================================================\n\n/**\n * Extract extraMetadata from ComfyUI prompt or entryRecord\n *\n * Civitai upscale workflows embed original generation params in extraMetadata field.\n * This can be:\n * 1. Inside the prompt JSON (JPEG format: single JSON with all data)\n * 2. As a separate entry (PNG format: extraMetadata as separate chunk)\n */\nexport function extractExtraMetadata(\n prompt: ComfyNodeGraph,\n entryRecord?: EntryRecord,\n): CivitaiExtraMetadata | undefined {\n // First try to find extraMetadata inside the prompt (JPEG format)\n const extraMetaField = prompt.extraMetadata;\n if (typeof extraMetaField === 'string') {\n const parsed = parseJson<CivitaiExtraMetadata>(extraMetaField);\n if (parsed.ok && parsed.type === 'object') return parsed.value;\n }\n\n // Fall back to separate entry (PNG format)\n if (entryRecord?.extraMetadata) {\n const parsed = parseJson<CivitaiExtraMetadata>(entryRecord.extraMetadata);\n if (parsed.ok && parsed.type === 'object') return parsed.value;\n }\n\n return undefined;\n}\n\n/**\n * Extract metadata from CivitAI extraMetadata (fallback source)\n *\n * Used when ComfyUI nodes don't contain the expected data\n * (e.g., upscale-only workflows from Civitai).\n *\n * @param extraMeta - CivitAI extraMetadata\n * @returns Partial metadata from CivitAI extraMetadata\n */\nexport function extractCivitaiMetadata(\n extraMeta: CivitaiExtraMetadata | undefined,\n): CivitaiPartialMetadata | undefined {\n if (!extraMeta) return undefined;\n\n const upscale = buildCivitaiUpscale(extraMeta);\n const sampling = buildCivitaiSampling(extraMeta);\n\n return trimObject({\n prompt: extraMeta.prompt,\n negativePrompt: extraMeta.negativePrompt,\n width: extraMeta.width,\n height: extraMeta.height,\n model: extraMeta.baseModel ? { name: extraMeta.baseModel } : undefined,\n ...sampling,\n ...upscale,\n });\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/**\n * Build upscale settings from CivitAI transformations\n *\n * CivitAI stores upscale information in extraMetadata.transformations array.\n * This extracts the upscale transformation and calculates the scale factor.\n */\nfunction buildCivitaiUpscale(\n extraMeta: CivitaiExtraMetadata,\n): Pick<CivitaiPartialMetadata, 'upscale'> {\n if (!extraMeta.transformations) return {};\n\n const upscaleTransform = extraMeta.transformations.find(\n (t) => t.type === 'upscale',\n );\n if (!upscaleTransform?.upscaleWidth) return {};\n\n const scale = calculateScale(\n upscaleTransform.upscaleWidth,\n extraMeta.width ?? 0,\n );\n if (scale === undefined) return {};\n\n return {\n upscale: { scale },\n };\n}\n\n/**\n * Build sampling settings from CivitAI extraMetadata\n *\n * Only creates sampling object if at least one field is defined.\n */\nfunction buildCivitaiSampling(\n extraMeta: CivitaiExtraMetadata,\n): Pick<CivitaiPartialMetadata, 'sampling'> {\n if (\n extraMeta.seed === undefined &&\n extraMeta.steps === undefined &&\n extraMeta.cfgScale === undefined &&\n extraMeta.sampler === undefined\n ) {\n return {};\n }\n\n return {\n sampling: {\n seed: extraMeta.seed,\n steps: extraMeta.steps,\n cfg: extraMeta.cfgScale,\n sampler: extraMeta.sampler,\n },\n };\n}\n","/**\n * ComfyUI metadata parser\n *\n * Parses ComfyUI-format prompt data from node graphs.\n * Also handles Civitai extraMetadata fallbacks for upscale workflows.\n */\n\nimport type {\n ComfyNodeGraph,\n HiresSettings,\n ModelSettings,\n SamplingSettings,\n UpscaleSettings,\n} from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport {\n extractCivitaiMetadata,\n extractExtraMetadata,\n} from './comfyui-civitai';\nimport {\n type ClassifiedNodes,\n calculateScale,\n classifyNodes,\n extractClipSkip,\n extractDimensions,\n extractModel,\n extractPromptTexts,\n extractSampling,\n findHiresSampler,\n isNodeReference,\n} from './comfyui-nodes';\nimport type { InternalParseResult } from './types';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/**\n * CivitAI extension keys that are not ComfyUI nodes\n *\n * These keys are stored alongside nodes in JPEG format but should be\n * excluded from the nodes object to maintain type safety.\n */\nconst CIVITAI_EXTENSION_KEYS = ['extra', 'extraMetadata', 'resource-stack'];\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Partial metadata extracted from a single source\n *\n * All fields optional - will be merged later with other sources.\n */\ninterface PartialMetadata {\n prompt?: string;\n negativePrompt?: string;\n width?: number;\n height?: number;\n model?: ModelSettings;\n sampling?: SamplingSettings;\n hires?: HiresSettings;\n upscale?: UpscaleSettings;\n}\n\n/**\n * Merged metadata with required base fields\n *\n * Result of merging ComfyUI and CivitAI metadata sources.\n */\ninterface MergedMetadata {\n prompt: string;\n negativePrompt: string;\n width: number;\n height: number;\n model?: ModelSettings;\n sampling?: SamplingSettings;\n hires?: HiresSettings;\n upscale?: UpscaleSettings;\n}\n\n// =============================================================================\n// Main Parser\n// =============================================================================\n\n/**\n * Parse ComfyUI metadata from entries\n *\n * ComfyUI stores metadata with:\n * - prompt: JSON containing node graph with inputs\n * - workflow: JSON containing the full workflow (stored in raw, not parsed)\n *\n * This parser extracts metadata from ComfyUI nodes and merges with\n * CivitAI extraMetadata fallbacks when available.\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseComfyUI(entries: EntryRecord): InternalParseResult {\n // Find and parse prompt JSON\n const promptText = findPromptJson(entries);\n if (!promptText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n const parsed = parseJson<ComfyNodeGraph>(promptText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in prompt entry',\n });\n }\n const prompt = parsed.value;\n\n // Extract metadata from both sources\n const comfyMetadata = extractComfyUIMetadata(prompt);\n const civitaiMetadata = extractCivitaiMetadata(\n extractExtraMetadata(prompt, entries),\n );\n\n // Merge with ComfyUI taking priority\n const merged = mergeMetadata(civitaiMetadata, comfyMetadata);\n\n return Result.ok({\n software: 'comfyui',\n // Build pure ComfyUI nodes (exclude CivitAI extensions)\n nodes: Object.fromEntries(\n Object.entries(prompt).filter(\n ([key]) => !CIVITAI_EXTENSION_KEYS.includes(key),\n ),\n ),\n ...merged,\n });\n}\n\n// =============================================================================\n// Denoise Normalization\n// =============================================================================\n\n/**\n * Strip denoise from sampling when it equals the default (1.0)\n *\n * denoise = 1.0 means full denoising (txt2img default) and is not meaningful\n * to store. Only non-default values (< 1.0, e.g. img2img / hires fix) are\n * kept in the output.\n */\nfunction normalizeDenoise(\n sampling: SamplingSettings | undefined,\n): SamplingSettings | undefined {\n if (!sampling || typeof sampling.denoise !== 'number') return sampling;\n if (sampling.denoise >= 1) {\n const { denoise: _, ...rest } = sampling;\n return rest;\n }\n return sampling;\n}\n\n// =============================================================================\n// JSON Utilities\n// =============================================================================\n\n/**\n * Clean JSON string for parsing\n *\n * Handles common issues in ComfyUI JSON:\n * - Remove null terminators that some tools append\n * - Replace NaN with null (NaN is not valid in JSON spec)\n */\nfunction cleanJsonString(json: string): string {\n return json.replace(/\\0+$/, '').replace(/:\\s*NaN\\b/g, ': null');\n}\n\n// =============================================================================\n// Prompt Finding\n// =============================================================================\n\n/**\n * Find ComfyUI prompt JSON from entry record\n *\n * PNG uses 'prompt', JPEG/WebP may use Comment, Description, or Make.\n */\nfunction findPromptJson(entryRecord: EntryRecord): string | undefined {\n // PNG format: prompt entry\n if (entryRecord.prompt) {\n return cleanJsonString(entryRecord.prompt);\n }\n\n // JPEG/WebP format: may be in various entries\n const candidates = [\n entryRecord.UserComment,\n entryRecord.ImageDescription,\n entryRecord.Make,\n entryRecord.Prompt, // save-image-extended uses this\n entryRecord.Workflow, // Not a prompt, but may contain nodes info\n ];\n\n for (const candidate of candidates) {\n if (!candidate) continue;\n\n // Check if it's JSON that looks like ComfyUI prompt\n if (candidate.startsWith('{')) {\n const cleaned = cleanJsonString(candidate);\n const parsed = parseJson(cleaned);\n if (!parsed.ok || parsed.type !== 'object') continue;\n\n // Check if it's wrapped in {\"prompt\": {...}} format\n if (parsed.value.prompt && typeof parsed.value.prompt === 'object') {\n return JSON.stringify(parsed.value.prompt);\n }\n // Check for nodes with class_type\n const values = Object.values(parsed.value);\n if (values.some((v) => v && typeof v === 'object' && 'class_type' in v)) {\n return cleaned; // Return cleaned JSON, not original candidate\n }\n }\n }\n\n return undefined;\n}\n\n// =============================================================================\n// ComfyUI Metadata Extraction\n// =============================================================================\n\n/**\n * Extract metadata from ComfyUI nodes\n *\n * Extracts prompt, dimensions, model, sampling, and hires settings\n * from standard ComfyUI node structure using class_type-based lookup.\n *\n * @param nodes - Parsed ComfyUI prompt (node graph)\n * @returns Partial metadata from ComfyUI nodes\n */\nexport function extractComfyUIMetadata(\n nodes: ComfyNodeGraph,\n): PartialMetadata | undefined {\n // Classify all nodes in a single pass\n const c = classifyNodes(nodes);\n\n // Extract from pre-classified nodes\n const { promptText, negativeText } = extractPromptTexts(nodes, c.sampler);\n const { width, height } = extractDimensions(\n c.latentImage,\n c.latentImageRgthree,\n );\n\n // Find hires sampler and resolve its sampling parameters\n const hiresSamplerNode = findHiresSampler(nodes);\n const hiresSampling = normalizeDenoise(\n hiresSamplerNode ? extractSampling(nodes, hiresSamplerNode) : undefined,\n );\n\n // Resolve hires scale from available sources\n const hiresScale = resolveHiresScale(nodes, c, width);\n\n const upscalerName = c.hiresModelUpscale?.inputs.model_name as\n | string\n | undefined;\n\n const rawSampling = normalizeDenoise(extractSampling(nodes, c.sampler));\n const clipSkip = extractClipSkip(c.clipSetLastLayer);\n\n return trimObject({\n prompt: promptText || undefined,\n negativePrompt: negativeText || undefined,\n width: width > 0 ? width : undefined,\n height: height > 0 ? height : undefined,\n model: extractModel(c.checkpoint, c.unetLoader),\n sampling: trimObject({ ...rawSampling, clipSkip }),\n ...buildHiresOrUpscale(upscalerName, hiresScale, hiresSampling),\n });\n}\n\n/**\n * Resolve hires scale factor from available node sources\n *\n * Priority:\n * 1. LatentUpscaleBy.scale_by (direct value)\n * 2. ImageScale.width as node reference → source node's clip_scale (rgthree)\n * 3. ImageScale.width as number → calculate ratio against base width\n */\nfunction resolveHiresScale(\n nodes: ComfyNodeGraph,\n c: ClassifiedNodes,\n baseWidth: number,\n): number | undefined {\n const latentUpscale = c.latentUpscale?.inputs;\n if (latentUpscale?.scale_by !== undefined) {\n return latentUpscale.scale_by as number;\n }\n\n const widthInput = c.hiresImageScale?.inputs.width;\n if (widthInput === undefined) return undefined;\n\n // Width from a node reference (e.g. rgthree provides scaled dimensions)\n if (isNodeReference(widthInput)) {\n const sourceNode = nodes[String(widthInput[0])];\n if (typeof sourceNode?.inputs.clip_scale === 'number') {\n return sourceNode.inputs.clip_scale;\n }\n return undefined;\n }\n\n // Width as direct number value\n if (typeof widthInput === 'number') {\n return calculateScale(widthInput, baseWidth);\n }\n\n return undefined;\n}\n\n/**\n * Build hires or upscale settings from resolved values\n *\n * @param upscalerName - Upscale model name (from UpscaleModelLoader)\n * @param scale - Pre-resolved scale factor\n * @param hiresSampling - Resolved sampling settings for hires sampler\n * @returns Hires or upscale settings\n */\nfunction buildHiresOrUpscale(\n upscalerName: string | undefined,\n scale: number | undefined,\n hiresSampling: SamplingSettings | undefined,\n): Pick<PartialMetadata, 'hires' | 'upscale'> {\n if (!upscalerName && scale === undefined && !hiresSampling) return {};\n\n if (hiresSampling) {\n return {\n hires: {\n upscaler: upscalerName,\n scale,\n steps: hiresSampling.steps,\n denoise: hiresSampling.denoise,\n },\n };\n }\n\n // Pure upscale without sampler requires upscaler model\n if (!upscalerName) return {};\n\n return {\n upscale: {\n upscaler: upscalerName,\n scale,\n },\n };\n}\n\n// =============================================================================\n// Metadata Merging\n// =============================================================================\n\n/**\n * Shallow merge two objects, skipping undefined values\n *\n * Unlike spread, undefined in override does NOT overwrite defined base values.\n * Returns undefined if result is empty.\n */\nfunction mergeObjects<T extends object>(\n base: T | undefined,\n override: T | undefined,\n): T | undefined {\n if (!base && !override) return undefined;\n const merged: Record<string, unknown> = {};\n for (const obj of [base, override]) {\n if (!obj) continue;\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined) merged[k] = v;\n }\n }\n return Object.keys(merged).length > 0 ? (merged as T) : undefined;\n}\n\n/**\n * Merge two partial metadata objects\n *\n * ComfyUI metadata takes priority over CivitAI fallback.\n * Handles:\n * - Required fields: defaults to empty/zero if both undefined\n * - Optional fields: omitted if undefined (not set to undefined)\n * - Nested objects (upscale, hires): deep merge preserving defined values from both\n *\n * @param base - Base metadata (lower priority, e.g., CivitAI fallback)\n * @param override - Override metadata (higher priority, e.g., ComfyUI nodes)\n * @returns Merged metadata with required fields and optional fields\n */\nfunction mergeMetadata(\n base: PartialMetadata | undefined,\n override: PartialMetadata | undefined,\n): MergedMetadata {\n // Deep merge for nested objects (upscale, hires)\n // mergeObjects excludes undefined values, preserving defined values from both sources\n const upscale = mergeObjects(base?.upscale, override?.upscale);\n const hires = mergeObjects(base?.hires, override?.hires);\n\n return {\n // Required fields with defaults (override takes priority)\n prompt: override?.prompt ?? base?.prompt ?? '',\n negativePrompt: override?.negativePrompt ?? base?.negativePrompt ?? '',\n width: override?.width ?? base?.width ?? 0,\n height: override?.height ?? base?.height ?? 0,\n // Optional fields - only include if defined\n ...trimObject({\n model: override?.model ?? base?.model,\n sampling: override?.sampling ?? base?.sampling,\n hires,\n upscale,\n }),\n };\n}\n","import type { GenerationSoftware } from '../types';\nimport type { EntryRecord } from '../utils/entries';\n\n// =============================================================================\n// Detection Marker Strings\n// =============================================================================\n\n// Unique chunk keywords\nconst M_INVOKEAI = 'invokeai_metadata';\nconst M_TENSORART = 'generation_data';\nconst M_STABILITY_MATRIX = 'smproj';\nconst M_CIVITAI_EXTRA = 'extraMetadata';\n\n// Content patterns\nconst M_SWARMUI = 'sui_image_params';\nconst M_SWARM_VERSION = 'swarm_version';\nconst M_COMFYUI_NODE = 'class_type';\nconst M_NOVELAI_SCHEDULE = 'noise_schedule';\nconst M_NOVELAI_V4 = 'v4_prompt';\nconst M_NOVELAI_UNCOND = 'uncond_scale';\nconst M_CIVITAI_NS = 'civitai:';\nconst M_CIVITAI_RESOURCES = 'Civitai resources:';\nconst M_RUINED_FOOOCUS = 'RuinedFooocus';\nconst M_EASYDIFFUSION = 'use_stable_diffusion_model';\nconst M_HF_MODEL = '\"Model\"';\nconst M_HF_RESOLUTION = '\"resolution\"';\nconst M_FOOOCUS_BASE = '\"base_model\"';\n\n/**\n * Detect generation software from metadata entries\n *\n * Analyzes entry keywords and content to identify the software that\n * generated the image. This centralized detection allows parsers to\n * focus on extracting structured data.\n *\n * @param entries - Metadata entries to analyze\n * @returns Detected software or null if unknown\n */\nexport function detectSoftware(\n entries: EntryRecord,\n): GenerationSoftware | null {\n // Tier 1: Fastest - unique keywords\n const uniqueResult = detectUniqueKeywords(entries);\n if (uniqueResult) return uniqueResult;\n\n // Tier 2: Format-specific structured detection\n const comfyResult = detectComfyUIEntries(entries);\n if (comfyResult) return comfyResult;\n\n // Tier 3: Content analysis\n const text = entries.parameters ?? entries.UserComment ?? entries.Comment;\n if (text) {\n return detectFromTextContent(text);\n }\n\n return null;\n}\n\n/**\n * Detect software from unique chunk keywords\n *\n * Checks for presence of keys that uniquely identify software.\n * Used for both direct entry records (PNG) and parsed JSON (JPEG/WebP conversions).\n */\nfunction detectByUniqueKey(\n record: Record<string, unknown>,\n): GenerationSoftware | null {\n if (M_INVOKEAI in record) return 'invokeai';\n if (M_TENSORART in record) return 'tensorart';\n if (M_STABILITY_MATRIX in record) return 'stability-matrix';\n if (M_CIVITAI_EXTRA in record) return 'civitai';\n return null;\n}\n\n/**\n * Detect software from unique keywords (Tier 1)\n *\n * Fast path: checks for presence of specific keywords that uniquely\n * identify each software. These are the most reliable indicators.\n *\n * Includes:\n * - Unique PNG chunk keywords\n * - Unique content patterns in parameters\n * - JPEG/WebP Comment JSON parsing (conversion cases)\n */\nfunction detectUniqueKeywords(\n entryRecord: EntryRecord,\n): GenerationSoftware | null {\n // ========================================\n // PNG Chunk Keywords\n // ========================================\n\n // NovelAI: Uses \"Software\" chunk with \"NovelAI\" value\n if (entryRecord.Software?.startsWith('NovelAI')) {\n return 'novelai';\n }\n\n // Unique key detection (InvokeAI, TensorArt, Stability Matrix, CivitAI)\n const keyResult = detectByUniqueKey(entryRecord);\n if (keyResult) return keyResult;\n\n // Fooocus: Has \"fooocus_scheme\" chunk (PNG only)\n if ('fooocus_scheme' in entryRecord) {\n return 'fooocus';\n }\n\n // Easy Diffusion: Has \"use_stable_diffusion_model\" keyword (unique to Easy Diffusion)\n if ('use_stable_diffusion_model' in entryRecord) {\n return 'easydiffusion';\n }\n\n // ========================================\n // Parameters Content Patterns\n // ========================================\n\n // SwarmUI: Check parameters for \"sui_image_params\"\n // MUST check here to catch it before ComfyUI detection\n const parameters = entryRecord.parameters;\n if (parameters?.includes(M_SWARMUI)) {\n return 'swarmui';\n }\n\n // ========================================\n // JPEG/WebP UserComment/Comment JSON\n // ========================================\n\n // Check UserComment (Exif) first, then Comment (JPEG COM)\n const comment = entryRecord.UserComment ?? entryRecord.Comment;\n if (comment?.startsWith('{')) {\n return detectFromCommentJson(comment);\n }\n\n return null;\n}\n\n/**\n * Detect software from Comment JSON (conversion cases)\n *\n * Handles PNG→JPEG/WebP conversions where chunks become JSON.\n */\nfunction detectFromCommentJson(comment: string): GenerationSoftware | null {\n try {\n const parsed = JSON.parse(comment) as Record<string, unknown>;\n\n // Unique key detection (InvokeAI, TensorArt, Stability Matrix, CivitAI)\n const keyResult = detectByUniqueKey(parsed);\n if (keyResult) return keyResult;\n\n // ComfyUI: Has both prompt and workflow in JSON\n if ('prompt' in parsed && 'workflow' in parsed) {\n const workflow = parsed.workflow;\n const prompt = parsed.prompt;\n\n const isObject =\n typeof workflow === 'object' || typeof prompt === 'object';\n const isJsonString =\n (typeof workflow === 'string' && workflow.startsWith('{')) ||\n (typeof prompt === 'string' && prompt.startsWith('{'));\n\n if (isObject || isJsonString) {\n return 'comfyui';\n }\n }\n\n // SwarmUI: Same as parameters check, but from Comment JSON\n if (M_SWARMUI in parsed) {\n return 'swarmui';\n }\n\n // SwarmUI alternative format\n if ('prompt' in parsed && 'parameters' in parsed) {\n const params = String(parsed.parameters || '');\n if (params.includes(M_SWARMUI) || params.includes(M_SWARM_VERSION)) {\n return 'swarmui';\n }\n }\n } catch {\n // Invalid JSON\n }\n\n return null;\n}\n\n/**\n * Detect ComfyUI from specific entry combinations (Tier 2)\n *\n * ComfyUI has unique entry combinations that can be detected\n * before analyzing text content.\n */\nfunction detectComfyUIEntries(\n entryRecord: EntryRecord,\n): GenerationSoftware | null {\n // ComfyUI: Both prompt AND workflow chunks exist\n // Checks both lowercase (PNG) and capitalized (EXIF prefix) keys\n if (\n ('prompt' in entryRecord && 'workflow' in entryRecord) ||\n ('Prompt' in entryRecord && 'Workflow' in entryRecord)\n ) {\n return 'comfyui';\n }\n\n // ComfyUI: Workflow chunk only (rare, but valid)\n if ('workflow' in entryRecord || 'Workflow' in entryRecord) {\n return 'comfyui';\n }\n\n // ComfyUI: Prompt chunk with workflow JSON data\n // IMPORTANT: Check SwarmUI and CivitAI FIRST\n const promptText = entryRecord.prompt ?? entryRecord.Prompt;\n if (promptText?.startsWith('{')) {\n // SwarmUI: Must check FIRST\n if (promptText.includes(M_SWARMUI)) {\n return 'swarmui';\n }\n\n // CivitAI: Has extraMetadata key in prompt JSON\n // This detects CivitAI Orchestration format where all data is in single prompt chunk\n if (promptText.includes(`\"${M_CIVITAI_EXTRA}\"`)) {\n return 'civitai';\n }\n\n // ComfyUI: Has class_type in prompt JSON\n if (promptText.includes(M_COMFYUI_NODE)) {\n return 'comfyui';\n }\n }\n\n return null;\n}\n\n/**\n * Detect software from text content (Tier 3)\n *\n * Analyzes text content which can be either JSON format or A1111 text format.\n * This is the slowest but most thorough detection path.\n */\nfunction detectFromTextContent(text: string): GenerationSoftware | null {\n // JSON format detection\n if (text.startsWith('{')) {\n return detectFromJsonFormat(text);\n }\n\n // A1111-style text format detection\n return detectFromA1111Format(text);\n}\n\n/**\n * Detect software from JSON-formatted metadata\n *\n * Priority order:\n * 1. Unique string patterns (most specific)\n * 2. Multi-field combinations (moderately specific)\n * 3. Generic patterns (least specific, fallback)\n */\nfunction detectFromJsonFormat(json: string): GenerationSoftware | null {\n // ========================================\n // Tier 1: Unique String Identifiers\n // ========================================\n\n // SwarmUI: Has \"sui_image_params\" (unique identifier)\n if (json.includes(M_SWARMUI)) {\n return 'swarmui';\n }\n\n // Ruined Fooocus: Has explicit software field\n if (\n json.includes(`\"software\":\"${M_RUINED_FOOOCUS}\"`) ||\n json.includes(`\"software\": \"${M_RUINED_FOOOCUS}\"`)\n ) {\n return 'ruined-fooocus';\n }\n\n // Easy Diffusion: Has unique field name\n if (json.includes(`\"${M_EASYDIFFUSION}\"`)) {\n return 'easydiffusion';\n }\n\n // CivitAI: Has \"civitai:\" namespace prefix OR \"extraMetadata\" key\n if (json.includes(M_CIVITAI_NS) || json.includes(`\"${M_CIVITAI_EXTRA}\"`)) {\n return 'civitai';\n }\n\n // ========================================\n // Tier 2: Multi-Field Combinations\n // ========================================\n\n // NovelAI: Has distinctive v4_prompt or noise_schedule fields\n if (\n json.includes(`\"${M_NOVELAI_V4}\"`) ||\n json.includes(`\"${M_NOVELAI_SCHEDULE}\"`) ||\n json.includes(`\"${M_NOVELAI_UNCOND}\"`) ||\n json.includes('\"Software\":\"NovelAI\"') ||\n json.includes(`\\\\\"${M_NOVELAI_SCHEDULE}\\\\\"`) ||\n json.includes(`\\\\\"${M_NOVELAI_V4}\\\\\"`)\n ) {\n return 'novelai';\n }\n\n // HuggingFace Space: Combination of Model + resolution\n if (json.includes(M_HF_MODEL) && json.includes(M_HF_RESOLUTION)) {\n return 'hf-space';\n }\n\n // Fooocus: Has prompt + base_model combination\n if (json.includes('\"prompt\"') && json.includes(M_FOOOCUS_BASE)) {\n return 'fooocus';\n }\n\n // ========================================\n // Tier 3: Generic Fallback Patterns\n // ========================================\n\n // ComfyUI: Has \"prompt\" or \"nodes\" (very generic, last resort)\n if (json.includes('\"prompt\"') || json.includes('\"nodes\"')) {\n return 'comfyui';\n }\n\n return null;\n}\n\n/**\n * Detect software from A1111-style text format\n *\n * Priority order:\n * 1. SwarmUI indicators (check first as it has unique markers)\n * 2. Version field analysis (sd-webui, forge family, comfyui)\n * 3. App field (SD.Next)\n * 4. Resource markers (Civitai)\n * 5. Default A1111 format (steps + sampler, fallback for embed-created data)\n */\nfunction detectFromA1111Format(text: string): GenerationSoftware | null {\n // ========================================\n // Tier 1: SwarmUI Detection\n // ========================================\n\n // SwarmUI: Has sui_image_params or swarm_version\n if (text.includes(M_SWARMUI) || text.includes(M_SWARM_VERSION)) {\n return 'swarmui';\n }\n\n // ========================================\n // Tier 2: Version Field Analysis\n // ========================================\n\n const versionMatch = text.match(/Version:\\s*([^\\s,]+)/);\n if (versionMatch) {\n const version = versionMatch[1];\n\n // SD WebUI: Version starts with \"v\" + digit (e.g., \"v1.10.1\")\n if (version && /^v\\d/.test(version)) {\n return 'sd-webui';\n }\n\n // Forge Classic: Version is literal \"classic\"\n if (version === 'classic') {\n return 'forge-classic';\n }\n\n // Forge Neo: Version starts with \"neo\"\n if (version === 'neo' || version?.startsWith('neo')) {\n return 'forge-neo';\n }\n\n // Forge family: Version starts with \"f\" followed by a digit\n if (version?.startsWith('f') && /^f\\d/.test(version)) {\n // EasyReforge: f{semver}-v pattern (dash directly after forge semver)\n if (/^f\\d+\\.\\d+(\\.\\d+)?-v/.test(version)) return 'easy-reforge';\n // reForge: f{semver}v{N}-v pattern (version number then dash-v)\n if (/^f\\d+\\.\\d+(\\.\\d+)?v\\d+-v/.test(version)) return 'reforge';\n // Forge: remaining f{digit} prefix (default for current Forge)\n return 'forge';\n }\n\n // Fooocus: Version starts with \"Fooocus\"\n if (version?.startsWith('Fooocus')) {\n return 'fooocus';\n }\n\n // ComfyUI: Version explicitly says \"ComfyUI\"\n if (version === 'ComfyUI') {\n return 'comfyui';\n }\n }\n\n // ========================================\n // Tier 3: Other Unique Text Markers\n // ========================================\n\n // SD.Next: Has App field with SD.Next value\n if (text.includes('App: SD.Next') || text.includes('App:SD.Next')) {\n return 'sd-next';\n }\n\n // Civitai: Has resource list marker\n if (text.includes(M_CIVITAI_RESOURCES)) {\n return 'civitai';\n }\n\n // ========================================\n // Tier 4: Default A1111 Format (fallback)\n // ========================================\n\n // SD-WebUI (fallback): Has typical A1111 parameters but no Version field.\n // Catches embed()-created data and legacy images without version info.\n if (\n text.includes('Steps:') ||\n text.includes('Sampler:') ||\n text.includes('Negative prompt:')\n ) {\n return 'sd-webui';\n }\n\n return null;\n}\n","import type { StandardMetadata } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * Easy Diffusion metadata parser\n *\n * ⚠️ UNVERIFIED: This parser has not been verified with actual Easy Diffusion samples.\n * The implementation is based on source code analysis of Easy Diffusion\n * (github.com/easydiffusion/easydiffusion). Please report any issues if you encounter\n * problems with Easy Diffusion metadata parsing.\n *\n * Easy Diffusion stores metadata using snake_case keys:\n * - PNG: Each field as a separate tEXt chunk (e.g., `prompt`, `seed`, `sampler_name`)\n * - JPEG/WebP: All fields as JSON in EXIF UserComment (0x9286)\n */\n\n/**\n * Extract model name from path\n *\n * Easy Diffusion stores full path like \"path/to/model.safetensors\"\n */\nfunction extractModelName(path: string | undefined): string | undefined {\n if (!path) return undefined;\n // Handle both Windows and POSIX paths\n const parts = path.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1];\n}\n\n/**\n * Parse Easy Diffusion metadata from entries\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseEasyDiffusion(entries: EntryRecord): InternalParseResult {\n // PNG: each field is a separate tEXt chunk\n if ('use_stable_diffusion_model' in entries) {\n return buildMetadata(entries);\n }\n\n // JPEG/WebP: all fields as JSON in UserComment (or parameters for conversion cases)\n const jsonText = entries.UserComment?.startsWith('{')\n ? entries.UserComment\n : entries.parameters?.startsWith('{')\n ? entries.parameters\n : undefined;\n\n if (!jsonText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n const parsed = parseJson<Record<string, unknown>>(jsonText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in Easy Diffusion metadata',\n });\n }\n\n return buildMetadata(parsed.value);\n}\n\n/**\n * Build metadata from a key-value record\n *\n * Handles both PNG entries (string values) and parsed JSON (typed values).\n * All keys use snake_case format matching Easy Diffusion's internal naming.\n */\nfunction buildMetadata(data: Record<string, unknown>): InternalParseResult {\n const str = (key: string): string | undefined => {\n const v = data[key];\n return typeof v === 'string' ? v : undefined;\n };\n const num = (key: string): number | undefined => {\n const v = Number(data[key]);\n return v || undefined;\n };\n\n const prompt = (str('prompt') ?? '').trim();\n const negativePrompt = (str('negative_prompt') ?? '').trim();\n const modelPath = str('use_stable_diffusion_model');\n\n // Upscale settings (post-processing, only present when upscaler is used)\n const upscale = trimObject({\n upscaler: str('use_upscale'),\n scale: num('upscale_amount'),\n });\n\n const metadata: StandardMetadata = {\n software: 'easydiffusion',\n prompt,\n negativePrompt,\n width: num('width') ?? 0,\n height: num('height') ?? 0,\n model: trimObject({\n name: extractModelName(modelPath),\n vae: str('use_vae_model'),\n }),\n sampling: trimObject({\n sampler: str('sampler_name'),\n steps: num('num_inference_steps'),\n cfg: num('guidance_scale'),\n seed: num('seed'),\n clipSkip: num('clip_skip'),\n denoise: num('prompt_strength'),\n }),\n ...(upscale ? { upscale } : {}),\n };\n\n return Result.ok(metadata);\n}\n","import type { StandardMetadata } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * Fooocus JSON metadata structure\n *\n * ⚠️ UNVERIFIED: This parser has not been verified with actual Fooocus samples.\n * The implementation is based on source code analysis of Fooocus\n * (github.com/lllyasviel/Fooocus). Please report any issues if you encounter\n * problems with Fooocus metadata parsing.\n *\n * Fooocus uses the \"fooocus\" metadata scheme which embeds JSON in:\n * - PNG: `parameters` tEXt chunk\n * - JPEG/WebP: EXIF UserComment (0x9286)\n */\ninterface FooocusJsonMetadata {\n prompt?: string;\n negative_prompt?: string;\n resolution?: string;\n base_model?: string;\n base_model_hash?: string;\n vae?: string;\n sampler?: string;\n scheduler?: string;\n seed?: number;\n guidance_scale?: number;\n steps?: number;\n clip_skip?: number;\n}\n\n/**\n * Parse resolution string \"(W, H)\" into width and height\n *\n * Fooocus stores resolution as a Python tuple string, e.g., \"(1024, 1024)\".\n *\n * @param resolution - Resolution string in format \"(width, height)\"\n * @returns Parsed dimensions or { width: 0, height: 0 } on failure\n */\nfunction parseResolution(resolution: string | undefined): {\n width: number;\n height: number;\n} {\n if (!resolution) return { width: 0, height: 0 };\n\n const match = resolution.match(/\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)/);\n if (!match?.[1] || !match[2]) return { width: 0, height: 0 };\n\n return {\n width: Number(match[1]),\n height: Number(match[2]),\n };\n}\n\n/**\n * Parse Fooocus JSON metadata from entries\n *\n * Reads from `parameters` (PNG) or `UserComment` (JPEG/WebP).\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseFooocus(entries: EntryRecord): InternalParseResult {\n const jsonText = entries.parameters ?? entries.UserComment;\n\n if (!jsonText || !jsonText.startsWith('{')) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n const parsed = parseJson<FooocusJsonMetadata>(jsonText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in Fooocus metadata',\n });\n }\n const json = parsed.value;\n\n const { width, height } = parseResolution(json.resolution);\n\n const metadata: StandardMetadata = {\n software: 'fooocus',\n prompt: json.prompt?.trim() ?? '',\n negativePrompt: json.negative_prompt?.trim() ?? '',\n width,\n height,\n model: trimObject({\n name: json.base_model,\n hash: json.base_model_hash,\n vae: json.vae,\n }),\n sampling: trimObject({\n sampler: json.sampler,\n scheduler: json.scheduler,\n steps: json.steps,\n cfg: json.guidance_scale,\n seed: json.seed,\n clipSkip: json.clip_skip,\n }),\n };\n\n return Result.ok(metadata);\n}\n","import type { StandardMetadata } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * HuggingFace Space upscaler settings\n *\n * Present when image was upscaled during generation.\n */\ninterface HfSpaceUpscaler {\n upscale_method?: string;\n upscaler_strength?: number;\n upscale_by?: number;\n upscale_steps?: number;\n new_resolution?: string;\n}\n\n/**\n * HuggingFace Space JSON metadata structure\n */\ninterface HfSpaceJsonMetadata {\n prompt?: string;\n negative_prompt?: string;\n resolution?: string;\n guidance_scale?: number;\n num_inference_steps?: number;\n style_preset?: string;\n seed?: number;\n sampler?: string;\n Model?: string;\n 'Model hash'?: string;\n use_upscaler?: HfSpaceUpscaler | null;\n}\n\n/**\n * Parse HuggingFace Space metadata from entries\n *\n * HuggingFace Spaces using Gradio + Diffusers store metadata as JSON\n * in the parameters chunk.\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseHfSpace(entries: EntryRecord): InternalParseResult {\n // Find parameters entry\n // PNG: stored in 'parameters' chunk\n // JPEG/WebP (after conversion): stored in 'UserComment' (from exifUserComment)\n const parametersText = entries.parameters ?? entries.UserComment;\n if (!parametersText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse JSON\n const parsed = parseJson<HfSpaceJsonMetadata>(parametersText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in parameters entry',\n });\n }\n const json = parsed.value;\n\n // Parse resolution (format: \"832 x 1216\")\n const parseResolution = (res?: string) => {\n const match = res?.match(/(\\d+)\\s*x\\s*(\\d+)/);\n return match?.[1] && match?.[2]\n ? {\n width: Number.parseInt(match[1], 10),\n height: Number.parseInt(match[2], 10),\n }\n : { width: 0, height: 0 };\n };\n const { width, height } = parseResolution(json.resolution);\n\n // Build metadata\n const metadata: Omit<StandardMetadata, 'raw'> = {\n software: 'hf-space',\n prompt: json.prompt ?? '',\n negativePrompt: json.negative_prompt ?? '',\n width,\n height,\n model: trimObject({\n name: json.Model,\n hash: json['Model hash'],\n }),\n sampling: trimObject({\n sampler: json.sampler,\n steps: json.num_inference_steps,\n cfg: json.guidance_scale,\n seed: json.seed,\n }),\n hires: json.use_upscaler\n ? trimObject({\n upscaler: json.use_upscaler.upscale_method,\n denoise: json.use_upscaler.upscaler_strength,\n scale: json.use_upscaler.upscale_by,\n steps: json.use_upscaler.upscale_steps,\n })\n : undefined,\n };\n\n return Result.ok(metadata);\n}\n","import { parseJson } from './json';\n\n/**\n * Entry record type (readonly for immutability)\n *\n * Maps metadata keywords to their text values for O(1) lookup.\n */\nexport type EntryRecord = Readonly<Record<string, string>>;\n\n/**\n * Extract a field from UserComment JSON\n *\n * Many parsers store metadata as JSON in the UserComment entry.\n * This utility extracts a specific field, handling both string and object values.\n *\n * @param entryRecord - Entry record from buildEntryRecord\n * @param key - JSON key to extract\n * @returns Extracted value as string, or undefined if not found\n *\n * @example\n * ```typescript\n * // UserComment: {\"generation_data\": \"{...}\", \"other\": {...}}\n * const data = extractFromCommentJson(entryRecord, 'generation_data');\n * // Returns the string value or JSON.stringify of object value\n * ```\n */\nexport function extractFromCommentJson(\n entryRecord: EntryRecord,\n key: string,\n): string | undefined {\n if (!entryRecord.UserComment?.startsWith('{')) return undefined;\n\n const parsed = parseJson(entryRecord.UserComment);\n if (!parsed.ok || parsed.type !== 'object') return undefined;\n\n const value = parsed.value[key];\n if (typeof value === 'string') return value;\n if (typeof value === 'object' && value !== null) return JSON.stringify(value);\n return undefined;\n}\n","import { Result } from '../types';\nimport { type EntryRecord, extractFromCommentJson } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * InvokeAI metadata JSON structure\n */\ninterface InvokeAIMetadataJson {\n positive_prompt?: string;\n negative_prompt?: string;\n width?: number;\n height?: number;\n seed?: number;\n steps?: number;\n cfg_scale?: number;\n scheduler?: string;\n model?: {\n name?: string;\n hash?: string;\n };\n upscale_model?: {\n name?: string;\n };\n upscale_scale?: number;\n}\n\n/**\n * Extract InvokeAI metadata from entry record\n *\n * Checks direct 'invokeai_metadata' entry first, then tries to extract from Comment JSON\n */\nfunction extractInvokeAIMetadata(entryRecord: EntryRecord): string | undefined {\n return (\n entryRecord.invokeai_metadata ??\n extractFromCommentJson(entryRecord, 'invokeai_metadata')\n );\n}\n\n/**\n * Parse InvokeAI metadata from entries\n *\n * InvokeAI stores metadata with:\n * - invokeai_metadata: JSON containing generation parameters\n * - invokeai_graph: JSON containing the full node graph (not parsed here)\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseInvokeAI(entries: EntryRecord): InternalParseResult {\n // Find invokeai_metadata entry\n // For PNG: direct keyword\n // For JPEG/WebP: inside Comment JSON\n const metadataText = extractInvokeAIMetadata(entries);\n\n if (!metadataText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse metadata JSON\n const parsed = parseJson<InvokeAIMetadataJson>(metadataText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in invokeai_metadata entry',\n });\n }\n const data = parsed.value;\n\n // Extract dimensions (fallback to 0 for IHDR extraction)\n const width = data.width ?? 0;\n const height = data.height ?? 0;\n\n // Build upscale settings if present\n const upscale = buildUpscale(data);\n\n return Result.ok({\n software: 'invokeai',\n prompt: data.positive_prompt ?? '',\n negativePrompt: data.negative_prompt ?? '',\n width,\n height,\n model: trimObject({\n name: data.model?.name,\n hash: data.model?.hash,\n }),\n sampling: trimObject({\n seed: data.seed,\n steps: data.steps,\n cfg: data.cfg_scale,\n sampler: data.scheduler,\n }),\n ...upscale,\n });\n}\n\n/**\n * Build upscale settings from InvokeAI metadata\n * - upscale_model.name: The upscaler model name\n * - upscale_scale: The scale factor (e.g., 2 for 2x upscale)\n */\nfunction buildUpscale(\n data: InvokeAIMetadataJson,\n): { upscale: { upscaler?: string; scale?: number } } | Record<string, never> {\n if (!data.upscale_model?.name && data.upscale_scale === undefined) {\n return {};\n }\n\n const upscale = trimObject({\n upscaler: data.upscale_model?.name,\n scale: data.upscale_scale,\n });\n\n return upscale ? { upscale } : {};\n}\n","import type { CharacterPrompt } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * NovelAI Comment JSON structure\n */\ninterface NovelAIComment {\n prompt: string;\n uc?: string;\n steps?: number;\n height?: number;\n width?: number;\n scale?: number;\n seed?: number;\n noise_schedule?: string;\n sampler?: string;\n /** V4 prompt structure */\n v4_prompt?: V4Prompt;\n /** V4 negative prompt structure */\n v4_negative_prompt?: V4Prompt;\n}\n\n/**\n * NovelAI V4 prompt structure\n */\ninterface V4Prompt {\n caption?: {\n base_caption?: string;\n char_captions?: Array<{\n char_caption?: string;\n centers?: Array<{ x: number; y: number }>;\n }>;\n };\n use_coords?: boolean;\n use_order?: boolean;\n}\n\n/**\n * Parse NovelAI metadata from entries\n *\n * NovelAI stores metadata with:\n * - Software: \"NovelAI\"\n * - Comment: JSON containing generation parameters\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseNovelAI(entries: EntryRecord): InternalParseResult {\n // Parse Comment JSON\n // NovelAI natively supports PNG and WebP, using Exif UserComment for JPEG/WebP.\n // COM segment (→ Comment) is a fallback for non-standard converted images.\n const commentText = entries.UserComment ?? entries.Comment;\n if (!commentText) {\n return Result.error({\n type: 'parseError',\n message: 'Missing Comment/UserComment entry',\n });\n }\n\n const parsed = parseJson<NovelAIComment>(commentText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in Comment entry',\n });\n }\n const comment = parsed.value;\n\n // Extract dimensions (fallback to 0 for IHDR extraction)\n const width = comment.width ?? 0;\n const height = comment.height ?? 0;\n\n // Extract prompt - prefer V4 base_caption if available\n const prompt =\n comment.v4_prompt?.caption?.base_caption ?? comment.prompt ?? '';\n const negativePrompt =\n comment.v4_negative_prompt?.caption?.base_caption ?? comment.uc ?? '';\n\n // Extract V4 character prompts\n const charCaptions = comment.v4_prompt?.caption?.char_captions;\n const characterPrompts =\n charCaptions && charCaptions.length > 0\n ? charCaptions\n .map((cc): CharacterPrompt | null => {\n if (!cc.char_caption) return null;\n return {\n prompt: cc.char_caption,\n center: cc.centers?.[0],\n };\n })\n .filter((cp): cp is CharacterPrompt => cp !== null)\n : undefined;\n\n return Result.ok({\n software: 'novelai',\n prompt,\n negativePrompt,\n width,\n height,\n sampling: trimObject({\n steps: comment.steps,\n cfg: comment.scale,\n seed: comment.seed,\n sampler: comment.sampler,\n scheduler: comment.noise_schedule,\n }),\n characterPrompts,\n useCoords: characterPrompts ? comment.v4_prompt?.use_coords : undefined,\n useOrder: characterPrompts ? comment.v4_prompt?.use_order : undefined,\n });\n}\n","import type { StandardMetadata } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport type { InternalParseResult } from './types';\n\n/**\n * Ruined Fooocus JSON metadata structure\n *\n * Ruined Fooocus stores metadata as JSON in the `parameters` chunk.\n * It has a `software` field set to \"RuinedFooocus\" for identification.\n */\ninterface RuinedFooocusJsonMetadata {\n Prompt?: string;\n Negative?: string;\n steps?: number;\n cfg?: number;\n width?: number;\n height?: number;\n seed?: number;\n sampler_name?: string;\n scheduler?: string;\n base_model_name?: string;\n base_model_hash?: string;\n loras?: Array<{ name: string; weight: number }>;\n clip_skip?: number;\n software?: string;\n}\n\n/**\n * Parse Ruined Fooocus metadata from entries\n *\n * Ruined Fooocus stores metadata as JSON in the `parameters` chunk,\n * with a `software` field set to \"RuinedFooocus\".\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseRuinedFooocus(entries: EntryRecord): InternalParseResult {\n // Find JSON in parameters entry\n // PNG: stored in 'parameters' chunk\n // JPEG/WebP (after conversion): stored in 'UserComment' (from exifUserComment)\n const jsonText = entries.parameters ?? entries.UserComment;\n\n if (!jsonText || !jsonText.startsWith('{')) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse JSON\n const parsed = parseJson<RuinedFooocusJsonMetadata>(jsonText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in Ruined Fooocus metadata',\n });\n }\n const json = parsed.value;\n\n const metadata: Omit<StandardMetadata, 'raw'> = {\n software: 'ruined-fooocus',\n prompt: json.Prompt?.trim() ?? '',\n negativePrompt: json.Negative?.trim() ?? '',\n width: json.width ?? 0,\n height: json.height ?? 0,\n model: {\n name: json.base_model_name,\n hash: json.base_model_hash,\n },\n sampling: {\n sampler: json.sampler_name,\n scheduler: json.scheduler,\n steps: json.steps,\n cfg: json.cfg,\n seed: json.seed,\n clipSkip: json.clip_skip,\n },\n };\n\n return Result.ok(metadata);\n}\n","import { Result } from '../types';\nimport { type EntryRecord, extractFromCommentJson } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { parseComfyUI } from './comfyui';\nimport type { InternalParseResult } from './types';\n\n/**\n * Stability Matrix parameters-json structure\n */\ninterface StabilityMatrixJson {\n PositivePrompt?: string;\n NegativePrompt?: string;\n Width?: number;\n Height?: number;\n Seed?: number;\n Steps?: number;\n CfgScale?: number;\n Sampler?: string;\n ModelName?: string;\n ModelHash?: string;\n}\n\n/**\n * Parse Stability Matrix metadata from entries\n *\n * Stability Matrix stores metadata with:\n * - prompt: ComfyUI-compatible workflow JSON (primary source)\n * - parameters-json: JSON containing generation parameters\n * - Used to override prompts (more complete than workflow)\n * - parameters: A1111-style text (fallback)\n * - smproj: Project data (not parsed here)\n *\n * Strategy:\n * 1. Parse as ComfyUI workflow (workflow, model, sampling, etc.)\n * 2. Override prompts from parameters-json (more complete)\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseStabilityMatrix(\n entries: EntryRecord,\n): InternalParseResult {\n // First, parse as ComfyUI workflow to get base metadata\n const comfyResult = parseComfyUI(entries);\n if (!comfyResult.ok || comfyResult.value.software !== 'comfyui') {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Find parameters-json entry for prompt/model override\n // PNG: stored in 'parameters-json' chunk\n // JPEG/WebP (after conversion): stored in 'Comment' as {\"parameters-json\": ..., ...}\n const jsonText =\n entries['parameters-json'] ??\n extractFromCommentJson(entries, 'parameters-json');\n const parsed = jsonText\n ? parseJson<StabilityMatrixJson>(jsonText)\n : undefined;\n const data =\n parsed?.ok && parsed.type === 'object' ? parsed.value : undefined;\n\n return Result.ok({\n ...comfyResult.value,\n software: 'stability-matrix',\n // Override prompts from parameters-json (more complete than workflow)\n prompt: data?.PositivePrompt ?? comfyResult.value.prompt,\n negativePrompt: data?.NegativePrompt ?? comfyResult.value.negativePrompt,\n // Override model if either name or hash is provided\n model:\n data?.ModelName !== undefined || data?.ModelHash !== undefined\n ? { name: data?.ModelName, hash: data?.ModelHash }\n : comfyResult.value.model,\n });\n}\n","import type { ComfyNodeGraph } from '../types';\nimport { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport type { InternalParseResult } from './types';\n\n/**\n * SwarmUI parameters JSON structure\n */\ninterface SwarmUIParameters {\n sui_image_params?: {\n prompt?: string;\n negativeprompt?: string;\n model?: string;\n seed?: number;\n steps?: number;\n cfgscale?: number;\n width?: number;\n height?: number;\n sampler?: string;\n scheduler?: string;\n // Refiner/Upscale settings\n refinerupscale?: number;\n refinerupscalemethod?: string;\n refinercontrolpercentage?: number;\n };\n}\n\n/**\n * Extract SwarmUI parameters from entry record\n *\n * Checks direct 'parameters' entry first, then tries to extract from Comment JSON.\n * After converter fix, Comment JSON contains direct sui_image_params (native WebP format).\n */\nfunction extractSwarmUIParameters(\n entryRecord: Record<string, string | undefined>,\n): string | undefined {\n // Direct parameters entry (PNG format)\n if (entryRecord.parameters) {\n return entryRecord.parameters;\n }\n\n // Try to extract from UserComment JSON (JPEG/WebP format)\n if (!entryRecord.UserComment) {\n return undefined;\n }\n\n const commentParsed = parseJson(entryRecord.UserComment);\n if (!commentParsed.ok || commentParsed.type !== 'object') {\n return undefined;\n }\n\n // Native WebP format: direct sui_image_params\n if ('sui_image_params' in commentParsed.value) {\n return entryRecord.UserComment; // Return as-is to preserve full structure\n }\n\n return undefined;\n}\n\n/**\n * Parse SwarmUI metadata from entries\n *\n * SwarmUI stores metadata with:\n * - parameters: JSON containing sui_image_params\n * - prompt: ComfyUI-style node graph (fallback)\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseSwarmUI(entries: EntryRecord): InternalParseResult {\n // Find parameters entry\n // For PNG: direct keyword 'parameters'\n // For JPEG/WebP: inside Comment JSON\n const parametersText = extractSwarmUIParameters(entries);\n\n if (!parametersText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse parameters JSON\n const parsed = parseJson<SwarmUIParameters>(parametersText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in parameters entry',\n });\n }\n\n // Verify SwarmUI format (has sui_image_params)\n const params = parsed.value.sui_image_params;\n if (!params) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Extract dimensions (fallback to 0 for IHDR extraction)\n const width = params.width ?? 0;\n const height = params.height ?? 0;\n\n // Parse nodes from prompt chunk (PNG format) or Make field (JPEG/WebP extended format)\n const promptSource = entries.prompt || entries.Make;\n const promptParsed = promptSource\n ? parseJson<ComfyNodeGraph>(promptSource)\n : undefined;\n const nodes =\n promptParsed?.ok && promptParsed.type === 'object'\n ? promptParsed.value\n : undefined;\n\n return Result.ok({\n software: 'swarmui',\n prompt: params.prompt ?? '',\n negativePrompt: params.negativeprompt ?? '',\n width,\n height,\n nodes,\n model: trimObject({ name: params.model }),\n sampling: trimObject({\n seed: params.seed,\n steps: params.steps,\n cfg: params.cfgscale,\n sampler: params.sampler,\n scheduler: params.scheduler,\n }),\n hires: trimObject({\n scale: params.refinerupscale,\n upscaler: params.refinerupscalemethod,\n denoise: params.refinercontrolpercentage,\n }),\n });\n}\n","import type { ComfyNodeGraph } from '../types';\nimport { Result } from '../types';\nimport { type EntryRecord, extractFromCommentJson } from '../utils/entries';\nimport { parseJson } from '../utils/json';\nimport { trimObject } from '../utils/object';\nimport { extractComfyUIMetadata } from './comfyui';\nimport type { InternalParseResult } from './types';\n\n/**\n * TensorArt generation_data JSON structure\n *\n * Only prompt, negativePrompt, and baseModel are used from generation_data.\n * All other parameters (sampling, dimensions, etc.) are extracted from the\n * ComfyUI node graph, which provides more complete information.\n */\ninterface TensorArtGenerationData {\n prompt?: string;\n negativePrompt?: string;\n baseModel?: {\n modelFileName?: string;\n hash?: string;\n };\n}\n\n/**\n * Parse TensorArt metadata from entries\n *\n * TensorArt stores metadata with:\n * - generation_data: JSON containing generation parameters\n * - prompt: ComfyUI-style node graph (workflow)\n *\n * Strategy: Delegate node graph parsing to the ComfyUI parser for sampling,\n * dimensions, hires, and upscale. Only prompt text and model info are taken\n * from generation_data, since TensorArt uses a custom checkpoint loader\n * (ECHOCheckpointLoaderSimple) that the standard ComfyUI parser cannot detect.\n *\n * @param entries - Metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseTensorArt(entries: EntryRecord): InternalParseResult {\n // Find generation_data entry\n // PNG: stored in 'generation_data' chunk\n // JPEG/WebP (after conversion): stored in 'Comment' as {\"generation_data\": ..., \"prompt\": ...}\n const dataText =\n entries.generation_data ??\n extractFromCommentJson(entries, 'generation_data');\n const promptChunk =\n entries.prompt ?? extractFromCommentJson(entries, 'prompt');\n\n if (!dataText) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n\n // Parse JSON (TensorArt appends NUL characters)\n const cleanedText = dataText.replace(/\\0+$/, '');\n const parsed = parseJson<TensorArtGenerationData>(cleanedText);\n if (!parsed.ok || parsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in generation_data entry',\n });\n }\n const data = parsed.value;\n\n // Parse nodes from prompt chunk (required for TensorArt)\n if (!promptChunk) {\n return Result.error({ type: 'unsupportedFormat' });\n }\n const promptParsed = parseJson<ComfyNodeGraph>(promptChunk);\n if (!promptParsed.ok || promptParsed.type !== 'object') {\n return Result.error({\n type: 'parseError',\n message: 'Invalid JSON in prompt chunk',\n });\n }\n const nodes = promptParsed.value;\n\n // Delegate node parsing to ComfyUI parser\n const comfy = extractComfyUIMetadata(nodes);\n\n // Model from generation_data (ECHOCheckpointLoaderSimple is not detected by ComfyUI parser)\n const model = trimObject({\n name: data.baseModel?.modelFileName,\n hash: data.baseModel?.hash,\n });\n\n return Result.ok({\n software: 'tensorart',\n nodes,\n // Prompt from generation_data, fallback to nodes\n prompt: data.prompt ?? comfy?.prompt ?? '',\n negativePrompt: data.negativePrompt ?? comfy?.negativePrompt ?? '',\n // Dimensions from nodes, fallback to generation_data\n width: comfy?.width ?? 0,\n height: comfy?.height ?? 0,\n // Model from generation_data, fallback to nodes\n model: model ?? comfy?.model,\n // Sampling, hires, upscale entirely from nodes\n sampling: comfy?.sampling,\n hires: comfy?.hires,\n upscale: comfy?.upscale,\n });\n}\n","import { Result } from '../types';\nimport type { EntryRecord } from '../utils/entries';\nimport { parseA1111 } from './a1111';\nimport { parseComfyUI } from './comfyui';\nimport { detectSoftware } from './detect';\nimport { parseEasyDiffusion } from './easydiffusion';\nimport { parseFooocus } from './fooocus';\nimport { parseHfSpace } from './hf-space';\nimport { parseInvokeAI } from './invokeai';\nimport { parseNovelAI } from './novelai';\nimport { parseRuinedFooocus } from './ruined-fooocus';\nimport { parseStabilityMatrix } from './stability-matrix';\nimport { parseSwarmUI } from './swarmui';\nimport { parseTensorArt } from './tensorart';\nimport type { InternalParseResult } from './types';\n\n/**\n * Parse metadata entries to unified format\n *\n * Automatically detects the generation software and applies the appropriate parser.\n * This function returns metadata WITHOUT the `raw` field; callers should attach it.\n *\n * @param entries - Format-agnostic metadata entries\n * @returns Parsed metadata or error\n */\nexport function parseMetadata(entries: EntryRecord): InternalParseResult {\n // Detect software from entries\n const software = detectSoftware(entries);\n\n // Route to appropriate parser based on detected software\n switch (software) {\n case 'novelai':\n return parseNovelAI(entries);\n\n case 'sd-webui':\n case 'forge':\n case 'forge-classic':\n case 'forge-neo':\n case 'reforge':\n case 'easy-reforge':\n case 'sd-next':\n return parseA1111(entries, software);\n\n case 'hf-space':\n return parseHfSpace(entries);\n\n case 'civitai': {\n // Civitai uses ComfyUI JSON format with custom structure\n const comfyResult = parseComfyUI(entries);\n if (comfyResult.ok) {\n // Override software to preserve CivitAI detection\n comfyResult.value.software = 'civitai';\n return comfyResult;\n }\n // Fallback to A1111 format\n return parseA1111(entries, 'civitai');\n }\n\n case 'comfyui': {\n // ComfyUI can use either JSON or A1111 text format (e.g., comfy-image-saver)\n const comfyResult = parseComfyUI(entries);\n if (comfyResult.ok) return comfyResult;\n // Fallback to A1111 text format (treat as sd-webui since Version is absent)\n return parseA1111(entries, 'sd-webui');\n }\n\n case 'invokeai':\n return parseInvokeAI(entries);\n\n case 'swarmui':\n return parseSwarmUI(entries);\n\n case 'tensorart':\n return parseTensorArt(entries);\n\n case 'stability-matrix':\n return parseStabilityMatrix(entries);\n\n case 'easydiffusion':\n return parseEasyDiffusion(entries);\n\n case 'fooocus': {\n // Fooocus supports two user-selectable metadata schemes:\n // - 'fooocus': JSON format (default)\n // - 'a1111': A1111-compatible text format\n const scheme = entries.fooocus_scheme;\n const content = entries.parameters ?? entries.UserComment;\n if (scheme === 'a1111' || (content && !content.startsWith('{'))) {\n return parseA1111(entries, 'fooocus');\n }\n return parseFooocus(entries);\n }\n\n case 'ruined-fooocus':\n return parseRuinedFooocus(entries);\n\n default:\n return Result.error({ type: 'unsupportedFormat' });\n }\n}\n","/**\n * Image dimension extraction from binary headers\n *\n * Reads width and height from image format headers without parsing metadata.\n * Supports PNG (IHDR), JPEG (SOF markers), and WebP (VP8X/VP8/VP8L).\n */\n\nimport type { ImageFormat } from '../utils/binary';\nimport {\n readChunkType,\n readUint24LE,\n readUint32BE,\n readUint32LE,\n} from '../utils/binary';\n\nexport interface ImageDimensions {\n width: number;\n height: number;\n}\n\n/**\n * Read image dimensions from binary data based on format\n */\nexport function readImageDimensions(\n data: Uint8Array,\n format: ImageFormat,\n): ImageDimensions | null {\n if (format === 'png') return readPngDimensions(data);\n if (format === 'jpeg') return readJpegDimensions(data);\n return readWebpDimensions(data);\n}\n\n/**\n * Read width and height from PNG IHDR chunk\n */\nfunction readPngDimensions(data: Uint8Array): ImageDimensions | null {\n const PNG_SIGNATURE_LENGTH = 8;\n if (data.length < 24) return null;\n // IHDR data starts at offset 16 (8 sig + 4 len + 4 type)\n // We assume valid PNG if detectFormat passed, and IHDR is always first.\n return {\n width: readUint32BE(data, PNG_SIGNATURE_LENGTH + 8),\n height: readUint32BE(data, PNG_SIGNATURE_LENGTH + 12),\n };\n}\n\n/**\n * Read width and height from JPEG SOF markers\n */\nfunction readJpegDimensions(data: Uint8Array): ImageDimensions | null {\n let offset = 2;\n while (offset < data.length - 4) {\n if (data[offset] !== 0xff) {\n offset++;\n continue;\n }\n\n const marker = data[offset + 1] ?? 0;\n if (marker === 0xff) {\n offset++;\n continue; // Padding\n }\n\n // Read length (16-bit BE)\n const length = ((data[offset + 2] ?? 0) << 8) | (data[offset + 3] ?? 0);\n\n // SOF0 (C0) ... SOF15 (CF), except C4 (DHT), C8 (JPG), CC (DAC)\n if (\n marker >= 0xc0 &&\n marker <= 0xcf &&\n marker !== 0xc4 &&\n marker !== 0xc8 &&\n marker !== 0xcc\n ) {\n // Structure: Precision(1), Height(2), Width(2)\n // Offset: Marker(2) + Length(2) + Precision(1) = 5\n const height = ((data[offset + 5] ?? 0) << 8) | (data[offset + 6] ?? 0);\n const width = ((data[offset + 7] ?? 0) << 8) | (data[offset + 8] ?? 0);\n return { width, height };\n }\n\n offset += 2 + length;\n if (marker === 0xda) break; // SOS\n }\n return null;\n}\n\n/**\n * Read width and height from WebP chunks (VP8X, VP8, VP8L)\n */\nfunction readWebpDimensions(data: Uint8Array): ImageDimensions | null {\n // RIFF(4) + Size(4) + WEBP(4) = 12 bytes\n let offset = 12;\n\n while (offset < data.length) {\n if (offset + 8 > data.length) break;\n\n const chunkType = readChunkType(data, offset);\n const chunkSize = readUint32LE(data, offset + 4);\n const paddedSize = chunkSize + (chunkSize % 2);\n\n if (chunkType === 'VP8X') {\n // VP8X: Width (3 bytes @ offset 12) + Height (3 bytes @ offset 15)\n // Both are 1-based (stored value is width-1)\n const wMinus1 = readUint24LE(data, offset + 12);\n const hMinus1 = readUint24LE(data, offset + 15);\n return { width: wMinus1 + 1, height: hMinus1 + 1 };\n }\n\n if (chunkType === 'VP8 ') {\n // VP8 (lossy): Check keyframe\n const start = offset + 8;\n const tag =\n (data[start] ?? 0) |\n ((data[start + 1] ?? 0) << 8) |\n ((data[start + 2] ?? 0) << 16);\n const keyFrame = !(tag & 1);\n\n if (keyFrame) {\n // Validation code: 0x9d 0x01 0x2a bytes @ start+3\n if (\n data[start + 3] === 0x9d &&\n data[start + 4] === 0x01 &&\n data[start + 5] === 0x2a\n ) {\n const wRaw = (data[start + 6] ?? 0) | ((data[start + 7] ?? 0) << 8);\n const hRaw = (data[start + 8] ?? 0) | ((data[start + 9] ?? 0) << 8);\n return { width: wRaw & 0x3fff, height: hRaw & 0x3fff };\n }\n }\n }\n\n if (chunkType === 'VP8L') {\n // VP8L (lossless)\n if (data[offset + 8] === 0x2f) {\n const bits = readUint32LE(data, offset + 9);\n const width = (bits & 0x3fff) + 1;\n const height = ((bits >> 14) & 0x3fff) + 1;\n return { width, height };\n }\n }\n\n offset += 8 + paddedSize;\n }\n return null;\n}\n","/**\n * Exif reading utilities\n *\n * Functions for parsing Exif/TIFF structures and extracting metadata segments.\n */\n\nimport type { MetadataSegment } from '../types';\nimport { readUint16, readUint32 } from '../utils/binary';\nimport {\n EXIF_IFD_POINTER_TAG,\n IMAGE_DESCRIPTION_TAG,\n MAKE_TAG,\n USER_COMMENT_TAG,\n} from '../utils/exif-constants';\n\n/**\n * Parse Exif TIFF structure and extract all metadata segments\n *\n * Extracts metadata from:\n * - ImageDescription (0x010E) - Used by ComfyUI Save Image Extended (with \"Workflow:\" prefix)\n * - Make (0x010F) - Used by ComfyUI Save Image Extended (with \"Prompt:\" prefix)\n * - UserComment (0x9286) - Used by most tools\n *\n * @param exifData - TIFF data (starting with II/MM byte order marker)\n * @returns Array of metadata segments found\n */\nexport function parseExifMetadataSegments(\n exifData: Uint8Array,\n): MetadataSegment[] {\n if (exifData.length < 8) return [];\n\n // Check TIFF byte order\n const isLittleEndian = exifData[0] === 0x49 && exifData[1] === 0x49; // \"II\"\n const isBigEndian = exifData[0] === 0x4d && exifData[1] === 0x4d; // \"MM\"\n\n if (!isLittleEndian && !isBigEndian) return [];\n\n // Verify TIFF magic number (42)\n const magic = readUint16(exifData, 2, isLittleEndian);\n if (magic !== 42) return [];\n\n // Get IFD0 offset\n const ifd0Offset = readUint32(exifData, 4, isLittleEndian);\n\n // Extract all tags from IFD0\n const ifd0Segments = extractTagsFromIfd(exifData, ifd0Offset, isLittleEndian);\n\n // Find Exif IFD and extract UserComment from there\n const exifIfdOffset = findExifIfdOffset(exifData, ifd0Offset, isLittleEndian);\n const exifIfdSegments =\n exifIfdOffset !== null\n ? extractTagsFromIfd(exifData, exifIfdOffset, isLittleEndian)\n : [];\n\n return [...ifd0Segments, ...exifIfdSegments];\n}\n\n/**\n * Extract metadata tags from an IFD\n */\nfunction extractTagsFromIfd(\n data: Uint8Array,\n ifdOffset: number,\n isLittleEndian: boolean,\n): MetadataSegment[] {\n const segments: MetadataSegment[] = [];\n\n if (ifdOffset + 2 > data.length) return segments;\n\n const entryCount = readUint16(data, ifdOffset, isLittleEndian);\n let offset = ifdOffset + 2;\n\n for (let i = 0; i < entryCount; i++) {\n if (offset + 12 > data.length) return segments;\n\n const tag = readUint16(data, offset, isLittleEndian);\n const type = readUint16(data, offset + 2, isLittleEndian);\n const count = readUint32(data, offset + 4, isLittleEndian);\n\n // Calculate data size based on type\n const typeSize = getTypeSize(type);\n const dataSize = count * typeSize;\n\n let valueOffset: number;\n if (dataSize <= 4) {\n valueOffset = offset + 8;\n } else {\n valueOffset = readUint32(data, offset + 8, isLittleEndian);\n }\n\n if (valueOffset + dataSize > data.length) {\n offset += 12;\n continue;\n }\n\n const tagData = data.slice(valueOffset, valueOffset + dataSize);\n\n // Process known tags\n if (tag === IMAGE_DESCRIPTION_TAG) {\n const text = decodeAsciiString(tagData);\n if (text) {\n const prefix = extractPrefix(text);\n segments.push({\n source: { type: 'exifImageDescription', prefix: prefix ?? undefined },\n data: prefix ? text.slice(prefix.length + 2) : text,\n });\n }\n } else if (tag === MAKE_TAG) {\n const text = decodeAsciiString(tagData);\n if (text) {\n const prefix = extractPrefix(text);\n segments.push({\n source: { type: 'exifMake', prefix: prefix ?? undefined },\n data: prefix ? text.slice(prefix.length + 2) : text,\n });\n }\n } else if (tag === USER_COMMENT_TAG) {\n const text = decodeUserComment(tagData);\n if (text) {\n segments.push({\n source: { type: 'exifUserComment' },\n data: text,\n });\n }\n }\n\n offset += 12;\n }\n\n return segments;\n}\n\n/**\n * Extract prefix from text like \"Workflow: {...}\" -> \"Workflow\"\n */\nfunction extractPrefix(text: string): string | null {\n const match = text.match(/^([A-Za-z]+):\\s/);\n return match?.[1] ?? null;\n}\n\n/**\n * Get size in bytes for TIFF data type\n */\nfunction getTypeSize(type: number): number {\n switch (type) {\n case 1:\n return 1; // BYTE\n case 2:\n return 1; // ASCII\n case 3:\n return 2; // SHORT\n case 4:\n return 4; // LONG\n case 5:\n return 8; // RATIONAL\n case 7:\n return 1; // UNDEFINED\n default:\n return 1;\n }\n}\n\n/**\n * Decode ASCII/UTF-8 string from tag data\n */\nfunction decodeAsciiString(data: Uint8Array): string | null {\n try {\n const decoder = new TextDecoder('utf-8', { fatal: false });\n let text = decoder.decode(data);\n // Remove null terminator if present\n if (text.endsWith('\\0')) {\n text = text.slice(0, -1);\n }\n return text.trim() || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Find Exif IFD offset from IFD0\n */\nfunction findExifIfdOffset(\n data: Uint8Array,\n ifdOffset: number,\n isLittleEndian: boolean,\n): number | null {\n if (ifdOffset + 2 > data.length) return null;\n\n const entryCount = readUint16(data, ifdOffset, isLittleEndian);\n let offset = ifdOffset + 2;\n\n for (let i = 0; i < entryCount; i++) {\n if (offset + 12 > data.length) return null;\n\n const tag = readUint16(data, offset, isLittleEndian);\n\n if (tag === EXIF_IFD_POINTER_TAG) {\n // Exif IFD pointer found\n return readUint32(data, offset + 8, isLittleEndian);\n }\n\n offset += 12;\n }\n\n return null;\n}\n\n/**\n * Decode UserComment based on encoding prefix\n *\n * @param data - UserComment data including encoding prefix\n * @returns Decoded string\n */\nexport function decodeUserComment(data: Uint8Array): string | null {\n if (data.length < 8) return null;\n\n // Check for UNICODE prefix\n if (\n data[0] === 0x55 && // U\n data[1] === 0x4e && // N\n data[2] === 0x49 && // I\n data[3] === 0x43 && // C\n data[4] === 0x4f && // O\n data[5] === 0x44 && // D\n data[6] === 0x45 && // E\n data[7] === 0x00 // NULL\n ) {\n // UTF-16 encoded - detect byte order by looking at first character\n const textData = data.slice(8);\n if (textData.length >= 2) {\n const isLikelyLE = textData[0] !== 0x00 && textData[1] === 0x00;\n return isLikelyLE ? decodeUtf16LE(textData) : decodeUtf16BE(textData);\n }\n return decodeUtf16BE(textData);\n }\n\n // Check for ASCII prefix\n if (\n data[0] === 0x41 && // A\n data[1] === 0x53 && // S\n data[2] === 0x43 && // C\n data[3] === 0x49 && // I\n data[4] === 0x49 && // I\n data[5] === 0x00 && // NULL\n data[6] === 0x00 && // NULL\n data[7] === 0x00 // NULL\n ) {\n // ASCII encoded\n return decodeAscii(data.slice(8));\n }\n\n // Try UTF-8 (for ComfyUI JSON format without prefix)\n try {\n const decoder = new TextDecoder('utf-8', { fatal: true });\n let result = decoder.decode(data);\n // Strip null terminator if present\n if (result.endsWith('\\0')) {\n result = result.slice(0, -1);\n }\n return result;\n } catch {\n return null;\n }\n}\n\n/**\n * Decode UTF-16BE string\n */\nfunction decodeUtf16BE(data: Uint8Array): string {\n const chars: string[] = [];\n\n for (let i = 0; i < data.length - 1; i += 2) {\n const code = ((data[i] ?? 0) << 8) | (data[i + 1] ?? 0);\n if (code === 0) break;\n chars.push(String.fromCharCode(code));\n }\n\n return chars.join('');\n}\n\n/**\n * Decode UTF-16LE string\n */\nfunction decodeUtf16LE(data: Uint8Array): string {\n const chars: string[] = [];\n\n for (let i = 0; i < data.length - 1; i += 2) {\n const code = (data[i] ?? 0) | ((data[i + 1] ?? 0) << 8);\n if (code === 0) break;\n chars.push(String.fromCharCode(code));\n }\n\n return chars.join('');\n}\n\n/**\n * Decode ASCII string\n */\nfunction decodeAscii(data: Uint8Array): string {\n const chars: string[] = [];\n\n for (let i = 0; i < data.length; i++) {\n if (data[i] === 0) break;\n chars.push(String.fromCharCode(data[i] ?? 0));\n }\n\n return chars.join('');\n}\n","import type { MetadataSegment } from '../types';\nimport { Result } from '../types';\nimport { arraysEqual, isJpeg } from '../utils/binary';\nimport { parseExifMetadataSegments } from './exif';\n\n// Internal types (co-located with reader)\ntype JpegReadError = { type: 'invalidSignature' };\n\ntype JpegMetadataResult = Result<MetadataSegment[], JpegReadError>;\n\n/** APP1 marker */\nconst APP1_MARKER = 0xe1;\n\n/** COM (Comment) marker */\nconst COM_MARKER = 0xfe;\n\n/** Exif header: \"Exif\\0\\0\" */\nconst EXIF_HEADER = new Uint8Array([0x45, 0x78, 0x69, 0x66, 0x00, 0x00]);\n\n/**\n * Read JPEG metadata from binary data\n *\n * Collects metadata from multiple sources:\n * - Exif tags (APP1 segment): UserComment, ImageDescription, Make\n * - COM segment - Used by NovelAI\n *\n * @param data - JPEG file data as Uint8Array\n * @returns Result containing all metadata segments or error\n */\nexport function readJpegMetadata(data: Uint8Array): JpegMetadataResult {\n if (!isJpeg(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n const segments: MetadataSegment[] = [];\n\n // Extract all Exif metadata (UserComment, ImageDescription, Make)\n const app1 = findApp1Segment(data);\n if (app1) {\n const exifData = data.slice(app1.offset, app1.offset + app1.length);\n const exifSegments = parseExifMetadataSegments(exifData);\n segments.push(...exifSegments);\n }\n\n // Try COM segment (NovelAI uses this)\n const comSegment = findComSegment(data);\n if (comSegment) {\n const comData = data.slice(\n comSegment.offset,\n comSegment.offset + comSegment.length,\n );\n const comText = decodeComSegment(comData);\n\n if (comText !== null) {\n segments.push({\n source: { type: 'jpegCom' },\n data: comText,\n });\n }\n }\n\n return Result.ok(segments);\n}\n\n/**\n * Find APP1 segment containing Exif data\n *\n * @param data - JPEG file data\n * @returns Offset and length of APP1 segment data, or null if not found\n */\nfunction findApp1Segment(\n data: Uint8Array,\n): { offset: number; length: number } | null {\n let offset = 2; // Skip SOI marker\n\n while (offset < data.length - 4) {\n // Check for marker\n if (data[offset] !== 0xff) {\n offset++;\n continue;\n }\n\n const marker = data[offset + 1];\n\n // Skip padding bytes\n if (marker === 0xff) {\n offset++;\n continue;\n }\n\n // Get segment length (big-endian, includes length bytes)\n const length = ((data[offset + 2] ?? 0) << 8) | (data[offset + 3] ?? 0);\n\n // Check for APP1 marker\n if (marker === APP1_MARKER) {\n // Verify Exif header\n const headerStart = offset + 4;\n if (headerStart + 6 <= data.length) {\n const header = data.slice(headerStart, headerStart + 6);\n if (arraysEqual(header, EXIF_HEADER)) {\n // Return offset to TIFF data (after Exif header)\n return {\n offset: headerStart + 6,\n length: length - 8, // Subtract length bytes and Exif header\n };\n }\n }\n }\n\n // Move to next segment\n offset += 2 + length;\n\n // Stop at SOS (Start of Scan) or EOI\n if (marker === 0xda || marker === 0xd9) {\n break;\n }\n }\n\n return null;\n}\n\n/**\n * Find COM (Comment) segment\n *\n * COM segments are used by NovelAI to store metadata as UTF-8 JSON.\n *\n * @param data - JPEG file data\n * @returns Offset and length of COM segment data, or null if not found\n */\nfunction findComSegment(\n data: Uint8Array,\n): { offset: number; length: number } | null {\n let offset = 2; // Skip SOI marker\n\n while (offset < data.length - 4) {\n // Check for marker\n if (data[offset] !== 0xff) {\n offset++;\n continue;\n }\n\n const marker = data[offset + 1];\n\n // Skip padding bytes\n if (marker === 0xff) {\n offset++;\n continue;\n }\n\n // Get segment length (big-endian, includes length bytes)\n const length = ((data[offset + 2] ?? 0) << 8) | (data[offset + 3] ?? 0);\n\n // Check for COM marker\n if (marker === COM_MARKER) {\n // Return offset to comment data (after marker and length)\n return {\n offset: offset + 4,\n length: length - 2, // Subtract length bytes only\n };\n }\n\n // Move to next segment\n offset += 2 + length;\n\n // Stop at SOS (Start of Scan) or EOI\n if (marker === 0xda || marker === 0xd9) {\n break;\n }\n }\n\n return null;\n}\n\n/**\n * Decode COM segment data as UTF-8 string\n *\n * @param data - COM segment data\n * @returns Decoded string or null if invalid\n */\nfunction decodeComSegment(data: Uint8Array): string | null {\n try {\n const decoder = new TextDecoder('utf-8', { fatal: true });\n return decoder.decode(data);\n } catch {\n return null;\n }\n}\n","import type { ITXtChunk, PngTextChunk, TExtChunk } from '../types';\nimport { Result } from '../types';\nimport { isPng, readChunkType, readUint32BE } from '../utils/binary';\n\n// Internal types (co-located with reader)\ntype PngReadError =\n | { type: 'invalidSignature' }\n | { type: 'corruptedChunk'; message: string };\n\ntype PngMetadataResult = Result<PngTextChunk[], PngReadError>;\n\n/**\n * Read PNG metadata from binary data\n * @param data - PNG file data as Uint8Array\n * @returns Result containing metadata or error\n */\nexport function readPngMetadata(data: Uint8Array): PngMetadataResult {\n // Validate PNG signature\n if (!isPng(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n // Extract text chunks\n const chunksResult = extractTextChunks(data);\n if (!chunksResult.ok) {\n return chunksResult;\n }\n\n return Result.ok(chunksResult.value);\n}\n\n/**\n * Extract tEXt and iTXt chunks from PNG data\n */\n// 8 bytes for PNG signature\nconst PNG_SIGNATURE_LENGTH = 8;\n\n/**\n * Extract tEXt and iTXt chunks from PNG data\n */\nfunction extractTextChunks(\n data: Uint8Array,\n): Result<PngTextChunk[], PngReadError> {\n const chunks: PngTextChunk[] = [];\n let offset = PNG_SIGNATURE_LENGTH;\n\n while (offset < data.length) {\n // Read chunk length (4 bytes, big-endian)\n if (offset + 4 > data.length) {\n return Result.error({\n type: 'corruptedChunk',\n message: 'Unexpected end of file while reading chunk length',\n });\n }\n const length = readUint32BE(data, offset);\n offset += 4;\n\n // Read chunk type (4 bytes)\n if (offset + 4 > data.length) {\n return Result.error({\n type: 'corruptedChunk',\n message: 'Unexpected end of file while reading chunk type',\n });\n }\n const chunkType = readChunkType(data, offset);\n offset += 4;\n\n // Read chunk data\n if (offset + length > data.length) {\n return Result.error({\n type: 'corruptedChunk',\n message: `Unexpected end of file while reading chunk data (${chunkType})`,\n });\n }\n const chunkData = data.slice(offset, offset + length);\n offset += length;\n\n // Skip CRC (4 bytes)\n offset += 4;\n\n // Parse text chunks\n if (chunkType === 'tEXt') {\n const parsed = parseTExtChunk(chunkData);\n if (parsed) {\n chunks.push(parsed);\n }\n } else if (chunkType === 'iTXt') {\n const parsed = parseITXtChunk(chunkData);\n if (parsed) {\n chunks.push(parsed);\n }\n }\n\n // Stop at IEND\n if (chunkType === 'IEND') {\n break;\n }\n }\n\n return Result.ok(chunks);\n}\n\n/**\n * Parse tEXt chunk data\n *\n * Per PNG specification, tEXt chunks use Latin-1 (ISO-8859-1) encoding.\n * However, some tools (notably TensorArt) incorrectly write UTF-8 bytes\n * directly into tEXt chunks. To handle these non-compliant tools, we\n * attempt UTF-8 decoding first and fall back to Latin-1 if that fails.\n */\nfunction parseTExtChunk(data: Uint8Array): TExtChunk | null {\n // Find null separator\n const nullIndex = data.indexOf(0);\n if (nullIndex === -1) {\n return null;\n }\n\n // Keyword is Latin-1 encoded (per spec, keywords are ASCII-safe)\n const keyword = latin1Decode(data.slice(0, nullIndex));\n\n // Text: Try UTF-8 first (for non-compliant tools), fallback to Latin-1\n const textData = data.slice(nullIndex + 1);\n const text = tryUtf8Decode(textData) ?? latin1Decode(textData);\n\n return { type: 'tEXt', keyword, text };\n}\n\n/**\n * Try to decode data as UTF-8, return null if invalid\n */\nfunction tryUtf8Decode(data: Uint8Array): string | null {\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(data);\n } catch {\n return null;\n }\n}\n\n/**\n * Parse iTXt chunk data\n */\nfunction parseITXtChunk(data: Uint8Array): ITXtChunk | null {\n let offset = 0;\n\n // Read keyword (null-terminated)\n const keywordEnd = findNull(data, offset);\n if (keywordEnd === -1) return null;\n const keyword = utf8Decode(data.slice(offset, keywordEnd));\n offset = keywordEnd + 1;\n\n // Read compression flag (1 byte)\n if (offset >= data.length) return null;\n const compressionFlag = data[offset] ?? 0;\n offset += 1;\n\n // Read compression method (1 byte)\n if (offset >= data.length) return null;\n const compressionMethod = data[offset] ?? 0;\n offset += 1;\n\n // Read language tag (null-terminated)\n const langEnd = findNull(data, offset);\n if (langEnd === -1) return null;\n const languageTag = utf8Decode(data.slice(offset, langEnd));\n offset = langEnd + 1;\n\n // Read translated keyword (null-terminated)\n const transEnd = findNull(data, offset);\n if (transEnd === -1) return null;\n const translatedKeyword = utf8Decode(data.slice(offset, transEnd));\n offset = transEnd + 1;\n\n // Read text (rest of data)\n let text: string;\n if (compressionFlag === 1) {\n // Compressed with zlib\n const decompressed = decompressZlib(data.slice(offset));\n if (!decompressed) return null;\n text = utf8Decode(decompressed);\n } else {\n text = utf8Decode(data.slice(offset));\n }\n\n return {\n type: 'iTXt',\n keyword,\n compressionFlag,\n compressionMethod,\n languageTag,\n translatedKeyword,\n text,\n };\n}\n\n/**\n * Find null byte in data starting from offset\n */\nfunction findNull(data: Uint8Array, offset: number): number {\n for (let i = offset; i < data.length; i++) {\n if (data[i] === 0) {\n return i;\n }\n }\n return -1;\n}\n\n/**\n * Decode Latin-1 (ISO-8859-1) bytes to string\n */\nfunction latin1Decode(data: Uint8Array): string {\n let result = '';\n for (let i = 0; i < data.length; i++) {\n result += String.fromCharCode(data[i] ?? 0);\n }\n return result;\n}\n\n/**\n * Decode UTF-8 bytes to string\n */\nfunction utf8Decode(data: Uint8Array): string {\n return new TextDecoder('utf-8').decode(data);\n}\n\n/**\n * Decompress zlib-compressed data\n *\n * Currently unimplemented: All surveyed sample images use uncompressed iTXt.\n * When a sample with compressed iTXt is found, implement using pako library.\n *\n * @see https://www.npmjs.com/package/pako\n */\nfunction decompressZlib(_data: Uint8Array): Uint8Array | null {\n // Not yet implemented - no compressed iTXt samples encountered\n return null;\n}\n","import type { MetadataSegment } from '../types';\nimport { Result } from '../types';\nimport { arraysEqual, isWebp, readUint32LE } from '../utils/binary';\nimport { parseExifMetadataSegments } from './exif';\n\n// Internal types (co-located with reader)\ntype WebpReadError = { type: 'invalidSignature' };\n\ntype WebpMetadataResult = Result<MetadataSegment[], WebpReadError>;\n\n/** EXIF chunk type */\nconst EXIF_CHUNK_TYPE = new Uint8Array([0x45, 0x58, 0x49, 0x46]);\n\n/**\n * Read WebP metadata from binary data\n *\n * Extracts metadata from EXIF chunk in WebP files.\n * The EXIF chunk contains TIFF-formatted data identical to JPEG Exif.\n *\n * @param data - WebP file data as Uint8Array\n * @returns Result containing all metadata segments or error\n */\nexport function readWebpMetadata(data: Uint8Array): WebpMetadataResult {\n if (!isWebp(data)) {\n return Result.error({ type: 'invalidSignature' });\n }\n\n const exifChunk = findExifChunk(data);\n if (!exifChunk) {\n return Result.ok([]);\n }\n\n const exifData = data.slice(\n exifChunk.offset,\n exifChunk.offset + exifChunk.length,\n );\n\n // Parse all EXIF metadata segments (UserComment, ImageDescription, Make)\n const segments = parseExifMetadataSegments(exifData);\n\n return Result.ok(segments);\n}\n\n/**\n * Find EXIF chunk in WebP file\n *\n * WebP uses RIFF container format with named chunks.\n * EXIF chunk contains TIFF data starting with \"II\" or \"MM\" byte order marker.\n *\n * @param data - WebP file data\n * @returns Offset and length of EXIF chunk data, or null if not found\n */\nexport function findExifChunk(\n data: Uint8Array,\n): { offset: number; length: number } | null {\n // Start after RIFF header (12 bytes: \"RIFF\" + size + \"WEBP\")\n let offset = 12;\n\n while (offset < data.length - 8) {\n // Read chunk type (4 bytes)\n const chunkType = data.slice(offset, offset + 4);\n\n // Read chunk size (4 bytes, little-endian)\n const chunkSize = readUint32LE(data, offset + 4);\n\n // Check for EXIF chunk\n if (arraysEqual(chunkType, EXIF_CHUNK_TYPE)) {\n // EXIF chunk data starts after type and size\n return {\n offset: offset + 8,\n length: chunkSize,\n };\n }\n\n // Move to next chunk (chunk size + type + size fields)\n // RIFF chunks are padded to even byte boundaries\n const paddedSize = chunkSize + (chunkSize % 2);\n offset += 8 + paddedSize;\n }\n\n return null;\n}\n","import type {\n MetadataSegment,\n MetadataSegmentSource,\n PngTextChunk,\n} from '../types';\nimport type { EntryRecord } from './entries';\nimport { parseJson } from './json';\n\n/**\n * Convert PNG text chunks to entry record\n *\n * @param chunks - PNG tEXt/iTXt chunks\n * @returns Entry record for keyword lookup\n */\nexport function pngChunksToRecord(chunks: PngTextChunk[]): EntryRecord {\n return Object.fromEntries(chunks.map((c) => [c.keyword, c.text]));\n}\n\n/**\n * Convert JPEG/WebP metadata segments to entry record\n *\n * Maps segment sources to conventional keywords:\n * - jpegCom → 'Comment'\n * - exifUserComment → 'UserComment' (or expand if NovelAI WebP format)\n * - exifImageDescription → prefix or 'ImageDescription'\n * - exifMake → prefix or 'Make'\n *\n * Special handling for NovelAI WebP format where metadata is stored as:\n * {\"Comment\": \"{...inner JSON...}\", \"Software\": \"NovelAI\", ...}\n *\n * @param segments - Metadata segments from JPEG/WebP reader\n * @returns Entry record for keyword lookup\n */\nexport function segmentsToRecord(segments: MetadataSegment[]): EntryRecord {\n const record: Record<string, string> = {};\n\n for (const segment of segments) {\n const keyword = sourceToKeyword(segment.source);\n const text = segment.data;\n\n // Try to detect and expand NovelAI WebP format\n // Format: {\"Comment\": \"{...}\", \"Software\": \"NovelAI\", ...}\n if (segment.source.type === 'exifUserComment' && text.startsWith('{')) {\n const expanded = tryExpandNovelAIWebpFormat(text);\n if (expanded) {\n Object.assign(record, expanded);\n continue;\n }\n }\n\n record[keyword] = text;\n }\n\n return record;\n}\n\n/**\n * Try to expand NovelAI WebP format metadata\n *\n * NovelAI WebP stores metadata as outer JSON with:\n * - Software: \"NovelAI\"\n * - Comment: inner JSON string with actual parameters\n *\n * @param text - JSON text to try to expand\n * @returns Entry record if NovelAI format, null otherwise\n */\nfunction tryExpandNovelAIWebpFormat(text: string): EntryRecord | null {\n const outerParsed = parseJson(text);\n if (!outerParsed.ok || outerParsed.type !== 'object') {\n return null;\n }\n\n const outer = outerParsed.value;\n\n // Check if this is NovelAI WebP format\n if (\n (typeof outer.Software === 'string' &&\n !outer.Software.startsWith('NovelAI')) ||\n typeof outer.Comment !== 'string'\n ) {\n return null;\n }\n\n // Parse and add inner Comment as Comment entry\n const innerParsed = parseJson(outer.Comment);\n\n return {\n Software: typeof outer.Software === 'string' ? outer.Software : 'NovelAI',\n Comment: innerParsed.ok ? JSON.stringify(innerParsed.value) : outer.Comment,\n };\n}\n\n/**\n * Map metadata segment source to keyword\n */\nfunction sourceToKeyword(source: MetadataSegmentSource): string {\n switch (source.type) {\n case 'jpegCom':\n return 'Comment';\n case 'exifUserComment':\n return 'UserComment';\n case 'exifImageDescription':\n return source.prefix ?? 'ImageDescription';\n case 'exifMake':\n return source.prefix ?? 'Make';\n }\n}\n","/**\n * Read API for sd-metadata\n *\n * Handles reading and parsing metadata from images.\n * Automatically detects image format and extracts embedded generation metadata.\n */\n\nimport { parseMetadata } from '../parsers';\nimport { readImageDimensions } from '../readers/dimensions';\nimport { readJpegMetadata } from '../readers/jpeg';\nimport { readPngMetadata } from '../readers/png';\nimport { readWebpMetadata } from '../readers/webp';\nimport type {\n MetadataSegment,\n ParseResult,\n PngTextChunk,\n RawMetadata,\n ReadOptions,\n} from '../types';\nimport { detectFormat, type ImageFormat, toUint8Array } from '../utils/binary';\nimport { pngChunksToRecord, segmentsToRecord } from '../utils/convert';\n\n/**\n * Read and parse metadata from an image\n *\n * Automatically detects the image format (PNG, JPEG, WebP) and parses\n * any embedded generation metadata.\n *\n * @param input - Image file data (Uint8Array or ArrayBuffer)\n * @param options - Read options\n * @returns Parse result containing metadata and raw data\n */\nexport function read(\n input: Uint8Array | ArrayBuffer,\n options?: ReadOptions,\n): ParseResult {\n const data = toUint8Array(input);\n const format = detectFormat(data);\n\n if (!format) {\n return { status: 'invalid', message: 'Unknown image format' };\n }\n\n // 1. Read raw metadata based on format\n const rawResult = readRawMetadata(data, format);\n if (rawResult.status !== 'success') {\n return rawResult;\n }\n const raw = rawResult.raw;\n\n // 2. Convert to entry record\n const entries =\n raw.format === 'png'\n ? pngChunksToRecord(raw.chunks)\n : segmentsToRecord(raw.segments);\n\n // 3. Parse metadata\n const parseResult = parseMetadata(entries);\n if (!parseResult.ok) {\n return { status: 'unrecognized', raw };\n }\n\n const metadata = parseResult.value;\n\n // 4. Fallback for dimensions if missing (unless strict mode)\n if (!options?.strict && (metadata.width === 0 || metadata.height === 0)) {\n const dims = readImageDimensions(data, format);\n\n if (dims) {\n metadata.width = metadata.width || dims.width;\n metadata.height = metadata.height || dims.height;\n }\n }\n\n return { status: 'success', metadata, raw };\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Result type for readRawMetadata */\ntype RawReadResult =\n | { status: 'success'; raw: RawMetadata }\n | { status: 'empty' }\n | { status: 'invalid'; message: string };\n\n/**\n * Read raw metadata from image data\n */\nfunction readRawMetadata(data: Uint8Array, format: ImageFormat): RawReadResult {\n const result =\n format === 'png'\n ? readPngMetadata(data)\n : format === 'jpeg'\n ? readJpegMetadata(data)\n : readWebpMetadata(data);\n\n if (!result.ok) {\n const message =\n result.error.type === 'invalidSignature'\n ? `Invalid ${format.toUpperCase()} signature`\n : result.error.message;\n return { status: 'invalid', message };\n }\n\n if (result.value.length === 0) return { status: 'empty' };\n\n // PNG uses PngTextChunk[], JPEG/WebP use MetadataSegment[]\n if (format === 'png') {\n return {\n status: 'success',\n raw: { format: 'png', chunks: result.value as PngTextChunk[] },\n };\n }\n return {\n status: 'success',\n raw: { format, segments: result.value as MetadataSegment[] },\n };\n}\n","/**\n * A1111-format metadata conversion utilities\n *\n * Handles conversion for sd-webui, forge-classic, forge-neo, and civitai.\n * A1111 format stores metadata as plain text in:\n * - PNG: `parameters` tEXt/iTXt chunk (dynamic selection)\n * - JPEG/WebP: Exif UserComment\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { createEncodedChunk } from './chunk-encoding';\n\n/**\n * Convert A1111-format PNG chunks to JPEG/WebP segments\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertA1111PngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n // Find parameters chunk\n const parameters = chunks.find((c) => c.keyword === 'parameters');\n if (!parameters) {\n return [];\n }\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: parameters.text,\n },\n ];\n}\n\n/**\n * Convert JPEG/WebP segments to A1111-format PNG chunks\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertA1111SegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = segments.find((s) => s.source.type === 'exifUserComment');\n if (!userComment) {\n return [];\n }\n\n // Use dynamic selection (tEXt for ASCII, iTXt for non-ASCII)\n return createEncodedChunk('parameters', userComment.data, 'dynamic');\n}\n","/**\n * CivitAI metadata conversion utilities\n *\n * CivitAI has TWO formats:\n *\n * 1. CivitAI Orchestration (ComfyUI-like JSON):\n * - Node IDs as keys instead of standard \"prompt\"/\"workflow\" chunks\n * - \"resource-stack\" for model/checkpoint info\n * - \"extra\" and \"extraMetadata\" for CivitAI-specific data\n *\n * 2. CivitAI A1111 (plain text):\n * - Standard A1111 format with \"Civitai resources:\" marker\n * - Should be converted like regular A1111\n *\n * This converter handles BOTH formats by detecting the data type.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport { convertA1111PngToSegments, convertA1111SegmentsToPng } from './a1111';\nimport { createEncodedChunk } from './chunk-encoding';\nimport { findSegment } from './utils';\n\n/**\n * Convert CivitAI PNG chunks to JPEG/WebP segments\n *\n * Handles BOTH CivitAI formats:\n * - Orchestration (prompt chunk with JSON): Combine into single JSON object\n * - A1111 (parameters chunk with text): Preserve as A1111 format\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertCivitaiPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n // Check if this is A1111-style (has parameters chunk with non-JSON text)\n const parametersChunk = chunks.find((c) => c.keyword === 'parameters');\n if (parametersChunk && !parametersChunk.text.trimStart().startsWith('{')) {\n // A1111-style: use A1111 converter\n return convertA1111PngToSegments(chunks);\n }\n\n // Orchestration-style: combine chunks into JSON\n const data: Record<string, unknown> = {};\n\n for (const chunk of chunks) {\n if (chunk.keyword === 'prompt') {\n // Expand prompt chunk contents as top-level keys\n const parsed = parseJson(chunk.text);\n if (parsed.ok && parsed.type === 'object') {\n Object.assign(data, parsed.value);\n }\n } else if (chunk.keyword === 'extraMetadata') {\n // Keep extraMetadata as string (matches original JPEG format)\n // The parser expects a JSON string, not a parsed object\n data[chunk.keyword] = chunk.text;\n } else {\n // Other chunks: parse JSON if possible\n const parsed = parseJson(chunk.text);\n data[chunk.keyword] = parsed.ok ? parsed.value : chunk.text;\n }\n }\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(data),\n },\n ];\n}\n\n/**\n * Convert JPEG/WebP segments to CivitAI PNG chunks\n *\n * Handles BOTH CivitAI formats:\n * - Orchestration (JSON): Store entire JSON in single \"prompt\" chunk\n * - A1111 (text): Fall back to A1111 converter\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertCivitaiSegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n if (!userComment) return [];\n\n // Check if data is JSON (CivitAI Orchestration) or text (CivitAI A1111)\n const isJson = userComment.data.trimStart().startsWith('{');\n\n if (!isJson) {\n // Fall back to A1111 conversion for text format\n return convertA1111SegmentsToPng(segments);\n }\n\n // Orchestration format: store entire JSON as single \"prompt\" chunk\n // This preserves the original structure for round-trip compatibility\n return createEncodedChunk('prompt', userComment.data, 'text-unicode-escape');\n}\n","/**\n * Shared logic for JSON-based metadata converters\n *\n * Many formats (ComfyUI, InvokeAI) roughly follow a \"Key-Value\" pattern:\n * - PNG: Multiple chunks where Keyword=Key, Text=Value (Value might be JSON string)\n * - JPEG/WebP: A single UserComment containing a JSON object { Key: Value, ... }\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport {\n type ChunkEncodingStrategy,\n createEncodedChunk,\n unescapeUnicode,\n} from './chunk-encoding';\nimport { findSegment, stringify } from './utils';\n\n/**\n * Convert KV-style PNG chunks to a unified JSON object in a segment\n *\n * @param chunks - Raw PNG chunks\n * @returns Array containing a single UserComment segment with JSON data\n */\nexport function convertKvPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n const data: Record<string, unknown> = {};\n\n for (const chunk of chunks) {\n const parsed = parseJson(chunk.text);\n if (parsed.ok) {\n data[chunk.keyword] = parsed.value;\n } else {\n data[chunk.keyword] = chunk.text;\n }\n }\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(data),\n },\n ];\n}\n\n/**\n * Convert a unified JSON object in a segment back to KV-style PNG chunks\n *\n * @param segments - Metadata segments\n * @param encodingStrategy - Encoding strategy to use for chunks\n * @returns Array of PNG chunks\n */\nexport function convertKvSegmentsToPng(\n segments: MetadataSegment[],\n encodingStrategy: ChunkEncodingStrategy,\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n if (!userComment) {\n return [];\n }\n\n const parsed = parseJson(userComment.data);\n if (!parsed.ok || parsed.type !== 'object') {\n // If not JSON object, we can't blindly map keys.\n // Return empty here, letting specific converters handle fallback if needed.\n return [];\n }\n\n // Map each key back to a chunk\n // Value is stringified if it's an object/array\n // Unescape Unicode sequences that may have been escaped during PNG→JPEG conversion\n return Object.entries(parsed.value).flatMap(([keyword, value]) => {\n const text = stringify(value);\n return createEncodedChunk(\n keyword,\n text !== undefined ? unescapeUnicode(text) : undefined,\n encodingStrategy,\n );\n });\n}\n","/**\n * ComfyUI metadata conversion utilities\n *\n * ComfyUI stores metadata as:\n * - PNG: `prompt` + `workflow` tEXt chunks (both JSON)\n * - JPEG/WebP: exifUserComment with {\"prompt\": {...}, \"workflow\": {...}} (saveimage-plus format)\n *\n * Also handles: tensorart, stability-matrix (same format)\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { convertKvPngToSegments, convertKvSegmentsToPng } from './base-json';\nimport { createEncodedChunk } from './chunk-encoding';\nimport { findSegment } from './utils';\n\n/**\n * Convert ComfyUI PNG chunks to JPEG/WebP segments\n *\n * Uses saveimage-plus format: stores chunk keywords as JSON keys.\n * For chunks that contain JSON strings (prompt, workflow), parse them\n * and store as objects to match saveimage-plus format.\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertComfyUIPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n return convertKvPngToSegments(chunks);\n}\n\n/**\n * Try save-image-extended format (exifImageDescription + exifMake)\n *\n * @returns PNG chunks if format matches, null otherwise\n */\nconst tryParseExtendedFormat = (\n segments: MetadataSegment[],\n): PngTextChunk[] | null => {\n const imageDescription = findSegment(segments, 'exifImageDescription');\n const make = findSegment(segments, 'exifMake');\n\n if (!imageDescription && !make) {\n return null;\n }\n\n return [\n ...createEncodedChunk('prompt', make?.data, 'text-unicode-escape'),\n ...createEncodedChunk(\n 'workflow',\n imageDescription?.data,\n 'text-unicode-escape',\n ),\n ];\n};\n\n/**\n * Try saveimage-plus format (exifUserComment with JSON)\n *\n * @returns PNG chunks if format matches, null otherwise\n */\nconst tryParseSaveImagePlusFormat = (\n segments: MetadataSegment[],\n): PngTextChunk[] | null => {\n const chunks = convertKvSegmentsToPng(segments, 'text-unicode-escape');\n return chunks.length > 0 ? chunks : null;\n};\n\n/**\n * Convert JPEG/WebP segments to ComfyUI PNG chunks\n *\n * Supports:\n * - save-image-extended format: exifImageDescription (workflow) + exifMake (prompt)\n * - saveimage-plus format: exifUserComment with {\"prompt\": {...}, \"workflow\": {...}}\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertComfyUISegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n // Try each format in order of priority\n return (\n tryParseExtendedFormat(segments) ??\n tryParseSaveImagePlusFormat(segments) ??\n []\n );\n}\n","/**\n * Easy Diffusion metadata conversion utilities\n *\n * Easy Diffusion stores metadata using snake_case keys:\n * - PNG: Each field as a separate tEXt/iTXt chunk (e.g., `prompt`, `seed`, `sampler_name`)\n * - JPEG/WebP: All fields as JSON in EXIF UserComment\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport { createEncodedChunk } from './chunk-encoding';\nimport { findSegment, stringify } from './utils';\n\n/**\n * Convert Easy Diffusion PNG chunks to JPEG/WebP segments\n *\n * Easy Diffusion PNG stores metadata as individual chunks.\n * We combine them into a JSON object for JPEG/WebP storage.\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertEasyDiffusionPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n const json = Object.fromEntries(\n chunks.map((chunk) => [chunk.keyword, chunk.text]),\n );\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(json),\n },\n ];\n}\n\n/**\n * Convert JPEG/WebP segments to Easy Diffusion PNG chunks\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertEasyDiffusionSegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n if (!userComment) {\n return [];\n }\n\n const parsed = parseJson(userComment.data);\n if (!parsed.ok || parsed.type !== 'object') {\n return [];\n }\n\n // Convert each key-value pair to a chunk with dynamic selection\n return Object.entries(parsed.value).flatMap(([keyword, value]) =>\n createEncodedChunk(keyword, stringify(value), 'dynamic'),\n );\n}\n","/**\n * InvokeAI metadata conversion utilities\n *\n * InvokeAI stores metadata as:\n * - PNG: `invokeai_metadata` + `invokeai_graph` iTXt/tEXt chunks (both JSON, dynamic selection)\n * - JPEG/WebP: Not officially supported by InvokeAI\n *\n * For conversion, we use a JSON format similar to ComfyUI saveimage-plus:\n * {\"invokeai_metadata\": {...}, \"invokeai_graph\": {...}}\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { convertKvPngToSegments, convertKvSegmentsToPng } from './base-json';\n\n/**\n * Convert InvokeAI PNG chunks to JPEG/WebP segments\n *\n * Parses JSON chunks and stores them as objects.\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertInvokeAIPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n // Use generic KV converter\n return convertKvPngToSegments(chunks);\n}\n\n/**\n * Convert JPEG/WebP segments to InvokeAI PNG chunks\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertInvokeAISegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n // Use generic KV converter with dynamic encoding strategy\n return convertKvSegmentsToPng(segments, 'dynamic');\n}\n","/**\n * NovelAI metadata conversion utilities\n *\n * Converts NovelAI metadata between PNG chunks and JPEG/WebP segments.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport { createEncodedChunk } from './chunk-encoding';\nimport { createTextChunk, findSegment, stringify } from './utils';\n\n/** Fixed values for NovelAI PNG chunks */\nconst NOVELAI_TITLE = 'NovelAI generated image';\nconst NOVELAI_SOFTWARE = 'NovelAI';\n\n/**\n * Convert NovelAI PNG chunks to JPEG/WebP segments\n *\n * PNG structure:\n * - Title: \\\"NovelAI generated image\\\"\n * - Description: short prompt\n * - Software: \\\"NovelAI\\\"\n * - Source: version info\n * - Generation time: time\n * - Comment: full JSON parameters\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertNovelaiPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n // Build segments array declaratively\n const description = chunks.find((c) => c.keyword === 'Description');\n const descriptionSegment: MetadataSegment | undefined = description && {\n source: { type: 'exifImageDescription' },\n data: `\\0\\0\\0\\0${description.text}`,\n };\n\n const data = buildUserCommentJson(chunks);\n const userCommentSegment: MetadataSegment = {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(data),\n };\n\n return [descriptionSegment, userCommentSegment].filter(\n (segment) => segment !== undefined,\n );\n}\n\n/**\n * Build UserComment JSON from PNG chunks in NovelAI's standard key order\n */\nfunction buildUserCommentJson(chunks: PngTextChunk[]): Record<string, string> {\n return NOVELAI_KEY_ORDER.map((key) => {\n const chunk = chunks.find((c) => c.keyword === key);\n return chunk ? { [key]: chunk.text } : null;\n })\n .filter((entry): entry is Record<string, string> => entry !== null)\n .reduce(\n (acc, entry) => Object.assign(acc, entry),\n {} as Record<string, string>,\n );\n}\n\n/**\n * NovelAI standard key order for UserComment JSON\n */\nconst NOVELAI_KEY_ORDER = [\n 'Comment',\n 'Description',\n 'Generation time',\n 'Software',\n 'Source',\n 'Title',\n] as const;\n\n/**\n * Convert JPEG/WebP segments to NovelAI PNG chunks\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertNovelaiSegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userCommentSeg = findSegment(segments, 'exifUserComment');\n const descriptionSeg = findSegment(segments, 'exifImageDescription');\n\n return parseSegments(userCommentSeg, descriptionSeg);\n}\n\n/**\n * Parse UserComment JSON and convert to PNG chunks\n */\nfunction parseSegments(\n userCommentSeg: MetadataSegment | undefined,\n descriptionSeg: MetadataSegment | undefined,\n): PngTextChunk[] {\n if (!userCommentSeg || !descriptionSeg) {\n return [];\n }\n\n const parsed = parseJson(userCommentSeg.data);\n if (!parsed.ok || parsed.type !== 'object') {\n // If parsing fails or not an object, treat the whole thing as Comment\n return createTextChunk('Comment', userCommentSeg.data);\n }\n\n const jsonData = parsed.value;\n\n // Extract Description text (prefer exifImageDescription over corrupted JSON)\n const descriptionText = extractDescriptionText(\n descriptionSeg,\n stringify(jsonData.Description),\n );\n\n return [\n // Title (required, use default if missing)\n createTextChunk('Title', stringify(jsonData.Title) ?? NOVELAI_TITLE),\n // Description (optional, prefer exifImageDescription over JSON)\n createEncodedChunk('Description', descriptionText, 'dynamic'),\n // Software (required, use default if missing)\n createTextChunk(\n 'Software',\n stringify(jsonData.Software) ?? NOVELAI_SOFTWARE,\n ),\n // Source (optional)\n createTextChunk('Source', stringify(jsonData.Source)),\n // Generation time (optional)\n createTextChunk('Generation time', stringify(jsonData['Generation time'])),\n // Comment (optional)\n createTextChunk('Comment', stringify(jsonData.Comment)),\n ].flat();\n}\n\n/**\n * Extract Description text from exifImageDescription or UserComment JSON\n *\n * NovelAI WebP has corrupted UTF-8 in UserComment JSON Description,\n * so we prefer the clean exifImageDescription segment when available.\n */\nfunction extractDescriptionText(\n descriptionSeg: MetadataSegment | undefined,\n jsonDescription: string | undefined,\n): string | undefined {\n // First, try exifImageDescription segment (strip 4-byte null prefix)\n if (descriptionSeg?.data) {\n const data = descriptionSeg.data;\n // NovelAI WebP format has 4-byte null prefix before ImageDescription\n return data.startsWith('\\0\\0\\0\\0') ? data.slice(4) : data;\n }\n\n // Fallback: use JSON value (for non-NovelAI WebP sources)\n if (jsonDescription) {\n // Strip 4-byte null prefix if present\n return jsonDescription.startsWith('\\0\\0\\0\\0')\n ? jsonDescription.slice(4)\n : jsonDescription;\n }\n\n return undefined;\n}\n","/**\n * Simple chunk converter utilities\n *\n * Factory functions for converters that simply copy a single chunk keyword\n * between PNG and JPEG/WebP formats, with encoding based on tool strategy.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport {\n type ChunkEncodingStrategy,\n createEncodedChunk,\n} from './chunk-encoding';\n\n/**\n * Create a PNG-to-segments converter that extracts a single chunk by keyword\n *\n * @param keyword - The PNG chunk keyword to extract\n * @returns Converter function\n */\nexport function createPngToSegments(\n keyword: string,\n): (chunks: PngTextChunk[]) => MetadataSegment[] {\n return (chunks) => {\n const chunk = chunks.find((c) => c.keyword === keyword);\n return !chunk\n ? []\n : [{ source: { type: 'exifUserComment' }, data: chunk.text }];\n };\n}\n\n/**\n * Create a segments-to-PNG converter that writes to a single chunk keyword\n *\n * @param keyword - The PNG chunk keyword to write\n * @param encodingStrategy - Encoding strategy to use\n * @returns Converter function\n */\nexport function createSegmentsToPng(\n keyword: string,\n encodingStrategy: ChunkEncodingStrategy,\n): (segments: MetadataSegment[]) => PngTextChunk[] {\n return (segments) => {\n const userComment = segments.find(\n (s) => s.source.type === 'exifUserComment',\n );\n if (!userComment) return [];\n\n return createEncodedChunk(keyword, userComment.data, encodingStrategy);\n };\n}\n","/**\n * Stability Matrix metadata conversion utilities\n *\n * Stability Matrix stores metadata with multiple PNG chunks:\n * - parameters: A1111-style text (RAW UTF-8 - non-compliant but matches original)\n * - parameters-json: JSON with generation params (Unicode escape)\n * - smproj: Project data (Unicode escape)\n * - prompt: ComfyUI node graph JSON (Unicode escape)\n *\n * Per-chunk encoding is critical for round-trip compatibility.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport {\n type ChunkEncodingMap,\n createEncodedChunks,\n unescapeUnicode,\n} from './chunk-encoding';\nimport { findSegment, stringify } from './utils';\n\n/**\n * Chunk encoding map for Stability Matrix\n *\n * - parameters: RAW UTF-8 (A1111-style text with Japanese chars)\n * - All others: Unicode escape (JSON should be ASCII-safe)\n */\nconst STABILITY_MATRIX_ENCODING: ChunkEncodingMap = {\n parameters: 'text-utf8-raw',\n default: 'text-unicode-escape',\n};\n\n/**\n * Convert Stability Matrix PNG chunks to JPEG/WebP segments\n *\n * Combines all chunks into a single JSON object stored in exifUserComment.\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertStabilityMatrixPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n const data: Record<string, unknown> = {};\n\n for (const chunk of chunks) {\n const parsed = parseJson(chunk.text);\n data[chunk.keyword] = parsed.ok ? parsed.value : chunk.text;\n }\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(data),\n },\n ];\n}\n\n/**\n * Convert JPEG/WebP segments to Stability Matrix PNG chunks\n *\n * Splits the JSON object back into individual chunks with per-chunk encoding.\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertStabilityMatrixSegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n if (!userComment) return [];\n\n const parsed = parseJson(userComment.data);\n if (!parsed.ok || parsed.type !== 'object') return [];\n\n const value = parsed.value as Record<string, unknown>;\n\n // Build entries from all keys in the object\n const entries: [string, string | undefined][] = Object.entries(value).map(\n ([key, val]) => {\n const text = stringify(val);\n return [key, text !== undefined ? unescapeUnicode(text) : undefined];\n },\n );\n\n return createEncodedChunks(entries, STABILITY_MATRIX_ENCODING);\n}\n","/**\n * SwarmUI metadata conversion utilities\n *\n * SwarmUI stores metadata as:\n * - PNG: `parameters` chunk containing sui_image_params JSON\n * - JPEG/WebP: exifUserComment contains sui_image_params JSON directly\n *\n * The converter extracts/wraps the content appropriately for each format.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport { createEncodedChunk } from './chunk-encoding';\nimport { findSegment } from './utils';\n\n/**\n * Convert SwarmUI PNG chunks to JPEG/WebP segments\n *\n * Extracts the 'parameters' chunk and optionally preserves 'prompt' chunk (ComfyUI workflow).\n * - parameters chunk → exifUserComment (matches native SwarmUI format)\n * - prompt chunk → exifMake (preserves ComfyUI node graph for round-trip)\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertSwarmUIPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n // Find 'parameters' chunk\n const parametersChunk = chunks.find((c) => c.keyword === 'parameters');\n if (!parametersChunk) {\n return [];\n }\n\n // Parse and return the JSON directly (no wrapping in parameters key)\n const parsed = parseJson(parametersChunk.text);\n const segments: MetadataSegment[] = [\n {\n source: { type: 'exifUserComment' },\n data: parsed.ok ? JSON.stringify(parsed.value) : parametersChunk.text,\n },\n ];\n\n // Preserve node graph if present (prompt chunk contains ComfyUI node graph)\n const promptChunk = chunks.find((c) => c.keyword === 'prompt');\n if (promptChunk) {\n segments.push({\n source: { type: 'exifMake' },\n data: promptChunk.text,\n });\n }\n\n return segments;\n}\n\n/**\n * Convert JPEG/WebP segments to SwarmUI PNG chunks\n *\n * Handles both native SwarmUI format and extended format with node graph:\n * - exifUserComment → parameters chunk (always present)\n * - exifMake → prompt chunk (optional, contains ComfyUI node graph)\n *\n * Chunk order matches original SwarmUI format: [prompt, parameters]\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertSwarmUISegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n const make = findSegment(segments, 'exifMake');\n\n const chunks: PngTextChunk[] = [\n // Restore node graph first if present (extended format)\n createEncodedChunk('prompt', make?.data, 'text-unicode-escape'),\n // Add parameters chunk second (always present)\n createEncodedChunk('parameters', userComment?.data, 'text-unicode-escape'),\n ].flat();\n\n return chunks;\n}\n","/**\n * TensorArt metadata conversion utilities\n *\n * TensorArt stores metadata with two PNG chunks:\n * - generation_data: JSON with generation params (RAW UTF-8 - non-compliant but matches original)\n * - prompt: ComfyUI node graph JSON (Unicode escape)\n *\n * Per-chunk encoding is critical for round-trip compatibility.\n */\n\nimport type { MetadataSegment, PngTextChunk } from '../types';\nimport { parseJson } from '../utils/json';\nimport {\n type ChunkEncodingMap,\n createEncodedChunks,\n unescapeUnicode,\n} from './chunk-encoding';\nimport { findSegment, stringify } from './utils';\n\n/**\n * Chunk encoding map for TensorArt\n *\n * - generation_data: RAW UTF-8 (contains Japanese chars in prompts)\n * - All others: Unicode escape (ComfyUI node graph)\n */\nconst TENSORART_ENCODING: ChunkEncodingMap = {\n generation_data: 'text-utf8-raw',\n default: 'text-unicode-escape',\n};\n\n/**\n * Convert TensorArt PNG chunks to JPEG/WebP segments\n *\n * Combines all chunks into a single JSON object stored in exifUserComment.\n *\n * @param chunks - PNG text chunks\n * @returns Metadata segments for JPEG/WebP\n */\nexport function convertTensorArtPngToSegments(\n chunks: PngTextChunk[],\n): MetadataSegment[] {\n const data: Record<string, unknown> = {};\n\n for (const chunk of chunks) {\n const parsed = parseJson(chunk.text);\n data[chunk.keyword] = parsed.ok ? parsed.value : chunk.text;\n }\n\n return [\n {\n source: { type: 'exifUserComment' },\n data: JSON.stringify(data),\n },\n ];\n}\n\n/**\n * Convert JPEG/WebP segments to TensorArt PNG chunks\n *\n * Splits the JSON object back into individual chunks with per-chunk encoding.\n *\n * @param segments - Metadata segments from JPEG/WebP\n * @returns PNG text chunks\n */\nexport function convertTensorArtSegmentsToPng(\n segments: MetadataSegment[],\n): PngTextChunk[] {\n const userComment = findSegment(segments, 'exifUserComment');\n if (!userComment) return [];\n\n const parsed = parseJson(userComment.data);\n if (!parsed.ok || parsed.type !== 'object') return [];\n\n const value = parsed.value as Record<string, unknown>;\n\n // Build entries from all keys in the object\n const entries: [string, string | undefined][] = Object.entries(value).map(\n ([key, val]) => {\n const text = stringify(val);\n return [key, text !== undefined ? unescapeUnicode(text) : undefined];\n },\n );\n\n return createEncodedChunks(entries, TENSORART_ENCODING);\n}\n","/**\n * Metadata conversion utilities\n *\n * Provides functions to convert metadata between different image formats.\n */\n\nimport type { ParseResult, RawMetadata } from '../types';\nimport { Result } from '../types';\n\nimport { convertA1111PngToSegments, convertA1111SegmentsToPng } from './a1111';\nimport {\n convertCivitaiPngToSegments,\n convertCivitaiSegmentsToPng,\n} from './civitai';\nimport {\n convertComfyUIPngToSegments,\n convertComfyUISegmentsToPng,\n} from './comfyui';\nimport {\n convertEasyDiffusionPngToSegments,\n convertEasyDiffusionSegmentsToPng,\n} from './easydiffusion';\nimport {\n convertInvokeAIPngToSegments,\n convertInvokeAISegmentsToPng,\n} from './invokeai';\nimport {\n convertNovelaiPngToSegments,\n convertNovelaiSegmentsToPng,\n} from './novelai';\nimport { createPngToSegments, createSegmentsToPng } from './simple-chunk';\nimport {\n convertStabilityMatrixPngToSegments,\n convertStabilityMatrixSegmentsToPng,\n} from './stability-matrix';\nimport {\n convertSwarmUIPngToSegments,\n convertSwarmUISegmentsToPng,\n} from './swarmui';\nimport {\n convertTensorArtPngToSegments,\n convertTensorArtSegmentsToPng,\n} from './tensorart';\n\n// Internal types\ntype ConversionTargetFormat = 'png' | 'jpeg' | 'webp';\n\ntype ConversionError =\n | { type: 'missingRawData' }\n | { type: 'invalidParseResult'; status: string }\n | { type: 'unsupportedSoftware'; software: string };\n\ntype ConversionResult = Result<RawMetadata, ConversionError>;\n\n/**\n * Convert metadata from one format to another\n *\n * Takes a ParseResult and converts the raw metadata to the target format.\n * Conversion strategy is determined by the detected software.\n *\n * @param parseResult - Result from parsePng, parseJpeg, or parseWebp\n * @param targetFormat - Target format ('png', 'jpeg', or 'webp')\n * @returns Converted RawMetadata or error\n *\n * @example\n * ```typescript\n * const pngResult = parsePng(pngData);\n * const converted = convertMetadata(pngResult, 'webp');\n * if (converted.ok) {\n * const webpWithMetadata = writeWebpMetadata(webpData, converted.value.segments);\n * }\n * ```\n */\nexport function convertMetadata(\n parseResult: ParseResult,\n targetFormat: ConversionTargetFormat,\n): ConversionResult {\n // Handle non-success statuses\n if (parseResult.status === 'empty') {\n return Result.error({ type: 'missingRawData' });\n }\n\n if (parseResult.status === 'invalid') {\n return Result.error({\n type: 'invalidParseResult',\n status: parseResult.status,\n });\n }\n\n // Handle unrecognized - should not reach here in normal flow\n // since write() handles unrecognized separately\n if (parseResult.status === 'unrecognized') {\n return Result.error({\n type: 'unsupportedSoftware',\n software: 'unknown',\n });\n }\n\n const raw = parseResult.raw;\n\n // If source and target are the same format, return as-is\n if (raw.format === targetFormat) {\n return Result.ok(raw);\n }\n\n const software = parseResult.metadata.software;\n\n // Get converter for detected software\n const converter = softwareConverters[software];\n if (!converter) {\n // This should never happen if software is a valid GenerationSoftware\n return Result.error({\n type: 'unsupportedSoftware',\n software,\n });\n }\n\n return converter(raw, targetFormat);\n}\n\n// Type for converter function\ntype ConverterFn = (\n raw: RawMetadata,\n targetFormat: ConversionTargetFormat,\n) => ConversionResult;\n\n// Type for PNG↔segment conversion functions\ntype PngToSegmentsFn = (\n chunks: import('../types').PngTextChunk[],\n) => import('../types').MetadataSegment[];\ntype SegmentsToPngFn = (\n segments: import('../types').MetadataSegment[],\n) => import('../types').PngTextChunk[];\n\n/**\n * Factory function to create format converters\n *\n * All converters follow the same pattern:\n * - PNG → JPEG/WebP: convert chunks to segments\n * - JPEG/WebP → PNG: convert segments to chunks\n * - Same format: return as-is\n */\nfunction createFormatConverter(\n pngToSegments: PngToSegmentsFn,\n segmentsToPng: SegmentsToPngFn,\n): ConverterFn {\n return (raw, targetFormat) => {\n if (raw.format === 'png') {\n // PNG → same format: return as-is\n if (targetFormat === 'png') {\n return Result.ok(raw);\n }\n // PNG → JPEG/WebP\n const segments = pngToSegments(raw.chunks);\n return Result.ok({ format: targetFormat, segments });\n }\n\n // JPEG/WebP → JPEG/WebP: just copy segments\n if (targetFormat === 'jpeg' || targetFormat === 'webp') {\n return Result.ok({ format: targetFormat, segments: raw.segments });\n }\n\n // JPEG/WebP → PNG\n const chunks = segmentsToPng(raw.segments);\n return Result.ok({ format: 'png', chunks });\n };\n}\n\n// Create converters using factory\nconst convertNovelai = createFormatConverter(\n convertNovelaiPngToSegments,\n convertNovelaiSegmentsToPng,\n);\n\nconst convertA1111 = createFormatConverter(\n convertA1111PngToSegments,\n convertA1111SegmentsToPng,\n);\n\nconst convertComfyUI = createFormatConverter(\n convertComfyUIPngToSegments,\n convertComfyUISegmentsToPng,\n);\n\nconst convertEasyDiffusion = createFormatConverter(\n convertEasyDiffusionPngToSegments,\n convertEasyDiffusionSegmentsToPng,\n);\n\nconst convertFooocus = createFormatConverter(\n createPngToSegments('parameters'),\n createSegmentsToPng('parameters', 'text-unicode-escape'),\n);\n\nconst convertRuinedFooocus = createFormatConverter(\n createPngToSegments('parameters'),\n createSegmentsToPng('parameters', 'text-unicode-escape'),\n);\n\nconst convertSwarmUI = createFormatConverter(\n convertSwarmUIPngToSegments,\n convertSwarmUISegmentsToPng,\n);\n\nconst convertInvokeAI = createFormatConverter(\n convertInvokeAIPngToSegments,\n convertInvokeAISegmentsToPng,\n);\n\nconst convertHfSpace = createFormatConverter(\n createPngToSegments('parameters'),\n createSegmentsToPng('parameters', 'text-unicode-escape'),\n);\n\nconst convertCivitai = createFormatConverter(\n convertCivitaiPngToSegments,\n convertCivitaiSegmentsToPng,\n);\n\nconst convertStabilityMatrix = createFormatConverter(\n convertStabilityMatrixPngToSegments,\n convertStabilityMatrixSegmentsToPng,\n);\n\nconst convertTensorArt = createFormatConverter(\n convertTensorArtPngToSegments,\n convertTensorArtSegmentsToPng,\n);\n\n/**\n * Lookup table: software name → converter function\n */\nconst softwareConverters = {\n // NovelAI\n novelai: convertNovelai,\n // A1111-format (sd-webui, forge family, sd-next)\n 'sd-webui': convertA1111,\n 'sd-next': convertA1111,\n forge: convertA1111,\n 'forge-classic': convertA1111,\n 'forge-neo': convertA1111,\n reforge: convertA1111,\n 'easy-reforge': convertA1111,\n // CivitAI Orchestration format\n civitai: convertCivitai,\n // ComfyUI-format\n comfyui: convertComfyUI,\n // TensorArt (per-chunk encoding: generation_data uses raw UTF-8)\n tensorart: convertTensorArt,\n // Stability Matrix (per-chunk encoding: parameters uses raw UTF-8)\n 'stability-matrix': convertStabilityMatrix,\n // Easy Diffusion\n easydiffusion: convertEasyDiffusion,\n // Fooocus variants\n fooocus: convertFooocus,\n 'ruined-fooocus': convertRuinedFooocus,\n // SwarmUI\n swarmui: convertSwarmUI,\n // InvokeAI\n invokeai: convertInvokeAI,\n // HuggingFace Space\n 'hf-space': convertHfSpace,\n} as const;\n","/**\n * Write API for sd-metadata\n *\n * Handles writing metadata to images with automatic format conversion.\n * Supports PNG, JPEG, and WebP formats.\n */\n\nimport { convertMetadata } from '../converters';\nimport type { ParseResult, WriteResult } from '../types';\n\nimport type { ImageFormat } from '../utils/binary';\nimport { detectFormat, toUint8Array } from '../utils/binary';\nimport { writeJpegMetadata } from '../writers/jpeg';\nimport { writePngMetadata } from '../writers/png';\nimport { writeWebpMetadata } from '../writers/webp';\n\n/**\n * Write metadata to an image\n *\n * Automatically detects the target image format and converts the metadata\n * if necessary. For unrecognized metadata with cross-format conversion,\n * metadata is dropped and a warning is returned.\n *\n * @param input - Target image file data (Uint8Array or ArrayBuffer)\n * @param metadata - ParseResult from `read()`\n * @returns New image data with embedded metadata (or warning if metadata was dropped)\n */\nexport function write(\n input: Uint8Array | ArrayBuffer,\n metadata: ParseResult,\n): WriteResult {\n const data = toUint8Array(input);\n const targetFormat = detectFormat(data);\n if (!targetFormat) {\n return { ok: false, error: { type: 'unsupportedFormat' } };\n }\n\n // Handle empty metadata: strip all metadata\n if (metadata.status === 'empty') {\n const result = HELPERS[targetFormat].writeEmpty(data, []);\n if (!result.ok) {\n return {\n ok: false,\n error: { type: 'writeFailed', message: result.error.type },\n };\n }\n return { ok: true, value: result.value };\n }\n\n // Handle invalid metadata\n if (metadata.status === 'invalid') {\n return {\n ok: false,\n error: { type: 'writeFailed', message: 'Cannot write invalid metadata' },\n };\n }\n\n // Handle unrecognized metadata\n if (metadata.status === 'unrecognized') {\n const sourceFormat = metadata.raw.format;\n\n // Same format: write as-is\n if (sourceFormat === targetFormat) {\n return writeRaw(data, targetFormat, metadata.raw);\n }\n\n // Cross-format: drop metadata and return with warning\n const result = HELPERS[targetFormat].writeEmpty(data, []);\n if (!result.ok) {\n return {\n ok: false,\n error: { type: 'writeFailed', message: result.error.type },\n };\n }\n return {\n ok: true,\n value: result.value,\n warning: { type: 'metadataDropped', reason: 'unrecognizedCrossFormat' },\n };\n }\n\n // Handle success metadata: convert if needed\n const conversionResult = convertMetadata(metadata, targetFormat);\n\n if (!conversionResult.ok) {\n return {\n ok: false,\n error: {\n type: 'conversionFailed',\n message: `Failed to convert metadata: ${conversionResult.error.type}`,\n },\n };\n }\n\n return writeRaw(data, targetFormat, conversionResult.value);\n}\n\n/**\n * Write raw metadata to image\n */\nfunction writeRaw(\n data: Uint8Array,\n targetFormat: ImageFormat,\n raw: import('../types').RawMetadata,\n): WriteResult {\n if (targetFormat === 'png' && raw.format === 'png') {\n const result = writePngMetadata(data, raw.chunks);\n if (!result.ok) {\n return {\n ok: false,\n error: { type: 'writeFailed', message: result.error.type },\n };\n }\n return { ok: true, value: result.value };\n }\n\n if (targetFormat === 'jpeg' && raw.format === 'jpeg') {\n const result = writeJpegMetadata(data, raw.segments);\n if (!result.ok) {\n return {\n ok: false,\n error: { type: 'writeFailed', message: result.error.type },\n };\n }\n return { ok: true, value: result.value };\n }\n\n if (targetFormat === 'webp' && raw.format === 'webp') {\n const result = writeWebpMetadata(data, raw.segments);\n if (!result.ok) {\n return {\n ok: false,\n error: { type: 'writeFailed', message: result.error.type },\n };\n }\n return { ok: true, value: result.value };\n }\n\n return {\n ok: false,\n error: {\n type: 'writeFailed',\n message: 'Internal error: format mismatch after conversion',\n },\n };\n}\n\n// ============================================================================\n// Format Helpers\n// ============================================================================\n\n/** Format-specific helper functions */\nconst HELPERS = {\n png: {\n writeEmpty: writePngMetadata,\n },\n jpeg: {\n writeEmpty: writeJpegMetadata,\n },\n webp: {\n writeEmpty: writeWebpMetadata,\n },\n} as const satisfies Record<ImageFormat, unknown>;\n","import type { GenerationSoftware } from './types';\n\n/**\n * Human-readable display labels for each generation software identifier.\n *\n * @example\n * ```typescript\n * import { softwareLabels } from '@enslo/sd-metadata';\n *\n * const result = read(imageData);\n * if (result.status === 'success') {\n * console.log(softwareLabels[result.metadata.software]);\n * // => \"NovelAI\", \"ComfyUI\", \"Stable Diffusion WebUI\", etc.\n * }\n * ```\n */\nexport const softwareLabels: Readonly<Record<GenerationSoftware, string>> =\n Object.freeze({\n novelai: 'NovelAI',\n comfyui: 'ComfyUI',\n swarmui: 'SwarmUI',\n tensorart: 'TensorArt',\n 'stability-matrix': 'Stability Matrix',\n invokeai: 'InvokeAI',\n 'sd-webui': 'Stable Diffusion WebUI',\n forge: 'Forge',\n 'forge-classic': 'Forge - Classic',\n 'forge-neo': 'Forge - Neo',\n reforge: 'reForge',\n 'easy-reforge': 'EasyReforge',\n 'sd-next': 'SD.Next',\n civitai: 'Civitai',\n 'hf-space': 'Hugging Face Space',\n easydiffusion: 'Easy Diffusion',\n fooocus: 'Fooocus',\n 'ruined-fooocus': 'Ruined Fooocus',\n });\n"],"mappings":"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","names":["paddedSize","stringify","settingsStart","parseResolution","PNG_SIGNATURE_LENGTH","APP1_MARKER","COM_MARKER","EXIF_HEADER","EXIF_CHUNK_TYPE"]}
|