@enslo/sd-metadata 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -96,8 +96,7 @@ function parseSettings(settings) {
96
96
  const result = /* @__PURE__ */ new Map();
97
97
  if (!settings) return result;
98
98
  const regex = /([A-Za-z][A-Za-z0-9 ]*?):\s*([^,]+?)(?=,\s*[A-Za-z][A-Za-z0-9 ]*?:|$)/g;
99
- const matches = Array.from(settings.matchAll(regex));
100
- for (const match of matches) {
99
+ for (const match of settings.matchAll(regex)) {
101
100
  const key = (match[1] ?? "").trim();
102
101
  const value = (match[2] ?? "").trim();
103
102
  result.set(key, value);
@@ -162,83 +161,45 @@ function toJsonResult(value) {
162
161
  }
163
162
  }
164
163
 
165
- // src/parsers/comfyui-civitai.ts
166
- function extractExtraMetadata(prompt, entryRecord) {
167
- const extraMetaField = prompt.extraMetadata;
168
- if (typeof extraMetaField === "string") {
169
- const parsed = parseJson(extraMetaField);
170
- if (parsed.ok && parsed.type === "object") return parsed.value;
171
- }
172
- if (entryRecord?.extraMetadata) {
173
- const parsed = parseJson(entryRecord.extraMetadata);
174
- if (parsed.ok && parsed.type === "object") return parsed.value;
175
- }
176
- return void 0;
177
- }
178
- function extractCivitaiMetadata(extraMeta) {
179
- if (!extraMeta) return void 0;
180
- const upscale = buildCivitaiUpscale(extraMeta);
181
- const sampling = buildCivitaiSampling(extraMeta);
182
- return trimObject({
183
- prompt: extraMeta.prompt,
184
- negativePrompt: extraMeta.negativePrompt,
185
- width: extraMeta.width,
186
- height: extraMeta.height,
187
- model: extraMeta.baseModel ? { name: extraMeta.baseModel } : void 0,
188
- ...sampling,
189
- ...upscale
190
- });
191
- }
192
- function calculateScale(targetWidth, baseWidth) {
193
- if (baseWidth <= 0 || targetWidth <= 0) return void 0;
194
- return Math.round(targetWidth / baseWidth * 100) / 100;
195
- }
196
- function buildCivitaiUpscale(extraMeta) {
197
- if (!extraMeta.transformations) return {};
198
- const upscaleTransform = extraMeta.transformations.find(
199
- (t) => t.type === "upscale"
200
- );
201
- if (!upscaleTransform?.upscaleWidth) return {};
202
- const scale = calculateScale(
203
- upscaleTransform.upscaleWidth,
204
- extraMeta.width ?? 0
205
- );
206
- if (scale === void 0) return {};
207
- return {
208
- upscale: { scale }
209
- };
210
- }
211
- function buildCivitaiSampling(extraMeta) {
212
- if (extraMeta.seed === void 0 && extraMeta.steps === void 0 && extraMeta.cfgScale === void 0 && extraMeta.sampler === void 0) {
213
- return {};
214
- }
215
- return {
216
- sampling: {
217
- seed: extraMeta.seed,
218
- steps: extraMeta.steps,
219
- cfg: extraMeta.cfgScale,
220
- sampler: extraMeta.sampler
164
+ // src/parsers/comfyui-nodes.ts
165
+ var SAMPLER_TYPES = ["KSampler", "KSamplerAdvanced", "SamplerCustomAdvanced"];
166
+ var LATENT_IMAGE_TYPES = ["EmptyLatentImage"];
167
+ var LATENT_IMAGE_RGTHREE_TYPES = ["SDXL Empty Latent Image (rgthree)"];
168
+ var CHECKPOINT_TYPES = ["CheckpointLoaderSimple", "CheckpointLoader"];
169
+ var UNET_LOADER_TYPES = ["UNETLoader"];
170
+ var HIRES_MODEL_UPSCALE_TYPES = ["UpscaleModelLoader"];
171
+ var HIRES_IMAGE_SCALE_TYPES = ["ImageScale", "ImageScaleBy"];
172
+ var LATENT_UPSCALE_TYPES = ["LatentUpscale", "LatentUpscaleBy"];
173
+ var VAE_ENCODE_TYPES = ["VAEEncode", "VAEEncodeTiled"];
174
+ function classifyNodes(nodes) {
175
+ const result = {};
176
+ for (const node of Object.values(nodes)) {
177
+ const ct = node.class_type;
178
+ if (!result.sampler && SAMPLER_TYPES.includes(ct)) {
179
+ result.sampler = node;
180
+ } else if (!result.latentImage && LATENT_IMAGE_TYPES.includes(ct)) {
181
+ result.latentImage = node;
182
+ } else if (!result.latentImageRgthree && LATENT_IMAGE_RGTHREE_TYPES.includes(ct)) {
183
+ result.latentImageRgthree = node;
184
+ } else if (!result.checkpoint && CHECKPOINT_TYPES.includes(ct)) {
185
+ result.checkpoint = node;
186
+ } else if (!result.unetLoader && UNET_LOADER_TYPES.includes(ct)) {
187
+ result.unetLoader = node;
188
+ } else if (!result.hiresModelUpscale && HIRES_MODEL_UPSCALE_TYPES.includes(ct)) {
189
+ result.hiresModelUpscale = node;
190
+ } else if (!result.hiresImageScale && HIRES_IMAGE_SCALE_TYPES.includes(ct)) {
191
+ result.hiresImageScale = node;
192
+ } else if (!result.latentUpscale && LATENT_UPSCALE_TYPES.includes(ct)) {
193
+ result.latentUpscale = node;
194
+ } else if (!result.vaeEncode && VAE_ENCODE_TYPES.includes(ct)) {
195
+ result.vaeEncode = node;
221
196
  }
222
- };
197
+ }
198
+ return result;
223
199
  }
224
-
225
- // src/parsers/comfyui-nodes.ts
226
- var CLASS_TYPES = {
227
- sampler: ["KSampler", "KSamplerAdvanced", "SamplerCustomAdvanced"],
228
- // Standard latent image nodes with width/height properties
229
- latentImage: ["EmptyLatentImage"],
230
- // rgthree latent image nodes with "dimensions" string property
231
- latentImageRgthree: ["SDXL Empty Latent Image (rgthree)"],
232
- checkpoint: ["CheckpointLoaderSimple", "CheckpointLoader"],
233
- hiresModelUpscale: ["UpscaleModelLoader"],
234
- hiresImageScale: ["ImageScale", "ImageScaleBy"],
235
- latentUpscale: ["LatentUpscale", "LatentUpscaleBy"],
236
- vaeEncode: ["VAEEncode", "VAEEncodeTiled"]
237
- };
238
- function findNode(nodes, classTypes) {
239
- return Object.values(nodes).find(
240
- (node) => classTypes.includes(node.class_type)
241
- );
200
+ function resolveNode(nodes, ref) {
201
+ if (!isNodeReference(ref)) return void 0;
202
+ return nodes[String(ref[0])];
242
203
  }
243
204
  function isNodeReference(value) {
244
205
  return Array.isArray(value) && value.length === 2 && (typeof value[0] === "string" || typeof value[0] === "number") && typeof value[1] === "number";
@@ -256,28 +217,31 @@ function extractText(nodes, nodeId, maxDepth = 10) {
256
217
  }
257
218
  return "";
258
219
  }
259
- function extractPromptTexts(nodes) {
260
- const sampler = findNode(nodes, CLASS_TYPES.sampler);
220
+ function resolveConditioningSource(nodes, sampler) {
221
+ const guiderNode = resolveNode(nodes, sampler.inputs.guider);
222
+ if (guiderNode) return guiderNode;
223
+ return sampler;
224
+ }
225
+ function extractPromptTexts(nodes, sampler) {
261
226
  if (!sampler) {
262
227
  return { promptText: "", negativeText: "" };
263
228
  }
264
- const positiveRef = sampler.inputs.positive;
265
- const negativeRef = sampler.inputs.negative;
229
+ const conditioningSource = resolveConditioningSource(nodes, sampler);
230
+ const positiveRef = conditioningSource.inputs.positive;
231
+ const negativeRef = conditioningSource.inputs.negative;
266
232
  return {
267
233
  promptText: isNodeReference(positiveRef) ? extractText(nodes, String(positiveRef[0])) : "",
268
234
  negativeText: isNodeReference(negativeRef) ? extractText(nodes, String(negativeRef[0])) : ""
269
235
  };
270
236
  }
271
- function extractDimensions(nodes) {
272
- const standardLatent = findNode(nodes, CLASS_TYPES.latentImage);
273
- if (standardLatent) {
274
- const width = Number(standardLatent.inputs.width) || 0;
275
- const height = Number(standardLatent.inputs.height) || 0;
237
+ function extractDimensions(latentImage, latentImageRgthree) {
238
+ if (latentImage) {
239
+ const width = Number(latentImage.inputs.width) || 0;
240
+ const height = Number(latentImage.inputs.height) || 0;
276
241
  if (width > 0 && height > 0) return { width, height };
277
242
  }
278
- const rgthreeLatent = findNode(nodes, CLASS_TYPES.latentImageRgthree);
279
- if (rgthreeLatent && typeof rgthreeLatent.inputs.dimensions === "string") {
280
- const match = rgthreeLatent.inputs.dimensions.match(/^(\d+)\s*x\s*(\d+)/);
243
+ if (latentImageRgthree && typeof latentImageRgthree.inputs.dimensions === "string") {
244
+ const match = latentImageRgthree.inputs.dimensions.match(/^(\d+)\s*x\s*(\d+)/);
281
245
  if (match?.[1] && match[2]) {
282
246
  return {
283
247
  width: Number.parseInt(match[1], 10),
@@ -287,9 +251,11 @@ function extractDimensions(nodes) {
287
251
  }
288
252
  return { width: 0, height: 0 };
289
253
  }
290
- function extractSampling(nodes) {
291
- const sampler = findNode(nodes, CLASS_TYPES.sampler);
254
+ function extractSampling(nodes, sampler) {
292
255
  if (!sampler) return void 0;
256
+ if (sampler.class_type === "SamplerCustomAdvanced") {
257
+ return extractAdvancedSampling(nodes, sampler);
258
+ }
293
259
  let seed = sampler.inputs.seed;
294
260
  if (isNodeReference(seed)) {
295
261
  const seedNode = nodes[String(seed[0])];
@@ -306,36 +272,112 @@ function extractSampling(nodes) {
306
272
  denoise
307
273
  };
308
274
  }
309
- function extractModel(nodes) {
310
- const checkpoint = findNode(nodes, CLASS_TYPES.checkpoint);
311
- if (!checkpoint?.inputs?.ckpt_name) return void 0;
312
- return { name: String(checkpoint.inputs.ckpt_name) };
275
+ function extractAdvancedSampling(nodes, sampler) {
276
+ const noiseNode = resolveNode(nodes, sampler.inputs.noise);
277
+ const guiderNode = resolveNode(nodes, sampler.inputs.guider);
278
+ const samplerSelectNode = resolveNode(nodes, sampler.inputs.sampler);
279
+ const schedulerNode = resolveNode(nodes, sampler.inputs.sigmas);
280
+ const rawDenoise = schedulerNode?.inputs.denoise;
281
+ const denoise = typeof rawDenoise === "number" && rawDenoise < 1 ? rawDenoise : void 0;
282
+ return {
283
+ seed: noiseNode?.inputs.noise_seed,
284
+ steps: schedulerNode?.inputs.steps,
285
+ cfg: guiderNode?.inputs.cfg,
286
+ sampler: samplerSelectNode?.inputs.sampler_name,
287
+ scheduler: schedulerNode?.inputs.scheduler,
288
+ denoise
289
+ };
290
+ }
291
+ function extractModel(checkpoint, unetLoader) {
292
+ if (checkpoint?.inputs?.ckpt_name) {
293
+ return { name: String(checkpoint.inputs.ckpt_name) };
294
+ }
295
+ if (unetLoader?.inputs?.unet_name) {
296
+ return { name: String(unetLoader.inputs.unet_name) };
297
+ }
298
+ return void 0;
299
+ }
300
+ function calculateScale(targetWidth, baseWidth) {
301
+ if (baseWidth <= 0 || targetWidth <= 0) return void 0;
302
+ return Math.round(targetWidth / baseWidth * 100) / 100;
313
303
  }
314
304
  function isHiresSampler(nodes, sampler) {
315
305
  const latentImageRef = sampler.inputs.latent_image;
316
306
  if (!isNodeReference(latentImageRef)) return false;
317
307
  const inputNode = nodes[String(latentImageRef[0])];
318
308
  if (!inputNode) return false;
319
- const latentUpscaleTypes = CLASS_TYPES.latentUpscale;
320
- if (latentUpscaleTypes.includes(inputNode.class_type)) {
309
+ if (LATENT_UPSCALE_TYPES.includes(inputNode.class_type)) {
321
310
  return true;
322
311
  }
323
- const vaeTypes = CLASS_TYPES.vaeEncode;
324
- if (!vaeTypes.includes(inputNode.class_type)) return false;
312
+ if (!VAE_ENCODE_TYPES.includes(inputNode.class_type)) return false;
325
313
  const pixelsRef = inputNode.inputs.pixels;
326
314
  if (!isNodeReference(pixelsRef)) return false;
327
315
  const upscaleNode = nodes[String(pixelsRef[0])];
328
316
  if (!upscaleNode) return false;
329
- const imageScaleTypes = CLASS_TYPES.hiresImageScale;
330
- return imageScaleTypes.includes(upscaleNode.class_type);
317
+ return HIRES_IMAGE_SCALE_TYPES.includes(upscaleNode.class_type);
331
318
  }
332
319
  function findHiresSampler(nodes) {
333
- const samplerTypes = CLASS_TYPES.sampler;
334
320
  return Object.values(nodes).find(
335
- (node) => samplerTypes.includes(node.class_type) && isHiresSampler(nodes, node)
321
+ (node) => SAMPLER_TYPES.includes(node.class_type) && isHiresSampler(nodes, node)
336
322
  );
337
323
  }
338
324
 
325
+ // src/parsers/comfyui-civitai.ts
326
+ function extractExtraMetadata(prompt, entryRecord) {
327
+ const extraMetaField = prompt.extraMetadata;
328
+ if (typeof extraMetaField === "string") {
329
+ const parsed = parseJson(extraMetaField);
330
+ if (parsed.ok && parsed.type === "object") return parsed.value;
331
+ }
332
+ if (entryRecord?.extraMetadata) {
333
+ const parsed = parseJson(entryRecord.extraMetadata);
334
+ if (parsed.ok && parsed.type === "object") return parsed.value;
335
+ }
336
+ return void 0;
337
+ }
338
+ function extractCivitaiMetadata(extraMeta) {
339
+ if (!extraMeta) return void 0;
340
+ const upscale = buildCivitaiUpscale(extraMeta);
341
+ const sampling = buildCivitaiSampling(extraMeta);
342
+ return trimObject({
343
+ prompt: extraMeta.prompt,
344
+ negativePrompt: extraMeta.negativePrompt,
345
+ width: extraMeta.width,
346
+ height: extraMeta.height,
347
+ model: extraMeta.baseModel ? { name: extraMeta.baseModel } : void 0,
348
+ ...sampling,
349
+ ...upscale
350
+ });
351
+ }
352
+ function buildCivitaiUpscale(extraMeta) {
353
+ if (!extraMeta.transformations) return {};
354
+ const upscaleTransform = extraMeta.transformations.find(
355
+ (t) => t.type === "upscale"
356
+ );
357
+ if (!upscaleTransform?.upscaleWidth) return {};
358
+ const scale = calculateScale(
359
+ upscaleTransform.upscaleWidth,
360
+ extraMeta.width ?? 0
361
+ );
362
+ if (scale === void 0) return {};
363
+ return {
364
+ upscale: { scale }
365
+ };
366
+ }
367
+ function buildCivitaiSampling(extraMeta) {
368
+ if (extraMeta.seed === void 0 && extraMeta.steps === void 0 && extraMeta.cfgScale === void 0 && extraMeta.sampler === void 0) {
369
+ return {};
370
+ }
371
+ return {
372
+ sampling: {
373
+ seed: extraMeta.seed,
374
+ steps: extraMeta.steps,
375
+ cfg: extraMeta.cfgScale,
376
+ sampler: extraMeta.sampler
377
+ }
378
+ };
379
+ }
380
+
339
381
  // src/parsers/comfyui.ts
340
382
  var CIVITAI_EXTENSION_KEYS = ["extra", "extraMetadata", "resource-stack"];
341
383
  function parseComfyUI(entries) {
@@ -373,10 +415,6 @@ function parseComfyUI(entries) {
373
415
  function cleanJsonString(json) {
374
416
  return json.replace(/\0+$/, "").replace(/:\s*NaN\b/g, ": null");
375
417
  }
376
- function calculateScale2(targetWidth, baseWidth) {
377
- if (baseWidth <= 0 || targetWidth <= 0) return void 0;
378
- return Math.round(targetWidth / baseWidth * 100) / 100;
379
- }
380
418
  function findPromptJson(entryRecord) {
381
419
  if (entryRecord.prompt) {
382
420
  return cleanJsonString(entryRecord.prompt);
@@ -408,51 +446,61 @@ function findPromptJson(entryRecord) {
408
446
  return void 0;
409
447
  }
410
448
  function extractComfyUIMetadata(nodes) {
411
- const { promptText, negativeText } = extractPromptTexts(nodes);
412
- const { width, height } = extractDimensions(nodes);
413
- const hiresModel = findNode(nodes, CLASS_TYPES.hiresModelUpscale)?.inputs;
414
- const hiresImageScale = findNode(nodes, CLASS_TYPES.hiresImageScale)?.inputs;
415
- const latentUpscale = findNode(nodes, CLASS_TYPES.latentUpscale)?.inputs;
416
- const hiresSampler = findHiresSampler(nodes)?.inputs;
449
+ const c = classifyNodes(nodes);
450
+ const { promptText, negativeText } = extractPromptTexts(nodes, c.sampler);
451
+ const { width, height } = extractDimensions(
452
+ c.latentImage,
453
+ c.latentImageRgthree
454
+ );
455
+ const hiresSamplerNode = findHiresSampler(nodes);
456
+ const hiresSampling = hiresSamplerNode ? extractSampling(nodes, hiresSamplerNode) : void 0;
457
+ const hiresScale = resolveHiresScale(nodes, c, width);
458
+ const upscalerName = c.hiresModelUpscale?.inputs.model_name;
417
459
  return trimObject({
418
460
  prompt: promptText || void 0,
419
461
  negativePrompt: negativeText || void 0,
420
462
  width: width > 0 ? width : void 0,
421
463
  height: height > 0 ? height : void 0,
422
- model: extractModel(nodes),
423
- sampling: extractSampling(nodes),
424
- ...buildHiresOrUpscale(
425
- hiresModel,
426
- hiresImageScale,
427
- latentUpscale,
428
- hiresSampler,
429
- width
430
- )
464
+ model: extractModel(c.checkpoint, c.unetLoader),
465
+ sampling: extractSampling(nodes, c.sampler),
466
+ ...buildHiresOrUpscale(upscalerName, hiresScale, hiresSampling)
431
467
  });
432
468
  }
433
- function buildHiresOrUpscale(hiresModel, hiresImageScale, latentUpscale, hiresSampler, baseWidth) {
434
- if (!hiresModel && !hiresImageScale && !latentUpscale) return {};
435
- let scale;
469
+ function resolveHiresScale(nodes, c, baseWidth) {
470
+ const latentUpscale = c.latentUpscale?.inputs;
436
471
  if (latentUpscale?.scale_by !== void 0) {
437
- scale = latentUpscale.scale_by;
438
- } else if (hiresImageScale?.width !== void 0) {
439
- scale = calculateScale2(hiresImageScale.width, baseWidth);
472
+ return latentUpscale.scale_by;
473
+ }
474
+ const widthInput = c.hiresImageScale?.inputs.width;
475
+ if (widthInput === void 0) return void 0;
476
+ if (isNodeReference(widthInput)) {
477
+ const sourceNode = nodes[String(widthInput[0])];
478
+ if (typeof sourceNode?.inputs.clip_scale === "number") {
479
+ return sourceNode.inputs.clip_scale;
480
+ }
481
+ return void 0;
482
+ }
483
+ if (typeof widthInput === "number") {
484
+ return calculateScale(widthInput, baseWidth);
440
485
  }
441
- const upscaler = hiresModel?.model_name;
442
- if (hiresSampler) {
486
+ return void 0;
487
+ }
488
+ function buildHiresOrUpscale(upscalerName, scale, hiresSampling) {
489
+ if (!upscalerName && scale === void 0 && !hiresSampling) return {};
490
+ if (hiresSampling) {
443
491
  return {
444
492
  hires: {
445
- upscaler,
493
+ upscaler: upscalerName,
446
494
  scale,
447
- steps: hiresSampler.steps,
448
- denoise: hiresSampler.denoise
495
+ steps: hiresSampling.steps,
496
+ denoise: hiresSampling.denoise
449
497
  }
450
498
  };
451
499
  }
452
- if (!upscaler) return {};
500
+ if (!upscalerName) return {};
453
501
  return {
454
502
  upscale: {
455
- upscaler,
503
+ upscaler: upscalerName,
456
504
  scale
457
505
  }
458
506
  };
@@ -460,14 +508,10 @@ function buildHiresOrUpscale(hiresModel, hiresImageScale, latentUpscale, hiresSa
460
508
  function mergeObjects(base, override) {
461
509
  if (!base && !override) return void 0;
462
510
  const merged = {};
463
- if (base) {
464
- for (const [key, value] of Object.entries(base)) {
465
- if (value !== void 0) merged[key] = value;
466
- }
467
- }
468
- if (override) {
469
- for (const [key, value] of Object.entries(override)) {
470
- if (value !== void 0) merged[key] = value;
511
+ for (const obj of [base, override]) {
512
+ if (!obj) continue;
513
+ for (const [k, v] of Object.entries(obj)) {
514
+ if (v !== void 0) merged[k] = v;
471
515
  }
472
516
  }
473
517
  return Object.keys(merged).length > 0 ? merged : void 0;
@@ -492,27 +536,23 @@ function mergeMetadata(base, override) {
492
536
  }
493
537
 
494
538
  // src/parsers/detect.ts
495
- var MARKERS = {
496
- // Unique chunk keywords
497
- INVOKEAI: "invokeai_metadata",
498
- TENSORART: "generation_data",
499
- STABILITY_MATRIX: "smproj",
500
- EASYDIFFUSION: "use_stable_diffusion_model",
501
- CIVITAI_EXTRA: "extraMetadata",
502
- // Content patterns
503
- SWARMUI: "sui_image_params",
504
- SWARM_VERSION: "swarm_version",
505
- COMFYUI_NODE: "class_type",
506
- NOVELAI_SCHEDULE: "noise_schedule",
507
- NOVELAI_V4: "v4_prompt",
508
- NOVELAI_UNCOND: "uncond_scale",
509
- CIVITAI_NS: "civitai:",
510
- CIVITAI_RESOURCES: "Civitai resources:",
511
- RUINED_FOOOCUS: "RuinedFooocus",
512
- HF_MODEL: '"Model"',
513
- HF_RESOLUTION: '"resolution"',
514
- FOOOCUS_BASE: '"base_model"'
515
- };
539
+ var M_INVOKEAI = "invokeai_metadata";
540
+ var M_TENSORART = "generation_data";
541
+ var M_STABILITY_MATRIX = "smproj";
542
+ var M_CIVITAI_EXTRA = "extraMetadata";
543
+ var M_SWARMUI = "sui_image_params";
544
+ var M_SWARM_VERSION = "swarm_version";
545
+ var M_COMFYUI_NODE = "class_type";
546
+ var M_NOVELAI_SCHEDULE = "noise_schedule";
547
+ var M_NOVELAI_V4 = "v4_prompt";
548
+ var M_NOVELAI_UNCOND = "uncond_scale";
549
+ var M_CIVITAI_NS = "civitai:";
550
+ var M_CIVITAI_RESOURCES = "Civitai resources:";
551
+ var M_RUINED_FOOOCUS = "RuinedFooocus";
552
+ var M_EASYDIFFUSION = "use_stable_diffusion_model";
553
+ var M_HF_MODEL = '"Model"';
554
+ var M_HF_RESOLUTION = '"resolution"';
555
+ var M_FOOOCUS_BASE = '"base_model"';
516
556
  function detectSoftware(entries) {
517
557
  const uniqueResult = detectUniqueKeywords(entries);
518
558
  if (uniqueResult) return uniqueResult;
@@ -524,27 +564,24 @@ function detectSoftware(entries) {
524
564
  }
525
565
  return null;
526
566
  }
567
+ function detectByUniqueKey(record) {
568
+ if (M_INVOKEAI in record) return "invokeai";
569
+ if (M_TENSORART in record) return "tensorart";
570
+ if (M_STABILITY_MATRIX in record) return "stability-matrix";
571
+ if (M_CIVITAI_EXTRA in record) return "civitai";
572
+ return null;
573
+ }
527
574
  function detectUniqueKeywords(entryRecord) {
528
575
  if (entryRecord.Software?.startsWith("NovelAI")) {
529
576
  return "novelai";
530
577
  }
531
- if (MARKERS.INVOKEAI in entryRecord) {
532
- return "invokeai";
533
- }
534
- if (MARKERS.TENSORART in entryRecord) {
535
- return "tensorart";
536
- }
537
- if (MARKERS.STABILITY_MATRIX in entryRecord) {
538
- return "stability-matrix";
539
- }
578
+ const keyResult = detectByUniqueKey(entryRecord);
579
+ if (keyResult) return keyResult;
540
580
  if ("negative_prompt" in entryRecord || "Negative Prompt" in entryRecord) {
541
581
  return "easydiffusion";
542
582
  }
543
- if (MARKERS.CIVITAI_EXTRA in entryRecord) {
544
- return "civitai";
545
- }
546
583
  const parameters = entryRecord.parameters;
547
- if (parameters?.includes(MARKERS.SWARMUI)) {
584
+ if (parameters?.includes(M_SWARMUI)) {
548
585
  return "swarmui";
549
586
  }
550
587
  const comment = entryRecord.UserComment ?? entryRecord.Comment;
@@ -556,18 +593,8 @@ function detectUniqueKeywords(entryRecord) {
556
593
  function detectFromCommentJson(comment) {
557
594
  try {
558
595
  const parsed = JSON.parse(comment);
559
- if (MARKERS.INVOKEAI in parsed) {
560
- return "invokeai";
561
- }
562
- if (MARKERS.TENSORART in parsed) {
563
- return "tensorart";
564
- }
565
- if (MARKERS.STABILITY_MATRIX in parsed) {
566
- return "stability-matrix";
567
- }
568
- if (MARKERS.CIVITAI_EXTRA in parsed) {
569
- return "civitai";
570
- }
596
+ const keyResult = detectByUniqueKey(parsed);
597
+ if (keyResult) return keyResult;
571
598
  if ("prompt" in parsed && "workflow" in parsed) {
572
599
  const workflow = parsed.workflow;
573
600
  const prompt = parsed.prompt;
@@ -577,12 +604,12 @@ function detectFromCommentJson(comment) {
577
604
  return "comfyui";
578
605
  }
579
606
  }
580
- if (MARKERS.SWARMUI in parsed) {
607
+ if (M_SWARMUI in parsed) {
581
608
  return "swarmui";
582
609
  }
583
610
  if ("prompt" in parsed && "parameters" in parsed) {
584
611
  const params = String(parsed.parameters || "");
585
- if (params.includes(MARKERS.SWARMUI) || params.includes(MARKERS.SWARM_VERSION)) {
612
+ if (params.includes(M_SWARMUI) || params.includes(M_SWARM_VERSION)) {
586
613
  return "swarmui";
587
614
  }
588
615
  }
@@ -600,13 +627,13 @@ function detectComfyUIEntries(entryRecord) {
600
627
  if ("prompt" in entryRecord) {
601
628
  const promptText = entryRecord.prompt;
602
629
  if (promptText?.startsWith("{")) {
603
- if (promptText.includes(MARKERS.SWARMUI)) {
630
+ if (promptText.includes(M_SWARMUI)) {
604
631
  return "swarmui";
605
632
  }
606
- if (promptText.includes(`"${MARKERS.CIVITAI_EXTRA}"`)) {
633
+ if (promptText.includes(`"${M_CIVITAI_EXTRA}"`)) {
607
634
  return "civitai";
608
635
  }
609
- if (promptText.includes(MARKERS.COMFYUI_NODE)) {
636
+ if (promptText.includes(M_COMFYUI_NODE)) {
610
637
  return "comfyui";
611
638
  }
612
639
  }
@@ -620,25 +647,25 @@ function detectFromTextContent(text) {
620
647
  return detectFromA1111Format(text);
621
648
  }
622
649
  function detectFromJsonFormat(json) {
623
- if (json.includes(MARKERS.SWARMUI)) {
650
+ if (json.includes(M_SWARMUI)) {
624
651
  return "swarmui";
625
652
  }
626
- if (json.includes(`"software":"${MARKERS.RUINED_FOOOCUS}"`) || json.includes(`"software": "${MARKERS.RUINED_FOOOCUS}"`)) {
653
+ if (json.includes(`"software":"${M_RUINED_FOOOCUS}"`) || json.includes(`"software": "${M_RUINED_FOOOCUS}"`)) {
627
654
  return "ruined-fooocus";
628
655
  }
629
- if (json.includes(`"${MARKERS.EASYDIFFUSION}"`)) {
656
+ if (json.includes(`"${M_EASYDIFFUSION}"`)) {
630
657
  return "easydiffusion";
631
658
  }
632
- if (json.includes(MARKERS.CIVITAI_NS) || json.includes(`"${MARKERS.CIVITAI_EXTRA}"`)) {
659
+ if (json.includes(M_CIVITAI_NS) || json.includes(`"${M_CIVITAI_EXTRA}"`)) {
633
660
  return "civitai";
634
661
  }
635
- if (json.includes(`"${MARKERS.NOVELAI_V4}"`) || json.includes(`"${MARKERS.NOVELAI_SCHEDULE}"`) || json.includes(`"${MARKERS.NOVELAI_UNCOND}"`) || json.includes('"Software":"NovelAI"') || json.includes(`\\"${MARKERS.NOVELAI_SCHEDULE}\\"`) || json.includes(`\\"${MARKERS.NOVELAI_V4}\\"`)) {
662
+ if (json.includes(`"${M_NOVELAI_V4}"`) || json.includes(`"${M_NOVELAI_SCHEDULE}"`) || json.includes(`"${M_NOVELAI_UNCOND}"`) || json.includes('"Software":"NovelAI"') || json.includes(`\\"${M_NOVELAI_SCHEDULE}\\"`) || json.includes(`\\"${M_NOVELAI_V4}\\"`)) {
636
663
  return "novelai";
637
664
  }
638
- if (json.includes(MARKERS.HF_MODEL) && json.includes(MARKERS.HF_RESOLUTION)) {
665
+ if (json.includes(M_HF_MODEL) && json.includes(M_HF_RESOLUTION)) {
639
666
  return "hf-space";
640
667
  }
641
- if (json.includes('"prompt"') && json.includes(MARKERS.FOOOCUS_BASE)) {
668
+ if (json.includes('"prompt"') && json.includes(M_FOOOCUS_BASE)) {
642
669
  return "fooocus";
643
670
  }
644
671
  if (json.includes('"prompt"') || json.includes('"nodes"')) {
@@ -647,7 +674,7 @@ function detectFromJsonFormat(json) {
647
674
  return null;
648
675
  }
649
676
  function detectFromA1111Format(text) {
650
- if (text.includes(MARKERS.SWARMUI) || text.includes(MARKERS.SWARM_VERSION)) {
677
+ if (text.includes(M_SWARMUI) || text.includes(M_SWARM_VERSION)) {
651
678
  return "swarmui";
652
679
  }
653
680
  const versionMatch = text.match(/Version:\s*([^\s,]+)/);
@@ -666,7 +693,7 @@ function detectFromA1111Format(text) {
666
693
  if (text.includes("App: SD.Next") || text.includes("App:SD.Next")) {
667
694
  return "sd-next";
668
695
  }
669
- if (text.includes(MARKERS.CIVITAI_RESOURCES)) {
696
+ if (text.includes(M_CIVITAI_RESOURCES)) {
670
697
  return "civitai";
671
698
  }
672
699
  if (text.includes("Steps:") && text.includes("Sampler:")) {
@@ -676,9 +703,6 @@ function detectFromA1111Format(text) {
676
703
  }
677
704
 
678
705
  // src/parsers/easydiffusion.ts
679
- function getValue(json, keyA, keyB) {
680
- return json[keyA] ?? json[keyB];
681
- }
682
706
  function extractModelName(path) {
683
707
  if (!path) return void 0;
684
708
  const parts = path.replace(/\\/g, "/").split("/");
@@ -686,7 +710,7 @@ function extractModelName(path) {
686
710
  }
687
711
  function parseEasyDiffusion(entries) {
688
712
  if (entries.negative_prompt || entries["Negative Prompt"]) {
689
- return parseFromEntries(entries);
713
+ return buildMetadata(entries);
690
714
  }
691
715
  const jsonText = entries.parameters?.startsWith("{") ? entries.parameters : entries.UserComment?.startsWith("{") && entries.UserComment;
692
716
  if (!jsonText) {
@@ -699,63 +723,38 @@ function parseEasyDiffusion(entries) {
699
723
  message: "Invalid JSON in Easy Diffusion metadata"
700
724
  });
701
725
  }
702
- return parseFromJson(parsed.value);
726
+ return buildMetadata(parsed.value);
703
727
  }
704
- function parseFromEntries(entryRecord) {
705
- const prompt = entryRecord.prompt ?? entryRecord.Prompt ?? "";
706
- const negativePrompt = entryRecord.negative_prompt ?? entryRecord["Negative Prompt"] ?? entryRecord.negative_prompt ?? "";
707
- const modelPath = entryRecord.use_stable_diffusion_model ?? entryRecord["Stable Diffusion model"];
708
- const width = Number(entryRecord.width ?? entryRecord.Width) || 0;
709
- const height = Number(entryRecord.height ?? entryRecord.Height) || 0;
710
- const metadata = {
711
- software: "easydiffusion",
712
- prompt: prompt.trim(),
713
- negativePrompt: negativePrompt.trim(),
714
- width,
715
- height,
716
- model: {
717
- name: extractModelName(modelPath),
718
- vae: entryRecord.use_vae_model ?? entryRecord["VAE model"]
719
- },
720
- sampling: {
721
- sampler: entryRecord.sampler_name ?? entryRecord.Sampler,
722
- steps: Number(entryRecord.num_inference_steps ?? entryRecord.Steps) || void 0,
723
- cfg: Number(entryRecord.guidance_scale ?? entryRecord["Guidance Scale"]) || void 0,
724
- seed: Number(entryRecord.seed ?? entryRecord.Seed) || void 0,
725
- clipSkip: Number(entryRecord.clip_skip ?? entryRecord["Clip Skip"]) || void 0
726
- }
728
+ function buildMetadata(data) {
729
+ const str = (keyA, keyB) => {
730
+ const v = data[keyA] ?? data[keyB];
731
+ return typeof v === "string" ? v : void 0;
727
732
  };
728
- return Result.ok(metadata);
729
- }
730
- function parseFromJson(json) {
731
- const prompt = getValue(json, "prompt", "Prompt") ?? "";
732
- const negativePrompt = getValue(json, "negative_prompt", "Negative Prompt") ?? "";
733
- const modelPath = getValue(
734
- json,
735
- "use_stable_diffusion_model",
736
- "Stable Diffusion model"
737
- );
738
- const width = getValue(json, "width", "Width") ?? 0;
739
- const height = getValue(json, "height", "Height") ?? 0;
740
- const metadata = {
733
+ const num = (keyA, keyB) => {
734
+ const v = Number(data[keyA] ?? data[keyB]);
735
+ return v || void 0;
736
+ };
737
+ const prompt = (str("prompt", "Prompt") ?? "").trim();
738
+ const negativePrompt = (str("negative_prompt", "Negative Prompt") ?? "").trim();
739
+ const modelPath = str("use_stable_diffusion_model", "Stable Diffusion model");
740
+ return Result.ok({
741
741
  software: "easydiffusion",
742
- prompt: prompt.trim(),
743
- negativePrompt: negativePrompt.trim(),
744
- width,
745
- height,
742
+ prompt,
743
+ negativePrompt,
744
+ width: num("width", "Width") ?? 0,
745
+ height: num("height", "Height") ?? 0,
746
746
  model: {
747
747
  name: extractModelName(modelPath),
748
- vae: getValue(json, "use_vae_model", "VAE model")
748
+ vae: str("use_vae_model", "VAE model")
749
749
  },
750
750
  sampling: {
751
- sampler: getValue(json, "sampler_name", "Sampler"),
752
- steps: getValue(json, "num_inference_steps", "Steps"),
753
- cfg: getValue(json, "guidance_scale", "Guidance Scale"),
754
- seed: getValue(json, "seed", "Seed"),
755
- clipSkip: getValue(json, "clip_skip", "Clip Skip")
751
+ sampler: str("sampler_name", "Sampler"),
752
+ steps: num("num_inference_steps", "Steps"),
753
+ cfg: num("guidance_scale", "Guidance Scale"),
754
+ seed: num("seed", "Seed"),
755
+ clipSkip: num("clip_skip", "Clip Skip")
756
756
  }
757
- };
758
- return Result.ok(metadata);
757
+ });
759
758
  }
760
759
 
761
760
  // src/parsers/fooocus.ts
@@ -832,7 +831,13 @@ function parseHfSpace(entries) {
832
831
  steps: json.num_inference_steps,
833
832
  cfg: json.guidance_scale,
834
833
  seed: json.seed
835
- })
834
+ }),
835
+ hires: json.use_upscaler ? trimObject({
836
+ upscaler: json.use_upscaler.upscale_method,
837
+ denoise: json.use_upscaler.upscaler_strength,
838
+ scale: json.use_upscaler.upscale_by,
839
+ steps: json.use_upscaler.upscale_steps
840
+ }) : void 0
836
841
  };
837
842
  return Result.ok(metadata);
838
843
  }
@@ -1294,6 +1299,80 @@ function detectFormat(data) {
1294
1299
  return null;
1295
1300
  }
1296
1301
 
1302
+ // src/readers/dimensions.ts
1303
+ function readImageDimensions(data, format) {
1304
+ if (format === "png") return readPngDimensions(data);
1305
+ if (format === "jpeg") return readJpegDimensions(data);
1306
+ return readWebpDimensions(data);
1307
+ }
1308
+ function readPngDimensions(data) {
1309
+ const PNG_SIGNATURE_LENGTH2 = 8;
1310
+ if (data.length < 24) return null;
1311
+ return {
1312
+ width: readUint32BE(data, PNG_SIGNATURE_LENGTH2 + 8),
1313
+ height: readUint32BE(data, PNG_SIGNATURE_LENGTH2 + 12)
1314
+ };
1315
+ }
1316
+ function readJpegDimensions(data) {
1317
+ let offset = 2;
1318
+ while (offset < data.length - 4) {
1319
+ if (data[offset] !== 255) {
1320
+ offset++;
1321
+ continue;
1322
+ }
1323
+ const marker = data[offset + 1] ?? 0;
1324
+ if (marker === 255) {
1325
+ offset++;
1326
+ continue;
1327
+ }
1328
+ const length = (data[offset + 2] ?? 0) << 8 | (data[offset + 3] ?? 0);
1329
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
1330
+ const height = (data[offset + 5] ?? 0) << 8 | (data[offset + 6] ?? 0);
1331
+ const width = (data[offset + 7] ?? 0) << 8 | (data[offset + 8] ?? 0);
1332
+ return { width, height };
1333
+ }
1334
+ offset += 2 + length;
1335
+ if (marker === 218) break;
1336
+ }
1337
+ return null;
1338
+ }
1339
+ function readWebpDimensions(data) {
1340
+ let offset = 12;
1341
+ while (offset < data.length) {
1342
+ if (offset + 8 > data.length) break;
1343
+ const chunkType = readChunkType(data, offset);
1344
+ const chunkSize = readUint32LE(data, offset + 4);
1345
+ const paddedSize = chunkSize + chunkSize % 2;
1346
+ if (chunkType === "VP8X") {
1347
+ const wMinus1 = readUint24LE(data, offset + 12);
1348
+ const hMinus1 = readUint24LE(data, offset + 15);
1349
+ return { width: wMinus1 + 1, height: hMinus1 + 1 };
1350
+ }
1351
+ if (chunkType === "VP8 ") {
1352
+ const start = offset + 8;
1353
+ const tag = (data[start] ?? 0) | (data[start + 1] ?? 0) << 8 | (data[start + 2] ?? 0) << 16;
1354
+ const keyFrame = !(tag & 1);
1355
+ if (keyFrame) {
1356
+ if (data[start + 3] === 157 && data[start + 4] === 1 && data[start + 5] === 42) {
1357
+ const wRaw = (data[start + 6] ?? 0) | (data[start + 7] ?? 0) << 8;
1358
+ const hRaw = (data[start + 8] ?? 0) | (data[start + 9] ?? 0) << 8;
1359
+ return { width: wRaw & 16383, height: hRaw & 16383 };
1360
+ }
1361
+ }
1362
+ }
1363
+ if (chunkType === "VP8L") {
1364
+ if (data[offset + 8] === 47) {
1365
+ const bits = readUint32LE(data, offset + 9);
1366
+ const width = (bits & 16383) + 1;
1367
+ const height = (bits >> 14 & 16383) + 1;
1368
+ return { width, height };
1369
+ }
1370
+ }
1371
+ offset += 8 + paddedSize;
1372
+ }
1373
+ return null;
1374
+ }
1375
+
1297
1376
  // src/utils/exif-constants.ts
1298
1377
  var USER_COMMENT_TAG = 37510;
1299
1378
  var IMAGE_DESCRIPTION_TAG = 270;
@@ -1758,9 +1837,7 @@ function findExifChunk(data) {
1758
1837
 
1759
1838
  // src/utils/convert.ts
1760
1839
  function pngChunksToRecord(chunks) {
1761
- return Object.freeze(
1762
- Object.fromEntries(chunks.map((c) => [c.keyword, c.text]))
1763
- );
1840
+ return Object.fromEntries(chunks.map((c) => [c.keyword, c.text]));
1764
1841
  }
1765
1842
  function segmentsToRecord(segments) {
1766
1843
  const record = {};
@@ -1776,7 +1853,7 @@ function segmentsToRecord(segments) {
1776
1853
  }
1777
1854
  record[keyword] = text;
1778
1855
  }
1779
- return Object.freeze(record);
1856
+ return record;
1780
1857
  }
1781
1858
  function tryExpandNovelAIWebpFormat(text) {
1782
1859
  const outerParsed = parseJson(text);
@@ -1788,10 +1865,10 @@ function tryExpandNovelAIWebpFormat(text) {
1788
1865
  return null;
1789
1866
  }
1790
1867
  const innerParsed = parseJson(outer.Comment);
1791
- return Object.freeze({
1868
+ return {
1792
1869
  Software: typeof outer.Software === "string" ? outer.Software : "NovelAI",
1793
1870
  Comment: innerParsed.ok ? JSON.stringify(innerParsed.value) : outer.Comment
1794
- });
1871
+ };
1795
1872
  }
1796
1873
  function sourceToKeyword(source) {
1797
1874
  switch (source.type) {
@@ -1825,7 +1902,7 @@ function read(input, options) {
1825
1902
  }
1826
1903
  const metadata = parseResult.value;
1827
1904
  if (!options?.strict && (metadata.width === 0 || metadata.height === 0)) {
1828
- const dims = HELPERS[format].readDimensions(data);
1905
+ const dims = readImageDimensions(data, format);
1829
1906
  if (dims) {
1830
1907
  metadata.width = metadata.width || dims.width;
1831
1908
  metadata.height = metadata.height || dims.height;
@@ -1833,31 +1910,8 @@ function read(input, options) {
1833
1910
  }
1834
1911
  return { status: "success", metadata, raw };
1835
1912
  }
1836
- var HELPERS = {
1837
- png: {
1838
- readMetadata: readPngMetadata,
1839
- readDimensions: readPngDimensions,
1840
- createRaw: (chunks) => ({ format: "png", chunks })
1841
- },
1842
- jpeg: {
1843
- readMetadata: readJpegMetadata,
1844
- readDimensions: readJpegDimensions,
1845
- createRaw: (segments) => ({
1846
- format: "jpeg",
1847
- segments
1848
- })
1849
- },
1850
- webp: {
1851
- readMetadata: readWebpMetadata,
1852
- readDimensions: readWebpDimensions,
1853
- createRaw: (segments) => ({
1854
- format: "webp",
1855
- segments
1856
- })
1857
- }
1858
- };
1859
1913
  function readRawMetadata(data, format) {
1860
- const result = HELPERS[format].readMetadata(data);
1914
+ const result = format === "png" ? readPngMetadata(data) : format === "jpeg" ? readJpegMetadata(data) : readWebpMetadata(data);
1861
1915
  if (!result.ok) {
1862
1916
  const message = result.error.type === "invalidSignature" ? `Invalid ${format.toUpperCase()} signature` : result.error.message;
1863
1917
  return { status: "invalid", message };
@@ -1866,81 +1920,14 @@ function readRawMetadata(data, format) {
1866
1920
  if (format === "png") {
1867
1921
  return {
1868
1922
  status: "success",
1869
- raw: HELPERS.png.createRaw(result.value)
1923
+ raw: { format: "png", chunks: result.value }
1870
1924
  };
1871
1925
  }
1872
1926
  return {
1873
1927
  status: "success",
1874
- raw: HELPERS[format].createRaw(result.value)
1928
+ raw: { format, segments: result.value }
1875
1929
  };
1876
1930
  }
1877
- function readPngDimensions(data) {
1878
- const PNG_SIGNATURE_LENGTH2 = 8;
1879
- if (data.length < 24) return null;
1880
- return {
1881
- width: readUint32BE(data, PNG_SIGNATURE_LENGTH2 + 8),
1882
- height: readUint32BE(data, PNG_SIGNATURE_LENGTH2 + 12)
1883
- };
1884
- }
1885
- function readJpegDimensions(data) {
1886
- let offset = 2;
1887
- while (offset < data.length - 4) {
1888
- if (data[offset] !== 255) {
1889
- offset++;
1890
- continue;
1891
- }
1892
- const marker = data[offset + 1] ?? 0;
1893
- if (marker === 255) {
1894
- offset++;
1895
- continue;
1896
- }
1897
- const length = (data[offset + 2] ?? 0) << 8 | (data[offset + 3] ?? 0);
1898
- if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
1899
- const height = (data[offset + 5] ?? 0) << 8 | (data[offset + 6] ?? 0);
1900
- const width = (data[offset + 7] ?? 0) << 8 | (data[offset + 8] ?? 0);
1901
- return { width, height };
1902
- }
1903
- offset += 2 + length;
1904
- if (marker === 218) break;
1905
- }
1906
- return null;
1907
- }
1908
- function readWebpDimensions(data) {
1909
- let offset = 12;
1910
- while (offset < data.length) {
1911
- if (offset + 8 > data.length) break;
1912
- const chunkType = readChunkType(data, offset);
1913
- const chunkSize = readUint32LE(data, offset + 4);
1914
- const paddedSize = chunkSize + chunkSize % 2;
1915
- if (chunkType === "VP8X") {
1916
- const wMinus1 = readUint24LE(data, offset + 12);
1917
- const hMinus1 = readUint24LE(data, offset + 15);
1918
- return { width: wMinus1 + 1, height: hMinus1 + 1 };
1919
- }
1920
- if (chunkType === "VP8 ") {
1921
- const start = offset + 8;
1922
- const tag = (data[start] ?? 0) | (data[start + 1] ?? 0) << 8 | (data[start + 2] ?? 0) << 16;
1923
- const keyFrame = !(tag & 1);
1924
- if (keyFrame) {
1925
- if (data[start + 3] === 157 && data[start + 4] === 1 && data[start + 5] === 42) {
1926
- const wRaw = (data[start + 6] ?? 0) | (data[start + 7] ?? 0) << 8;
1927
- const hRaw = (data[start + 8] ?? 0) | (data[start + 9] ?? 0) << 8;
1928
- return { width: wRaw & 16383, height: hRaw & 16383 };
1929
- }
1930
- }
1931
- }
1932
- if (chunkType === "VP8L") {
1933
- if (data[offset + 8] === 47) {
1934
- const bits = readUint32LE(data, offset + 9);
1935
- const width = (bits & 16383) + 1;
1936
- const height = (bits >> 14 & 16383) + 1;
1937
- return { width, height };
1938
- }
1939
- }
1940
- offset += 8 + paddedSize;
1941
- }
1942
- return null;
1943
- }
1944
1931
 
1945
1932
  // src/converters/utils.ts
1946
1933
  var createTextChunk = (keyword, text) => text !== void 0 ? [{ type: "tEXt", keyword, text }] : [];
@@ -1993,6 +1980,12 @@ function createEncodedChunk(keyword, text, strategy) {
1993
1980
  }
1994
1981
  }
1995
1982
  }
1983
+ function createEncodedChunks(entries, encodingMap) {
1984
+ return entries.flatMap(([keyword, text]) => {
1985
+ const strategy = encodingMap[keyword] ?? encodingMap.default;
1986
+ return createEncodedChunk(keyword, text, strategy);
1987
+ });
1988
+ }
1996
1989
 
1997
1990
  // src/converters/a1111.ts
1998
1991
  function convertA1111PngToSegments(chunks) {
@@ -2049,7 +2042,7 @@ function convertCivitaiSegmentsToPng(segments) {
2049
2042
  if (!isJson) {
2050
2043
  return convertA1111SegmentsToPng(segments);
2051
2044
  }
2052
- return createEncodedChunk("prompt", userComment.data, "text-utf8-raw");
2045
+ return createEncodedChunk("prompt", userComment.data, "text-unicode-escape");
2053
2046
  }
2054
2047
 
2055
2048
  // src/converters/base-json.ts
@@ -2109,7 +2102,7 @@ var tryParseExtendedFormat = (segments) => {
2109
2102
  ];
2110
2103
  };
2111
2104
  var tryParseSaveImagePlusFormat = (segments) => {
2112
- const chunks = convertKvSegmentsToPng(segments, "text-utf8-raw");
2105
+ const chunks = convertKvSegmentsToPng(segments, "text-unicode-escape");
2113
2106
  return chunks.length > 0 ? chunks : null;
2114
2107
  };
2115
2108
  function convertComfyUISegmentsToPng(segments) {
@@ -2249,6 +2242,39 @@ function createSegmentsToPng(keyword, encodingStrategy) {
2249
2242
  };
2250
2243
  }
2251
2244
 
2245
+ // src/converters/stability-matrix.ts
2246
+ var STABILITY_MATRIX_ENCODING = {
2247
+ parameters: "text-utf8-raw",
2248
+ default: "text-unicode-escape"
2249
+ };
2250
+ function convertStabilityMatrixPngToSegments(chunks) {
2251
+ const data = {};
2252
+ for (const chunk of chunks) {
2253
+ const parsed = parseJson(chunk.text);
2254
+ data[chunk.keyword] = parsed.ok ? parsed.value : chunk.text;
2255
+ }
2256
+ return [
2257
+ {
2258
+ source: { type: "exifUserComment" },
2259
+ data: JSON.stringify(data)
2260
+ }
2261
+ ];
2262
+ }
2263
+ function convertStabilityMatrixSegmentsToPng(segments) {
2264
+ const userComment = findSegment(segments, "exifUserComment");
2265
+ if (!userComment) return [];
2266
+ const parsed = parseJson(userComment.data);
2267
+ if (!parsed.ok || parsed.type !== "object") return [];
2268
+ const value = parsed.value;
2269
+ const entries = Object.entries(value).map(
2270
+ ([key, val]) => {
2271
+ const text = stringify(val);
2272
+ return [key, text !== void 0 ? unescapeUnicode(text) : void 0];
2273
+ }
2274
+ );
2275
+ return createEncodedChunks(entries, STABILITY_MATRIX_ENCODING);
2276
+ }
2277
+
2252
2278
  // src/converters/swarmui.ts
2253
2279
  function convertSwarmUIPngToSegments(chunks) {
2254
2280
  const parametersChunk = chunks.find((c) => c.keyword === "parameters");
@@ -2283,6 +2309,39 @@ function convertSwarmUISegmentsToPng(segments) {
2283
2309
  return chunks;
2284
2310
  }
2285
2311
 
2312
+ // src/converters/tensorart.ts
2313
+ var TENSORART_ENCODING = {
2314
+ generation_data: "text-utf8-raw",
2315
+ default: "text-unicode-escape"
2316
+ };
2317
+ function convertTensorArtPngToSegments(chunks) {
2318
+ const data = {};
2319
+ for (const chunk of chunks) {
2320
+ const parsed = parseJson(chunk.text);
2321
+ data[chunk.keyword] = parsed.ok ? parsed.value : chunk.text;
2322
+ }
2323
+ return [
2324
+ {
2325
+ source: { type: "exifUserComment" },
2326
+ data: JSON.stringify(data)
2327
+ }
2328
+ ];
2329
+ }
2330
+ function convertTensorArtSegmentsToPng(segments) {
2331
+ const userComment = findSegment(segments, "exifUserComment");
2332
+ if (!userComment) return [];
2333
+ const parsed = parseJson(userComment.data);
2334
+ if (!parsed.ok || parsed.type !== "object") return [];
2335
+ const value = parsed.value;
2336
+ const entries = Object.entries(value).map(
2337
+ ([key, val]) => {
2338
+ const text = stringify(val);
2339
+ return [key, text !== void 0 ? unescapeUnicode(text) : void 0];
2340
+ }
2341
+ );
2342
+ return createEncodedChunks(entries, TENSORART_ENCODING);
2343
+ }
2344
+
2286
2345
  // src/converters/index.ts
2287
2346
  function convertMetadata(parseResult, targetFormat) {
2288
2347
  if (parseResult.status === "empty") {
@@ -2370,6 +2429,14 @@ var convertCivitai = createFormatConverter(
2370
2429
  convertCivitaiPngToSegments,
2371
2430
  convertCivitaiSegmentsToPng
2372
2431
  );
2432
+ var convertStabilityMatrix = createFormatConverter(
2433
+ convertStabilityMatrixPngToSegments,
2434
+ convertStabilityMatrixSegmentsToPng
2435
+ );
2436
+ var convertTensorArt = createFormatConverter(
2437
+ convertTensorArtPngToSegments,
2438
+ convertTensorArtSegmentsToPng
2439
+ );
2373
2440
  var softwareConverters = {
2374
2441
  // NovelAI
2375
2442
  novelai: convertNovelai,
@@ -2380,10 +2447,12 @@ var softwareConverters = {
2380
2447
  "forge-neo": convertA1111,
2381
2448
  // CivitAI Orchestration format
2382
2449
  civitai: convertCivitai,
2383
- // ComfyUI-format (comfyui, tensorart, stability-matrix)
2450
+ // ComfyUI-format
2384
2451
  comfyui: convertComfyUI,
2385
- tensorart: convertComfyUI,
2386
- "stability-matrix": convertComfyUI,
2452
+ // TensorArt (per-chunk encoding: generation_data uses raw UTF-8)
2453
+ tensorart: convertTensorArt,
2454
+ // Stability Matrix (per-chunk encoding: parameters uses raw UTF-8)
2455
+ "stability-matrix": convertStabilityMatrix,
2387
2456
  // Easy Diffusion
2388
2457
  easydiffusion: convertEasyDiffusion,
2389
2458
  // Fooocus variants
@@ -2931,7 +3000,7 @@ function write(input, metadata) {
2931
3000
  return { ok: false, error: { type: "unsupportedFormat" } };
2932
3001
  }
2933
3002
  if (metadata.status === "empty") {
2934
- const result = HELPERS2[targetFormat].writeEmpty(data, []);
3003
+ const result = HELPERS[targetFormat].writeEmpty(data, []);
2935
3004
  if (!result.ok) {
2936
3005
  return {
2937
3006
  ok: false,
@@ -2951,7 +3020,7 @@ function write(input, metadata) {
2951
3020
  if (sourceFormat === targetFormat) {
2952
3021
  return writeRaw(data, targetFormat, metadata.raw);
2953
3022
  }
2954
- const result = HELPERS2[targetFormat].writeEmpty(data, []);
3023
+ const result = HELPERS[targetFormat].writeEmpty(data, []);
2955
3024
  if (!result.ok) {
2956
3025
  return {
2957
3026
  ok: false,
@@ -3015,7 +3084,7 @@ function writeRaw(data, targetFormat, raw) {
3015
3084
  }
3016
3085
  };
3017
3086
  }
3018
- var HELPERS2 = {
3087
+ var HELPERS = {
3019
3088
  png: {
3020
3089
  writeEmpty: writePngMetadata
3021
3090
  },