@sweberdev/witness 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +21 -2
- package/dist/ai-sdk.cjs +921 -0
- package/dist/ai-sdk.cjs.map +1 -0
- package/dist/ai-sdk.d.cts +95 -0
- package/dist/ai-sdk.d.ts +95 -0
- package/dist/ai-sdk.js +150 -0
- package/dist/ai-sdk.js.map +1 -0
- package/dist/bin.cjs +944 -0
- package/dist/bin.cjs.map +1 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +12 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-APXU3MTX.js +958 -0
- package/dist/chunk-APXU3MTX.js.map +1 -0
- package/dist/chunk-DHX7SNWE.js +310 -0
- package/dist/chunk-DHX7SNWE.js.map +1 -0
- package/dist/chunk-L65R2SM5.js +228 -0
- package/dist/chunk-L65R2SM5.js.map +1 -0
- package/dist/chunk-UBRK4CTM.js +156 -0
- package/dist/chunk-UBRK4CTM.js.map +1 -0
- package/dist/chunk-XJOQ5IUT.js +110 -0
- package/dist/chunk-XJOQ5IUT.js.map +1 -0
- package/dist/cli.cjs +964 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +7 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +9 -0
- package/dist/cli.js.map +1 -0
- package/dist/disclosure-5a3UIVQZ.d.cts +70 -0
- package/dist/disclosure-C7Y_uY73.d.ts +70 -0
- package/dist/elements-auto.cjs +642 -0
- package/dist/elements-auto.cjs.map +1 -0
- package/dist/elements-auto.d.cts +3 -0
- package/dist/elements-auto.d.ts +3 -0
- package/dist/elements-auto.js +16 -0
- package/dist/elements-auto.js.map +1 -0
- package/dist/elements.cjs +639 -0
- package/dist/elements.cjs.map +1 -0
- package/dist/elements.d.cts +47 -0
- package/dist/elements.d.ts +47 -0
- package/dist/elements.js +13 -0
- package/dist/elements.js.map +1 -0
- package/dist/image-B7YvdITx.d.ts +49 -0
- package/dist/image-BNrEO-hj.d.cts +49 -0
- package/dist/index.cjs +1344 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +124 -0
- package/dist/index.d.ts +124 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/dist/types-WWHzrNlc.d.cts +90 -0
- package/dist/types-WWHzrNlc.d.ts +90 -0
- package/package.json +123 -3
- package/src/ai-sdk.ts +252 -0
- package/src/bin.ts +6 -0
- package/src/cli.ts +160 -0
- package/src/disclosure.ts +172 -0
- package/src/elements-auto.ts +5 -0
- package/src/elements.ts +356 -0
- package/src/i18n.ts +259 -0
- package/src/image.ts +552 -0
- package/src/index.ts +66 -0
- package/src/inflate.ts +189 -0
- package/src/marking.ts +237 -0
- package/src/types.ts +115 -0
- package/src/watermark.ts +165 -0
- package/styles.css +103 -0
package/dist/ai-sdk.cjs
ADDED
|
@@ -0,0 +1,921 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/ai-sdk.ts
|
|
21
|
+
var ai_sdk_exports = {};
|
|
22
|
+
__export(ai_sdk_exports, {
|
|
23
|
+
witnessImageMiddleware: () => witnessImageMiddleware,
|
|
24
|
+
witnessMiddleware: () => witnessMiddleware,
|
|
25
|
+
witnessProviderMetadata: () => witnessProviderMetadata
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(ai_sdk_exports);
|
|
28
|
+
|
|
29
|
+
// src/inflate.ts
|
|
30
|
+
var LENGTH_BASE = [
|
|
31
|
+
3,
|
|
32
|
+
4,
|
|
33
|
+
5,
|
|
34
|
+
6,
|
|
35
|
+
7,
|
|
36
|
+
8,
|
|
37
|
+
9,
|
|
38
|
+
10,
|
|
39
|
+
11,
|
|
40
|
+
13,
|
|
41
|
+
15,
|
|
42
|
+
17,
|
|
43
|
+
19,
|
|
44
|
+
23,
|
|
45
|
+
27,
|
|
46
|
+
31,
|
|
47
|
+
35,
|
|
48
|
+
43,
|
|
49
|
+
51,
|
|
50
|
+
59,
|
|
51
|
+
67,
|
|
52
|
+
83,
|
|
53
|
+
99,
|
|
54
|
+
115,
|
|
55
|
+
131,
|
|
56
|
+
163,
|
|
57
|
+
195,
|
|
58
|
+
227,
|
|
59
|
+
258
|
|
60
|
+
];
|
|
61
|
+
var LENGTH_EXTRA = [
|
|
62
|
+
0,
|
|
63
|
+
0,
|
|
64
|
+
0,
|
|
65
|
+
0,
|
|
66
|
+
0,
|
|
67
|
+
0,
|
|
68
|
+
0,
|
|
69
|
+
0,
|
|
70
|
+
1,
|
|
71
|
+
1,
|
|
72
|
+
1,
|
|
73
|
+
1,
|
|
74
|
+
2,
|
|
75
|
+
2,
|
|
76
|
+
2,
|
|
77
|
+
2,
|
|
78
|
+
3,
|
|
79
|
+
3,
|
|
80
|
+
3,
|
|
81
|
+
3,
|
|
82
|
+
4,
|
|
83
|
+
4,
|
|
84
|
+
4,
|
|
85
|
+
4,
|
|
86
|
+
5,
|
|
87
|
+
5,
|
|
88
|
+
5,
|
|
89
|
+
5,
|
|
90
|
+
0
|
|
91
|
+
];
|
|
92
|
+
var DIST_BASE = [
|
|
93
|
+
1,
|
|
94
|
+
2,
|
|
95
|
+
3,
|
|
96
|
+
4,
|
|
97
|
+
5,
|
|
98
|
+
7,
|
|
99
|
+
9,
|
|
100
|
+
13,
|
|
101
|
+
17,
|
|
102
|
+
25,
|
|
103
|
+
33,
|
|
104
|
+
49,
|
|
105
|
+
65,
|
|
106
|
+
97,
|
|
107
|
+
129,
|
|
108
|
+
193,
|
|
109
|
+
257,
|
|
110
|
+
385,
|
|
111
|
+
513,
|
|
112
|
+
769,
|
|
113
|
+
1025,
|
|
114
|
+
1537,
|
|
115
|
+
2049,
|
|
116
|
+
3073,
|
|
117
|
+
4097,
|
|
118
|
+
6145,
|
|
119
|
+
8193,
|
|
120
|
+
12289,
|
|
121
|
+
16385,
|
|
122
|
+
24577
|
|
123
|
+
];
|
|
124
|
+
var DIST_EXTRA = [
|
|
125
|
+
0,
|
|
126
|
+
0,
|
|
127
|
+
0,
|
|
128
|
+
0,
|
|
129
|
+
1,
|
|
130
|
+
1,
|
|
131
|
+
2,
|
|
132
|
+
2,
|
|
133
|
+
3,
|
|
134
|
+
3,
|
|
135
|
+
4,
|
|
136
|
+
4,
|
|
137
|
+
5,
|
|
138
|
+
5,
|
|
139
|
+
6,
|
|
140
|
+
6,
|
|
141
|
+
7,
|
|
142
|
+
7,
|
|
143
|
+
8,
|
|
144
|
+
8,
|
|
145
|
+
9,
|
|
146
|
+
9,
|
|
147
|
+
10,
|
|
148
|
+
10,
|
|
149
|
+
11,
|
|
150
|
+
11,
|
|
151
|
+
12,
|
|
152
|
+
12,
|
|
153
|
+
13,
|
|
154
|
+
13
|
|
155
|
+
];
|
|
156
|
+
var CODE_LENGTH_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
|
|
157
|
+
function buildTree(lengths, offset, count) {
|
|
158
|
+
const tree = { counts: new Uint16Array(16), symbols: new Uint16Array(count) };
|
|
159
|
+
for (let i = 0; i < count; i++) {
|
|
160
|
+
const length = lengths[offset + i] ?? 0;
|
|
161
|
+
tree.counts[length] = (tree.counts[length] ?? 0) + 1;
|
|
162
|
+
}
|
|
163
|
+
tree.counts[0] = 0;
|
|
164
|
+
const offsets = new Uint16Array(16);
|
|
165
|
+
let sum = 0;
|
|
166
|
+
for (let i = 0; i < 16; i++) {
|
|
167
|
+
offsets[i] = sum;
|
|
168
|
+
sum += tree.counts[i] ?? 0;
|
|
169
|
+
}
|
|
170
|
+
for (let i = 0; i < count; i++) {
|
|
171
|
+
const length = lengths[offset + i] ?? 0;
|
|
172
|
+
if (length) {
|
|
173
|
+
const at = offsets[length] ?? 0;
|
|
174
|
+
tree.symbols[at] = i;
|
|
175
|
+
offsets[length] = at + 1;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return tree;
|
|
179
|
+
}
|
|
180
|
+
var fixed = null;
|
|
181
|
+
function fixedTrees() {
|
|
182
|
+
if (!fixed) {
|
|
183
|
+
const lengths = new Uint8Array(288);
|
|
184
|
+
lengths.fill(8, 0, 144);
|
|
185
|
+
lengths.fill(9, 144, 256);
|
|
186
|
+
lengths.fill(7, 256, 280);
|
|
187
|
+
lengths.fill(8, 280, 288);
|
|
188
|
+
fixed = { lit: buildTree(lengths, 0, 288), dist: buildTree(new Uint8Array(30).fill(5), 0, 30) };
|
|
189
|
+
}
|
|
190
|
+
return fixed;
|
|
191
|
+
}
|
|
192
|
+
var Reader = class {
|
|
193
|
+
constructor(data) {
|
|
194
|
+
this.data = data;
|
|
195
|
+
this.pos = 0;
|
|
196
|
+
this.bit = 0;
|
|
197
|
+
this.bits = 0;
|
|
198
|
+
}
|
|
199
|
+
getBit() {
|
|
200
|
+
if (this.bits === 0) {
|
|
201
|
+
if (this.pos >= this.data.length) throw new Error("inflate: unexpected end of data");
|
|
202
|
+
this.bit = this.data[this.pos++] ?? 0;
|
|
203
|
+
this.bits = 8;
|
|
204
|
+
}
|
|
205
|
+
const value = this.bit & 1;
|
|
206
|
+
this.bit >>>= 1;
|
|
207
|
+
this.bits--;
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
read(count, base = 0) {
|
|
211
|
+
let value = 0;
|
|
212
|
+
for (let i = 0; i < count; i++) value |= this.getBit() << i;
|
|
213
|
+
return value + base;
|
|
214
|
+
}
|
|
215
|
+
decode(tree) {
|
|
216
|
+
let sum = 0;
|
|
217
|
+
let cur = 0;
|
|
218
|
+
let len = 0;
|
|
219
|
+
do {
|
|
220
|
+
cur = 2 * cur + this.getBit();
|
|
221
|
+
len++;
|
|
222
|
+
if (len > 15) throw new Error("inflate: invalid code");
|
|
223
|
+
sum += tree.counts[len] ?? 0;
|
|
224
|
+
cur -= tree.counts[len] ?? 0;
|
|
225
|
+
} while (cur >= 0);
|
|
226
|
+
return tree.symbols[sum + cur] ?? 0;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
function dynamicTrees(r) {
|
|
230
|
+
const hlit = r.read(5, 257);
|
|
231
|
+
const hdist = r.read(5, 1);
|
|
232
|
+
const hclen = r.read(4, 4);
|
|
233
|
+
const codeLengths = new Uint8Array(19);
|
|
234
|
+
for (let i = 0; i < hclen; i++) codeLengths[CODE_LENGTH_ORDER[i] ?? 0] = r.read(3);
|
|
235
|
+
const codeTree = buildTree(codeLengths, 0, 19);
|
|
236
|
+
const lengths = new Uint8Array(hlit + hdist);
|
|
237
|
+
for (let n = 0; n < hlit + hdist; ) {
|
|
238
|
+
const sym = r.decode(codeTree);
|
|
239
|
+
if (sym < 16) {
|
|
240
|
+
lengths[n++] = sym;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
let repeat;
|
|
244
|
+
let value = 0;
|
|
245
|
+
if (sym === 16) {
|
|
246
|
+
if (n === 0) throw new Error("inflate: invalid repeat");
|
|
247
|
+
value = lengths[n - 1] ?? 0;
|
|
248
|
+
repeat = r.read(2, 3);
|
|
249
|
+
} else if (sym === 17) {
|
|
250
|
+
repeat = r.read(3, 3);
|
|
251
|
+
} else {
|
|
252
|
+
repeat = r.read(7, 11);
|
|
253
|
+
}
|
|
254
|
+
while (repeat-- > 0) lengths[n++] = value;
|
|
255
|
+
}
|
|
256
|
+
return { lit: buildTree(lengths, 0, hlit), dist: buildTree(lengths, hlit, hdist) };
|
|
257
|
+
}
|
|
258
|
+
function inflateZlib(data, maxLength = 16 * 1024 * 1024) {
|
|
259
|
+
if (data.length < 2 || ((data[0] ?? 0) & 15) !== 8 || ((data[0] ?? 0) << 8 | (data[1] ?? 0)) % 31 !== 0) {
|
|
260
|
+
throw new Error("inflate: not a zlib stream");
|
|
261
|
+
}
|
|
262
|
+
const r = new Reader(data);
|
|
263
|
+
r.pos = 2;
|
|
264
|
+
let out = new Uint8Array(Math.max(1024, data.length * 4));
|
|
265
|
+
let length = 0;
|
|
266
|
+
const push = (byte) => {
|
|
267
|
+
if (length >= out.length) {
|
|
268
|
+
if (out.length >= maxLength) throw new Error("inflate: output too large");
|
|
269
|
+
const next = new Uint8Array(Math.min(maxLength, out.length * 2));
|
|
270
|
+
next.set(out);
|
|
271
|
+
out = next;
|
|
272
|
+
}
|
|
273
|
+
out[length++] = byte;
|
|
274
|
+
};
|
|
275
|
+
let final = 0;
|
|
276
|
+
do {
|
|
277
|
+
final = r.getBit();
|
|
278
|
+
const type = r.read(2);
|
|
279
|
+
if (type === 0) {
|
|
280
|
+
r.bits = 0;
|
|
281
|
+
if (r.pos + 4 > data.length) throw new Error("inflate: unexpected end of data");
|
|
282
|
+
const len = (data[r.pos] ?? 0) | (data[r.pos + 1] ?? 0) << 8;
|
|
283
|
+
r.pos += 4;
|
|
284
|
+
if (r.pos + len > data.length) throw new Error("inflate: unexpected end of data");
|
|
285
|
+
for (let i = 0; i < len; i++) push(data[r.pos++] ?? 0);
|
|
286
|
+
} else if (type === 1 || type === 2) {
|
|
287
|
+
const { lit, dist } = type === 1 ? fixedTrees() : dynamicTrees(r);
|
|
288
|
+
for (; ; ) {
|
|
289
|
+
const sym = r.decode(lit);
|
|
290
|
+
if (sym === 256) break;
|
|
291
|
+
if (sym < 256) {
|
|
292
|
+
push(sym);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const index = sym - 257;
|
|
296
|
+
const len = r.read(LENGTH_EXTRA[index] ?? 0, LENGTH_BASE[index] ?? 0);
|
|
297
|
+
const distSym = r.decode(dist);
|
|
298
|
+
const offset = r.read(DIST_EXTRA[distSym] ?? 0, DIST_BASE[distSym] ?? 0);
|
|
299
|
+
if (offset > length) throw new Error("inflate: invalid distance");
|
|
300
|
+
for (let i = 0; i < len; i++) push(out[length - offset] ?? 0);
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
throw new Error("inflate: invalid block type");
|
|
304
|
+
}
|
|
305
|
+
} while (!final);
|
|
306
|
+
return out.subarray(0, length);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/marking.ts
|
|
310
|
+
var IPTC_BASE = "http://cv.iptc.org/newscodes/digitalsourcetype/";
|
|
311
|
+
var DEFAULT_SOURCE = {
|
|
312
|
+
generated: "trainedAlgorithmicMedia",
|
|
313
|
+
edited: "compositeWithTrainedAlgorithmicMedia",
|
|
314
|
+
deepfake: "trainedAlgorithmicMedia"
|
|
315
|
+
};
|
|
316
|
+
function iptcSourceType(type) {
|
|
317
|
+
return IPTC_BASE + type;
|
|
318
|
+
}
|
|
319
|
+
function createMarking(input = {}) {
|
|
320
|
+
const kind = input.kind ?? "generated";
|
|
321
|
+
const createdAt = input.createdAt instanceof Date ? input.createdAt.toISOString() : input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
322
|
+
const marking = {
|
|
323
|
+
kind,
|
|
324
|
+
sourceType: input.sourceType ?? DEFAULT_SOURCE[kind],
|
|
325
|
+
createdAt,
|
|
326
|
+
humanReviewed: input.humanReviewed ?? false
|
|
327
|
+
};
|
|
328
|
+
if (input.generator) marking.generator = input.generator;
|
|
329
|
+
if (input.generatorVersion) marking.generatorVersion = input.generatorVersion;
|
|
330
|
+
if (input.provider) marking.provider = input.provider;
|
|
331
|
+
if (input.disclosureUrl) marking.disclosureUrl = input.disclosureUrl;
|
|
332
|
+
if (input.description) marking.description = input.description;
|
|
333
|
+
return marking;
|
|
334
|
+
}
|
|
335
|
+
function generatorName(m) {
|
|
336
|
+
if (!m.generator) return "";
|
|
337
|
+
return m.generatorVersion ? `${m.generator} ${m.generatorVersion}` : m.generator;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/image.ts
|
|
341
|
+
var XMP_NS = "http://ns.adobe.com/xap/1.0/\0";
|
|
342
|
+
var WITNESS_NS = "https://packages.sweber.dev/witness/ns/1.0/";
|
|
343
|
+
function detectImageFormat(bytes) {
|
|
344
|
+
if (bytes.length >= 8 && bytes[0] === 137 && ascii(bytes, 1, 3) === "PNG") return "png";
|
|
345
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
346
|
+
return "jpeg";
|
|
347
|
+
}
|
|
348
|
+
if (bytes.length >= 12 && ascii(bytes, 0, 4) === "RIFF" && ascii(bytes, 8, 4) === "WEBP") {
|
|
349
|
+
return "webp";
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
function markImage(bytes, input = {}, options = {}) {
|
|
354
|
+
const format = detectImageFormat(bytes);
|
|
355
|
+
if (!format) return { bytes, status: "unsupported", format };
|
|
356
|
+
if (options.c2pa !== "overwrite" && hasC2pa(bytes, format)) {
|
|
357
|
+
return { bytes, status: "skipped-c2pa", format };
|
|
358
|
+
}
|
|
359
|
+
const marking = "sourceType" in input && "humanReviewed" in input ? input : createMarking(input);
|
|
360
|
+
const existing = readXmp(bytes, format);
|
|
361
|
+
const xmp = buildXmp(marking, existing);
|
|
362
|
+
const out = format === "png" ? writePng(bytes, xmp) : format === "jpeg" ? writeJpeg(bytes, xmp) : writeWebp(bytes, xmp);
|
|
363
|
+
return { bytes: out, status: "marked", format };
|
|
364
|
+
}
|
|
365
|
+
function buildXmp(marking, existing) {
|
|
366
|
+
const description = buildDescription(marking);
|
|
367
|
+
if (existing?.includes("</rdf:RDF>")) {
|
|
368
|
+
const cleaned = existing.replace(/<rdf:Description\b[^>]*witness:marker="1"[^>]*\/>\s*/g, "").replace(
|
|
369
|
+
/<rdf:Description\b[^>]*witness:marker="1"[^>]*>[\s\S]*?<\/rdf:Description>\s*/g,
|
|
370
|
+
""
|
|
371
|
+
);
|
|
372
|
+
return cleaned.replace("</rdf:RDF>", `${description}
|
|
373
|
+
</rdf:RDF>`);
|
|
374
|
+
}
|
|
375
|
+
return [
|
|
376
|
+
'<?xpacket begin="\uFEFF" id="W5M0MpCehiHzreSzNTczkc9d"?>',
|
|
377
|
+
'<x:xmpmeta xmlns:x="adobe:ns:meta/">',
|
|
378
|
+
'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">',
|
|
379
|
+
description,
|
|
380
|
+
"</rdf:RDF>",
|
|
381
|
+
"</x:xmpmeta>",
|
|
382
|
+
'<?xpacket end="w"?>'
|
|
383
|
+
].join("\n");
|
|
384
|
+
}
|
|
385
|
+
function buildDescription(m) {
|
|
386
|
+
const attributes = [
|
|
387
|
+
["rdf:about", ""],
|
|
388
|
+
["xmlns:Iptc4xmpExt", "http://iptc.org/std/Iptc4xmpExt/2008-02-29/"],
|
|
389
|
+
["xmlns:xmp", "http://ns.adobe.com/xap/1.0/"],
|
|
390
|
+
["xmlns:dc", "http://purl.org/dc/elements/1.1/"],
|
|
391
|
+
["xmlns:witness", WITNESS_NS],
|
|
392
|
+
["witness:marker", "1"],
|
|
393
|
+
["witness:kind", m.kind],
|
|
394
|
+
["Iptc4xmpExt:DigitalSourceType", iptcSourceType(m.sourceType)],
|
|
395
|
+
["xmp:CreateDate", m.createdAt]
|
|
396
|
+
];
|
|
397
|
+
if (m.generator) {
|
|
398
|
+
attributes.push(["Iptc4xmpExt:AISystemUsed", m.generator]);
|
|
399
|
+
attributes.push(["xmp:CreatorTool", generatorName(m)]);
|
|
400
|
+
}
|
|
401
|
+
if (m.generatorVersion) attributes.push(["Iptc4xmpExt:AISystemVersionUsed", m.generatorVersion]);
|
|
402
|
+
if (m.provider) attributes.push(["witness:provider", m.provider]);
|
|
403
|
+
if (m.disclosureUrl) attributes.push(["witness:disclosureUrl", m.disclosureUrl]);
|
|
404
|
+
if (m.humanReviewed) attributes.push(["witness:humanReviewed", "True"]);
|
|
405
|
+
const attrs = attributes.map(([name, value]) => `
|
|
406
|
+
${name}="${escapeXml(value)}"`).join("");
|
|
407
|
+
const description = m.description ? `
|
|
408
|
+
<dc:description><rdf:Alt><rdf:li xml:lang="x-default">${escapeXml(m.description)}</rdf:li></rdf:Alt></dc:description>
|
|
409
|
+
` : "";
|
|
410
|
+
return description ? `<rdf:Description${attrs}>${description}</rdf:Description>` : `<rdf:Description${attrs}/>`;
|
|
411
|
+
}
|
|
412
|
+
function writePng(bytes, xmp) {
|
|
413
|
+
const chunks = pngChunks(bytes).filter((chunk) => !isPngXmp(bytes, chunk));
|
|
414
|
+
const itxt = pngChunk(
|
|
415
|
+
"iTXt",
|
|
416
|
+
concat(latin1("XML:com.adobe.xmp"), new Uint8Array([0, 0, 0, 0, 0]), utf8(xmp))
|
|
417
|
+
);
|
|
418
|
+
const parts = [bytes.subarray(0, 8)];
|
|
419
|
+
for (const chunk of chunks) {
|
|
420
|
+
parts.push(bytes.subarray(chunk.offset, chunk.end));
|
|
421
|
+
if (chunk.type === "IHDR") parts.push(itxt);
|
|
422
|
+
}
|
|
423
|
+
return concat(...parts);
|
|
424
|
+
}
|
|
425
|
+
function pngChunks(bytes) {
|
|
426
|
+
const chunks = [];
|
|
427
|
+
let offset = 8;
|
|
428
|
+
while (offset + 12 <= bytes.length) {
|
|
429
|
+
const length = readU32BE(bytes, offset);
|
|
430
|
+
const type = ascii(bytes, offset + 4, 4);
|
|
431
|
+
const dataStart = offset + 8;
|
|
432
|
+
const dataEnd = dataStart + length;
|
|
433
|
+
const end = dataEnd + 4;
|
|
434
|
+
if (end > bytes.length) throw new Error("Witness: truncated PNG chunk");
|
|
435
|
+
chunks.push({ type, offset, dataStart, dataEnd, end });
|
|
436
|
+
offset = end;
|
|
437
|
+
if (type === "IEND") break;
|
|
438
|
+
}
|
|
439
|
+
return chunks;
|
|
440
|
+
}
|
|
441
|
+
function isPngXmp(bytes, chunk) {
|
|
442
|
+
return (chunk.type === "iTXt" || chunk.type === "zTXt") && ascii(bytes, chunk.dataStart, 18) === "XML:com.adobe.xmp\0";
|
|
443
|
+
}
|
|
444
|
+
function readPngXmp(bytes) {
|
|
445
|
+
for (const chunk of pngChunks(bytes)) {
|
|
446
|
+
if (!isPngXmp(bytes, chunk)) continue;
|
|
447
|
+
let p = chunk.dataStart + 18;
|
|
448
|
+
let compressed;
|
|
449
|
+
if (chunk.type === "zTXt") {
|
|
450
|
+
compressed = true;
|
|
451
|
+
p += 1;
|
|
452
|
+
} else {
|
|
453
|
+
compressed = bytes[p] === 1;
|
|
454
|
+
p += 2;
|
|
455
|
+
while (p < chunk.dataEnd && bytes[p] !== 0) p++;
|
|
456
|
+
p++;
|
|
457
|
+
while (p < chunk.dataEnd && bytes[p] !== 0) p++;
|
|
458
|
+
p++;
|
|
459
|
+
}
|
|
460
|
+
const data = bytes.subarray(p, chunk.dataEnd);
|
|
461
|
+
try {
|
|
462
|
+
return new TextDecoder().decode(compressed ? inflateZlib(data) : data);
|
|
463
|
+
} catch {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
function pngChunk(type, data) {
|
|
470
|
+
const out = new Uint8Array(12 + data.length);
|
|
471
|
+
writeU32BE(out, 0, data.length);
|
|
472
|
+
out.set(latin1(type), 4);
|
|
473
|
+
out.set(data, 8);
|
|
474
|
+
writeU32BE(out, 8 + data.length, crc32(out.subarray(4, 8 + data.length)));
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
477
|
+
function jpegSegments(bytes) {
|
|
478
|
+
const segments = [];
|
|
479
|
+
let offset = 2;
|
|
480
|
+
while (offset + 4 <= bytes.length) {
|
|
481
|
+
if (bytes[offset] !== 255) throw new Error("Witness: invalid JPEG marker");
|
|
482
|
+
let markerOffset = offset;
|
|
483
|
+
while (bytes[markerOffset + 1] === 255) markerOffset++;
|
|
484
|
+
const marker = bytes[markerOffset + 1] ?? 0;
|
|
485
|
+
if (marker === 218 || marker === 217) return { segments, scanStart: offset };
|
|
486
|
+
const length = readU16BE(bytes, markerOffset + 2);
|
|
487
|
+
const end = markerOffset + 2 + length;
|
|
488
|
+
if (end > bytes.length) throw new Error("Witness: truncated JPEG segment");
|
|
489
|
+
segments.push({ marker, offset, dataStart: markerOffset + 4, end });
|
|
490
|
+
offset = end;
|
|
491
|
+
}
|
|
492
|
+
return { segments, scanStart: offset };
|
|
493
|
+
}
|
|
494
|
+
function isJpegXmp(bytes, segment) {
|
|
495
|
+
return segment.marker === 225 && ascii(bytes, segment.dataStart, XMP_NS.length) === XMP_NS;
|
|
496
|
+
}
|
|
497
|
+
function writeJpeg(bytes, xmp) {
|
|
498
|
+
const { segments, scanStart } = jpegSegments(bytes);
|
|
499
|
+
const payload = concat(latin1(XMP_NS), utf8(xmp));
|
|
500
|
+
if (payload.length + 2 > 65535) throw new Error("Witness: XMP packet too large for JPEG");
|
|
501
|
+
const app1 = new Uint8Array(4 + payload.length);
|
|
502
|
+
app1[0] = 255;
|
|
503
|
+
app1[1] = 225;
|
|
504
|
+
app1[2] = payload.length + 2 >> 8 & 255;
|
|
505
|
+
app1[3] = payload.length + 2 & 255;
|
|
506
|
+
app1.set(payload, 4);
|
|
507
|
+
const kept = segments.filter((segment) => !isJpegXmp(bytes, segment));
|
|
508
|
+
let insertAt = 0;
|
|
509
|
+
while (insertAt < kept.length) {
|
|
510
|
+
const segment = kept[insertAt];
|
|
511
|
+
if (!segment) break;
|
|
512
|
+
const isJfif = segment.marker === 224;
|
|
513
|
+
const isExif = segment.marker === 225 && ascii(bytes, segment.dataStart, 4) === "Exif";
|
|
514
|
+
if (!isJfif && !isExif) break;
|
|
515
|
+
insertAt++;
|
|
516
|
+
}
|
|
517
|
+
const parts = [bytes.subarray(0, 2)];
|
|
518
|
+
kept.forEach((segment, index) => {
|
|
519
|
+
if (index === insertAt) parts.push(app1);
|
|
520
|
+
parts.push(bytes.subarray(segment.offset, segment.end));
|
|
521
|
+
});
|
|
522
|
+
if (insertAt >= kept.length) parts.push(app1);
|
|
523
|
+
parts.push(bytes.subarray(scanStart));
|
|
524
|
+
return concat(...parts);
|
|
525
|
+
}
|
|
526
|
+
function readJpegXmp(bytes) {
|
|
527
|
+
for (const segment of jpegSegments(bytes).segments) {
|
|
528
|
+
if (isJpegXmp(bytes, segment)) {
|
|
529
|
+
return new TextDecoder().decode(
|
|
530
|
+
bytes.subarray(segment.dataStart + XMP_NS.length, segment.end)
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
function webpChunks(bytes) {
|
|
537
|
+
const chunks = [];
|
|
538
|
+
const riffEnd = Math.min(bytes.length, 8 + readU32LE(bytes, 4));
|
|
539
|
+
let offset = 12;
|
|
540
|
+
while (offset + 8 <= riffEnd) {
|
|
541
|
+
const fourcc = ascii(bytes, offset, 4);
|
|
542
|
+
const size = readU32LE(bytes, offset + 4);
|
|
543
|
+
const dataStart = offset + 8;
|
|
544
|
+
const end = dataStart + size + size % 2;
|
|
545
|
+
if (dataStart + size > bytes.length) throw new Error("Witness: truncated WebP chunk");
|
|
546
|
+
chunks.push({ fourcc, offset, dataStart, size, end: Math.min(end, bytes.length) });
|
|
547
|
+
offset = end;
|
|
548
|
+
}
|
|
549
|
+
return chunks;
|
|
550
|
+
}
|
|
551
|
+
function writeWebp(bytes, xmp) {
|
|
552
|
+
const chunks = webpChunks(bytes).filter((chunk) => chunk.fourcc !== "XMP ");
|
|
553
|
+
const first = chunks[0];
|
|
554
|
+
if (!first) throw new Error("Witness: empty WebP file");
|
|
555
|
+
const parts = [];
|
|
556
|
+
if (first.fourcc === "VP8X") {
|
|
557
|
+
const vp8x = bytes.slice(first.offset, first.end);
|
|
558
|
+
vp8x[8] = (vp8x[8] ?? 0) | 4;
|
|
559
|
+
parts.push(vp8x);
|
|
560
|
+
for (const chunk of chunks.slice(1)) parts.push(bytes.subarray(chunk.offset, chunk.end));
|
|
561
|
+
} else {
|
|
562
|
+
const { width, height, alpha } = webpCanvas(bytes, first);
|
|
563
|
+
const data = new Uint8Array(10);
|
|
564
|
+
data[0] = 4 | (alpha ? 16 : 0);
|
|
565
|
+
writeU24LE(data, 4, width - 1);
|
|
566
|
+
writeU24LE(data, 7, height - 1);
|
|
567
|
+
parts.push(riffChunk("VP8X", data));
|
|
568
|
+
for (const chunk of chunks) parts.push(bytes.subarray(chunk.offset, chunk.end));
|
|
569
|
+
}
|
|
570
|
+
parts.push(riffChunk("XMP ", utf8(xmp)));
|
|
571
|
+
const body = concat(...parts);
|
|
572
|
+
const out = new Uint8Array(12 + body.length);
|
|
573
|
+
out.set(latin1("RIFF"), 0);
|
|
574
|
+
writeU32LE(out, 4, 4 + body.length);
|
|
575
|
+
out.set(latin1("WEBP"), 8);
|
|
576
|
+
out.set(body, 12);
|
|
577
|
+
return out;
|
|
578
|
+
}
|
|
579
|
+
function webpCanvas(bytes, chunk) {
|
|
580
|
+
const d = chunk.dataStart;
|
|
581
|
+
if (chunk.fourcc === "VP8 ") {
|
|
582
|
+
if (bytes[d + 3] !== 157 || bytes[d + 4] !== 1 || bytes[d + 5] !== 42) {
|
|
583
|
+
throw new Error("Witness: invalid VP8 frame");
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
width: readU16LE(bytes, d + 6) & 16383,
|
|
587
|
+
height: readU16LE(bytes, d + 8) & 16383,
|
|
588
|
+
alpha: false
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
if (chunk.fourcc === "VP8L") {
|
|
592
|
+
if (bytes[d] !== 47) throw new Error("Witness: invalid VP8L frame");
|
|
593
|
+
const bits = readU32LE(bytes, d + 1);
|
|
594
|
+
return {
|
|
595
|
+
width: (bits & 16383) + 1,
|
|
596
|
+
height: (bits >>> 14 & 16383) + 1,
|
|
597
|
+
alpha: (bits >>> 28 & 1) === 1
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
throw new Error(`Witness: unexpected WebP chunk ${chunk.fourcc}`);
|
|
601
|
+
}
|
|
602
|
+
function readWebpXmp(bytes) {
|
|
603
|
+
const chunk = webpChunks(bytes).find((c) => c.fourcc === "XMP ");
|
|
604
|
+
return chunk ? new TextDecoder().decode(bytes.subarray(chunk.dataStart, chunk.dataStart + chunk.size)) : null;
|
|
605
|
+
}
|
|
606
|
+
function riffChunk(fourcc, data) {
|
|
607
|
+
const out = new Uint8Array(8 + data.length + data.length % 2);
|
|
608
|
+
out.set(latin1(fourcc), 0);
|
|
609
|
+
writeU32LE(out, 4, data.length);
|
|
610
|
+
out.set(data, 8);
|
|
611
|
+
return out;
|
|
612
|
+
}
|
|
613
|
+
function readXmp(bytes, format) {
|
|
614
|
+
if (format === "png") return readPngXmp(bytes);
|
|
615
|
+
if (format === "jpeg") return readJpegXmp(bytes);
|
|
616
|
+
return readWebpXmp(bytes);
|
|
617
|
+
}
|
|
618
|
+
function hasC2pa(bytes, format) {
|
|
619
|
+
if (format === "png") return pngChunks(bytes).some((chunk) => chunk.type === "caBX");
|
|
620
|
+
if (format === "webp") return webpChunks(bytes).some((chunk) => chunk.fourcc === "C2PA");
|
|
621
|
+
return jpegSegments(bytes).segments.some(
|
|
622
|
+
(segment) => segment.marker === 235 && ascii(bytes, segment.dataStart, Math.min(64, segment.end - segment.dataStart)).includes(
|
|
623
|
+
"c2pa"
|
|
624
|
+
)
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
function ascii(bytes, offset, length) {
|
|
628
|
+
let out = "";
|
|
629
|
+
for (let i = offset; i < offset + length && i < bytes.length; i++) {
|
|
630
|
+
out += String.fromCharCode(bytes[i] ?? 0);
|
|
631
|
+
}
|
|
632
|
+
return out;
|
|
633
|
+
}
|
|
634
|
+
function latin1(text) {
|
|
635
|
+
const out = new Uint8Array(text.length);
|
|
636
|
+
for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
|
|
637
|
+
return out;
|
|
638
|
+
}
|
|
639
|
+
function utf8(text) {
|
|
640
|
+
return new TextEncoder().encode(text);
|
|
641
|
+
}
|
|
642
|
+
function concat(...parts) {
|
|
643
|
+
const out = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0));
|
|
644
|
+
let offset = 0;
|
|
645
|
+
for (const part of parts) {
|
|
646
|
+
out.set(part, offset);
|
|
647
|
+
offset += part.length;
|
|
648
|
+
}
|
|
649
|
+
return out;
|
|
650
|
+
}
|
|
651
|
+
function readU16BE(b, o) {
|
|
652
|
+
return (b[o] ?? 0) << 8 | (b[o + 1] ?? 0);
|
|
653
|
+
}
|
|
654
|
+
function readU16LE(b, o) {
|
|
655
|
+
return (b[o] ?? 0) | (b[o + 1] ?? 0) << 8;
|
|
656
|
+
}
|
|
657
|
+
function readU32BE(b, o) {
|
|
658
|
+
return ((b[o] ?? 0) << 24 | (b[o + 1] ?? 0) << 16 | (b[o + 2] ?? 0) << 8 | (b[o + 3] ?? 0)) >>> 0;
|
|
659
|
+
}
|
|
660
|
+
function readU32LE(b, o) {
|
|
661
|
+
return ((b[o] ?? 0) | (b[o + 1] ?? 0) << 8 | (b[o + 2] ?? 0) << 16 | (b[o + 3] ?? 0) << 24) >>> 0;
|
|
662
|
+
}
|
|
663
|
+
function writeU32BE(b, o, v) {
|
|
664
|
+
b[o] = v >>> 24 & 255;
|
|
665
|
+
b[o + 1] = v >>> 16 & 255;
|
|
666
|
+
b[o + 2] = v >>> 8 & 255;
|
|
667
|
+
b[o + 3] = v & 255;
|
|
668
|
+
}
|
|
669
|
+
function writeU32LE(b, o, v) {
|
|
670
|
+
b[o] = v & 255;
|
|
671
|
+
b[o + 1] = v >>> 8 & 255;
|
|
672
|
+
b[o + 2] = v >>> 16 & 255;
|
|
673
|
+
b[o + 3] = v >>> 24 & 255;
|
|
674
|
+
}
|
|
675
|
+
function writeU24LE(b, o, v) {
|
|
676
|
+
b[o] = v & 255;
|
|
677
|
+
b[o + 1] = v >>> 8 & 255;
|
|
678
|
+
b[o + 2] = v >>> 16 & 255;
|
|
679
|
+
}
|
|
680
|
+
var crcTable = null;
|
|
681
|
+
function crc32(data) {
|
|
682
|
+
if (!crcTable) {
|
|
683
|
+
crcTable = new Uint32Array(256);
|
|
684
|
+
for (let n = 0; n < 256; n++) {
|
|
685
|
+
let c = n;
|
|
686
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
687
|
+
crcTable[n] = c >>> 0;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
let crc = 4294967295;
|
|
691
|
+
for (const byte of data) crc = (crcTable[(crc ^ byte) & 255] ?? 0) ^ crc >>> 8;
|
|
692
|
+
return (crc ^ 4294967295) >>> 0;
|
|
693
|
+
}
|
|
694
|
+
function escapeXml(value) {
|
|
695
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/watermark.ts
|
|
699
|
+
var MAGIC = [87, 49];
|
|
700
|
+
var MAX_PAYLOAD = 255;
|
|
701
|
+
function watermarkText(text, mark = {}) {
|
|
702
|
+
if (text.length === 0) return text;
|
|
703
|
+
return stripTextWatermark(text) + watermarkSuffix(mark);
|
|
704
|
+
}
|
|
705
|
+
function watermarkSuffix(mark = {}) {
|
|
706
|
+
const payload = encodePayload(mark);
|
|
707
|
+
return [...MAGIC, payload.length, ...payload].map(byteToSelector).join("");
|
|
708
|
+
}
|
|
709
|
+
function stripTextWatermark(text) {
|
|
710
|
+
const chars = [...text];
|
|
711
|
+
const remove = /* @__PURE__ */ new Set();
|
|
712
|
+
for (const run of selectorRuns(text)) {
|
|
713
|
+
let offset = 0;
|
|
714
|
+
while (offset < run.bytes.length) {
|
|
715
|
+
const found = findInRun(run.bytes.slice(offset));
|
|
716
|
+
if (!found) break;
|
|
717
|
+
const start = offset + found.start;
|
|
718
|
+
const end = offset + found.end;
|
|
719
|
+
for (let i = start; i < end; i++) remove.add(run.start + i);
|
|
720
|
+
offset = end;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (remove.size === 0) return text;
|
|
724
|
+
return chars.filter((_, index) => !remove.has(index)).join("");
|
|
725
|
+
}
|
|
726
|
+
function encodePayload(mark) {
|
|
727
|
+
const fields = [];
|
|
728
|
+
if (mark.generator) fields.push(`g=${encodeURIComponent(mark.generator)}`);
|
|
729
|
+
if (mark.createdAt) fields.push(`t=${encodeURIComponent(mark.createdAt)}`);
|
|
730
|
+
if (mark.id) fields.push(`i=${encodeURIComponent(mark.id)}`);
|
|
731
|
+
let bytes = [...new TextEncoder().encode(fields.join(";"))];
|
|
732
|
+
if (bytes.length > MAX_PAYLOAD) {
|
|
733
|
+
while (fields.length > 0 && bytes.length > MAX_PAYLOAD) {
|
|
734
|
+
fields.pop();
|
|
735
|
+
bytes = [...new TextEncoder().encode(fields.join(";"))];
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return bytes;
|
|
739
|
+
}
|
|
740
|
+
function byteToSelector(byte) {
|
|
741
|
+
return byte < 16 ? String.fromCodePoint(65024 + byte) : String.fromCodePoint(917760 + (byte - 16));
|
|
742
|
+
}
|
|
743
|
+
function selectorToByte(codePoint) {
|
|
744
|
+
if (codePoint >= 65024 && codePoint <= 65039) return codePoint - 65024;
|
|
745
|
+
if (codePoint >= 917760 && codePoint <= 917999) return codePoint - 917760 + 16;
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
function selectorRuns(text) {
|
|
749
|
+
const runs = [];
|
|
750
|
+
let current = null;
|
|
751
|
+
let index = 0;
|
|
752
|
+
for (const char of text) {
|
|
753
|
+
const byte = selectorToByte(char.codePointAt(0) ?? 0);
|
|
754
|
+
if (byte === null) {
|
|
755
|
+
current = null;
|
|
756
|
+
} else {
|
|
757
|
+
if (!current) {
|
|
758
|
+
current = { start: index, bytes: [] };
|
|
759
|
+
runs.push(current);
|
|
760
|
+
}
|
|
761
|
+
current.bytes.push(byte);
|
|
762
|
+
}
|
|
763
|
+
index++;
|
|
764
|
+
}
|
|
765
|
+
return runs;
|
|
766
|
+
}
|
|
767
|
+
function findInRun(bytes) {
|
|
768
|
+
for (let i = 0; i + MAGIC.length < bytes.length; i++) {
|
|
769
|
+
if (bytes[i] !== MAGIC[0] || bytes[i + 1] !== MAGIC[1]) continue;
|
|
770
|
+
const length = bytes[i + 2];
|
|
771
|
+
if (length === void 0) continue;
|
|
772
|
+
const start = i + 3;
|
|
773
|
+
const end = start + length;
|
|
774
|
+
if (end > bytes.length) continue;
|
|
775
|
+
return { start: i, end, payload: bytes.slice(start, end) };
|
|
776
|
+
}
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/ai-sdk.ts
|
|
781
|
+
function witnessProviderMetadata(marking) {
|
|
782
|
+
return {
|
|
783
|
+
aiGenerated: true,
|
|
784
|
+
kind: marking.kind,
|
|
785
|
+
sourceType: iptcSourceType(marking.sourceType),
|
|
786
|
+
createdAt: marking.createdAt,
|
|
787
|
+
...marking.generator ? { generator: marking.generator } : {},
|
|
788
|
+
...marking.provider ? { provider: marking.provider } : {}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function witnessMiddleware(options = {}) {
|
|
792
|
+
const watermark = options.watermark ?? true;
|
|
793
|
+
const markingFor = (model) => createMarking({
|
|
794
|
+
kind: options.kind ?? "generated",
|
|
795
|
+
generator: generatorFor(options.generator, model),
|
|
796
|
+
...options.provider ? { provider: options.provider } : {}
|
|
797
|
+
});
|
|
798
|
+
const watermarkFor = (marking) => ({
|
|
799
|
+
...marking.generator ? { generator: marking.generator } : {},
|
|
800
|
+
createdAt: marking.createdAt,
|
|
801
|
+
...options.id ? { id: options.id() } : {}
|
|
802
|
+
});
|
|
803
|
+
return {
|
|
804
|
+
specificationVersion: options.specificationVersion ?? "v4",
|
|
805
|
+
async wrapGenerate({
|
|
806
|
+
doGenerate,
|
|
807
|
+
model
|
|
808
|
+
}) {
|
|
809
|
+
const result = await doGenerate();
|
|
810
|
+
const marking = markingFor(model);
|
|
811
|
+
const content = result.content.map((part) => {
|
|
812
|
+
if (part.type !== "text" || typeof part.text !== "string" || part.text.length === 0) {
|
|
813
|
+
return part;
|
|
814
|
+
}
|
|
815
|
+
options.onMarked?.({ type: "text", marking });
|
|
816
|
+
return watermark ? { ...part, text: watermarkText(part.text, watermarkFor(marking)) } : part;
|
|
817
|
+
});
|
|
818
|
+
return {
|
|
819
|
+
...result,
|
|
820
|
+
content,
|
|
821
|
+
providerMetadata: { ...result.providerMetadata, witness: witnessProviderMetadata(marking) }
|
|
822
|
+
};
|
|
823
|
+
},
|
|
824
|
+
async wrapStream({
|
|
825
|
+
doStream,
|
|
826
|
+
model
|
|
827
|
+
}) {
|
|
828
|
+
const result = await doStream();
|
|
829
|
+
const marking = markingFor(model);
|
|
830
|
+
const withText = /* @__PURE__ */ new Set();
|
|
831
|
+
const transform = new TransformStream({
|
|
832
|
+
transform(part, controller) {
|
|
833
|
+
const id = part.id ?? "";
|
|
834
|
+
if (part.type === "text-delta" && typeof part.delta === "string" && part.delta.length > 0) {
|
|
835
|
+
withText.add(id);
|
|
836
|
+
}
|
|
837
|
+
if (part.type === "text-end" && withText.has(id)) {
|
|
838
|
+
withText.delete(id);
|
|
839
|
+
options.onMarked?.({ type: "text", marking, partId: id });
|
|
840
|
+
if (watermark) {
|
|
841
|
+
controller.enqueue({
|
|
842
|
+
type: "text-delta",
|
|
843
|
+
id,
|
|
844
|
+
delta: watermarkSuffix(watermarkFor(marking))
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (part.type === "finish") {
|
|
849
|
+
controller.enqueue({
|
|
850
|
+
...part,
|
|
851
|
+
providerMetadata: {
|
|
852
|
+
...part.providerMetadata,
|
|
853
|
+
witness: witnessProviderMetadata(marking)
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
controller.enqueue(part);
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
return { ...result, stream: result.stream.pipeThrough(transform) };
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
function witnessImageMiddleware(options = {}) {
|
|
866
|
+
return {
|
|
867
|
+
specificationVersion: options.specificationVersion ?? "v4",
|
|
868
|
+
async wrapGenerate({
|
|
869
|
+
doGenerate,
|
|
870
|
+
model
|
|
871
|
+
}) {
|
|
872
|
+
const result = await doGenerate();
|
|
873
|
+
const marking = createMarking({
|
|
874
|
+
kind: options.kind ?? "generated",
|
|
875
|
+
generator: generatorFor(options.generator, model),
|
|
876
|
+
...options.provider ? { provider: options.provider } : {}
|
|
877
|
+
});
|
|
878
|
+
const warnings = [...result.warnings];
|
|
879
|
+
const markOne = (bytes) => {
|
|
880
|
+
const marked = markImage(bytes, marking, options.c2pa ? { c2pa: options.c2pa } : {});
|
|
881
|
+
options.onMarked?.({ type: "image", marking, status: marked.status });
|
|
882
|
+
if (marked.status !== "marked") {
|
|
883
|
+
warnings.push({
|
|
884
|
+
type: "other",
|
|
885
|
+
message: marked.status === "skipped-c2pa" ? "Witness: image already carries a C2PA manifest and was left unchanged." : "Witness: image format not supported (PNG, JPEG, WebP); image is not marked."
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
return marked.bytes;
|
|
889
|
+
};
|
|
890
|
+
const images = result.images.map(
|
|
891
|
+
(image) => typeof image === "string" ? toBase64(markOne(fromBase64(image))) : markOne(image)
|
|
892
|
+
);
|
|
893
|
+
return { ...result, images, warnings };
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
function generatorFor(option, model) {
|
|
898
|
+
if (typeof option === "function") return option(model);
|
|
899
|
+
return option ?? `${model.provider}/${model.modelId}`;
|
|
900
|
+
}
|
|
901
|
+
function fromBase64(value) {
|
|
902
|
+
const binary = atob(value);
|
|
903
|
+
const bytes = new Uint8Array(binary.length);
|
|
904
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
905
|
+
return bytes;
|
|
906
|
+
}
|
|
907
|
+
function toBase64(bytes) {
|
|
908
|
+
let binary = "";
|
|
909
|
+
const step = 32768;
|
|
910
|
+
for (let i = 0; i < bytes.length; i += step) {
|
|
911
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + step));
|
|
912
|
+
}
|
|
913
|
+
return btoa(binary);
|
|
914
|
+
}
|
|
915
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
916
|
+
0 && (module.exports = {
|
|
917
|
+
witnessImageMiddleware,
|
|
918
|
+
witnessMiddleware,
|
|
919
|
+
witnessProviderMetadata
|
|
920
|
+
});
|
|
921
|
+
//# sourceMappingURL=ai-sdk.cjs.map
|