@enslo/sd-metadata 1.3.0 → 1.4.1

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
@@ -314,7 +314,7 @@ function detectSoftware(entries) {
314
314
  return null;
315
315
  }
316
316
  function detectUniqueKeywords(entryRecord) {
317
- if (entryRecord.Software === "NovelAI") {
317
+ if (entryRecord.Software?.startsWith("NovelAI")) {
318
318
  return "novelai";
319
319
  }
320
320
  if ("invokeai_metadata" in entryRecord) {
@@ -671,7 +671,7 @@ function parseInvokeAI(entries) {
671
671
  // src/parsers/novelai.ts
672
672
  function parseNovelAI(entries) {
673
673
  const entryRecord = buildEntryRecord(entries);
674
- if (entryRecord.Software !== "NovelAI") {
674
+ if (!entryRecord.Software?.startsWith("NovelAI")) {
675
675
  return Result.error({ type: "unsupportedFormat" });
676
676
  }
677
677
  const commentText = entryRecord.Comment;
@@ -1092,7 +1092,9 @@ function detectFormat(data) {
1092
1092
  // src/utils/exif-constants.ts
1093
1093
  var USER_COMMENT_TAG = 37510;
1094
1094
  var IMAGE_DESCRIPTION_TAG = 270;
1095
+ var DOCUMENT_NAME_TAG = 269;
1095
1096
  var MAKE_TAG = 271;
1097
+ var SOFTWARE_TAG = 305;
1096
1098
  var EXIF_IFD_POINTER_TAG = 34665;
1097
1099
 
1098
1100
  // src/readers/exif.ts
@@ -1150,6 +1152,22 @@ function extractTagsFromIfd(data, ifdOffset, isLittleEndian) {
1150
1152
  data: prefix ? text.slice(prefix.length + 2) : text
1151
1153
  });
1152
1154
  }
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
+ }
1153
1171
  } else if (tag === USER_COMMENT_TAG) {
1154
1172
  const text = decodeUserComment(tagData);
1155
1173
  if (text) {
@@ -1580,7 +1598,7 @@ function tryExpandNovelAIWebpFormat(text) {
1580
1598
  return null;
1581
1599
  }
1582
1600
  const outer = outerParsed.value;
1583
- if (typeof outer !== "object" || outer === null || outer.Software !== "NovelAI" || typeof outer.Comment !== "string") {
1601
+ if (typeof outer !== "object" || outer === null || typeof outer.Software === "string" && !outer.Software.startsWith("NovelAI") || typeof outer.Comment !== "string") {
1584
1602
  return null;
1585
1603
  }
1586
1604
  const entries = [{ keyword: "Software", text: "NovelAI" }];
@@ -1600,6 +1618,10 @@ function sourceToKeyword(source) {
1600
1618
  return source.prefix ?? "Description";
1601
1619
  case "exifMake":
1602
1620
  return source.prefix ?? "Make";
1621
+ case "exifSoftware":
1622
+ return "Software";
1623
+ case "exifDocumentName":
1624
+ return "Title";
1603
1625
  }
1604
1626
  }
1605
1627
 
@@ -1758,30 +1780,6 @@ var stringify = (value) => {
1758
1780
  };
1759
1781
 
1760
1782
  // src/converters/chunk-encoding.ts
1761
- var CHUNK_ENCODING_STRATEGIES = {
1762
- // Dynamic selection tools
1763
- a1111: "dynamic",
1764
- forge: "dynamic",
1765
- "forge-neo": "dynamic",
1766
- "sd-webui": "dynamic",
1767
- invokeai: "dynamic",
1768
- novelai: "dynamic",
1769
- "sd-next": "dynamic",
1770
- easydiffusion: "dynamic",
1771
- blind: "dynamic",
1772
- // Unicode escape tools (spec-compliant)
1773
- comfyui: "text-unicode-escape",
1774
- swarmui: "text-unicode-escape",
1775
- fooocus: "text-unicode-escape",
1776
- "ruined-fooocus": "text-unicode-escape",
1777
- "hf-space": "text-unicode-escape",
1778
- // Raw UTF-8 tools (non-compliant but compatible)
1779
- "stability-matrix": "text-utf8-raw",
1780
- tensorart: "text-utf8-raw"
1781
- };
1782
- function getEncodingStrategy(tool) {
1783
- return CHUNK_ENCODING_STRATEGIES[tool] ?? "text-unicode-escape";
1784
- }
1785
1783
  function escapeUnicode(text) {
1786
1784
  return text.replace(/[\u0100-\uffff]/g, (char) => {
1787
1785
  const code = char.charCodeAt(0).toString(16).padStart(4, "0");
@@ -1814,58 +1812,74 @@ function convertA1111PngToSegments(chunks) {
1814
1812
  if (!parameters) {
1815
1813
  return [];
1816
1814
  }
1817
- return [
1815
+ const segments = [
1818
1816
  {
1819
1817
  source: { type: "exifUserComment" },
1820
1818
  data: parameters.text
1821
1819
  }
1822
1820
  ];
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;
1823
1850
  }
1824
1851
  function convertA1111SegmentsToPng(segments) {
1825
1852
  const userComment = segments.find((s) => s.source.type === "exifUserComment");
1826
1853
  if (!userComment) {
1827
1854
  return [];
1828
1855
  }
1829
- return createEncodedChunk(
1856
+ const parametersChunks = createEncodedChunk(
1830
1857
  "parameters",
1831
1858
  userComment.data,
1832
- getEncodingStrategy("a1111")
1833
- );
1834
- }
1835
-
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])
1859
+ "dynamic"
1841
1860
  );
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
- });
1861
+ const chunks = [...parametersChunks];
1862
+ const software = findSegment(segments, "exifSoftware");
1863
+ if (software) {
1864
+ chunks.push(...createTextChunk("Software", software.data));
1859
1865
  }
1860
- return createEncodedChunk(
1861
- "metadata",
1862
- userComment.data,
1863
- getEncodingStrategy("blind")
1864
- );
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;
1865
1879
  }
1866
1880
 
1867
- // src/converters/comfyui.ts
1868
- function convertComfyUIPngToSegments(chunks) {
1881
+ // src/converters/base-json.ts
1882
+ function convertKvPngToSegments(chunks) {
1869
1883
  const data = {};
1870
1884
  for (const chunk of chunks) {
1871
1885
  const parsed = parseJson(chunk.text);
@@ -1882,6 +1896,24 @@ function convertComfyUIPngToSegments(chunks) {
1882
1896
  }
1883
1897
  ];
1884
1898
  }
1899
+ function convertKvSegmentsToPng(segments, encodingStrategy) {
1900
+ const userComment = findSegment(segments, "exifUserComment");
1901
+ if (!userComment) {
1902
+ return [];
1903
+ }
1904
+ const parsed = parseJson(userComment.data);
1905
+ if (!parsed.ok) {
1906
+ return [];
1907
+ }
1908
+ return Object.entries(parsed.value).flatMap(
1909
+ ([keyword, value]) => createEncodedChunk(keyword, stringify(value), encodingStrategy)
1910
+ );
1911
+ }
1912
+
1913
+ // src/converters/comfyui.ts
1914
+ function convertComfyUIPngToSegments(chunks) {
1915
+ return convertKvPngToSegments(chunks);
1916
+ }
1885
1917
  var tryParseExtendedFormat = (segments) => {
1886
1918
  const imageDescription = findSegment(segments, "exifImageDescription");
1887
1919
  const make = findSegment(segments, "exifMake");
@@ -1889,34 +1921,17 @@ var tryParseExtendedFormat = (segments) => {
1889
1921
  return null;
1890
1922
  }
1891
1923
  return [
1892
- ...createEncodedChunk("prompt", make?.data, getEncodingStrategy("comfyui")),
1924
+ ...createEncodedChunk("prompt", make?.data, "text-unicode-escape"),
1893
1925
  ...createEncodedChunk(
1894
1926
  "workflow",
1895
1927
  imageDescription?.data,
1896
- getEncodingStrategy("comfyui")
1928
+ "text-unicode-escape"
1897
1929
  )
1898
1930
  ];
1899
1931
  };
1900
1932
  var tryParseSaveImagePlusFormat = (segments) => {
1901
- const userComment = findSegment(segments, "exifUserComment");
1902
- if (!userComment) {
1903
- return null;
1904
- }
1905
- const parsed = parseJson(userComment.data);
1906
- if (!parsed.ok) {
1907
- return createEncodedChunk(
1908
- "prompt",
1909
- userComment.data,
1910
- getEncodingStrategy("comfyui")
1911
- );
1912
- }
1913
- return Object.entries(parsed.value).flatMap(
1914
- ([keyword, value]) => createEncodedChunk(
1915
- keyword,
1916
- stringify(value),
1917
- getEncodingStrategy("comfyui")
1918
- )
1919
- );
1933
+ const chunks = convertKvSegmentsToPng(segments, "text-unicode-escape");
1934
+ return chunks.length > 0 ? chunks : null;
1920
1935
  };
1921
1936
  function convertComfyUISegmentsToPng(segments) {
1922
1937
  return tryParseExtendedFormat(segments) ?? tryParseSaveImagePlusFormat(segments) ?? [];
@@ -1943,93 +1958,49 @@ function convertEasyDiffusionSegmentsToPng(segments) {
1943
1958
  if (!parsed.ok) {
1944
1959
  return [];
1945
1960
  }
1946
- return Object.entries(parsed.value).flatMap(([keyword, value]) => {
1947
- const text = value != null ? typeof value === "string" ? value : String(value) : void 0;
1948
- if (!text) return [];
1949
- return createEncodedChunk(
1950
- keyword,
1951
- text,
1952
- getEncodingStrategy("easydiffusion")
1953
- );
1954
- });
1961
+ return Object.entries(parsed.value).flatMap(
1962
+ ([keyword, value]) => createEncodedChunk(keyword, stringify(value), "dynamic")
1963
+ );
1955
1964
  }
1956
1965
 
1957
1966
  // src/converters/invokeai.ts
1958
1967
  function convertInvokeAIPngToSegments(chunks) {
1959
- const data = {};
1960
- for (const chunk of chunks) {
1961
- const parsed = parseJson(chunk.text);
1962
- if (parsed.ok) {
1963
- data[chunk.keyword] = parsed.value;
1964
- } else {
1965
- data[chunk.keyword] = chunk.text;
1966
- }
1967
- }
1968
- return [
1969
- {
1970
- source: { type: "exifUserComment" },
1971
- data: JSON.stringify(data)
1972
- }
1973
- ];
1968
+ return convertKvPngToSegments(chunks);
1974
1969
  }
1975
1970
  function convertInvokeAISegmentsToPng(segments) {
1976
- const userComment = findSegment(segments, "exifUserComment");
1977
- if (!userComment) {
1978
- return [];
1979
- }
1980
- const parsed = parseJson(userComment.data);
1981
- if (!parsed.ok) {
1982
- return createEncodedChunk(
1983
- "invokeai_metadata",
1984
- userComment.data,
1985
- getEncodingStrategy("invokeai")
1986
- );
1987
- }
1988
- const metadataText = stringify(parsed.value.invokeai_metadata);
1989
- const graphText = stringify(parsed.value.invokeai_graph);
1990
- const chunks = [
1991
- ...createEncodedChunk(
1992
- "invokeai_metadata",
1993
- metadataText,
1994
- getEncodingStrategy("invokeai")
1995
- ),
1996
- ...createEncodedChunk(
1997
- "invokeai_graph",
1998
- graphText,
1999
- getEncodingStrategy("invokeai")
2000
- )
2001
- ];
2002
- if (chunks.length > 0) {
2003
- return chunks;
2004
- }
2005
- return createEncodedChunk(
2006
- "invokeai_metadata",
2007
- userComment.data,
2008
- getEncodingStrategy("invokeai")
2009
- );
1971
+ return convertKvSegmentsToPng(segments, "dynamic");
2010
1972
  }
2011
1973
 
2012
1974
  // src/converters/novelai.ts
2013
- var NOVELAI_TITLE = "NovelAI generated image";
2014
1975
  var NOVELAI_SOFTWARE = "NovelAI";
1976
+ var NOVELAI_TITLE = "NovelAI generated image";
2015
1977
  function convertNovelaiPngToSegments(chunks) {
2016
- const comment = chunks.find((c) => c.keyword === "Comment");
2017
- if (!comment) {
2018
- return [];
2019
- }
2020
- const description = chunks.find((c) => c.keyword === "Description");
2021
1978
  const data = buildUserCommentJson(chunks);
2022
- const descriptionSegment = description ? [
2023
- {
2024
- source: { type: "exifImageDescription" },
2025
- data: `\0\0\0\0${description.text}`
2026
- }
2027
- ] : [];
2028
1979
  const userCommentSegment = {
2029
1980
  source: { type: "exifUserComment" },
2030
1981
  data: JSON.stringify(data)
2031
1982
  };
2032
- return [...descriptionSegment, userCommentSegment];
1983
+ const description = chunks.find((c) => c.keyword === "Description");
1984
+ const descriptionSegment = description && {
1985
+ source: { type: "exifImageDescription" },
1986
+ data: `\0\0\0\0${description.text}`
1987
+ };
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
1997
+ };
1998
+ return [
1999
+ userCommentSegment,
2000
+ descriptionSegment,
2001
+ softwareSegment,
2002
+ titleSegment
2003
+ ].filter((segment) => Boolean(segment));
2033
2004
  }
2034
2005
  function buildUserCommentJson(chunks) {
2035
2006
  return NOVELAI_KEY_ORDER.map((key) => {
@@ -2051,9 +2022,11 @@ var NOVELAI_KEY_ORDER = [
2051
2022
  function convertNovelaiSegmentsToPng(segments) {
2052
2023
  const userCommentSeg = findSegment(segments, "exifUserComment");
2053
2024
  const descriptionSeg = findSegment(segments, "exifImageDescription");
2054
- return parseSegments(userCommentSeg, descriptionSeg);
2025
+ const softwareSeg = findSegment(segments, "exifSoftware");
2026
+ const titleSeg = findSegment(segments, "exifDocumentName");
2027
+ return parseSegments(userCommentSeg, descriptionSeg, softwareSeg, titleSeg);
2055
2028
  }
2056
- function parseSegments(userCommentSeg, descriptionSeg) {
2029
+ function parseSegments(userCommentSeg, descriptionSeg, softwareSeg, titleSeg) {
2057
2030
  if (!userCommentSeg || !descriptionSeg) {
2058
2031
  return [];
2059
2032
  }
@@ -2066,20 +2039,18 @@ function parseSegments(userCommentSeg, descriptionSeg) {
2066
2039
  descriptionSeg,
2067
2040
  stringify(jsonData.Description)
2068
2041
  );
2069
- const descriptionChunks = descriptionText ? createEncodedChunk(
2070
- "Description",
2071
- descriptionText,
2072
- getEncodingStrategy("novelai")
2073
- ) : [];
2074
2042
  return [
2075
2043
  // Title (required, use default if missing)
2076
- createTextChunk("Title", stringify(jsonData.Title) ?? NOVELAI_TITLE),
2044
+ createTextChunk(
2045
+ "Title",
2046
+ titleSeg?.data ?? stringify(jsonData.Title) ?? NOVELAI_TITLE
2047
+ ),
2077
2048
  // Description (optional, prefer exifImageDescription over JSON)
2078
- ...descriptionChunks,
2049
+ createEncodedChunk("Description", descriptionText, "dynamic"),
2079
2050
  // Software (required, use default if missing)
2080
2051
  createTextChunk(
2081
2052
  "Software",
2082
- stringify(jsonData.Software) ?? NOVELAI_SOFTWARE
2053
+ softwareSeg?.data ?? stringify(jsonData.Software) ?? NOVELAI_SOFTWARE
2083
2054
  ),
2084
2055
  // Source (optional)
2085
2056
  createTextChunk("Source", stringify(jsonData.Source)),
@@ -2107,17 +2078,13 @@ function createPngToSegments(keyword) {
2107
2078
  return !chunk ? [] : [{ source: { type: "exifUserComment" }, data: chunk.text }];
2108
2079
  };
2109
2080
  }
2110
- function createSegmentsToPng(keyword) {
2081
+ function createSegmentsToPng(keyword, encodingStrategy) {
2111
2082
  return (segments) => {
2112
2083
  const userComment = segments.find(
2113
2084
  (s) => s.source.type === "exifUserComment"
2114
2085
  );
2115
2086
  if (!userComment) return [];
2116
- return createEncodedChunk(
2117
- keyword,
2118
- userComment.data,
2119
- getEncodingStrategy(keyword)
2120
- );
2087
+ return createEncodedChunk(keyword, userComment.data, encodingStrategy);
2121
2088
  };
2122
2089
  }
2123
2090
 
@@ -2128,11 +2095,10 @@ function convertSwarmUIPngToSegments(chunks) {
2128
2095
  return [];
2129
2096
  }
2130
2097
  const parsed = parseJson(parametersChunk.text);
2131
- const data = parsed.ok ? parsed.value : parametersChunk.text;
2132
2098
  const segments = [
2133
2099
  {
2134
2100
  source: { type: "exifUserComment" },
2135
- data: typeof data === "string" ? data : JSON.stringify(data)
2101
+ data: parsed.ok ? JSON.stringify(parsed.value) : parametersChunk.text
2136
2102
  }
2137
2103
  ];
2138
2104
  const promptChunk = chunks.find((c) => c.keyword === "prompt");
@@ -2146,32 +2112,18 @@ function convertSwarmUIPngToSegments(chunks) {
2146
2112
  }
2147
2113
  function convertSwarmUISegmentsToPng(segments) {
2148
2114
  const userComment = findSegment(segments, "exifUserComment");
2149
- if (!userComment) {
2150
- return [];
2151
- }
2152
- const chunks = [];
2153
2115
  const make = findSegment(segments, "exifMake");
2154
- if (make) {
2155
- chunks.push(
2156
- ...createEncodedChunk(
2157
- "prompt",
2158
- make.data,
2159
- getEncodingStrategy("swarmui")
2160
- )
2161
- );
2162
- }
2163
- chunks.push(
2164
- ...createEncodedChunk(
2165
- "parameters",
2166
- userComment.data,
2167
- getEncodingStrategy("swarmui")
2168
- )
2169
- );
2116
+ const chunks = [
2117
+ // Restore node graph first if present (extended format)
2118
+ createEncodedChunk("prompt", make?.data, "text-unicode-escape"),
2119
+ // Add parameters chunk second (always present)
2120
+ createEncodedChunk("parameters", userComment?.data, "text-unicode-escape")
2121
+ ].flat();
2170
2122
  return chunks;
2171
2123
  }
2172
2124
 
2173
2125
  // src/converters/index.ts
2174
- function convertMetadata(parseResult, targetFormat, force = false) {
2126
+ function convertMetadata(parseResult, targetFormat) {
2175
2127
  if (parseResult.status === "empty") {
2176
2128
  return Result.error({ type: "missingRawData" });
2177
2129
  }
@@ -2181,17 +2133,17 @@ function convertMetadata(parseResult, targetFormat, force = false) {
2181
2133
  status: parseResult.status
2182
2134
  });
2183
2135
  }
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({
2136
+ if (parseResult.status === "unrecognized") {
2137
+ return Result.error({
2191
2138
  type: "unsupportedSoftware",
2192
2139
  software: "unknown"
2193
2140
  });
2194
2141
  }
2142
+ const raw = parseResult.raw;
2143
+ if (raw.format === "png" && targetFormat === "png" || raw.format === "jpeg" && targetFormat === "jpeg" || raw.format === "webp" && targetFormat === "webp") {
2144
+ return Result.ok(raw);
2145
+ }
2146
+ const software = parseResult.metadata.software;
2195
2147
  const converter = softwareConverters[software];
2196
2148
  if (!converter) {
2197
2149
  return Result.error({
@@ -2235,11 +2187,11 @@ var convertEasyDiffusion = createFormatConverter(
2235
2187
  );
2236
2188
  var convertFooocus = createFormatConverter(
2237
2189
  createPngToSegments("Comment"),
2238
- createSegmentsToPng("Comment")
2190
+ createSegmentsToPng("Comment", "text-unicode-escape")
2239
2191
  );
2240
2192
  var convertRuinedFooocus = createFormatConverter(
2241
2193
  createPngToSegments("parameters"),
2242
- createSegmentsToPng("parameters")
2194
+ createSegmentsToPng("parameters", "text-unicode-escape")
2243
2195
  );
2244
2196
  var convertSwarmUI = createFormatConverter(
2245
2197
  convertSwarmUIPngToSegments,
@@ -2251,11 +2203,7 @@ var convertInvokeAI = createFormatConverter(
2251
2203
  );
2252
2204
  var convertHfSpace = createFormatConverter(
2253
2205
  createPngToSegments("parameters"),
2254
- createSegmentsToPng("parameters")
2255
- );
2256
- var convertBlind = createFormatConverter(
2257
- blindPngToSegments,
2258
- blindSegmentsToPng
2206
+ createSegmentsToPng("parameters", "text-unicode-escape")
2259
2207
  );
2260
2208
  var softwareConverters = {
2261
2209
  // NovelAI
@@ -2286,7 +2234,7 @@ var softwareConverters = {
2286
2234
  // src/writers/exif.ts
2287
2235
  function buildExifTiffData(segments) {
2288
2236
  const ifd0Segments = segments.filter(
2289
- (s) => s.source.type === "exifImageDescription" || s.source.type === "exifMake"
2237
+ (s) => s.source.type === "exifImageDescription" || s.source.type === "exifMake" || s.source.type === "exifSoftware" || s.source.type === "exifDocumentName"
2290
2238
  );
2291
2239
  const exifIfdSegments = segments.filter(
2292
2240
  (s) => s.source.type === "exifUserComment"
@@ -2304,6 +2252,12 @@ function buildExifTiffData(segments) {
2304
2252
  } else if (seg.source.type === "exifMake") {
2305
2253
  const data = encodeAsciiTag(seg.data, seg.source.prefix);
2306
2254
  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 });
2307
2261
  }
2308
2262
  }
2309
2263
  for (const seg of exifIfdSegments) {
@@ -2431,7 +2385,7 @@ function writeJpegMetadata(data, segments) {
2431
2385
  }
2432
2386
  const comSegments = segments.filter((s) => s.source.type === "jpegCom");
2433
2387
  const exifSegments = segments.filter(
2434
- (s) => s.source.type === "exifUserComment" || s.source.type === "exifImageDescription" || s.source.type === "exifMake"
2388
+ (s) => s.source.type === "exifUserComment" || s.source.type === "exifImageDescription" || s.source.type === "exifMake" || s.source.type === "exifSoftware" || s.source.type === "exifDocumentName"
2435
2389
  );
2436
2390
  const collectResult = collectNonMetadataSegments(data);
2437
2391
  if (!collectResult.ok) {
@@ -2791,7 +2745,7 @@ function collectNonExifChunks(data) {
2791
2745
  }
2792
2746
  function buildExifChunk(segments) {
2793
2747
  const exifSegments = segments.filter(
2794
- (s) => s.source.type === "exifUserComment" || s.source.type === "exifImageDescription" || s.source.type === "exifMake"
2748
+ (s) => s.source.type === "exifUserComment" || s.source.type === "exifImageDescription" || s.source.type === "exifMake" || s.source.type === "exifSoftware" || s.source.type === "exifDocumentName"
2795
2749
  );
2796
2750
  if (exifSegments.length === 0) {
2797
2751
  return null;
@@ -2810,58 +2764,95 @@ function buildExifChunk(segments) {
2810
2764
  }
2811
2765
 
2812
2766
  // src/api/write.ts
2813
- function write(data, metadata, options) {
2767
+ function write(data, metadata) {
2814
2768
  const targetFormat = detectFormat(data);
2815
2769
  if (!targetFormat) {
2816
- return Result.error({ type: "unsupportedFormat" });
2770
+ return { ok: false, error: { type: "unsupportedFormat" } };
2817
2771
  }
2818
2772
  if (metadata.status === "empty") {
2819
2773
  const result = HELPERS2[targetFormat].writeEmpty(data, []);
2820
2774
  if (!result.ok) {
2821
- return Result.error({ type: "writeFailed", message: result.error.type });
2775
+ return {
2776
+ ok: false,
2777
+ error: { type: "writeFailed", message: result.error.type }
2778
+ };
2822
2779
  }
2823
- return Result.ok(result.value);
2780
+ return { ok: true, value: result.value };
2824
2781
  }
2825
2782
  if (metadata.status === "invalid") {
2826
- return Result.error({
2827
- type: "writeFailed",
2828
- message: "Cannot write invalid metadata"
2829
- });
2783
+ return {
2784
+ ok: false,
2785
+ error: { type: "writeFailed", message: "Cannot write invalid metadata" }
2786
+ };
2830
2787
  }
2831
- const conversionResult = convertMetadata(
2832
- metadata,
2833
- targetFormat,
2834
- options?.force ?? false
2835
- );
2788
+ if (metadata.status === "unrecognized") {
2789
+ const sourceFormat = metadata.raw.format;
2790
+ if (sourceFormat === targetFormat) {
2791
+ return writeRaw(data, targetFormat, metadata.raw);
2792
+ }
2793
+ const result = HELPERS2[targetFormat].writeEmpty(data, []);
2794
+ if (!result.ok) {
2795
+ return {
2796
+ ok: false,
2797
+ error: { type: "writeFailed", message: result.error.type }
2798
+ };
2799
+ }
2800
+ return {
2801
+ ok: true,
2802
+ value: result.value,
2803
+ warning: { type: "metadataDropped", reason: "unrecognizedCrossFormat" }
2804
+ };
2805
+ }
2806
+ const conversionResult = convertMetadata(metadata, targetFormat);
2836
2807
  if (!conversionResult.ok) {
2837
- return Result.error({
2838
- type: "conversionFailed",
2839
- message: `Failed to convert metadata: ${conversionResult.error.type}`
2840
- });
2808
+ return {
2809
+ ok: false,
2810
+ error: {
2811
+ type: "conversionFailed",
2812
+ message: `Failed to convert metadata: ${conversionResult.error.type}`
2813
+ }
2814
+ };
2841
2815
  }
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);
2816
+ return writeRaw(data, targetFormat, conversionResult.value);
2817
+ }
2818
+ function writeRaw(data, targetFormat, raw) {
2819
+ if (targetFormat === "png" && raw.format === "png") {
2820
+ const result = writePngMetadata(data, raw.chunks);
2821
+ if (!result.ok) {
2822
+ return {
2823
+ ok: false,
2824
+ error: { type: "writeFailed", message: result.error.type }
2825
+ };
2826
+ }
2827
+ return { ok: true, value: result.value };
2860
2828
  }
2861
- return Result.error({
2862
- type: "writeFailed",
2863
- message: "Internal error: format mismatch after conversion"
2864
- });
2829
+ if (targetFormat === "jpeg" && raw.format === "jpeg") {
2830
+ const result = writeJpegMetadata(data, raw.segments);
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 };
2838
+ }
2839
+ if (targetFormat === "webp" && raw.format === "webp") {
2840
+ const result = writeWebpMetadata(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
+ return {
2850
+ ok: false,
2851
+ error: {
2852
+ type: "writeFailed",
2853
+ message: "Internal error: format mismatch after conversion"
2854
+ }
2855
+ };
2865
2856
  }
2866
2857
  var HELPERS2 = {
2867
2858
  png: {
@@ -3001,8 +2992,7 @@ function writeAsWebUI(data, metadata) {
3001
2992
  return Result.ok(writeResult.value);
3002
2993
  }
3003
2994
  function createPngChunks(text) {
3004
- const strategy = getEncodingStrategy("a1111");
3005
- return createEncodedChunk("parameters", text, strategy);
2995
+ return createEncodedChunk("parameters", text, "dynamic");
3006
2996
  }
3007
2997
  function createExifSegments(text) {
3008
2998
  return [