@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/CHANGELOG.md +403 -0
- package/README.ja.md +3 -21
- package/README.md +3 -21
- package/dist/index.global.js +7 -7
- package/dist/index.js +163 -191
- package/dist/index.js.map +1 -1
- package/docs/types.ja.md +796 -0
- package/docs/types.md +796 -0
- package/package.json +9 -11
- package/LICENSE +0 -21
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
|
|
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
|
-
|
|
283
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
651
|
-
const
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2000
|
-
|
|
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
|
|
2049
|
+
const length = readUint16BE(data, offset + 2);
|
|
2061
2050
|
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
2062
|
-
const height = (data
|
|
2063
|
-
const width = (data
|
|
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
|
|
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
|
|
2090
|
-
const hRaw = (data
|
|
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
|
|
2270
|
-
|
|
2271
|
-
|
|
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
|
|
2279
|
-
|
|
2280
|
-
|
|
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
|
|
2288
|
-
|
|
2289
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
2550
|
-
const chunkType = data.slice(offset, offset + 4);
|
|
2523
|
+
while (offset + 8 <= data.length) {
|
|
2551
2524
|
const chunkSize = readUint32LE(data, offset + 4);
|
|
2552
|
-
if (
|
|
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
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
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 = [
|