@enslo/sd-metadata 2.1.1 → 2.2.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/dist/index.js CHANGED
@@ -164,6 +164,62 @@ function writeUint32(data, offset, value, isLittleEndian) {
164
164
  );
165
165
  }
166
166
 
167
+ // src/utils/xmp.ts
168
+ var XMP_KEYWORD = "XML:com.adobe.xmp";
169
+ var XMP_APP1_PREFIX = /* @__PURE__ */ new TextEncoder().encode(
170
+ "http://ns.adobe.com/xap/1.0/\0"
171
+ );
172
+ function matchesXmpPrefix(data, offset) {
173
+ if (offset + XMP_APP1_PREFIX.length > data.length) return false;
174
+ for (let i = 0; i < XMP_APP1_PREFIX.length; i++) {
175
+ if (data[offset + i] !== XMP_APP1_PREFIX[i]) return false;
176
+ }
177
+ return true;
178
+ }
179
+ function isXmpKeyword(keyword) {
180
+ return keyword === XMP_KEYWORD;
181
+ }
182
+ var MAX_XMP_TEXT_LENGTH = 65535;
183
+ function extractXmpEntries(xmpText) {
184
+ if (xmpText.length > MAX_XMP_TEXT_LENGTH) return null;
185
+ const candidates = [
186
+ ["CreatorTool", extractSimpleElement(xmpText, "xmp", "CreatorTool")],
187
+ ["UserComment", extractAltElement(xmpText, "exif", "UserComment")],
188
+ ["parameters", extractAltElement(xmpText, "dc", "description")]
189
+ ];
190
+ const entries = candidates.filter(
191
+ (entry) => entry[1] !== void 0
192
+ );
193
+ return entries.length > 0 ? Object.fromEntries(entries) : null;
194
+ }
195
+ function extractSimpleElement(xmp, ns, field) {
196
+ const pattern = new RegExp(`<${ns}:${field}>([^<]*)</${ns}:${field}>`);
197
+ const match = xmp.match(pattern);
198
+ return match?.[1] ? decodeXmlEntities(match[1]) : void 0;
199
+ }
200
+ function extractAltElement(xmp, ns, field) {
201
+ const pattern = new RegExp(
202
+ `<${ns}:${field}>[\\s\\S]*?<rdf:li[^>]*(?<!/)>([\\s\\S]*?)</rdf:li>[\\s\\S]*?</${ns}:${field}>`
203
+ );
204
+ const match = xmp.match(pattern);
205
+ return match?.[1] ? decodeXmlEntities(match[1]) : void 0;
206
+ }
207
+ function decodeXmlEntities(text) {
208
+ return text.replace(/&#x([0-9a-fA-F]+);/g, (match, hex) => {
209
+ try {
210
+ return String.fromCodePoint(parseInt(hex, 16));
211
+ } catch {
212
+ return match;
213
+ }
214
+ }).replace(/&#(\d+);/g, (match, dec) => {
215
+ try {
216
+ return String.fromCodePoint(parseInt(dec, 10));
217
+ } catch {
218
+ return match;
219
+ }
220
+ }).replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
221
+ }
222
+
167
223
  // src/utils/exif-constants.ts
168
224
  var USER_COMMENT_TAG = 37510;
169
225
  var IMAGE_DESCRIPTION_TAG = 270;
@@ -317,6 +373,7 @@ function writeJpegMetadata(data, segments) {
317
373
  return Result.error({ type: "invalidSignature" });
318
374
  }
319
375
  const comSegments = segments.filter((s) => s.source.type === "jpegCom");
376
+ const xmpSegments = segments.filter((s) => s.source.type === "xmpPacket");
320
377
  const exifSegments = segments.filter(
321
378
  (s) => s.source.type === "exifUserComment" || s.source.type === "exifImageDescription" || s.source.type === "exifMake"
322
379
  );
