@enslo/sd-metadata 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -66,76 +66,6 @@ var Result = {
66
66
  function toUint8Array(input) {
67
67
  return input instanceof ArrayBuffer ? new Uint8Array(input) : input;
68
68
  }
69
- function readUint24LE(data, offset) {
70
- return (data[offset] ?? 0) | (data[offset + 1] ?? 0) << 8 | (data[offset + 2] ?? 0) << 16;
71
- }
72
- function readUint32BE(data, offset) {
73
- return (data[offset] ?? 0) << 24 | (data[offset + 1] ?? 0) << 16 | (data[offset + 2] ?? 0) << 8 | (data[offset + 3] ?? 0);
74
- }
75
- function readUint32LE(data, offset) {
76
- return (data[offset] ?? 0) | (data[offset + 1] ?? 0) << 8 | (data[offset + 2] ?? 0) << 16 | (data[offset + 3] ?? 0) << 24;
77
- }
78
- function writeUint32BE(data, offset, value) {
79
- data[offset] = value >>> 24 & 255;
80
- data[offset + 1] = value >>> 16 & 255;
81
- data[offset + 2] = value >>> 8 & 255;
82
- data[offset + 3] = value & 255;
83
- }
84
- function readChunkType(data, offset) {
85
- return String.fromCharCode(
86
- data[offset] ?? 0,
87
- data[offset + 1] ?? 0,
88
- data[offset + 2] ?? 0,
89
- data[offset + 3] ?? 0
90
- );
91
- }
92
- function readUint16(data, offset, isLittleEndian) {
93
- if (isLittleEndian) {
94
- return (data[offset] ?? 0) | (data[offset + 1] ?? 0) << 8;
95
- }
96
- return (data[offset] ?? 0) << 8 | (data[offset + 1] ?? 0);
97
- }
98
- function readUint32(data, offset, isLittleEndian) {
99
- if (isLittleEndian) {
100
- return (data[offset] ?? 0) | (data[offset + 1] ?? 0) << 8 | (data[offset + 2] ?? 0) << 16 | (data[offset + 3] ?? 0) << 24;
101
- }
102
- return (data[offset] ?? 0) << 24 | (data[offset + 1] ?? 0) << 16 | (data[offset + 2] ?? 0) << 8 | (data[offset + 3] ?? 0);
103
- }
104
- function arraysEqual(a, b) {
105
- if (a.length !== b.length) return false;
106
- for (let i = 0; i < a.length; i++) {
107
- if (a[i] !== b[i]) return false;
108
- }
109
- return true;
110
- }
111
- function writeUint16(data, offset, value, isLittleEndian) {
112
- if (isLittleEndian) {
113
- data[offset] = value & 255;
114
- data[offset + 1] = value >>> 8 & 255;
115
- } else {
116
- data[offset] = value >>> 8 & 255;
117
- data[offset + 1] = value & 255;
118
- }
119
- }
120
- function writeUint32(data, offset, value, isLittleEndian) {
121
- if (isLittleEndian) {
122
- data[offset] = value & 255;
123
- data[offset + 1] = value >>> 8 & 255;
124
- data[offset + 2] = value >>> 16 & 255;
125
- data[offset + 3] = value >>> 24 & 255;
126
- } else {
127
- data[offset] = value >>> 24 & 255;
128
- data[offset + 1] = value >>> 16 & 255;
129
- data[offset + 2] = value >>> 8 & 255;
130
- data[offset + 3] = value & 255;
131
- }
132
- }
133
- function writeUint32LE(data, offset, value) {
134
- data[offset] = value & 255;
135
- data[offset + 1] = value >>> 8 & 255;
136
- data[offset + 2] = value >>> 16 & 255;
137
- data[offset + 3] = value >>> 24 & 255;
138
- }
139
69
  function isPng(data) {
140
70
  if (data.length < 8) return false;
141
71
  return data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71 && data[4] === 13 && data[5] === 10 && data[6] === 26 && data[7] === 10;
@@ -161,6 +91,78 @@ function detectFormat(data) {
161
91
  if (isWebp(data)) return "webp";
162
92
  return null;
163
93
  }
94
+ function readUint16BE(data, offset) {
95
+ return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint16(
96
+ offset
97
+ );
98
+ }
99
+ function readUint16(data, offset, isLittleEndian) {
100
+ return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint16(
101
+ offset,
102
+ isLittleEndian
103
+ );
104
+ }
105
+ function readUint24LE(data, offset) {
106
+ return (data[offset] ?? 0) | (data[offset + 1] ?? 0) << 8 | (data[offset + 2] ?? 0) << 16;
107
+ }
108
+ function readUint32BE(data, offset) {
109
+ return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(
110
+ offset
111
+ );
112
+ }
113
+ function readUint32LE(data, offset) {
114
+ return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(
115
+ offset,
116
+ true
117
+ );
118
+ }
119
+ function readUint32(data, offset, isLittleEndian) {
120
+ return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(
121
+ offset,
122
+ isLittleEndian
123
+ );
124
+ }
125
+ function readChunkType(data, offset) {
126
+ return String.fromCharCode(
127
+ data[offset] ?? 0,
128
+ data[offset + 1] ?? 0,
129
+ data[offset + 2] ?? 0,
130
+ data[offset + 3] ?? 0
131
+ );
132
+ }
133
+ function writeUint16BE(data, offset, value) {
134
+ new DataView(data.buffer, data.byteOffset, data.byteLength).setUint16(
135
+ offset,
136
+ value
137
+ );
138
+ }
139
+ function writeUint16(data, offset, value, isLittleEndian) {
140
+ new DataView(data.buffer, data.byteOffset, data.byteLength).setUint16(
141
+ offset,
142
+ value,
143
+ isLittleEndian
144
+ );
145
+ }
146
+ function writeUint32BE(data, offset, value) {
147
+ new DataView(data.buffer, data.byteOffset, data.byteLength).setUint32(
148
+ offset,
149
+ value
150
+ );
151
+ }
152
+ function writeUint32LE(data, offset, value) {
153
+ new DataView(data.buffer, data.byteOffset, data.byteLength).setUint32(
154
+ offset,
155
+ value,
156
+ true
157
+ );
158
+ }
159
+ function writeUint32(data, offset, value, isLittleEndian) {
160
+ new DataView(data.buffer, data.byteOffset, data.byteLength).setUint32(
161
+ offset,
162
+ value,
163
+ isLittleEndian
164
+ );
165
+ }
164
166
 
165
167
  // src/utils/exif-constants.ts
166
168
  var USER_COMMENT_TAG = 37510;
@@ -276,23 +278,23 @@ function writeIfdEntry(data, offset, tag, dataOffset, isLittleEndian) {
276
278
  writeUint32(data, offset + 8, dataOffset ?? 0, isLittleEndian);
277
279
  }
278
280
  }
281
+ var UNICODE_PREFIX = new Uint8Array([
282
+ 85,
283
+ 78,
284
+ 73,
285
+ 67,
286
+ 79,
287
+ 68,
288
+ 69,
289
+ 0
290
+ ]);
279
291
  function encodeUserComment(text) {
280
- const utf16Data = [];
292
+ const result = new Uint8Array(8 + text.length * 2);
293
+ const dataView = new DataView(result.buffer);
294
+ result.set(UNICODE_PREFIX);
281
295
  for (let i = 0; i < text.length; i++) {
282
- const code = text.charCodeAt(i);
283
- utf16Data.push(code & 255);
284
- utf16Data.push(code >> 8 & 255);
285
- }
286
- const result = new Uint8Array(8 + utf16Data.length);
287
- result[0] = 85;
288
- result[1] = 78;
289
- result[2] = 73;
290
- result[3] = 67;
291
- result[4] = 79;
292
- result[5] = 68;
293
- result[6] = 69;
294
- result[7] = 0;
295
- result.set(new Uint8Array(utf16Data), 8);
296
+ dataView.setUint16(8 + i * 2, text.charCodeAt(i), true);
297
+ }
296
298
  return result;
297
299
  }
