@enslo/sd-metadata 1.4.1 → 1.4.2

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
@@ -153,7 +153,7 @@ if (result.status === 'success') {
153
153
  > For production use, pin to a specific version instead of `@latest`:
154
154
  >
155
155
  > ```text
156
- > https://cdn.jsdelivr.net/npm/@enslo/sd-metadata@1.4.1/dist/index.js
156
+ > https://cdn.jsdelivr.net/npm/@enslo/sd-metadata@1.4.2/dist/index.js
157
157
  > ```
158
158
 
159
159
  ### Advanced Examples
package/dist/index.d.ts CHANGED
@@ -14,10 +14,6 @@ type MetadataSegmentSource = {
14
14
  } | {
15
15
  type: 'exifMake';
16
16
  prefix?: string;
17
- } | {
18
- type: 'exifSoftware';
19
- } | {
20
- type: 'exifDocumentName';
21
17
  } | {
22
18
  type: 'jpegCom';
23
19
  };
package/dist/index.js CHANGED
@@ -345,6 +345,12 @@ function detectFromCommentJson(comment) {
345
345
  if ("invokeai_metadata" in parsed) {
346
346
  return "invokeai";
347
347
  }
348
+ if ("generation_data" in parsed) {
349
+ return "tensorart";
350
+ }
351
+ if ("smproj" in parsed) {
352
+ return "stability-matrix";
353
+ }
348
354
  if ("prompt" in parsed && "workflow" in parsed) {
349
355
  const workflow = parsed.workflow;
350
356
  const prompt = parsed.prompt;
@@ -574,7 +580,7 @@ function parseFooocus(entries) {
574
580
  // src/parsers/hf-space.ts
575
581
  function parseHfSpace(entries) {
576
582
  const entryRecord = buildEntryRecord(entries);
577
- const parametersText = entryRecord.parameters;
583
+ const parametersText = entryRecord.parameters ?? entryRecord.Comment;
578
584
  if (!parametersText) {
579
585
  return Result.error({ type: "unsupportedFormat" });
580
586
  }
@@ -727,7 +733,7 @@ function parseNovelAI(entries) {
727
733
  // src/parsers/ruined-fooocus.ts
728
734
  function parseRuinedFooocus(entries) {
729
735
  const entryRecord = buildEntryRecord(entries);
730
- const jsonText = entryRecord.parameters;
736
+ const jsonText = entryRecord.parameters ?? entryRecord.Comment;
731
737
  if (!jsonText || !jsonText.startsWith("{")) {
732
738
  return Result.error({ type: "unsupportedFormat" });
733
739
  }
@@ -775,7 +781,20 @@ function parseStabilityMatrix(entries) {
775
781
  ...comfyResult.value,
776
782
  software: "stability-matrix"
777
783
  };
778
- const jsonText = entryRecord["parameters-json"];
784
+ let jsonText = entryRecord["parameters-json"];
785
+ if (!jsonText && entryRecord.Comment?.startsWith("{")) {
786
+ const commentParsed = parseJson(
787
+ entryRecord.Comment
788
+ );
789
+ if (commentParsed.ok) {
790
+ const commentData = commentParsed.value;
791
+ if (typeof commentData["parameters-json"] === "string") {
792
+ jsonText = commentData["parameters-json"];
793
+ } else if (typeof commentData["parameters-json"] === "object") {
794
+ jsonText = JSON.stringify(commentData["parameters-json"]);
795
+ }
796
+ }
797
+ }
779
798
  if (jsonText) {
780
799
  const parsed = parseJson(jsonText);
781
800
  if (parsed.ok) {
@@ -874,7 +893,26 @@ function parseSwarmUI(entries) {
874
893
  // src/parsers/tensorart.ts
875
894
  function parseTensorArt(entries) {
876
895
  const entryRecord = buildEntryRecord(entries);
877
- const dataText = entryRecord.generation_data;
896
+ let dataText = entryRecord.generation_data;
897
+ let promptChunk = entryRecord.prompt;
898
+ if (!dataText && entryRecord.Comment?.startsWith("{")) {
899
+ const commentParsed = parseJson(
900
+ entryRecord.Comment
901
+ );
902
+ if (commentParsed.ok) {
903
+ const commentData = commentParsed.value;
904
+ if (typeof commentData.generation_data === "string") {
905
+ dataText = commentData.generation_data;
906
+ } else if (typeof commentData.generation_data === "object") {
907
+ dataText = JSON.stringify(commentData.generation_data);
908
+ }
909
+ if (typeof commentData.prompt === "string") {
910
+ promptChunk = commentData.prompt;
911
+ } else if (typeof commentData.prompt === "object") {
912
+ promptChunk = JSON.stringify(commentData.prompt);
913
+ }
914
+ }
915
+ }
878
916
  if (!dataText) {
879
917
  return Result.error({ type: "unsupportedFormat" });
880
918
  }
@@ -889,7 +927,6 @@ function parseTensorArt(entries) {
889
927
  const data = parsed.value;
890
928
  const width = data.width ?? 0;
891
929
  const height = data.height ?? 0;
892
- const promptChunk = entryRecord.prompt;
893
930
  if (!promptChunk) {
894
931
  return Result.error({ type: "unsupportedFormat" });
895
932
  }
@@ -1092,9 +1129,7 @@ function detectFormat(data) {
1092
1129
  // src/utils/exif-constants.ts
1093
1130
  var USER_COMMENT_TAG = 37510;
1094
1131
  var IMAGE_DESCRIPTION_TAG = 270;
1095
- var DOCUMENT_NAME_TAG = 269;
1096
1132
  var MAKE_TAG = 271;
1097
- var SOFTWARE_TAG = 305;
1098
1133
  var EXIF_IFD_POINTER_TAG = 34665;
1099
1134
 
1100
1135
  // src/readers/exif.ts
@@ -1152,22 +1187,6 @@ function extractTagsFromIfd(data, ifdOffset, isLittleEndian) {
1152
1187
  data: prefix ? text.slice(prefix.length + 2) : text
1153
1188
  });
1154
1189
  }
1155
- } else if (tag === SOFTWARE_TAG) {
1156
- const text = decodeAsciiString(tagData);
1157
- if (text) {
1158
- segments.push({
1159
- source: { type: "exifSoftware" },
1160
- data: text
1161
- });
1162
- }
1163
- } else if (tag === DOCUMENT_NAME_TAG) {
1164
- const text = decodeAsciiString(tagData);
1165
- if (text) {
1166
- segments.push({
1167
- source: { type: "exifDocumentName" },
1168
- data: text
1169
- });
1170
- }
1171
1190
  } else if (tag === USER_COMMENT_TAG) {
1172
1191
  const text = decodeUserComment(tagData);
1173
1192
  if (text) {
@@ -1618,10 +1637,6 @@ function sourceToKeyword(source) {
1618
1637
  return source.prefix ?? "Description";
1619
1638
  case "exifMake":
1620
1639
  return source.prefix ?? "Make";
1621
- case "exifSoftware":
1622
- return "Software";
1623
- case "exifDocumentName":
1624
- return "Title";
1625
1640
  }
1626
1641
  }
1627
1642
 
@@ -1786,6 +1801,12 @@ function escapeUnicode(text) {
1786
1801
  return `\\u${code}`;
1787
1802
  });
1788
1803
  }
1804
+ function unescapeUnicode(text) {
1805
+ return text.replace(
1806
+ /\\u([0-9a-fA-F]{4})/g,
1807
+ (_, hex) => String.fromCharCode(Number.parseInt(hex, 16))
1808
+ );
1809
+ }
1789
1810
  function hasNonLatin1(text) {
1790
1811
  return /[^\x00-\xFF]/.test(text);
1791
1812
  }
@@ -1812,70 +1833,19 @@ function convertA1111PngToSegments(chunks) {
1812
1833
  if (!parameters) {
1813
1834
  return [];
1814
1835
  }
1815
- const segments = [
1836
+ return [
1816
1837
  {
1817
1838
  source: { type: "exifUserComment" },
1818
1839
  data: parameters.text
1819
1840
  }
1820
1841
  ];
1821
- const software = chunks.find((c) => c.keyword === "Software");
1822
- if (software) {
1823
- segments.push({
1824
- source: { type: "exifSoftware" },
1825
- data: software.text
1826
- });
1827
- }
1828
- const title = chunks.find((c) => c.keyword === "Title");
1829
- if (title) {
1830
- segments.push({
1831
- source: { type: "exifDocumentName" },
1832
- data: title.text
1833
- });
1834
- }
1835
- const description = chunks.find((c) => c.keyword === "Description");
1836
- if (description) {
1837
- segments.push({
1838
- source: { type: "exifImageDescription" },
1839
- data: description.text
1840
- });
1841
- }
1842
- const make = chunks.find((c) => c.keyword === "Make");
1843
- if (make) {
1844
- segments.push({
1845
- source: { type: "exifMake" },
1846
- data: make.text
1847
- });
1848
- }
1849
- return segments;
1850
1842
  }
1851
1843
  function convertA1111SegmentsToPng(segments) {
1852
1844
  const userComment = segments.find((s) => s.source.type === "exifUserComment");
1853
1845
  if (!userComment) {
1854
1846
  return [];
1855
1847
  }
1856
- const parametersChunks = createEncodedChunk(
1857
- "parameters",
1858
- userComment.data,
1859
- "dynamic"
1860
- );
1861
- const chunks = [...parametersChunks];
1862
- const software = findSegment(segments, "exifSoftware");
1863
- if (software) {
1864
- chunks.push(...createTextChunk("Software", software.data));
1865
- }
1866
- const title = findSegment(segments, "exifDocumentName");
1867
- if (title) {
1868
- chunks.push(...createTextChunk("Title", title.data));
1869
- }
1870
- const description = findSegment(segments, "exifImageDescription");
1871
- if (description) {
1872
- chunks.push(...createTextChunk("Description", description.data));
1873
- }
1874
- const make = findSegment(segments, "exifMake");
1875
- if (make) {
1876
- chunks.push(...createTextChunk("Make", make.data));
1877
- }
1878
- return chunks;
1848
+ return createEncodedChunk("parameters", userComment.data, "dynamic");
1879
1849
  }
1880
1850
 
1881
1851
  // src/converters/base-json.ts
@@ -1905,9 +1875,14 @@ function convertKvSegmentsToPng(segments, encodingStrategy) {
1905
1875
  if (!parsed.ok) {
1906
1876
  return [];
1907
1877
  }
1908
- return Object.entries(parsed.value).flatMap(
1909
- ([keyword, value]) => createEncodedChunk(keyword, stringify(value), encodingStrategy)
1910
- );
1878
+ return Object.entries(parsed.value).flatMap(([keyword, value]) => {
1879
+ const text = stringify(value);
1880
+ return createEncodedChunk(
1881
+ keyword,
1882
+ text !== void 0 ? unescapeUnicode(text) : void 0,
1883
+ encodingStrategy
1884
+ );
1885
+ });
1911
1886
  }
1912
1887
 
1913
1888
  // src/converters/comfyui.ts
@@ -1930,7 +1905,7 @@ var tryParseExtendedFormat = (segments) => {
1930
1905
  ];
1931
1906
  };
1932
1907
  var tryParseSaveImagePlusFormat = (segments) => {
1933
- const chunks = convertKvSegmentsToPng(segments, "text-unicode-escape");
1908
+ const chunks = convertKvSegmentsToPng(segments, "text-utf8-raw");
1934
1909
  return chunks.length > 0 ? chunks : null;
1935
1910
  };
1936
1911
  function convertComfyUISegmentsToPng(segments) {
@@ -1972,35 +1947,22 @@ function convertInvokeAISegmentsToPng(segments) {
1972
1947
  }
1973
1948
 
1974
1949
  // src/converters/novelai.ts
1975
- var NOVELAI_SOFTWARE = "NovelAI";
1976
1950
  var NOVELAI_TITLE = "NovelAI generated image";
1951
+ var NOVELAI_SOFTWARE = "NovelAI";
1977
1952
  function convertNovelaiPngToSegments(chunks) {
1978
- const data = buildUserCommentJson(chunks);
1979
- const userCommentSegment = {
1980
- source: { type: "exifUserComment" },
1981
- data: JSON.stringify(data)
1982
- };
1983
1953
  const description = chunks.find((c) => c.keyword === "Description");
1984
1954
  const descriptionSegment = description && {
1985
1955
  source: { type: "exifImageDescription" },
1986
1956
  data: `\0\0\0\0${description.text}`
1987
1957
  };
1988
- const software = chunks.find((c) => c.keyword === "Software");
1989
- const softwareSegment = software && {
1990
- source: { type: "exifSoftware" },
1991
- data: software.text
1992
- };
1993
- const title = chunks.find((c) => c.keyword === "Title");
1994
- const titleSegment = title && {
1995
- source: { type: "exifDocumentName" },
1996
- data: title.text
1958
+ const data = buildUserCommentJson(chunks);
1959
+ const userCommentSegment = {
1960
+ source: { type: "exifUserComment" },
1961
+ data: JSON.stringify(data)
1997
1962
  };
1998
- return [
1999
- userCommentSegment,
2000
- descriptionSegment,
2001
- softwareSegment,
2002
- titleSegment
2003
- ].filter((segment) => Boolean(segment));
1963
+ return [descriptionSegment, userCommentSegment].filter(
1964
+ (segment) => segment !== void 0
1965
+ );
2004
1966
  }
2005
1967
  function buildUserCommentJson(chunks) {
2006
1968
  return NOVELAI_KEY_ORDER.map((key) => {
@@ -2022,11 +1984,9 @@ var NOVELAI_KEY_ORDER = [
2022
1984
  function convertNovelaiSegmentsToPng(segments) {
2023
1985
  const userCommentSeg = findSegment(segments, "exifUserComment");
2024
1986
  const descriptionSeg = findSegment(segments, "exifImageDescription");
2025
- const softwareSeg = findSegment(segments, "exifSoftware");
2026
- const titleSeg = findSegment(segments, "exifDocumentName");
2027
- return parseSegments(userCommentSeg, descriptionSeg, softwareSeg, titleSeg);
1987
+ return parseSegments(userCommentSeg, descriptionSeg);
2028
1988
  }
2029
- function parseSegments(userCommentSeg, descriptionSeg, softwareSeg, titleSeg) {
1989
+ function parseSegments(userCommentSeg, descriptionSeg) {
2030
1990
  if (!userCommentSeg || !descriptionSeg) {
2031
1991
  return [];
2032
1992
  }
@@ -2041,16 +2001,13 @@ function parseSegments(userCommentSeg, descriptionSeg, softwareSeg, titleSeg) {
2041
2001
  );
2042
2002
  return [
2043
2003
  // Title (required, use default if missing)
2044
- createTextChunk(
2045
- "Title",
2046
- titleSeg?.data ?? stringify(jsonData.Title) ?? NOVELAI_TITLE
2047
- ),
2004
+ createTextChunk("Title", stringify(jsonData.Title) ?? NOVELAI_TITLE),
2048
2005
  // Description (optional, prefer exifImageDescription over JSON)
2049
2006
  createEncodedChunk("Description", descriptionText, "dynamic"),
2050
2007
  // Software (required, use default if missing)
2051
2008
  createTextChunk(
2052
2009
  "Software",
2053
- softwareSeg?.data ?? stringify(jsonData.Software) ?? NOVELAI_SOFTWARE
2010
+ stringify(jsonData.Software) ?? NOVELAI_SOFTWARE
2054
2011
  ),
2055
2012
  // Source (optional)
2056
2013
  createTextChunk("Source", stringify(jsonData.Source)),
@@ -2234,7 +2191,7 @@ var softwareConverters = {
2234
2191
  // src/writers/exif.ts
2235
2192
  function buildExifTiffData(segments) {
2236
2193
  const ifd0Segments = segments.filter(
2237
- (s) => s.source.type === "exifImageDescription" || s.source.type === "exifMake" || s.source.type === "exifSoftware" || s.source.type === "exifDocumentName"
2194
+ (s) => s.source.type === "exifImageDescription" || s.source.type === "exifMake"
2238
2195
  );
2239
2196
  const exifIfdSegments = segments.filter(
2240
2197
  (s) => s.source.type === "exifUserComment"
@@ -2252,12 +2209,6 @@ function buildExifTiffData(segments) {
2252
2209
  } else if (seg.source.type === "exifMake") {
2253
2210
  const data = encodeAsciiTag(seg.data, seg.source.prefix);
2254
2211
  ifd0Tags.push({ tag: MAKE_TAG, type: 2, data });
2255
- } else if (seg.source.type === "exifSoftware") {
2256
- const data = encodeAsciiTag(seg.data);
2257
- ifd0Tags.push({ tag: SOFTWARE_TAG, type: 2, data });
2258
- } else if (seg.source.type === "exifDocumentName") {
2259
- const data = encodeAsciiTag(seg.data);
2260
- ifd0Tags.push({ tag: DOCUMENT_NAME_TAG, type: 2, data });
2261
2212
  }
2262
2213
  }
2263
2214
  for (const seg of exifIfdSegments) {
@@ -2385,7 +2336,7 @@ function writeJpegMetadata(data, segments) {
2385
2336
  }
2386
2337
  const comSegments = segments.filter((s) => s.source.type === "jpegCom");
2387
2338
  const exifSegments = segments.filter(
2388
- (s) => s.source.type === "exifUserComment" || s.source.type === "exifImageDescription" || s.source.type === "exifMake" || s.source.type === "exifSoftware" || s.source.type === "exifDocumentName"
2339
+ (s) => s.source.type === "exifUserComment" || s.source.type === "exifImageDescription" || s.source.type === "exifMake"
2389
2340
  );
2390
2341
  const collectResult = collectNonMetadataSegments(data);
2391
2342
  if (!collectResult.ok) {
@@ -2745,7 +2696,7 @@ function collectNonExifChunks(data) {
2745
2696
  }
2746
2697
  function buildExifChunk(segments) {
2747
2698
  const exifSegments = segments.filter(
2748
- (s) => s.source.type === "exifUserComment" || s.source.type === "exifImageDescription" || s.source.type === "exifMake" || s.source.type === "exifSoftware" || s.source.type === "exifDocumentName"
2699
+ (s) => s.source.type === "exifUserComment" || s.source.type === "exifImageDescription" || s.source.type === "exifMake"
2749
2700
  );
2750
2701
  if (exifSegments.length === 0) {
2751
2702
  return null;