@@ -326,11 +383,16 @@ function writeJpegMetadata(data, segments) {
326
383
  }
327
384
  const { beforeSos, scanData } = collectResult.value;
328
385
  const app1Segment = exifSegments.length > 0 ? buildApp1Segment(exifSegments) : null;
329
- const comSegmentData = comSegments.map((s) => buildComSegment(s.data));
386
+ const firstXmp = xmpSegments[0];
387
+ const xmpApp1Segment = firstXmp ? buildXmpApp1Segment(firstXmp.data) : null;
388
+ const comSegmentData = comSegments.map((s) => buildComSegment(s.data)).filter((s) => s !== null);
330
389
  let totalSize = 2;
331
390
  if (app1Segment) {
332
391
  totalSize += app1Segment.length;
333
392
  }
393
+ if (xmpApp1Segment) {
394
+ totalSize += xmpApp1Segment.length;
395
+ }
334
396
  for (const seg of beforeSos) {
335
397
  totalSize += seg.length;
336
398
  }
@@ -346,6 +408,10 @@ function writeJpegMetadata(data, segments) {
346
408
  output.set(app1Segment, offset);
347
409
  offset += app1Segment.length;
348
410
  }
411
+ if (xmpApp1Segment) {
412
+ output.set(xmpApp1Segment, offset);
413
+ offset += xmpApp1Segment.length;
414
+ }
349
415
  for (const seg of beforeSos) {
350
416
  output.set(seg, offset);
351
417
  offset += seg.length;
@@ -400,8 +466,9 @@ function collectNonMetadataSegments(data) {
400
466
  data[offset + 5] === 102 && // f
401
467
  data[offset + 6] === 0 && // NULL
402
468
  data[offset + 7] === 0;
469
+ const isXmpApp1 = marker === APP1_MARKER && !isExifApp1 && matchesXmpPrefix(data, offset + 2);
403
470
  const isCom = marker === COM_MARKER;
404
- if (!isExifApp1 && !isCom) {
471
+ if (!isExifApp1 && !isXmpApp1 && !isCom) {
405
472
  beforeSos.push(data.slice(segmentStart, segmentEnd));
406
473
  }
407
474
  offset = segmentEnd;
@@ -425,9 +492,26 @@ function buildApp1Segment(segments) {
425
492
  segment.set(tiffData, 4 + EXIF_HEADER.length);
426
493
  return segment;
427
494
  }
495
+ function buildXmpApp1Segment(xmpText) {
496
+ const textBytes = new TextEncoder().encode(xmpText);
497
+ const segmentLength = 2 + XMP_APP1_PREFIX.length + textBytes.length;
498
+ if (segmentLength > 65535) {
499
+ return null;
500
+ }
501
+ const segment = new Uint8Array(2 + segmentLength);
502
+ segment[0] = 255;
503
+ segment[1] = APP1_MARKER;
504
+ writeUint16BE(segment, 2, segmentLength);
505
+ segment.set(XMP_APP1_PREFIX, 4);
506
+ segment.set(textBytes, 4 + XMP_APP1_PREFIX.length);
507
+ return segment;
508
+ }
428
509
  function buildComSegment(text) {
429
510
  const textBytes = new TextEncoder().encode(text);
430
511
  const segmentLength = 2 + textBytes.length;
512
+ if (segmentLength > 65535) {
513
+ return null;
514
+ }
431
515
  const segment = new Uint8Array(2 + segmentLength);
432
516
  segment[0] = 255;
433
517
  segment[1] = COM_MARKER;
@@ -594,18 +678,25 @@ function writeWebpMetadata(data, segments) {
594
678
  if (!isWebp(data)) {
595
679
  return Result.error({ type: "invalidSignature" });
596
680
  }
597
- const collectResult = collectNonExifChunks(data);
681
+ const xmpSegments = segments.filter((s) => s.source.type === "xmpPacket");
682
+ const exifSegments = segments.filter((s) => s.source.type !== "xmpPacket");
683
+ const collectResult = collectNonMetadataChunks(data);
598
684
  if (!collectResult.ok) {
599
685
  return collectResult;
600
686
  }
601
687
  const { chunks } = collectResult.value;
602
- const exifChunk = buildExifChunk(segments);
688
+ const exifChunk = buildExifChunk(exifSegments);
689
+ const firstXmp = xmpSegments[0];
690
+ const xmpChunk = firstXmp ? buildXmpChunk(firstXmp.data) : null;
691
+ const metadataChunks = [];
692
+ if (exifChunk) metadataChunks.push(exifChunk);
693
+ if (xmpChunk) metadataChunks.push(xmpChunk);
603
694
  let newFileSize = 4;
604
695
  for (const chunk of chunks) {
605
696
  newFileSize += chunk.length;
606
697
  }
607
- if (exifChunk) {
608
- newFileSize += exifChunk.length;
698
+ for (const meta of metadataChunks) {
699
+ newFileSize += meta.length;
609
700
  }
610
701
  const output = new Uint8Array(8 + newFileSize);
611
702
  let offset = 0;
@@ -615,18 +706,23 @@ function writeWebpMetadata(data, segments) {
615
706
  offset += 4;
616
707
  output.set(WEBP_MARKER, offset);
617
708
  offset += 4;
618
- let exifWritten = false;
709
+ let metadataWritten = false;
619
710
  for (const chunk of chunks) {
620
711
  output.set(chunk, offset);
621
712
  offset += chunk.length;
622
- if (!exifWritten && exifChunk && isImageChunk(chunk)) {
623
- output.set(exifChunk, offset);
624
- offset += exifChunk.length;
625
- exifWritten = true;
713
+ if (!metadataWritten && metadataChunks.length > 0 && isImageChunk(chunk)) {
714
+ for (const meta of metadataChunks) {
715
+ output.set(meta, offset);
716
+ offset += meta.length;
717
+ }
718
+ metadataWritten = true;
626
719
  }
627
720
  }
628
- if (!exifWritten && exifChunk) {
629
- output.set(exifChunk, offset);
721
+ if (!metadataWritten) {
722
+ for (const meta of metadataChunks) {
723
+ output.set(meta, offset);
724
+ offset += meta.length;
725
+ }
630
726
  }
631
727
  return Result.ok(output);
632
728
  }
@@ -635,7 +731,7 @@ function isImageChunk(chunk) {
635
731
  const type = readChunkType(chunk, 0);
636
732
  return type === "VP8 " || type === "VP8L" || type === "VP8X";
637
733
  }
638
- function collectNonExifChunks(data) {
734
+ function collectNonMetadataChunks(data) {
639
735
  const chunks = [];
640
736
  let firstChunkType = "";
641
737
  let offset = 12;
@@ -651,7 +747,7 @@ function collectNonExifChunks(data) {
651
747
  message: `Chunk extends beyond file at offset ${offset}`
652
748
  });
653
749
  }
654
- if (typeStr !== "EXIF") {
750
+ if (typeStr !== "EXIF" && typeStr !== "XMP ") {
655
751
  const paddedSize2 = chunkSize + chunkSize % 2;
656
752
  const chunkData = data.slice(offset, offset + 8 + paddedSize2);
657
753
  chunks.push(chunkData);
@@ -662,13 +758,10 @@ function collectNonExifChunks(data) {
662
758
  return Result.ok({ chunks, firstChunkType });
663
759
  }
664
760
  function buildExifChunk(segments) {
665
- const exifSegments = segments.filter(
666
- (s) => s.source.type === "exifUserComment" || s.source.type === "exifImageDescription" || s.source.type === "exifMake"
667
- );
668
- if (exifSegments.length === 0) {
761
+ if (segments.length === 0) {
669
762
  return null;
670
763
  }
671
- const tiffData = buildExifTiffData(exifSegments);
764
+ const tiffData = buildExifTiffData(segments);
672
765
  if (tiffData.length === 0) {
673
766
  return null;
674
767
  }
@@ -680,6 +773,16 @@ function buildExifChunk(segments) {
680
773
  chunk.set(tiffData, 8);
681
774
  return chunk;
682
775
  }
776
+ function buildXmpChunk(xmpText) {
777
+ const textBytes = new TextEncoder().encode(xmpText);
778
+ const chunkSize = textBytes.length;
779
+ const paddedSize = chunkSize + chunkSize % 2;
780
+ const chunk = new Uint8Array(8 + paddedSize);
781
+ chunk.set(new TextEncoder().encode("XMP "));
782
+ writeUint32LE(chunk, 4, chunkSize);
783
+ chunk.set(textBytes, 8);
784
+ return chunk;
785
+ }
683
786
 
684
787
  // src/api/stringify.ts
685
788
  function normalizeLineEndings(text) {
@@ -1393,6 +1496,9 @@ function detectUniqueKeywords(entryRecord) {
1393
1496
  if (entryRecord.Software?.startsWith("NovelAI")) {
1394
1497
  return "novelai";
1395
1498
  }
1499
+ if (entryRecord.CreatorTool?.startsWith("Draw Things")) {
1500
+ return "draw-things";
1501
+ }
1396
1502
  const keyResult = detectByUniqueKey(entryRecord);
1397
1503
  if (keyResult) return keyResult;
1398
1504
  if ("fooocus_scheme" in entryRecord) {
@@ -1531,6 +1637,60 @@ function detectFromA1111Format(text) {
1531
1637
  return null;
1532
1638
  }
1533
1639
 
1640
+ // src/parsers/draw-things.ts
1641
+ function parseDrawThings(entries) {
1642
+ const jsonText = entries.UserComment?.startsWith("{") ? entries.UserComment : entries.Comment?.startsWith("{") ? entries.Comment : void 0;
1643
+ if (!jsonText) {
1644
+ return Result.error({ type: "unsupportedFormat" });
1645
+ }
1646
+ const parsed = parseJson(jsonText);
1647
+ if (!parsed.ok || parsed.type !== "object") {
1648
+ return Result.error({
1649
+ type: "parseError",
1650
+ message: "Invalid JSON in Draw Things metadata"
1651
+ });
1652
+ }
1653
+ return buildMetadata(parsed.value);
1654
+ }
1655
+ function parseSize2(size) {
1656
+ if (typeof size !== "string") return { width: 0, height: 0 };
1657
+ const match = size.match(/^(\d+)x(\d+)$/);
1658
+ if (!match) return { width: 0, height: 0 };
1659
+ return { width: Number(match[1]), height: Number(match[2]) };
1660
+ }
1661
+ function buildMetadata(data) {
1662
+ const str = (key) => {
1663
+ const v = data[key];
1664
+ return typeof v === "string" ? v : void 0;
1665
+ };
1666
+ const num = (key) => {
1667
+ const v = data[key];
1668
+ if (typeof v === "number" && v !== 0) return v;
1669
+ return void 0;
1670
+ };
1671
+ const prompt = (str("c") ?? "").trim();
1672
+ const negativePrompt = (str("uc") ?? "").trim();
1673
+ const { width, height } = parseSize2(data.size);
1674
+ const metadata = {
1675
+ software: "draw-things",
1676
+ prompt,
1677
+ negativePrompt,
1678
+ width,
1679
+ height,
1680
+ model: trimObject({
1681
+ name: str("model")
1682
+ }),
1683
+ sampling: trimObject({
1684
+ sampler: str("sampler"),
1685
+ steps: num("steps"),
1686
+ cfg: num("scale"),
1687
+ seed: num("seed"),
1688
+ denoise: num("strength")
1689
+ })
1690
+ };
1691
+ return Result.ok(metadata);
1692
+ }
1693
+
1534
1694
  // src/parsers/easydiffusion.ts
1535
1695
  function extractModelName(path) {
1536
1696
  if (!path) return void 0;
@@ -1539,7 +1699,7 @@ function extractModelName(path) {
1539
1699
  }
1540
1700
  function parseEasyDiffusion(entries) {
1541
1701
  if ("use_stable_diffusion_model" in entries) {
1542
- return buildMetadata(entries);
1702
+ return buildMetadata2(entries);
1543
1703
  }
1544
1704
  const jsonText = entries.UserComment?.startsWith("{") ? entries.UserComment : entries.parameters?.startsWith("{") ? entries.parameters : void 0;
1545
1705
  if (!jsonText) {
@@ -1552,9 +1712,9 @@ function parseEasyDiffusion(entries) {
1552
1712
  message: "Invalid JSON in Easy Diffusion metadata"
1553
1713
  });
1554
1714
  }
1555
- return buildMetadata(parsed.value);
1715
+ return buildMetadata2(parsed.value);
1556
1716
  }
1557
- function buildMetadata(data) {
1717
+ function buildMetadata2(data) {
1558
1718
  const str = (key) => {
1559
1719
  const v = data[key];
1560
1720
  return typeof v === "string" ? v : void 0;
@@ -2015,6 +2175,8 @@ function parseMetadata(entries) {
2015
2175
  }
2016
2176
  case "ruined-fooocus":
2017
2177
  return parseRuinedFooocus(entries);
2178
+ case "draw-things":
2179
+ return parseDrawThings(entries);
2018
2180
  default:
2019
2181
  return Result.error({ type: "unsupportedFormat" });
2020
2182
  }
@@ -2295,6 +2457,15 @@ function readJpegMetadata(data) {
2295
2457
  const exifSegments = parseExifMetadataSegments(exifData);
2296
2458
  segments.push(...exifSegments);
2297
2459
  }
2460
+ const xmpApp1 = findXmpApp1Segment(data);
2461
+ if (xmpApp1) {
2462
+ const xmpData = data.slice(xmpApp1.offset, xmpApp1.offset + xmpApp1.length);
2463
+ const xmpText = new TextDecoder("utf-8").decode(xmpData);
2464
+ segments.push({
2465
+ source: { type: "xmpPacket" },
2466
+ data: xmpText
2467
+ });
2468
+ }
2298
2469
  const comSegment = findComSegment(data);
2299
2470
  if (comSegment) {
2300
2471
  const comData = data.slice(
@@ -2343,6 +2514,35 @@ function findApp1Segment(data) {
2343
2514
  }
2344
2515
  return null;
2345
2516
  }
2517
+ function findXmpApp1Segment(data) {
2518
+ let offset = 2;
2519
+ while (offset < data.length - 4) {
2520
+ if (data[offset] !== 255) {
2521
+ offset++;
2522
+ continue;
2523
+ }
2524
+ const marker = data[offset + 1];
2525
+ if (marker === 255) {
2526
+ offset++;
2527
+ continue;
2528
+ }
2529
+ const length = readUint16BE(data, offset + 2);
2530
+ if (marker === APP1_MARKER2 && length >= XMP_APP1_PREFIX.length + 2) {
2531
+ const headerStart = offset + 4;
2532
+ if (matchesXmpPrefix(data, headerStart)) {
2533
+ return {
2534
+ offset: headerStart + XMP_APP1_PREFIX.length,
2535
+ length: length - 2 - XMP_APP1_PREFIX.length
2536
+ };
2537
+ }
2538
+ }
2539
+ offset += 2 + length;
2540
+ if (marker === 218 || marker === 217) {
2541
+ break;
2542
+ }
2543
+ }
2544
+ return null;
2545
+ }
2346
2546
  function findComSegment(data) {
2347
2547
  let offset = 2;
2348
2548
  while (offset < data.length - 4) {
@@ -2507,15 +2707,27 @@ function readWebpMetadata(data) {
2507
2707
  if (!isWebp(data)) {
2508
2708
  return Result.error({ type: "invalidSignature" });
2509
2709
  }
2710
+ const segments = [];
2510
2711
  const exifChunk = findExifChunk(data);
2511
- if (!exifChunk) {
2512
- return Result.ok([]);
2712
+ if (exifChunk) {
2713
+ const exifData = data.slice(
2714
+ exifChunk.offset,
2715
+ exifChunk.offset + exifChunk.length
2716
+ );
2717
+ segments.push(...parseExifMetadataSegments(exifData));
2718
+ }
2719
+ const xmpChunk = findXmpChunk(data);
2720
+ if (xmpChunk) {
2721
+ const xmpData = data.slice(
2722
+ xmpChunk.offset,
2723
+ xmpChunk.offset + xmpChunk.length
2724
+ );
2725
+ const xmpText = new TextDecoder("utf-8").decode(xmpData);
2726
+ segments.push({
2727
+ source: { type: "xmpPacket" },
2728
+ data: xmpText
2729
+ });
2513
2730
  }
2514
- const exifData = data.slice(
2515
- exifChunk.offset,
2516
- exifChunk.offset + exifChunk.length
2517
- );
2518
- const segments = parseExifMetadataSegments(exifData);
2519
2731
  return Result.ok(segments);
2520
2732
  }
2521
2733
  function findExifChunk(data) {
@@ -2523,6 +2735,23 @@ function findExifChunk(data) {
2523
2735
  while (offset + 8 <= data.length) {
2524
2736
  const chunkSize = readUint32LE(data, offset + 4);
2525
2737
  if (readChunkType(data, offset) === "EXIF") {
2738
+ if (offset + 8 + chunkSize > data.length) return null;
2739
+ return {
2740
+ offset: offset + 8,
2741
+ length: chunkSize
2742
+ };
2743
+ }
2744
+ const paddedSize = chunkSize + chunkSize % 2;
2745
+ offset += 8 + paddedSize;
2746
+ }
2747
+ return null;
2748
+ }
2749
+ function findXmpChunk(data) {
2750
+ let offset = 12;
2751
+ while (offset + 8 <= data.length) {
2752
+ const chunkSize = readUint32LE(data, offset + 4);
2753
+ if (readChunkType(data, offset) === "XMP ") {
2754
+ if (offset + 8 + chunkSize > data.length) return null;
2526
2755
  return {
2527
2756
  offset: offset + 8,
2528
2757
  length: chunkSize
@@ -2536,20 +2765,37 @@ function findExifChunk(data) {
2536
2765
 
2537
2766
  // src/utils/convert.ts
2538
2767
  function pngChunksToRecord(chunks) {
2539
- return Object.fromEntries(chunks.map((c) => [c.keyword, c.text]));
2768
+ return Object.fromEntries(
2769
+ chunks.flatMap((chunk) => {
2770
+ if (isXmpKeyword(chunk.keyword)) {
2771
+ const expanded = extractXmpEntries(chunk.text);
2772
+ if (expanded) return Object.entries(expanded);
2773
+ }
2774
+ return [[chunk.keyword, chunk.text]];
2775
+ })
2776
+ );
2540
2777
  }
2541
2778
  function segmentsToRecord(segments) {
2542
2779
  const record = {};
2543
2780
  for (const segment of segments) {
2544
2781
  const keyword = sourceToKeyword(segment.source);
2545
2782
  const text = segment.data;
2546
- if (segment.source.type === "exifUserComment" && text.startsWith("{")) {
2547
- const expanded = tryExpandNovelAIWebpFormat(text);
2783
+ if (segment.source.type === "xmpPacket") {
2784
+ const expanded = extractXmpEntries(text);
2548
2785
  if (expanded) {
2549
2786
  Object.assign(record, expanded);
2550
2787
  continue;
2551
2788
  }
2552
2789
  }
2790
+ if (segment.source.type === "exifUserComment") {
2791
+ if (text.startsWith("{")) {
2792
+ const expanded = tryExpandNovelAIWebpFormat(text);
2793
+ if (expanded) {
2794
+ Object.assign(record, expanded);
2795
+ continue;
2796
+ }
2797
+ }
2798
+ }
2553
2799
  record[keyword] = text;
2554
2800
  }
2555
2801
  return record;
@@ -2579,6 +2825,8 @@ function sourceToKeyword(source) {
2579
2825
  return source.prefix ?? "ImageDescription";
2580
2826
  case "exifMake":
2581
2827
  return source.prefix ?? "Make";
2828
+ case "xmpPacket":
2829
+ return "XML:com.adobe.xmp";
2582
2830
  }
2583
2831
  }
2584
2832
 
@@ -2750,6 +2998,24 @@ function convertComfyUISegmentsToPng(segments) {
2750
2998
  return tryParseExtendedFormat(segments) ?? tryParseSaveImagePlusFormat(segments) ?? [];
2751
2999
  }
2752
3000
 
3001
+ // src/converters/draw-things.ts
3002
+ var XMP_KEYWORD2 = "XML:com.adobe.xmp";
3003
+ function convertDrawThingsPngToSegments(chunks) {
3004
+ const xmpChunk = chunks.find((c) => isXmpKeyword(c.keyword));
3005
+ if (!xmpChunk) return [];
3006
+ return [
3007
+ {
3008
+ source: { type: "xmpPacket" },
3009
+ data: xmpChunk.text
3010
+ }
3011
+ ];
3012
+ }
3013
+ function convertDrawThingsSegmentsToPng(segments) {
3014
+ const xmpSegment = findSegment(segments, "xmpPacket");
3015
+ if (!xmpSegment) return [];
3016
+ return createITxtChunk(XMP_KEYWORD2, xmpSegment.data);
3017
+ }
3018
+
2753
3019
  // src/converters/easydiffusion.ts
2754
3020
  function convertEasyDiffusionPngToSegments(chunks) {
2755
3021
  const json = Object.fromEntries(
@@ -3065,6 +3331,10 @@ var convertHfSpace = createFormatConverter(
3065
3331
  createPngToSegments("parameters"),
3066
3332
  createSegmentsToPng("parameters", "text-unicode-escape")
3067
3333
  );
3334
+ var convertDrawThings = createFormatConverter(
3335
+ convertDrawThingsPngToSegments,
3336
+ convertDrawThingsSegmentsToPng
3337
+ );
3068
3338
  var convertCivitai = createFormatConverter(
3069
3339
  convertCivitaiPngToSegments,
3070
3340
  convertCivitaiSegmentsToPng
@@ -3106,7 +3376,9 @@ var softwareConverters = {
3106
3376
  // InvokeAI
3107
3377
  invokeai: convertInvokeAI,
3108
3378
  // HuggingFace Space
3109
- "hf-space": convertHfSpace
3379
+ "hf-space": convertHfSpace,
3380
+ // Draw Things (XMP format)
3381
+ "draw-things": convertDrawThings
3110
3382
  };
3111
3383
 
3112
3384
  // src/api/write.ts
@@ -3232,7 +3504,8 @@ var softwareLabels = Object.freeze({
3232
3504
  "hf-space": "Hugging Face Space",
3233
3505
  easydiffusion: "Easy Diffusion",
3234
3506
  fooocus: "Fooocus",
3235
- "ruined-fooocus": "Ruined Fooocus"
3507
+ "ruined-fooocus": "Ruined Fooocus",
3508
+ "draw-things": "Draw Things"
3236
3509
  });
3237
3510
  export {
3238
3511
  embed,