298
300
  function encodeAsciiTag(text, prefix) {
@@ -383,7 +385,7 @@ function collectNonMetadataSegments(data) {
383
385
  message: "Unexpected end of file"
384
386
  });
385
387
  }
386
- const length = (data[offset] ?? 0) << 8 | (data[offset + 1] ?? 0);
388
+ const length = readUint16BE(data, offset);
387
389
  const segmentStart = offset - 2;
388
390
  const segmentEnd = offset + length;
389
391
  if (segmentEnd > data.length) {
@@ -418,8 +420,7 @@ function buildApp1Segment(segments) {
418
420
  const segment = new Uint8Array(2 + segmentLength);
419
421
  segment[0] = 255;
420
422
  segment[1] = APP1_MARKER;
421
- segment[2] = segmentLength >> 8 & 255;
422
- segment[3] = segmentLength & 255;
423
+ writeUint16BE(segment, 2, segmentLength);
423
424
  segment.set(EXIF_HEADER, 4);
424
425
  segment.set(tiffData, 4 + EXIF_HEADER.length);
425
426
  return segment;
@@ -430,8 +431,7 @@ function buildComSegment(text) {
430
431
  const segment = new Uint8Array(2 + segmentLength);
431
432
  segment[0] = 255;
432
433
  segment[1] = COM_MARKER;
433
- segment[2] = segmentLength >> 8 & 255;
434
- segment[3] = segmentLength & 255;
434
+ writeUint16BE(segment, 2, segmentLength);
435
435
  segment.set(textBytes, 4);
436
436
  return segment;
437
437
  }
@@ -491,6 +491,7 @@ function collectNonTextChunks(data) {
491
491
  if (offset + 4 > data.length) break;
492
492
  const chunkType = readChunkType(data, offset);
493
493
  offset += 4;
494
+ if (offset + length + 4 > data.length) break;
494
495
  offset += length;
495
496
  offset += 4;
496
497
  const chunkEnd = offset;
@@ -545,9 +546,7 @@ function serializeITXtChunk(chunk) {
545
546
  function buildChunk(type, data) {
546
547
  const chunk = new Uint8Array(4 + 4 + data.length + 4);
547
548
  writeUint32BE(chunk, 0, data.length);
548
- for (let i = 0; i < 4; i++) {
549
- chunk[4 + i] = type.charCodeAt(i);
550
- }
549
+ chunk.set(new TextEncoder().encode(type), 4);
551
550
  chunk.set(data, 8);
552
551
  const crcData = chunk.slice(4, 8 + data.length);
553
552
  const crc = calculateCrc32(crcData);
@@ -591,7 +590,6 @@ function calculateCrc32(data) {
591
590
  // src/writers/webp.ts
592
591
  var RIFF_SIGNATURE = new Uint8Array([82, 73, 70, 70]);
593
592
  var WEBP_MARKER = new Uint8Array([87, 69, 66, 80]);
594
- var EXIF_CHUNK_TYPE = new Uint8Array([69, 88, 73, 70]);
595
593
  function writeWebpMetadata(data, segments) {
596
594
  if (!isWebp(data)) {
597
595
  return Result.error({ type: "invalidSignature" });
@@ -634,12 +632,7 @@ function writeWebpMetadata(data, segments) {
634
632
  }
635
633
  function isImageChunk(chunk) {
636
634
  if (chunk.length < 4) return false;
637
- const type = String.fromCharCode(
638
- chunk[0] ?? 0,
639
- chunk[1] ?? 0,
640
- chunk[2] ?? 0,
641
- chunk[3] ?? 0
642
- );
635
+ const type = readChunkType(chunk, 0);
643
636
  return type === "VP8 " || type === "VP8L" || type === "VP8X";
644
637
  }
645
638
  function collectNonExifChunks(data) {
@@ -647,24 +640,18 @@ function collectNonExifChunks(data) {
647
640
  let firstChunkType = "";
648
641
  let offset = 12;
649
642
  while (offset < data.length - 8) {
650
- const chunkType = data.slice(offset, offset + 4);
651
- const typeStr = String.fromCharCode(
652
- chunkType[0] ?? 0,
653
- chunkType[1] ?? 0,
654
- chunkType[2] ?? 0,
655
- chunkType[3] ?? 0
656
- );
643
+ const typeStr = readChunkType(data, offset);
644
+ const chunkSize = readUint32LE(data, offset + 4);
657
645
  if (!firstChunkType) {
658
646
  firstChunkType = typeStr;
659
647
  }
660
- const chunkSize = (data[offset + 4] ?? 0) | (data[offset + 5] ?? 0) << 8 | (data[offset + 6] ?? 0) << 16 | (data[offset + 7] ?? 0) << 24;
661
648
  if (offset + 8 + chunkSize > data.length) {
662
649
  return Result.error({
663
650
  type: "invalidRiffStructure",
664
651
  message: `Chunk extends beyond file at offset ${offset}`
665
652
  });
666
653
  }
667
- if (!arraysEqual(chunkType, EXIF_CHUNK_TYPE)) {
654
+ if (typeStr !== "EXIF") {
668
655
  const paddedSize2 = chunkSize + chunkSize % 2;
669
656
  const chunkData = data.slice(offset, offset + 8 + paddedSize2);
670
657
  chunks.push(chunkData);
@@ -688,7 +675,7 @@ function buildExifChunk(segments) {
688
675
  const chunkSize = tiffData.length;
689
676
  const paddedSize = chunkSize + chunkSize % 2;
690
677
  const chunk = new Uint8Array(8 + paddedSize);
691
- chunk.set(EXIF_CHUNK_TYPE, 0);
678
+ chunk.set(new TextEncoder().encode("EXIF"));
692
679
  writeUint32LE(chunk, 4, chunkSize);
693
680
  chunk.set(tiffData, 8);
694
681
  return chunk;
@@ -877,6 +864,7 @@ function parseParametersText(text) {
877
864
  function parseSettings(settings) {
878
865
  const result = /* @__PURE__ */ new Map();
879
866
  if (!settings) return result;
867
+ if (settings.length > 1e4) return result;
880
868
  const regex = /([A-Za-z][A-Za-z0-9 ]*?):\s*([^,]+?)(?=,\s*[A-Za-z][A-Za-z0-9 ]*?:|$)/g;
881
869
  for (const match of settings.matchAll(regex)) {
882
870
  const key = (match[1] ?? "").trim();
@@ -1424,29 +1412,28 @@ function detectUniqueKeywords(entryRecord) {
1424
1412
  return null;
1425
1413
  }
1426
1414
  function detectFromCommentJson(comment) {
1427
- try {
1428
- const parsed = JSON.parse(comment);
1429
- const keyResult = detectByUniqueKey(parsed);
1430
- if (keyResult) return keyResult;
1431
- if ("prompt" in parsed && "workflow" in parsed) {
1432
- const workflow = parsed.workflow;
1433
- const prompt = parsed.prompt;
1434
- const isObject = typeof workflow === "object" || typeof prompt === "object";
1435
- const isJsonString = typeof workflow === "string" && workflow.startsWith("{") || typeof prompt === "string" && prompt.startsWith("{");
1436
- if (isObject || isJsonString) {
1437
- return "comfyui";
1438
- }
1415
+ const result = parseJson(comment);
1416
+ if (!result.ok || result.type !== "object") return null;
1417
+ const parsed = result.value;
1418
+ const keyResult = detectByUniqueKey(parsed);
1419
+ if (keyResult) return keyResult;
1420
+ if ("prompt" in parsed && "workflow" in parsed) {
1421
+ const workflow = parsed.workflow;
1422
+ const prompt = parsed.prompt;
1423
+ const isObject = typeof workflow === "object" || typeof prompt === "object";
1424
+ const isJsonString = typeof workflow === "string" && workflow.startsWith("{") || typeof prompt === "string" && prompt.startsWith("{");
1425
+ if (isObject || isJsonString) {
1426
+ return "comfyui";
1439
1427
  }
1440
- if (M_SWARMUI in parsed) {
1428
+ }
1429
+ if (M_SWARMUI in parsed) {
1430
+ return "swarmui";
1431
+ }
1432
+ if ("prompt" in parsed && "parameters" in parsed) {
1433
+ const params = String(parsed.parameters || "");
1434
+ if (params.includes(M_SWARMUI) || params.includes(M_SWARM_VERSION)) {
1441
1435
  return "swarmui";
1442
1436
  }
1443
- if ("prompt" in parsed && "parameters" in parsed) {
1444
- const params = String(parsed.parameters || "");
1445
- if (params.includes(M_SWARMUI) || params.includes(M_SWARM_VERSION)) {
1446
- return "swarmui";
1447
- }
1448
- }
1449
- } catch {
1450
1437
  }
1451
1438
  return null;
1452
1439
  }
@@ -1996,8 +1983,10 @@ function parseMetadata(entries) {
1996
1983
  case "civitai": {
1997
1984
  const comfyResult = parseComfyUI(entries);
1998
1985
  if (comfyResult.ok) {
1999
- comfyResult.value.software = "civitai";
2000
- return comfyResult;
1986
+ return Result.ok({
1987
+ ...comfyResult.value,
1988
+ software: "civitai"
1989
+ });
2001
1990
  }
2002
1991
  return parseA1111(entries, "civitai");
2003
1992
  }
@@ -2057,10 +2046,10 @@ function readJpegDimensions(data) {
2057
2046
  offset++;
2058
2047
  continue;
2059
2048
  }
2060
- const length = (data[offset + 2] ?? 0) << 8 | (data[offset + 3] ?? 0);
2049
+ const length = readUint16BE(data, offset + 2);
2061
2050
  if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
2062
- const height = (data[offset + 5] ?? 0) << 8 | (data[offset + 6] ?? 0);
2063
- const width = (data[offset + 7] ?? 0) << 8 | (data[offset + 8] ?? 0);
2051
+ const height = readUint16BE(data, offset + 5);
2052
+ const width = readUint16BE(data, offset + 7);
2064
2053
  return { width, height };
2065
2054
  }
2066
2055
  offset += 2 + length;
@@ -2082,12 +2071,12 @@ function readWebpDimensions(data) {
2082
2071
  }
2083
2072
  if (chunkType === "VP8 ") {
2084
2073
  const start = offset + 8;
2085
- const tag = (data[start] ?? 0) | (data[start + 1] ?? 0) << 8 | (data[start + 2] ?? 0) << 16;
2074
+ const tag = readUint24LE(data, start);
2086
2075
  const keyFrame = !(tag & 1);
2087
2076
  if (keyFrame) {
2088
2077
  if (data[start + 3] === 157 && data[start + 4] === 1 && data[start + 5] === 42) {
2089
- const wRaw = (data[start + 6] ?? 0) | (data[start + 7] ?? 0) << 8;
2090
- const hRaw = (data[start + 8] ?? 0) | (data[start + 9] ?? 0) << 8;
2078
+ const wRaw = readUint16(data, start + 6, true);
2079
+ const hRaw = readUint16(data, start + 8, true);
2091
2080
  return { width: wRaw & 16383, height: hRaw & 16383 };
2092
2081
  }
2093
2082
  }
@@ -2131,6 +2120,10 @@ function extractTagsFromIfd(data, ifdOffset, isLittleEndian) {
2131
2120
  const count = readUint32(data, offset + 4, isLittleEndian);
2132
2121
  const typeSize = getTypeSize(type);
2133
2122
  const dataSize = count * typeSize;
2123
+ if (dataSize > data.length) {
2124
+ offset += 12;
2125
+ continue;
2126
+ }
2134
2127
  let valueOffset;
2135
2128
  if (dataSize <= 4) {
2136
2129
  valueOffset = offset + 8;
@@ -2266,36 +2259,31 @@ function decodeUserComment(data) {
2266
2259
  }
2267
2260
  }
2268
2261
  function decodeUtf16BE(data) {
2269
- const chars = [];
2270
- for (let i = 0; i < data.length - 1; i += 2) {
2271
- const code = (data[i] ?? 0) << 8 | (data[i + 1] ?? 0);
2272
- if (code === 0) break;
2273
- chars.push(String.fromCharCode(code));
2274
- }
2275
- return chars.join("");
2262
+ const decoded = new TextDecoder("utf-16be").decode(data);
2263
+ const nullIndex = decoded.indexOf("\0");
2264
+ return nullIndex >= 0 ? decoded.slice(0, nullIndex) : decoded;
2276
2265
  }
2277
2266
  function decodeUtf16LE(data) {
2278
- const chars = [];
2279
- for (let i = 0; i < data.length - 1; i += 2) {
2280
- const code = (data[i] ?? 0) | (data[i + 1] ?? 0) << 8;
2281
- if (code === 0) break;
2282
- chars.push(String.fromCharCode(code));
2283
- }
2284
- return chars.join("");
2267
+ const decoded = new TextDecoder("utf-16le").decode(data);
2268
+ const nullIndex = decoded.indexOf("\0");
2269
+ return nullIndex >= 0 ? decoded.slice(0, nullIndex) : decoded;
2285
2270
  }
2286
2271
  function decodeAscii(data) {
2287
- const chars = [];
2288
- for (let i = 0; i < data.length; i++) {
2289
- if (data[i] === 0) break;
2290
- chars.push(String.fromCharCode(data[i] ?? 0));
2291
- }
2292
- return chars.join("");
2272
+ const nullIndex = data.indexOf(0);
2273
+ const sliced = nullIndex >= 0 ? data.subarray(0, nullIndex) : data;
2274
+ return new TextDecoder("ascii").decode(sliced);
2293
2275
  }
2294
2276
 
2295
2277
  // src/readers/jpeg.ts
2296
2278
  var APP1_MARKER2 = 225;
2297
2279
  var COM_MARKER2 = 254;
2298
- var EXIF_HEADER2 = new Uint8Array([69, 120, 105, 102, 0, 0]);
2280
+ function matchesExifHeader(data, offset) {
2281
+ return data[offset] === 69 && // E
2282
+ data[offset + 1] === 120 && // x
2283
+ data[offset + 2] === 105 && // i
2284
+ data[offset + 3] === 102 && // f
2285
+ data[offset + 4] === 0 && data[offset + 5] === 0;
2286
+ }
2299
2287
  function readJpegMetadata(data) {
2300
2288
  if (!isJpeg(data)) {
2301
2289
  return Result.error({ type: "invalidSignature" });
@@ -2335,12 +2323,11 @@ function findApp1Segment(data) {
2335
2323
  offset++;
2336
2324
  continue;
2337
2325
  }
2338
- const length = (data[offset + 2] ?? 0) << 8 | (data[offset + 3] ?? 0);
2339
- if (marker === APP1_MARKER2) {
2326
+ const length = readUint16BE(data, offset + 2);
2327
+ if (marker === APP1_MARKER2 && length >= 8) {
2340
2328
  const headerStart = offset + 4;
2341
2329
  if (headerStart + 6 <= data.length) {
2342
- const header = data.slice(headerStart, headerStart + 6);
2343
- if (arraysEqual(header, EXIF_HEADER2)) {
2330
+ if (matchesExifHeader(data, headerStart)) {
2344
2331
  return {
2345
2332
  offset: headerStart + 6,
2346
2333
  length: length - 8
@@ -2368,8 +2355,8 @@ function findComSegment(data) {
2368
2355
  offset++;
2369
2356
  continue;
2370
2357
  }
2371
- const length = (data[offset + 2] ?? 0) << 8 | (data[offset + 3] ?? 0);
2372
- if (marker === COM_MARKER2) {
2358
+ const length = readUint16BE(data, offset + 2);
2359
+ if (marker === COM_MARKER2 && length >= 2) {
2373
2360
  return {
2374
2361
  offset: offset + 4,
2375
2362
  length: length - 2
@@ -2469,7 +2456,7 @@ function tryUtf8Decode(data) {
2469
2456
  }
2470
2457
  function parseITXtChunk(data) {
2471
2458
  let offset = 0;
2472
- const keywordEnd = findNull(data, offset);
2459
+ const keywordEnd = data.indexOf(0, offset);
2473
2460
  if (keywordEnd === -1) return null;
2474
2461
  const keyword = utf8Decode(data.slice(offset, keywordEnd));
2475
2462
  offset = keywordEnd + 1;
@@ -2479,11 +2466,11 @@ function parseITXtChunk(data) {
2479
2466
  if (offset >= data.length) return null;
2480
2467
  const compressionMethod = data[offset] ?? 0;
2481
2468
  offset += 1;
2482
- const langEnd = findNull(data, offset);
2469
+ const langEnd = data.indexOf(0, offset);
2483
2470
  if (langEnd === -1) return null;
2484
2471
  const languageTag = utf8Decode(data.slice(offset, langEnd));
2485
2472
  offset = langEnd + 1;
2486
- const transEnd = findNull(data, offset);
2473
+ const transEnd = data.indexOf(0, offset);
2487
2474
  if (transEnd === -1) return null;
2488
2475
  const translatedKeyword = utf8Decode(data.slice(offset, transEnd));
2489
2476
  offset = transEnd + 1;
@@ -2505,20 +2492,8 @@ function parseITXtChunk(data) {
2505
2492
  text
2506
2493
  };
2507
2494
  }
2508
- function findNull(data, offset) {
2509
- for (let i = offset; i < data.length; i++) {
2510
- if (data[i] === 0) {
2511
- return i;
2512
- }
2513
- }
2514
- return -1;
2515
- }
2516
2495
  function latin1Decode(data) {
2517
- let result = "";
2518
- for (let i = 0; i < data.length; i++) {
2519
- result += String.fromCharCode(data[i] ?? 0);
2520
- }
2521
- return result;
2496
+ return new TextDecoder("iso-8859-1").decode(data);
2522
2497
  }
2523
2498
  function utf8Decode(data) {
2524
2499
  return new TextDecoder("utf-8").decode(data);
@@ -2528,7 +2503,6 @@ function decompressZlib(_data) {
2528
2503
  }
2529
2504
 
2530
2505
  // src/readers/webp.ts
2531
- var EXIF_CHUNK_TYPE2 = new Uint8Array([69, 88, 73, 70]);
2532
2506
  function readWebpMetadata(data) {
2533
2507
  if (!isWebp(data)) {
2534
2508
  return Result.error({ type: "invalidSignature" });
@@ -2546,10 +2520,9 @@ function readWebpMetadata(data) {
2546
2520
  }
2547
2521
  function findExifChunk(data) {
2548
2522
  let offset = 12;
2549
- while (offset < data.length - 8) {
2550
- const chunkType = data.slice(offset, offset + 4);
2523
+ while (offset + 8 <= data.length) {
2551
2524
  const chunkSize = readUint32LE(data, offset + 4);
2552
- if (arraysEqual(chunkType, EXIF_CHUNK_TYPE2)) {
2525
+ if (readChunkType(data, offset) === "EXIF") {
2553
2526
  return {
2554
2527
  offset: offset + 8,
2555
2528
  length: chunkSize
@@ -2830,12 +2803,11 @@ function convertNovelaiPngToSegments(chunks) {
2830
2803
  );
2831
2804
  }
2832
2805
  function buildUserCommentJson(chunks) {
2833
- return NOVELAI_KEY_ORDER.map((key) => {
2834
- const chunk = chunks.find((c) => c.keyword === key);
2835
- return chunk ? { [key]: chunk.text } : null;
2836
- }).filter((entry) => entry !== null).reduce(
2837
- (acc, entry) => Object.assign(acc, entry),
2838
- {}
2806
+ return Object.fromEntries(
2807
+ NOVELAI_KEY_ORDER.flatMap((key) => {
2808
+ const chunk = chunks.find((c) => c.keyword === key);
2809
+ return chunk ? [[key, chunk.text]] : [];
2810
+ })
2839
2811
  );
2840
2812
  }
2841
2813
  var NOVELAI_KEY_ORDER = [