@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/README.ja.md +16 -13
- package/README.md +16 -13
- package/dist/index.d.ts +31 -41
- package/dist/index.js +241 -251
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1856
|
+
const parametersChunks = createEncodedChunk(
|
|
1830
1857
|
"parameters",
|
|
1831
1858
|
userComment.data,
|
|
1832
|
-
|
|
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
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
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
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
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/
|
|
1868
|
-
function
|
|
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,
|
|
1924
|
+
...createEncodedChunk("prompt", make?.data, "text-unicode-escape"),
|
|
1893
1925
|
...createEncodedChunk(
|
|
1894
1926
|
"workflow",
|
|
1895
1927
|
imageDescription?.data,
|
|
1896
|
-
|
|
1928
|
+
"text-unicode-escape"
|
|
1897
1929
|
)
|
|
1898
1930
|
];
|
|
1899
1931
|
};
|
|
1900
1932
|
var tryParseSaveImagePlusFormat = (segments) => {
|
|
1901
|
-
const
|
|
1902
|
-
|
|
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(
|
|
1947
|
-
|
|
1948
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
2044
|
+
createTextChunk(
|
|
2045
|
+
"Title",
|
|
2046
|
+
titleSeg?.data ?? stringify(jsonData.Title) ?? NOVELAI_TITLE
|
|
2047
|
+
),
|
|
2077
2048
|
// Description (optional, prefer exifImageDescription over JSON)
|
|
2078
|
-
|
|
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:
|
|
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
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
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
|
|
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
|
-
|
|
2185
|
-
|
|
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
|
|
2767
|
+
function write(data, metadata) {
|
|
2814
2768
|
const targetFormat = detectFormat(data);
|
|
2815
2769
|
if (!targetFormat) {
|
|
2816
|
-
return
|
|
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
|
|
2775
|
+
return {
|
|
2776
|
+
ok: false,
|
|
2777
|
+
error: { type: "writeFailed", message: result.error.type }
|
|
2778
|
+
};
|
|
2822
2779
|
}
|
|
2823
|
-
return
|
|
2780
|
+
return { ok: true, value: result.value };
|
|
2824
2781
|
}
|
|
2825
2782
|
if (metadata.status === "invalid") {
|
|
2826
|
-
return
|
|
2827
|
-
|
|
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
|
-
|
|
2832
|
-
metadata
|
|
2833
|
-
targetFormat
|
|
2834
|
-
|
|
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
|
|
2838
|
-
|
|
2839
|
-
|
|
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
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
return
|
|
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
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
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
|
-
|
|
3005
|
-
return createEncodedChunk("parameters", text, strategy);
|
|
2995
|
+
return createEncodedChunk("parameters", text, "dynamic");
|
|
3006
2996
|
}
|
|
3007
2997
|
function createExifSegments(text) {
|
|
3008
2998
|
return [
|