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