@enslo/sd-metadata 1.2.0 โ†’ 1.4.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.md CHANGED
@@ -4,6 +4,8 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@enslo/sd-metadata.svg)](https://www.npmjs.com/package/@enslo/sd-metadata)
5
5
  [![license](https://img.shields.io/npm/l/@enslo/sd-metadata.svg)](https://github.com/enslo/sd-metadata/blob/main/LICENSE)
6
6
 
7
+ ๐Ÿ‡ฏ๐Ÿ‡ต **[ๆ—ฅๆœฌ่ชž็‰ˆใฏใ“ใกใ‚‰](./README.ja.md)**
8
+
7
9
  A TypeScript library to read and write metadata embedded in AI-generated images.
8
10
 
9
11
  ## Features
@@ -88,7 +90,7 @@ const { read } = require('@enslo/sd-metadata');
88
90
  ### Node.js Usage
89
91
 
90
92
  ```typescript
91
- import { read, write } from 'sd-metadata';
93
+ import { read, write } from '@enslo/sd-metadata';
92
94
  import { readFileSync, writeFileSync } from 'fs';
93
95
 
94
96
  // Read metadata from any supported format
@@ -106,7 +108,7 @@ if (result.status === 'success') {
106
108
  ### Browser Usage
107
109
 
108
110
  ```typescript
109
- import { read } from 'sd-metadata';
111
+ import { read } from '@enslo/sd-metadata';
110
112
 
111
113
  // Handle file input
112
114
  const fileInput = document.querySelector('input[type="file"]');
@@ -151,15 +153,18 @@ if (result.status === 'success') {
151
153
  > For production use, pin to a specific version instead of `@latest`:
152
154
  >
153
155
  > ```text
154
- > https://cdn.jsdelivr.net/npm/@enslo/sd-metadata@1.2.0/dist/index.js
156
+ > https://cdn.jsdelivr.net/npm/@enslo/sd-metadata@1.4.0/dist/index.js
155
157
  > ```
156
158
 
157
- ### Format Conversion
159
+ ### Advanced Examples
160
+
161
+ <details>
162
+ <summary>Format Conversion</summary>
158
163
 
159
164
  Convert metadata between different image formats:
160
165
 
161
166
  ```typescript
162
- import { read, write } from 'sd-metadata';
167
+ import { read, write } from '@enslo/sd-metadata';
163
168
 
164
169
  // Read metadata from PNG
165
170
  const pngData = readFileSync('comfyui-output.png');
@@ -182,10 +187,13 @@ if (parseResult.status === 'success') {
182
187
  > [!TIP]
183
188
  > This library handles metadata read/write only. For actual image format conversion (decoding/encoding pixels), use image processing libraries like [sharp](https://www.npmjs.com/package/sharp), [jimp](https://www.npmjs.com/package/jimp), or browser Canvas API.
184
189
 
185
- ### Handling Different Result Types
190
+ </details>
191
+
192
+ <details>
193
+ <summary>Handling Different Result Types</summary>
186
194
 
187
195
  ```typescript
188
- import { read } from 'sd-metadata';
196
+ import { read } from '@enslo/sd-metadata';
189
197
 
190
198
  const result = read(imageData);
191
199
 
@@ -215,31 +223,41 @@ switch (result.status) {
215
223
  }
216
224
  ```
217
225
 
218
- ### Force Conversion for Unrecognized Formats
226
+ </details>
219
227
 
220
- When you have unrecognized metadata but still want to convert it:
228
+ <details>
229
+ <summary>Handling Unrecognized Metadata</summary>
230
+
231
+ When working with metadata from unsupported tools:
221
232
 
222
233
  ```typescript
223
- import { read, write } from 'sd-metadata';
234
+ import { read, write } from '@enslo/sd-metadata';
224
235
 
225
236
  const source = read(unknownImage);
226
237
  // source.status === 'unrecognized'
227
238
 
228
- // Force blind conversion (preserves all metadata chunks/segments)
229
- const result = write(targetImage, source, { force: true });
230
-
239
+ // Write to target image
240
+ // - Same format (e.g., PNG โ†’ PNG): metadata preserved as-is
241
+ // - Cross-format (e.g., PNG โ†’ JPEG): metadata dropped with warning
242
+ const result = write(targetImage, source);
231
243
  if (result.ok) {
232
- // Metadata successfully converted even though format wasn't recognized
233
- console.log('Forced conversion succeeded');
244
+ saveFile('output.png', result.value);
245
+ if (result.warning) {
246
+ // Metadata was dropped during cross-format conversion
247
+ console.warn('Metadata was dropped:', result.warning.reason);
248
+ }
234
249
  }
235
250
  ```
236
251
 
237
- ### Removing Metadata
252
+ </details>
253
+
254
+ <details>
255
+ <summary>Removing Metadata</summary>
238
256
 
239
257
  To strip all metadata from an image:
240
258
 
241
259
  ```typescript
242
- import { write } from 'sd-metadata';
260
+ import { write } from '@enslo/sd-metadata';
243
261
 
244
262
  const result = write(imageData, { status: 'empty' });
245
263
  if (result.ok) {
@@ -247,12 +265,15 @@ if (result.ok) {
247
265
  }
248
266
  ```
249
267
 
250
- ### Writing Metadata in WebUI Format
268
+ </details>
269
+
270
+ <details>
271
+ <summary>Writing Metadata in WebUI Format</summary>
251
272
 
252
273
  Create and embed custom metadata in SD WebUI (A1111) format:
253
274
 
254
275
  ```typescript
255
- import { writeAsWebUI } from 'sd-metadata';
276
+ import { writeAsWebUI } from '@enslo/sd-metadata';
256
277
 
257
278
  // Create custom metadata from scratch
258
279
  const metadata = {
@@ -284,20 +305,23 @@ if (result.ok) {
284
305
  > - Converting metadata from proprietary formats to WebUI-compatible format
285
306
  > - Building tools that need to output WebUI-readable metadata
286
307
 
287
- ### Formatting Metadata for Display
308
+ </details>
288
309
 
289
- Convert metadata to human-readable text in WebUI format:
310
+ <details>
311
+ <summary>Formatting Metadata for Display</summary>
312
+
313
+ Convert metadata from **any supported tool** to a unified, human-readable WebUI format. This normalizes the differences between tools (NovelAI, ComfyUI, Forge, etc.) into a consistent text format:
290
314
 
291
315
  ```typescript
292
- import { read, formatAsWebUI } from 'sd-metadata';
316
+ import { read, formatAsWebUI } from '@enslo/sd-metadata';
293
317
 
294
318
  const result = read(imageData);
295
319
  if (result.status === 'success') {
296
- // Convert to WebUI format text
320
+ // Works with any tool: NovelAI, ComfyUI, Forge, InvokeAI, etc.
297
321
  const text = formatAsWebUI(result.metadata);
298
322
  console.log(text);
299
323
 
300
- // Output example:
324
+ // Always outputs in consistent WebUI format:
301
325
  // masterpiece, best quality, 1girl
302
326
  // Negative prompt: lowres, bad quality
303
327
  // Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 12345, Size: 512x768, Model: model.safetensors
@@ -305,7 +329,9 @@ if (result.status === 'success') {
305
329
  ```
306
330
 
307
331
  > [!NOTE]
308
- > `formatAsWebUI` provides a tool-agnostic standard format for displaying generation metadata. It works with metadata from any supported tool (NovelAI, ComfyUI, etc.) and formats it consistently.
332
+ > Regardless of which tool generated the image, `formatAsWebUI` extracts the common generation parameters and formats them in a standardized way. This is ideal for displaying metadata to users without worrying about tool-specific formats.
333
+
334
+ </details>
309
335
 
310
336
  ## API Reference
311
337
 
@@ -324,7 +350,7 @@ Reads and parses metadata from an image file.
324
350
  - `{ status: 'invalid', message? }` - Corrupted or unsupported image format
325
351
  - `message`: Optional error description
326
352
 
327
- ### `write(data: Uint8Array, metadata: ParseResult, options?: WriteOptions): WriteResult`
353
+ ### `write(data: Uint8Array, metadata: ParseResult): WriteResult`
328
354
 
329
355
  Writes metadata to an image file.
330
356
 
@@ -333,15 +359,16 @@ Writes metadata to an image file.
333
359
  - `data` - Target image file data (PNG, JPEG, or WebP)
334
360
  - `metadata` - `ParseResult` from `read()`
335
361
  - `status: 'success'` or `'empty'` - Can write directly
336
- - `status: 'unrecognized'` - Requires `force: true` option
337
- - `options` - Optional settings:
338
- - `force?: boolean` - Required when writing `status: 'unrecognized'` metadata (blind conversion)
362
+ - `status: 'unrecognized'` - Same format: writes as-is; Cross-format: drops metadata with warning
339
363
 
340
364
  **Returns:**
341
365
 
342
- - `{ ok: true, value: Uint8Array }` - Successfully written (returns new image data)
343
- - `{ ok: false, error: { type: string, message?: string } }` - Failed
344
- - `type`: `'unsupportedFormat'`, `'conversionFailed'`, or `'writeFailed'`
366
+ - `{ ok: true, value: Uint8Array, warning?: WriteWarning }` - Successfully written
367
+ - `warning` is set when metadata was intentionally dropped (e.g., unrecognized cross-format)
368
+ - `{ ok: false, error: { type, message? } }` - Failed. `type` is one of:
369
+ - `'unsupportedFormat'`: Target image is not PNG, JPEG, or WebP
370
+ - `'conversionFailed'`: Metadata conversion failed (e.g., incompatible format)
371
+ - `'writeFailed'`: Failed to embed metadata into the image
345
372
 
346
373
  ### `writeAsWebUI(data: Uint8Array, metadata: GenerationMetadata): WriteResult`
347
374
 
@@ -357,8 +384,9 @@ Writes metadata to an image in SD WebUI (A1111) format.
357
384
  **Returns:**
358
385
 
359
386
  - `{ ok: true, value: Uint8Array }` - Successfully written (returns new image data)
360
- - `{ ok: false, error: { type: string, message?: string } }` - Failed
361
- - `type`: `'unsupportedFormat'` or `'writeFailed'`
387
+ - `{ ok: false, error: { type, message? } }` - Failed. `type` is one of:
388
+ - `'unsupportedFormat'`: Target image is not PNG, JPEG, or WebP
389
+ - `'writeFailed'`: Failed to embed metadata into the image
362
390
 
363
391
  **Use cases:**
364
392
 
@@ -393,6 +421,41 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 12345, Size: 512x768, ...
393
421
  - Copying generation parameters as text
394
422
  - Logging or debugging generation settings
395
423
 
424
+ ### `formatRaw(raw: RawMetadata): string`
425
+
426
+ Formats raw metadata as plain text.
427
+
428
+ **Parameters:**
429
+
430
+ - `raw` - Raw metadata from `ParseResult` (`result.raw`)
431
+
432
+ **Returns:**
433
+
434
+ - Plain text content from the metadata (multiple entries separated by blank lines)
435
+
436
+ **Use cases:**
437
+
438
+ - Displaying unrecognized metadata to users
439
+ - Quick inspection of raw metadata content
440
+ - Fallback display when parsing fails
441
+
442
+ **Example:**
443
+
444
+ ```typescript
445
+ import { read, formatAsWebUI, formatRaw } from '@enslo/sd-metadata';
446
+
447
+ const result = read(imageData);
448
+
449
+ switch (result.status) {
450
+ case 'success':
451
+ console.log(formatAsWebUI(result.metadata));
452
+ break;
453
+ case 'unrecognized':
454
+ console.log(formatRaw(result.raw));
455
+ break;
456
+ }
457
+ ```
458
+
396
459
  ## Type Reference
397
460
 
398
461
  This section provides an overview of the main types. For complete type definitions, see [Type Documentation](./docs/types.md).
@@ -511,7 +574,7 @@ type RawMetadata =
511
574
  >
512
575
  > Use your IDE's IntelliSense for auto-completion and inline documentation.
513
576
 
514
- For detailed documentation of all exported types including `BaseMetadata`, `ModelSettings`, `SamplingSettings`, and format-specific types, see the [Type Documentation](./docs/types.md).
577
+ For detailed documentation of all exported types including `ModelSettings`, `SamplingSettings`, and format-specific types, see the [Type Documentation](./docs/types.md).
515
578
 
516
579
  ## Development
517
580
 
package/dist/index.d.ts CHANGED
@@ -1,20 +1,3 @@
1
- /**
2
- * Result type for explicit error handling
3
- */
4
- type Result<T, E> = {
5
- ok: true;
6
- value: T;
7
- } | {
8
- ok: false;
9
- error: E;
10
- };
11
- /**
12
- * Helper functions for Result type
13
- */
14
- declare const Result: {
15
- ok: <T, E>(value: T) => Result<T, E>;
16
- error: <T, E>(error: E) => Result<T, E>;
17
- };
18
1
  /**
19
2
  * PNG text chunk (tEXt or iTXt)
20
3
  */
@@ -330,9 +313,16 @@ declare function read(data: Uint8Array): ParseResult;
330
313
  */
331
314
 
332
315
  /**
333
- * Result of the write operation
316
+ * Warning types for write operations
317
+ */
318
+ type WriteWarning = {
319
+ type: 'metadataDropped';
320
+ reason: 'unrecognizedCrossFormat';
321
+ };
322
+ /**
323
+ * Error types for write operations
334
324
  */
335
- type WriteResult = Result<Uint8Array, {
325
+ type WriteError = {
336
326
  type: 'unsupportedFormat';
337
327
  } | {
338
328
  type: 'conversionFailed';
@@ -340,36 +330,32 @@ type WriteResult = Result<Uint8Array, {
340
330
  } | {
341
331
  type: 'writeFailed';
342
332
  message: string;
343
- }>;
333
+ };
344
334
  /**
345
- * Options for write operation
335
+ * Result of the write operation
336
+ *
337
+ * Success case may include a warning when metadata was intentionally dropped.
346
338
  */
347
- interface WriteOptions {
348
- /**
349
- * Force blind conversion for unrecognized formats
350
- *
351
- * When true, converts raw chunks/segments between formats even when
352
- * the generating software is unknown. Enables format conversion for
353
- * unknown/future tools without parser implementation.
354
- *
355
- * When false (default), returns error for unrecognized formats.
356
- *
357
- * @default false
358
- */
359
- force?: boolean;
360
- }
339
+ type WriteResult = {
340
+ ok: true;
341
+ value: Uint8Array;
342
+ warning?: WriteWarning;
343
+ } | {
344
+ ok: false;
345
+ error: WriteError;
346
+ };
361
347
  /**
362
348
  * Write metadata to an image
363
349
  *
364
350
  * Automatically detects the target image format and converts the metadata
365
- * if necessary.
351
+ * if necessary. For unrecognized metadata with cross-format conversion,
352
+ * metadata is dropped and a warning is returned.
366
353
  *
367
354
  * @param data - Target image file data
368
- * @param metadata - ParseResult from `read()` (must be 'success' or contain raw data)
369
- * @param options - Write options (e.g., { force: true } for blind conversion)
370
- * @returns New image data with embedded metadata
355
+ * @param metadata - ParseResult from `read()`
356
+ * @returns New image data with embedded metadata (or warning if metadata was dropped)
371
357
  */
372
- declare function write(data: Uint8Array, metadata: ParseResult, options?: WriteOptions): WriteResult;
358
+ declare function write(data: Uint8Array, metadata: ParseResult): WriteResult;
373
359
 
374
360
  /**
375
361
  * WebUI (A1111) format writer for sd-metadata
@@ -397,7 +383,7 @@ declare function write(data: Uint8Array, metadata: ParseResult, options?: WriteO
397
383
  *
398
384
  * @example
399
385
  * ```typescript
400
- * import { writeAsWebUI } from 'sd-metadata';
386
+ * import { writeAsWebUI } from '@enslo/sd-metadata';
401
387
  *
402
388
  * // Create custom metadata
403
389
  * const metadata = {
@@ -445,7 +431,7 @@ declare function writeAsWebUI(data: Uint8Array, metadata: GenerationMetadata): W
445
431
  *
446
432
  * @example
447
433
  * ```typescript
448
- * import { read, formatAsWebUI } from 'sd-metadata';
434
+ * import { read, formatAsWebUI } from '@enslo/sd-metadata';
449
435
  *
450
436
  * const result = read(imageData);
451
437
  * if (result.status === 'success') {
@@ -460,4 +446,35 @@ declare function writeAsWebUI(data: Uint8Array, metadata: GenerationMetadata): W
460
446
  */
461
447
  declare function formatAsWebUI(metadata: GenerationMetadata): string;
462
448
 
463
- export { type CharacterPrompt, type GenerationMetadata, type HiresSettings, type ITXtChunk, type MetadataSegment, type MetadataSegmentSource, type ModelSettings, type ParseResult, type PngTextChunk, type RawMetadata, type SamplingSettings, type TExtChunk, type UpscaleSettings, type WriteOptions, type WriteResult, formatAsWebUI, read, write, writeAsWebUI };
449
+ /**
450
+ * Raw metadata serialization utilities
451
+ *
452
+ * Formats RawMetadata as human-readable plain text.
453
+ */
454
+
455
+ /**
456
+ * Format raw metadata as plain text
457
+ *
458
+ * Extracts text content from RawMetadata and returns it as a simple string.
459
+ * Multiple entries are separated by double newlines.
460
+ *
461
+ * This is useful for displaying unrecognized metadata to end users
462
+ * without needing to manually iterate over chunks or segments.
463
+ *
464
+ * @param raw - Raw metadata from ParseResult
465
+ * @returns Plain text content from the metadata
466
+ *
467
+ * @example
468
+ * ```typescript
469
+ * import { read, formatRaw } from '@enslo/sd-metadata';
470
+ *
471
+ * const result = read(imageData);
472
+ * if (result.status === 'unrecognized') {
473
+ * console.log(formatRaw(result.raw));
474
+ * // Output: the raw text content without prefixes
475
+ * }
476
+ * ```
477
+ */
478
+ declare function formatRaw(raw: RawMetadata): string;
479
+
480
+ export { type CharacterPrompt, type GenerationMetadata, type HiresSettings, type ITXtChunk, type MetadataSegment, type MetadataSegmentSource, type ModelSettings, type ParseResult, type PngTextChunk, type RawMetadata, type SamplingSettings, type TExtChunk, type UpscaleSettings, type WriteResult, type WriteWarning, formatAsWebUI, formatRaw, read, write, writeAsWebUI };
package/dist/index.js CHANGED
@@ -1768,7 +1768,6 @@ var CHUNK_ENCODING_STRATEGIES = {
1768
1768
  novelai: "dynamic",
1769
1769
  "sd-next": "dynamic",
1770
1770
  easydiffusion: "dynamic",
1771
- blind: "dynamic",
1772
1771
  // Unicode escape tools (spec-compliant)
1773
1772
  comfyui: "text-unicode-escape",
1774
1773
  swarmui: "text-unicode-escape",
@@ -1833,37 +1832,6 @@ function convertA1111SegmentsToPng(segments) {
1833
1832
  );
1834
1833
  }
1835
1834
 
1836
- // src/converters/blind.ts
1837
- function blindPngToSegments(chunks) {
1838
- if (chunks.length === 0) return [];
1839
- const chunkMap = Object.fromEntries(
1840
- chunks.map((chunk) => [chunk.keyword, chunk.text])
1841
- );
1842
- return [
1843
- {
1844
- source: { type: "exifUserComment" },
1845
- data: JSON.stringify(chunkMap)
1846
- }
1847
- ];
1848
- }
1849
- function blindSegmentsToPng(segments) {
1850
- const userComment = segments.find((s) => s.source.type === "exifUserComment");
1851
- if (!userComment) return [];
1852
- const parsed = parseJson(userComment.data);
1853
- if (parsed.ok) {
1854
- return Object.entries(parsed.value).flatMap(([keyword, value]) => {
1855
- const text = typeof value === "string" ? value : JSON.stringify(value);
1856
- if (!text) return [];
1857
- return createEncodedChunk(keyword, text, getEncodingStrategy("blind"));
1858
- });
1859
- }
1860
- return createEncodedChunk(
1861
- "metadata",
1862
- userComment.data,
1863
- getEncodingStrategy("blind")
1864
- );
1865
- }
1866
-
1867
1835
  // src/converters/comfyui.ts
1868
1836
  function convertComfyUIPngToSegments(chunks) {
1869
1837
  const data = {};
@@ -2171,7 +2139,7 @@ function convertSwarmUISegmentsToPng(segments) {
2171
2139
  }
2172
2140
 
2173
2141
  // src/converters/index.ts
2174
- function convertMetadata(parseResult, targetFormat, force = false) {
2142
+ function convertMetadata(parseResult, targetFormat) {
2175
2143
  if (parseResult.status === "empty") {
2176
2144
  return Result.error({ type: "missingRawData" });
2177
2145
  }
@@ -2181,17 +2149,17 @@ function convertMetadata(parseResult, targetFormat, force = false) {
2181
2149
  status: parseResult.status
2182
2150
  });
2183
2151
  }
2184
- const raw = parseResult.raw;
2185
- if (raw.format === "png" && targetFormat === "png" || raw.format === "jpeg" && targetFormat === "jpeg" || raw.format === "webp" && targetFormat === "webp") {
2186
- return Result.ok(raw);
2187
- }
2188
- const software = parseResult.status === "success" ? parseResult.metadata.software : null;
2189
- if (!software) {
2190
- return force ? convertBlind(raw, targetFormat) : Result.error({
2152
+ if (parseResult.status === "unrecognized") {
2153
+ return Result.error({
2191
2154
  type: "unsupportedSoftware",
2192
2155
  software: "unknown"
2193
2156
  });
2194
2157
  }
2158
+ const raw = parseResult.raw;
2159
+ if (raw.format === "png" && targetFormat === "png" || raw.format === "jpeg" && targetFormat === "jpeg" || raw.format === "webp" && targetFormat === "webp") {
2160
+ return Result.ok(raw);
2161
+ }
2162
+ const software = parseResult.metadata.software;
2195
2163
  const converter = softwareConverters[software];
2196
2164
  if (!converter) {
2197
2165
  return Result.error({
@@ -2253,10 +2221,6 @@ var convertHfSpace = createFormatConverter(
2253
2221
  createPngToSegments("parameters"),
2254
2222
  createSegmentsToPng("parameters")
2255
2223
  );
2256
- var convertBlind = createFormatConverter(
2257
- blindPngToSegments,
2258
- blindSegmentsToPng
2259
- );
2260
2224
  var softwareConverters = {
2261
2225
  // NovelAI
2262
2226
  novelai: convertNovelai,
@@ -2810,58 +2774,95 @@ function buildExifChunk(segments) {
2810
2774
  }
2811
2775
 
2812
2776
  // src/api/write.ts
2813
- function write(data, metadata, options) {
2777
+ function write(data, metadata) {
2814
2778
  const targetFormat = detectFormat(data);
2815
2779
  if (!targetFormat) {
2816
- return Result.error({ type: "unsupportedFormat" });
2780
+ return { ok: false, error: { type: "unsupportedFormat" } };
2817
2781
  }
2818
2782
  if (metadata.status === "empty") {
2819
2783
  const result = HELPERS2[targetFormat].writeEmpty(data, []);
2820
2784
  if (!result.ok) {
2821
- return Result.error({ type: "writeFailed", message: result.error.type });
2785
+ return {
2786
+ ok: false,
2787
+ error: { type: "writeFailed", message: result.error.type }
2788
+ };
2822
2789
  }
2823
- return Result.ok(result.value);
2790
+ return { ok: true, value: result.value };
2824
2791
  }
2825
2792
  if (metadata.status === "invalid") {
2826
- return Result.error({
2827
- type: "writeFailed",
2828
- message: "Cannot write invalid metadata"
2829
- });
2793
+ return {
2794
+ ok: false,
2795
+ error: { type: "writeFailed", message: "Cannot write invalid metadata" }
2796
+ };
2830
2797
  }
2831
- const conversionResult = convertMetadata(
2832
- metadata,
2833
- targetFormat,
2834
- options?.force ?? false
2835
- );
2798
+ if (metadata.status === "unrecognized") {
2799
+ const sourceFormat = metadata.raw.format;
2800
+ if (sourceFormat === targetFormat) {
2801
+ return writeRaw(data, targetFormat, metadata.raw);
2802
+ }
2803
+ const result = HELPERS2[targetFormat].writeEmpty(data, []);
2804
+ if (!result.ok) {
2805
+ return {
2806
+ ok: false,
2807
+ error: { type: "writeFailed", message: result.error.type }
2808
+ };
2809
+ }
2810
+ return {
2811
+ ok: true,
2812
+ value: result.value,
2813
+ warning: { type: "metadataDropped", reason: "unrecognizedCrossFormat" }
2814
+ };
2815
+ }
2816
+ const conversionResult = convertMetadata(metadata, targetFormat);
2836
2817
  if (!conversionResult.ok) {
2837
- return Result.error({
2838
- type: "conversionFailed",
2839
- message: `Failed to convert metadata: ${conversionResult.error.type}`
2840
- });
2818
+ return {
2819
+ ok: false,
2820
+ error: {
2821
+ type: "conversionFailed",
2822
+ message: `Failed to convert metadata: ${conversionResult.error.type}`
2823
+ }
2824
+ };
2841
2825
  }
2842
- const newRaw = conversionResult.value;
2843
- if (targetFormat === "png" && newRaw.format === "png") {
2844
- const result = writePngMetadata(data, newRaw.chunks);
2845
- if (!result.ok)
2846
- return Result.error({ type: "writeFailed", message: result.error.type });
2847
- return Result.ok(result.value);
2848
- }
2849
- if (targetFormat === "jpeg" && newRaw.format === "jpeg") {
2850
- const result = writeJpegMetadata(data, newRaw.segments);
2851
- if (!result.ok)
2852
- return Result.error({ type: "writeFailed", message: result.error.type });
2853
- return Result.ok(result.value);
2854
- }
2855
- if (targetFormat === "webp" && newRaw.format === "webp") {
2856
- const result = writeWebpMetadata(data, newRaw.segments);
2857
- if (!result.ok)
2858
- return Result.error({ type: "writeFailed", message: result.error.type });
2859
- return Result.ok(result.value);
2826
+ return writeRaw(data, targetFormat, conversionResult.value);
2827
+ }
2828
+ function writeRaw(data, targetFormat, raw) {
2829
+ if (targetFormat === "png" && raw.format === "png") {
2830
+ const result = writePngMetadata(data, raw.chunks);
2831
+ if (!result.ok) {
2832
+ return {
2833
+ ok: false,
2834
+ error: { type: "writeFailed", message: result.error.type }
2835
+ };
2836
+ }
2837
+ return { ok: true, value: result.value };
2860
2838
  }
2861
- return Result.error({
2862
- type: "writeFailed",
2863
- message: "Internal error: format mismatch after conversion"
2864
- });
2839
+ if (targetFormat === "jpeg" && raw.format === "jpeg") {
2840
+ const result = writeJpegMetadata(data, raw.segments);
2841
+ if (!result.ok) {
2842
+ return {
2843
+ ok: false,
2844
+ error: { type: "writeFailed", message: result.error.type }
2845
+ };
2846
+ }
2847
+ return { ok: true, value: result.value };
2848
+ }
2849
+ if (targetFormat === "webp" && raw.format === "webp") {
2850
+ const result = writeWebpMetadata(data, raw.segments);
2851
+ if (!result.ok) {
2852
+ return {
2853
+ ok: false,
2854
+ error: { type: "writeFailed", message: result.error.type }
2855
+ };
2856
+ }
2857
+ return { ok: true, value: result.value };
2858
+ }
2859
+ return {
2860
+ ok: false,
2861
+ error: {
2862
+ type: "writeFailed",
2863
+ message: "Internal error: format mismatch after conversion"
2864
+ }
2865
+ };
2865
2866
  }
2866
2867
  var HELPERS2 = {
2867
2868
  png: {
@@ -3012,8 +3013,20 @@ function createExifSegments(text) {
3012
3013
  }
3013
3014
  ];
3014
3015
  }
3016
+
3017
+ // src/serializers/raw.ts
3018
+ function formatRaw(raw) {
3019
+ switch (raw.format) {
3020
+ case "png":
3021
+ return raw.chunks.map((chunk) => chunk.text).join("\n\n");
3022
+ case "jpeg":
3023
+ case "webp":
3024
+ return raw.segments.map((segment) => segment.data).join("\n\n");
3025
+ }
3026
+ }
3015
3027
  export {
3016
3028
  formatAsWebUI,
3029
+ formatRaw,
3017
3030
  read,
3018
3031
  write,
3019
3032
  writeAsWebUI