@enslo/sd-metadata 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -49
- package/dist/index.d.ts +80 -37
- package/dist/index.js +135 -69
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -407,23 +407,45 @@ function convertSwarmUIPngToSegments(chunks) {
|
|
|
407
407
|
}
|
|
408
408
|
const parsed = parseJson(parametersChunk.text);
|
|
409
409
|
const data = parsed.ok ? parsed.value : parametersChunk.text;
|
|
410
|
-
|
|
410
|
+
const segments = [
|
|
411
411
|
{
|
|
412
412
|
source: { type: "exifUserComment" },
|
|
413
413
|
data: typeof data === "string" ? data : JSON.stringify(data)
|
|
414
414
|
}
|
|
415
415
|
];
|
|
416
|
+
const promptChunk = chunks.find((c) => c.keyword === "prompt");
|
|
417
|
+
if (promptChunk) {
|
|
418
|
+
segments.push({
|
|
419
|
+
source: { type: "exifMake" },
|
|
420
|
+
data: promptChunk.text
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return segments;
|
|
416
424
|
}
|
|
417
425
|
function convertSwarmUISegmentsToPng(segments) {
|
|
418
426
|
const userComment = findSegment(segments, "exifUserComment");
|
|
419
427
|
if (!userComment) {
|
|
420
428
|
return [];
|
|
421
429
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
430
|
+
const chunks = [];
|
|
431
|
+
const make = findSegment(segments, "exifMake");
|
|
432
|
+
if (make) {
|
|
433
|
+
chunks.push(
|
|
434
|
+
...createEncodedChunk(
|
|
435
|
+
"prompt",
|
|
436
|
+
make.data,
|
|
437
|
+
getEncodingStrategy("swarmui")
|
|
438
|
+
)
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
chunks.push(
|
|
442
|
+
...createEncodedChunk(
|
|
443
|
+
"parameters",
|
|
444
|
+
userComment.data,
|
|
445
|
+
getEncodingStrategy("swarmui")
|
|
446
|
+
)
|
|
426
447
|
);
|
|
448
|
+
return chunks;
|
|
427
449
|
}
|
|
428
450
|
|
|
429
451
|
// src/converters/index.ts
|
|
@@ -548,6 +570,10 @@ function parseA1111(entries) {
|
|
|
548
570
|
return Result.error({ type: "unsupportedFormat" });
|
|
549
571
|
}
|
|
550
572
|
const text = parametersEntry.text;
|
|
573
|
+
const hasAIMarkers = text.includes("Steps:") || text.includes("Sampler:") || text.includes("Negative prompt:");
|
|
574
|
+
if (!hasAIMarkers) {
|
|
575
|
+
return Result.error({ type: "unsupportedFormat" });
|
|
576
|
+
}
|
|
551
577
|
const { prompt, negativePrompt, settings } = parseParametersText(text);
|
|
552
578
|
const settingsMap = parseSettings(settings);
|
|
553
579
|
const size = settingsMap.get("Size") ?? "0x0";
|
|
@@ -556,7 +582,6 @@ function parseA1111(entries) {
|
|
|
556
582
|
const app = settingsMap.get("App");
|
|
557
583
|
const software = detectSoftwareVariant(version, app);
|
|
558
584
|
const metadata = {
|
|
559
|
-
type: "a1111",
|
|
560
585
|
software,
|
|
561
586
|
prompt,
|
|
562
587
|
negativePrompt,
|
|
@@ -705,12 +730,13 @@ function parseComfyUI(entries) {
|
|
|
705
730
|
const width = latentWidth || extraMeta?.width || 0;
|
|
706
731
|
const height = latentHeight || extraMeta?.height || 0;
|
|
707
732
|
const metadata = {
|
|
708
|
-
type: "comfyui",
|
|
709
733
|
software: "comfyui",
|
|
710
734
|
prompt: positiveText,
|
|
711
735
|
negativePrompt: negativeText,
|
|
712
736
|
width,
|
|
713
|
-
height
|
|
737
|
+
height,
|
|
738
|
+
nodes: prompt
|
|
739
|
+
// Store the parsed node graph
|
|
714
740
|
};
|
|
715
741
|
const checkpoint = findNode(prompt, ["CheckpointLoader_Base"])?.inputs?.ckpt_name;
|
|
716
742
|
if (checkpoint) {
|
|
@@ -778,7 +804,7 @@ function parseComfyUI(entries) {
|
|
|
778
804
|
}
|
|
779
805
|
function findPromptJson(entryRecord) {
|
|
780
806
|
if (entryRecord.prompt) {
|
|
781
|
-
return entryRecord.prompt;
|
|
807
|
+
return entryRecord.prompt.replace(/:\s*NaN\b/g, ": null");
|
|
782
808
|
}
|
|
783
809
|
const candidates = [
|
|
784
810
|
entryRecord.Comment,
|
|
@@ -792,7 +818,7 @@ function findPromptJson(entryRecord) {
|
|
|
792
818
|
for (const candidate of candidates) {
|
|
793
819
|
if (!candidate) continue;
|
|
794
820
|
if (candidate.startsWith("{")) {
|
|
795
|
-
const cleaned = candidate.replace(/\0+$/, "");
|
|
821
|
+
const cleaned = candidate.replace(/\0+$/, "").replace(/:\s*NaN\b/g, ": null");
|
|
796
822
|
const parsed = parseJson(cleaned);
|
|
797
823
|
if (!parsed.ok) continue;
|
|
798
824
|
if (parsed.value.prompt && typeof parsed.value.prompt === "object") {
|
|
@@ -800,7 +826,7 @@ function findPromptJson(entryRecord) {
|
|
|
800
826
|
}
|
|
801
827
|
const values = Object.values(parsed.value);
|
|
802
828
|
if (values.some((v) => v && typeof v === "object" && "class_type" in v)) {
|
|
803
|
-
return
|
|
829
|
+
return cleaned;
|
|
804
830
|
}
|
|
805
831
|
}
|
|
806
832
|
}
|
|
@@ -822,11 +848,17 @@ function extractExtraMetadata(prompt) {
|
|
|
822
848
|
// src/parsers/detect.ts
|
|
823
849
|
function detectSoftware(entries) {
|
|
824
850
|
const entryRecord = buildEntryRecord(entries);
|
|
825
|
-
const
|
|
826
|
-
if (
|
|
827
|
-
|
|
851
|
+
const uniqueResult = detectUniqueKeywords(entryRecord);
|
|
852
|
+
if (uniqueResult) return uniqueResult;
|
|
853
|
+
const comfyResult = detectComfyUIEntries(entryRecord);
|
|
854
|
+
if (comfyResult) return comfyResult;
|
|
855
|
+
const text = entryRecord.parameters ?? entryRecord.Comment ?? "";
|
|
856
|
+
if (text) {
|
|
857
|
+
return detectFromTextContent(text);
|
|
858
|
+
}
|
|
859
|
+
return null;
|
|
828
860
|
}
|
|
829
|
-
function
|
|
861
|
+
function detectUniqueKeywords(entryRecord) {
|
|
830
862
|
if (entryRecord.Software === "NovelAI") {
|
|
831
863
|
return "novelai";
|
|
832
864
|
}
|
|
@@ -842,56 +874,80 @@ function detectFromKeywords(entryRecord) {
|
|
|
842
874
|
if ("negative_prompt" in entryRecord || "Negative Prompt" in entryRecord) {
|
|
843
875
|
return "easydiffusion";
|
|
844
876
|
}
|
|
877
|
+
const parameters = entryRecord.parameters;
|
|
878
|
+
if (parameters?.includes("sui_image_params")) {
|
|
879
|
+
return "swarmui";
|
|
880
|
+
}
|
|
845
881
|
const comment = entryRecord.Comment;
|
|
846
882
|
if (comment?.startsWith("{")) {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
883
|
+
return detectFromCommentJson(comment);
|
|
884
|
+
}
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
function detectFromCommentJson(comment) {
|
|
888
|
+
try {
|
|
889
|
+
const parsed = JSON.parse(comment);
|
|
890
|
+
if ("invokeai_metadata" in parsed) {
|
|
891
|
+
return "invokeai";
|
|
892
|
+
}
|
|
893
|
+
if ("prompt" in parsed && "workflow" in parsed) {
|
|
894
|
+
const workflow = parsed.workflow;
|
|
895
|
+
const prompt = parsed.prompt;
|
|
896
|
+
const isObject = typeof workflow === "object" || typeof prompt === "object";
|
|
897
|
+
const isJsonString = typeof workflow === "string" && workflow.startsWith("{") || typeof prompt === "string" && prompt.startsWith("{");
|
|
898
|
+
if (isObject || isJsonString) {
|
|
899
|
+
return "comfyui";
|
|
860
900
|
}
|
|
861
|
-
|
|
901
|
+
}
|
|
902
|
+
if ("sui_image_params" in parsed) {
|
|
903
|
+
return "swarmui";
|
|
904
|
+
}
|
|
905
|
+
if ("prompt" in parsed && "parameters" in parsed) {
|
|
906
|
+
const params = String(parsed.parameters || "");
|
|
907
|
+
if (params.includes("sui_image_params") || params.includes("swarm_version")) {
|
|
862
908
|
return "swarmui";
|
|
863
909
|
}
|
|
864
|
-
if ("prompt" in parsed && "parameters" in parsed) {
|
|
865
|
-
const params = String(parsed.parameters || "");
|
|
866
|
-
if (params.includes("sui_image_params") || params.includes("swarm_version")) {
|
|
867
|
-
return "swarmui";
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
} catch {
|
|
871
910
|
}
|
|
911
|
+
} catch {
|
|
872
912
|
}
|
|
873
913
|
return null;
|
|
874
914
|
}
|
|
875
|
-
function
|
|
915
|
+
function detectComfyUIEntries(entryRecord) {
|
|
876
916
|
if ("prompt" in entryRecord && "workflow" in entryRecord) {
|
|
877
917
|
return "comfyui";
|
|
878
918
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
919
|
+
if ("workflow" in entryRecord) {
|
|
920
|
+
return "comfyui";
|
|
921
|
+
}
|
|
922
|
+
if ("prompt" in entryRecord) {
|
|
923
|
+
const promptText = entryRecord.prompt;
|
|
924
|
+
if (promptText?.startsWith("{")) {
|
|
925
|
+
if (promptText.includes("sui_image_params")) {
|
|
926
|
+
return "swarmui";
|
|
927
|
+
}
|
|
928
|
+
if (promptText.includes("class_type")) {
|
|
929
|
+
return "comfyui";
|
|
930
|
+
}
|
|
883
931
|
}
|
|
884
|
-
return null;
|
|
885
932
|
}
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
function detectFromTextContent(text) {
|
|
886
936
|
if (text.startsWith("{")) {
|
|
887
|
-
return
|
|
937
|
+
return detectFromJsonFormat(text);
|
|
888
938
|
}
|
|
889
|
-
return
|
|
939
|
+
return detectFromA1111Format(text);
|
|
890
940
|
}
|
|
891
|
-
function
|
|
941
|
+
function detectFromJsonFormat(json) {
|
|
892
942
|
if (json.includes("sui_image_params")) {
|
|
893
943
|
return "swarmui";
|
|
894
944
|
}
|
|
945
|
+
if (json.includes('"software":"RuinedFooocus"') || json.includes('"software": "RuinedFooocus"')) {
|
|
946
|
+
return "ruined-fooocus";
|
|
947
|
+
}
|
|
948
|
+
if (json.includes('"use_stable_diffusion_model"')) {
|
|
949
|
+
return "easydiffusion";
|
|
950
|
+
}
|
|
895
951
|
if (json.includes("civitai:") || json.includes('"resource-stack"')) {
|
|
896
952
|
return "civitai";
|
|
897
953
|
}
|
|
@@ -901,12 +957,6 @@ function detectFromJson(json) {
|
|
|
901
957
|
if (json.includes('"Model"') && json.includes('"resolution"')) {
|
|
902
958
|
return "hf-space";
|
|
903
959
|
}
|
|
904
|
-
if (json.includes('"use_stable_diffusion_model"')) {
|
|
905
|
-
return "easydiffusion";
|
|
906
|
-
}
|
|
907
|
-
if (json.includes('"software":"RuinedFooocus"') || json.includes('"software": "RuinedFooocus"')) {
|
|
908
|
-
return "ruined-fooocus";
|
|
909
|
-
}
|
|
910
960
|
if (json.includes('"prompt"') && json.includes('"base_model"')) {
|
|
911
961
|
return "fooocus";
|
|
912
962
|
}
|
|
@@ -915,11 +965,8 @@ function detectFromJson(json) {
|
|
|
915
965
|
}
|
|
916
966
|
return null;
|
|
917
967
|
}
|
|
918
|
-
function
|
|
919
|
-
if (text.includes("sui_image_params")) {
|
|
920
|
-
return "swarmui";
|
|
921
|
-
}
|
|
922
|
-
if (text.includes("swarm_version")) {
|
|
968
|
+
function detectFromA1111Format(text) {
|
|
969
|
+
if (text.includes("sui_image_params") || text.includes("swarm_version")) {
|
|
923
970
|
return "swarmui";
|
|
924
971
|
}
|
|
925
972
|
const versionMatch = text.match(/Version:\s*([^\s,]+)/);
|
|
@@ -981,7 +1028,6 @@ function parseFromEntries(entryRecord) {
|
|
|
981
1028
|
const width = Number(entryRecord.width ?? entryRecord.Width) || 0;
|
|
982
1029
|
const height = Number(entryRecord.height ?? entryRecord.Height) || 0;
|
|
983
1030
|
const metadata = {
|
|
984
|
-
type: "a1111",
|
|
985
1031
|
software: "easydiffusion",
|
|
986
1032
|
prompt: prompt.trim(),
|
|
987
1033
|
negativePrompt: negativePrompt.trim(),
|
|
@@ -1012,7 +1058,6 @@ function parseFromJson(json) {
|
|
|
1012
1058
|
const width = getValue(json, "width", "Width") ?? 0;
|
|
1013
1059
|
const height = getValue(json, "height", "Height") ?? 0;
|
|
1014
1060
|
const metadata = {
|
|
1015
|
-
type: "a1111",
|
|
1016
1061
|
software: "easydiffusion",
|
|
1017
1062
|
prompt: prompt.trim(),
|
|
1018
1063
|
negativePrompt: negativePrompt.trim(),
|
|
@@ -1052,7 +1097,6 @@ function parseFooocus(entries) {
|
|
|
1052
1097
|
return Result.error({ type: "unsupportedFormat" });
|
|
1053
1098
|
}
|
|
1054
1099
|
const metadata = {
|
|
1055
|
-
type: "a1111",
|
|
1056
1100
|
software: "fooocus",
|
|
1057
1101
|
prompt: json.prompt?.trim() ?? "",
|
|
1058
1102
|
negativePrompt: json.negative_prompt?.trim() ?? "",
|
|
@@ -1096,7 +1140,6 @@ function parseHfSpace(entries) {
|
|
|
1096
1140
|
};
|
|
1097
1141
|
const { width, height } = parseResolution(json.resolution);
|
|
1098
1142
|
const metadata = {
|
|
1099
|
-
type: "a1111",
|
|
1100
1143
|
software: "hf-space",
|
|
1101
1144
|
prompt: json.prompt ?? "",
|
|
1102
1145
|
negativePrompt: json.negative_prompt ?? "",
|
|
@@ -1147,7 +1190,6 @@ function parseInvokeAI(entries) {
|
|
|
1147
1190
|
const width = data.width ?? 0;
|
|
1148
1191
|
const height = data.height ?? 0;
|
|
1149
1192
|
const metadata = {
|
|
1150
|
-
type: "invokeai",
|
|
1151
1193
|
software: "invokeai",
|
|
1152
1194
|
prompt: data.positive_prompt ?? "",
|
|
1153
1195
|
negativePrompt: data.negative_prompt ?? "",
|
|
@@ -1197,7 +1239,6 @@ function parseNovelAI(entries) {
|
|
|
1197
1239
|
const prompt = comment.v4_prompt?.caption?.base_caption ?? comment.prompt ?? "";
|
|
1198
1240
|
const negativePrompt = comment.v4_negative_prompt?.caption?.base_caption ?? comment.uc ?? "";
|
|
1199
1241
|
const metadata = {
|
|
1200
|
-
type: "novelai",
|
|
1201
1242
|
software: "novelai",
|
|
1202
1243
|
prompt,
|
|
1203
1244
|
negativePrompt,
|
|
@@ -1247,7 +1288,6 @@ function parseRuinedFooocus(entries) {
|
|
|
1247
1288
|
return Result.error({ type: "unsupportedFormat" });
|
|
1248
1289
|
}
|
|
1249
1290
|
const metadata = {
|
|
1250
|
-
type: "a1111",
|
|
1251
1291
|
software: "ruined-fooocus",
|
|
1252
1292
|
prompt: json.Prompt?.trim() ?? "",
|
|
1253
1293
|
negativePrompt: json.Negative?.trim() ?? "",
|
|
@@ -1273,12 +1313,11 @@ function parseRuinedFooocus(entries) {
|
|
|
1273
1313
|
function parseStabilityMatrix(entries) {
|
|
1274
1314
|
const entryRecord = buildEntryRecord(entries);
|
|
1275
1315
|
const comfyResult = parseComfyUI(entries);
|
|
1276
|
-
if (!comfyResult.ok) {
|
|
1316
|
+
if (!comfyResult.ok || comfyResult.value.software !== "comfyui") {
|
|
1277
1317
|
return Result.error({ type: "unsupportedFormat" });
|
|
1278
1318
|
}
|
|
1279
1319
|
const metadata = {
|
|
1280
1320
|
...comfyResult.value,
|
|
1281
|
-
type: "comfyui",
|
|
1282
1321
|
software: "stability-matrix"
|
|
1283
1322
|
};
|
|
1284
1323
|
const jsonText = entryRecord["parameters-json"];
|
|
@@ -1340,13 +1379,19 @@ function parseSwarmUI(entries) {
|
|
|
1340
1379
|
const width = params.width ?? 0;
|
|
1341
1380
|
const height = params.height ?? 0;
|
|
1342
1381
|
const metadata = {
|
|
1343
|
-
type: "swarmui",
|
|
1344
1382
|
software: "swarmui",
|
|
1345
1383
|
prompt: params.prompt ?? "",
|
|
1346
1384
|
negativePrompt: params.negativeprompt ?? "",
|
|
1347
1385
|
width,
|
|
1348
1386
|
height
|
|
1349
1387
|
};
|
|
1388
|
+
const promptSource = entryRecord.prompt || entryRecord.Make;
|
|
1389
|
+
if (promptSource) {
|
|
1390
|
+
const promptParsed = parseJson(promptSource);
|
|
1391
|
+
if (promptParsed.ok) {
|
|
1392
|
+
metadata.nodes = promptParsed.value;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1350
1395
|
if (params.model) {
|
|
1351
1396
|
metadata.model = {
|
|
1352
1397
|
name: params.model
|
|
@@ -1389,13 +1434,24 @@ function parseTensorArt(entries) {
|
|
|
1389
1434
|
const data = parsed.value;
|
|
1390
1435
|
const width = data.width ?? 0;
|
|
1391
1436
|
const height = data.height ?? 0;
|
|
1437
|
+
const promptChunk = entryRecord.prompt;
|
|
1438
|
+
if (!promptChunk) {
|
|
1439
|
+
return Result.error({ type: "unsupportedFormat" });
|
|
1440
|
+
}
|
|
1441
|
+
const promptParsed = parseJson(promptChunk);
|
|
1442
|
+
if (!promptParsed.ok) {
|
|
1443
|
+
return Result.error({
|
|
1444
|
+
type: "parseError",
|
|
1445
|
+
message: "Invalid JSON in prompt chunk"
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1392
1448
|
const metadata = {
|
|
1393
|
-
type: "comfyui",
|
|
1394
1449
|
software: "tensorart",
|
|
1395
1450
|
prompt: data.prompt ?? "",
|
|
1396
1451
|
negativePrompt: data.negativePrompt ?? "",
|
|
1397
1452
|
width,
|
|
1398
|
-
height
|
|
1453
|
+
height,
|
|
1454
|
+
nodes: promptParsed.value
|
|
1399
1455
|
};
|
|
1400
1456
|
if (data.baseModel?.modelFileName || data.baseModel?.hash) {
|
|
1401
1457
|
metadata.model = {
|
|
@@ -1404,8 +1460,9 @@ function parseTensorArt(entries) {
|
|
|
1404
1460
|
};
|
|
1405
1461
|
}
|
|
1406
1462
|
if (data.seed !== void 0 || data.steps !== void 0 || data.cfgScale !== void 0 || data.clipSkip !== void 0) {
|
|
1463
|
+
const baseSeed = data.seed ? Number(data.seed) : void 0;
|
|
1407
1464
|
metadata.sampling = {
|
|
1408
|
-
seed:
|
|
1465
|
+
seed: baseSeed === -1 ? findActualSeed(promptParsed.value) : baseSeed,
|
|
1409
1466
|
steps: data.steps,
|
|
1410
1467
|
cfg: data.cfgScale,
|
|
1411
1468
|
clipSkip: data.clipSkip
|
|
@@ -1413,6 +1470,15 @@ function parseTensorArt(entries) {
|
|
|
1413
1470
|
}
|
|
1414
1471
|
return Result.ok(metadata);
|
|
1415
1472
|
}
|
|
1473
|
+
function findActualSeed(nodes) {
|
|
1474
|
+
const samplerNode = findSamplerNode(nodes);
|
|
1475
|
+
return samplerNode && typeof samplerNode.inputs.seed === "number" ? samplerNode.inputs.seed : -1;
|
|
1476
|
+
}
|
|
1477
|
+
function findSamplerNode(nodes) {
|
|
1478
|
+
return Object.values(nodes).find(
|
|
1479
|
+
(node) => node.class_type === "KSampler" || node.class_type.toLowerCase().includes("sampler")
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1416
1482
|
|
|
1417
1483
|
// src/parsers/index.ts
|
|
1418
1484
|
function parseMetadata(entries) {
|