@sweberdev/witness 0.0.0-stage → 0.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/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-L65R2SM5.js +228 -0
- package/dist/chunk-L65R2SM5.js.map +1 -0
- package/dist/chunk-O6TADCND.js +317 -0
- package/dist/chunk-O6TADCND.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 +649 -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 +646 -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 +363 -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/index.cjs
ADDED
|
@@ -0,0 +1,1344 @@
|
|
|
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/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BUILT_IN_LOCALES: () => BUILT_IN_LOCALES,
|
|
24
|
+
DISCLOSURE_KINDS: () => DISCLOSURE_KINDS,
|
|
25
|
+
buildXmp: () => buildXmp,
|
|
26
|
+
createDisclosure: () => createDisclosure,
|
|
27
|
+
createMarking: () => createMarking,
|
|
28
|
+
detectImageFormat: () => detectImageFormat,
|
|
29
|
+
disclosureText: () => disclosureText,
|
|
30
|
+
escapeHtml: () => escapeHtml,
|
|
31
|
+
format: () => format,
|
|
32
|
+
generatorName: () => generatorName,
|
|
33
|
+
getMessages: () => getMessages,
|
|
34
|
+
hasTextWatermark: () => hasTextWatermark,
|
|
35
|
+
iptcSourceType: () => iptcSourceType,
|
|
36
|
+
isAiSourceType: () => isAiSourceType,
|
|
37
|
+
labelHtml: () => labelHtml,
|
|
38
|
+
labelText: () => labelText,
|
|
39
|
+
markImage: () => markImage,
|
|
40
|
+
markingAttributes: () => markingAttributes,
|
|
41
|
+
markingJsonLd: () => markingJsonLd,
|
|
42
|
+
markingMetaTags: () => markingMetaTags,
|
|
43
|
+
memoryStorage: () => memoryStorage,
|
|
44
|
+
nextMetadata: () => nextMetadata,
|
|
45
|
+
parseSourceType: () => parseSourceType,
|
|
46
|
+
readImageMarking: () => readImageMarking,
|
|
47
|
+
readTextWatermark: () => readTextWatermark,
|
|
48
|
+
registerLocale: () => registerLocale,
|
|
49
|
+
registeredLocales: () => registeredLocales,
|
|
50
|
+
renderJsonLd: () => renderJsonLd,
|
|
51
|
+
renderMetaTags: () => renderMetaTags,
|
|
52
|
+
resolveLocale: () => resolveLocale,
|
|
53
|
+
schemaOrgSourceType: () => schemaOrgSourceType,
|
|
54
|
+
stripTextWatermark: () => stripTextWatermark,
|
|
55
|
+
watermarkSuffix: () => watermarkSuffix,
|
|
56
|
+
watermarkText: () => watermarkText
|
|
57
|
+
});
|
|
58
|
+
module.exports = __toCommonJS(index_exports);
|
|
59
|
+
|
|
60
|
+
// src/disclosure.ts
|
|
61
|
+
var PREFIX = "witness:ack:";
|
|
62
|
+
function createDisclosure(options = {}) {
|
|
63
|
+
const kind = options.kind ?? "chatbot";
|
|
64
|
+
const id = options.id ?? kind;
|
|
65
|
+
const version = options.version ?? "1";
|
|
66
|
+
const locale = options.locale ?? "en";
|
|
67
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
68
|
+
const storage = resolveStorage(options.storage);
|
|
69
|
+
const key = `${PREFIX}${id}`;
|
|
70
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
71
|
+
let shown = false;
|
|
72
|
+
const read = () => {
|
|
73
|
+
try {
|
|
74
|
+
const raw = storage.getItem(key);
|
|
75
|
+
if (!raw) return null;
|
|
76
|
+
const parsed = JSON.parse(raw);
|
|
77
|
+
if (typeof parsed.version !== "string" || typeof parsed.at !== "string") return null;
|
|
78
|
+
return { version: parsed.version, at: parsed.at };
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const emit = (type, at) => {
|
|
84
|
+
options.onEvent?.({ type, id, kind, version, locale, at });
|
|
85
|
+
};
|
|
86
|
+
const notify = () => {
|
|
87
|
+
for (const listener of listeners) listener();
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
id,
|
|
91
|
+
kind,
|
|
92
|
+
version,
|
|
93
|
+
needsAcknowledgement: () => read()?.version !== version,
|
|
94
|
+
acknowledgedAt: () => {
|
|
95
|
+
const entry = read();
|
|
96
|
+
return entry?.version === version ? entry.at : null;
|
|
97
|
+
},
|
|
98
|
+
markShown() {
|
|
99
|
+
if (shown) return;
|
|
100
|
+
shown = true;
|
|
101
|
+
emit("shown", now().toISOString());
|
|
102
|
+
},
|
|
103
|
+
acknowledge() {
|
|
104
|
+
const at = now().toISOString();
|
|
105
|
+
try {
|
|
106
|
+
storage.setItem(key, JSON.stringify({ version, at }));
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
emit("acknowledged", at);
|
|
110
|
+
notify();
|
|
111
|
+
},
|
|
112
|
+
reset() {
|
|
113
|
+
try {
|
|
114
|
+
storage.removeItem(key);
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
notify();
|
|
118
|
+
},
|
|
119
|
+
subscribe(listener) {
|
|
120
|
+
listeners.add(listener);
|
|
121
|
+
return () => listeners.delete(listener);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function resolveStorage(option) {
|
|
126
|
+
if (option && typeof option === "object") return option;
|
|
127
|
+
if (option !== "memory") {
|
|
128
|
+
try {
|
|
129
|
+
const candidate = option === "session" ? globalThis.sessionStorage : globalThis.localStorage;
|
|
130
|
+
if (candidate) {
|
|
131
|
+
const probe = `${PREFIX}probe`;
|
|
132
|
+
candidate.setItem(probe, "1");
|
|
133
|
+
candidate.removeItem(probe);
|
|
134
|
+
return candidate;
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return memoryStorage();
|
|
140
|
+
}
|
|
141
|
+
function memoryStorage() {
|
|
142
|
+
const map = /* @__PURE__ */ new Map();
|
|
143
|
+
return {
|
|
144
|
+
getItem: (key) => map.get(key) ?? null,
|
|
145
|
+
setItem: (key, value) => {
|
|
146
|
+
map.set(key, value);
|
|
147
|
+
},
|
|
148
|
+
removeItem: (key) => {
|
|
149
|
+
map.delete(key);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/i18n.ts
|
|
155
|
+
var en = {
|
|
156
|
+
kinds: {
|
|
157
|
+
chatbot: {
|
|
158
|
+
label: "AI assistant",
|
|
159
|
+
title: "You are chatting with an AI",
|
|
160
|
+
body: "This assistant is an AI system, not a person. Its answers are generated automatically and can be wrong."
|
|
161
|
+
},
|
|
162
|
+
generated: {
|
|
163
|
+
label: "AI-generated",
|
|
164
|
+
title: "AI-generated content",
|
|
165
|
+
body: "This content was generated by an AI system."
|
|
166
|
+
},
|
|
167
|
+
edited: {
|
|
168
|
+
label: "AI-edited",
|
|
169
|
+
title: "Edited with AI",
|
|
170
|
+
body: "This content was edited or altered with an AI system."
|
|
171
|
+
},
|
|
172
|
+
deepfake: {
|
|
173
|
+
label: "Artificially generated",
|
|
174
|
+
title: "Artificially generated or manipulated",
|
|
175
|
+
body: "This content was generated or manipulated with AI. It may show people, places or events differently from how they really were."
|
|
176
|
+
},
|
|
177
|
+
"emotion-recognition": {
|
|
178
|
+
label: "Emotion recognition",
|
|
179
|
+
title: "Emotion recognition is active",
|
|
180
|
+
body: "This service uses an AI system that recognises emotions or intentions from your data."
|
|
181
|
+
},
|
|
182
|
+
"biometric-categorisation": {
|
|
183
|
+
label: "Biometric categorisation",
|
|
184
|
+
title: "Biometric categorisation is active",
|
|
185
|
+
body: "This service uses an AI system that assigns people to categories based on biometric data."
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
ui: {
|
|
189
|
+
acknowledge: "Got it",
|
|
190
|
+
details: "About this label",
|
|
191
|
+
generatedWith: "Generated with {generator}",
|
|
192
|
+
createdOn: "Created on {date}",
|
|
193
|
+
humanReviewed: "Reviewed by a person",
|
|
194
|
+
moreInfo: "How we use AI",
|
|
195
|
+
close: "Close"
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
var de = {
|
|
199
|
+
kinds: {
|
|
200
|
+
chatbot: {
|
|
201
|
+
label: "KI-Assistent",
|
|
202
|
+
title: "Sie chatten mit einer KI",
|
|
203
|
+
body: "Dieser Assistent ist ein KI-System und kein Mensch. Seine Antworten werden automatisch erzeugt und k\xF6nnen falsch sein."
|
|
204
|
+
},
|
|
205
|
+
generated: {
|
|
206
|
+
label: "KI-generiert",
|
|
207
|
+
title: "KI-generierter Inhalt",
|
|
208
|
+
body: "Dieser Inhalt wurde von einem KI-System erzeugt."
|
|
209
|
+
},
|
|
210
|
+
edited: {
|
|
211
|
+
label: "Mit KI bearbeitet",
|
|
212
|
+
title: "Mit KI bearbeitet",
|
|
213
|
+
body: "Dieser Inhalt wurde mit einem KI-System bearbeitet oder ver\xE4ndert."
|
|
214
|
+
},
|
|
215
|
+
deepfake: {
|
|
216
|
+
label: "K\xFCnstlich erzeugt",
|
|
217
|
+
title: "K\xFCnstlich erzeugt oder ver\xE4ndert",
|
|
218
|
+
body: "Dieser Inhalt wurde mit KI erzeugt oder ver\xE4ndert. Er kann Personen, Orte oder Ereignisse anders zeigen, als sie wirklich waren."
|
|
219
|
+
},
|
|
220
|
+
"emotion-recognition": {
|
|
221
|
+
label: "Emotionserkennung",
|
|
222
|
+
title: "Emotionserkennung ist aktiv",
|
|
223
|
+
body: "Dieser Dienst setzt ein KI-System ein, das aus Ihren Daten Emotionen oder Absichten erkennt."
|
|
224
|
+
},
|
|
225
|
+
"biometric-categorisation": {
|
|
226
|
+
label: "Biometrische Kategorisierung",
|
|
227
|
+
title: "Biometrische Kategorisierung ist aktiv",
|
|
228
|
+
body: "Dieser Dienst setzt ein KI-System ein, das Personen anhand biometrischer Daten Kategorien zuordnet."
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
ui: {
|
|
232
|
+
acknowledge: "Verstanden",
|
|
233
|
+
details: "\xDCber diese Kennzeichnung",
|
|
234
|
+
generatedWith: "Erzeugt mit {generator}",
|
|
235
|
+
createdOn: "Erstellt am {date}",
|
|
236
|
+
humanReviewed: "Von einem Menschen gepr\xFCft",
|
|
237
|
+
moreInfo: "Wie wir KI einsetzen",
|
|
238
|
+
close: "Schliessen"
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
var fr = {
|
|
242
|
+
kinds: {
|
|
243
|
+
chatbot: {
|
|
244
|
+
label: "Assistant IA",
|
|
245
|
+
title: "Vous discutez avec une IA",
|
|
246
|
+
body: "Cet assistant est un syst\xE8me d\u2019IA, pas une personne. Ses r\xE9ponses sont g\xE9n\xE9r\xE9es automatiquement et peuvent \xEAtre erron\xE9es."
|
|
247
|
+
},
|
|
248
|
+
generated: {
|
|
249
|
+
label: "G\xE9n\xE9r\xE9 par IA",
|
|
250
|
+
title: "Contenu g\xE9n\xE9r\xE9 par IA",
|
|
251
|
+
body: "Ce contenu a \xE9t\xE9 g\xE9n\xE9r\xE9 par un syst\xE8me d\u2019IA."
|
|
252
|
+
},
|
|
253
|
+
edited: {
|
|
254
|
+
label: "Modifi\xE9 par IA",
|
|
255
|
+
title: "Modifi\xE9 avec l\u2019IA",
|
|
256
|
+
body: "Ce contenu a \xE9t\xE9 modifi\xE9 ou alt\xE9r\xE9 \xE0 l\u2019aide d\u2019un syst\xE8me d\u2019IA."
|
|
257
|
+
},
|
|
258
|
+
deepfake: {
|
|
259
|
+
label: "G\xE9n\xE9r\xE9 artificiellement",
|
|
260
|
+
title: "G\xE9n\xE9r\xE9 ou manipul\xE9 artificiellement",
|
|
261
|
+
body: "Ce contenu a \xE9t\xE9 g\xE9n\xE9r\xE9 ou manipul\xE9 avec l\u2019IA. Il peut montrer des personnes, des lieux ou des \xE9v\xE9nements autrement qu\u2019ils n\u2019ont r\xE9ellement \xE9t\xE9."
|
|
262
|
+
},
|
|
263
|
+
"emotion-recognition": {
|
|
264
|
+
label: "Reconnaissance des \xE9motions",
|
|
265
|
+
title: "La reconnaissance des \xE9motions est active",
|
|
266
|
+
body: "Ce service utilise un syst\xE8me d\u2019IA qui reconna\xEEt des \xE9motions ou des intentions \xE0 partir de vos donn\xE9es."
|
|
267
|
+
},
|
|
268
|
+
"biometric-categorisation": {
|
|
269
|
+
label: "Cat\xE9gorisation biom\xE9trique",
|
|
270
|
+
title: "La cat\xE9gorisation biom\xE9trique est active",
|
|
271
|
+
body: "Ce service utilise un syst\xE8me d\u2019IA qui classe les personnes dans des cat\xE9gories \xE0 partir de donn\xE9es biom\xE9triques."
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
ui: {
|
|
275
|
+
acknowledge: "Compris",
|
|
276
|
+
details: "\xC0 propos de ce label",
|
|
277
|
+
generatedWith: "G\xE9n\xE9r\xE9 avec {generator}",
|
|
278
|
+
createdOn: "Cr\xE9\xE9 le {date}",
|
|
279
|
+
humanReviewed: "V\xE9rifi\xE9 par une personne",
|
|
280
|
+
moreInfo: "Comment nous utilisons l\u2019IA",
|
|
281
|
+
close: "Fermer"
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
var it = {
|
|
285
|
+
kinds: {
|
|
286
|
+
chatbot: {
|
|
287
|
+
label: "Assistente IA",
|
|
288
|
+
title: "Stai parlando con un\u2019IA",
|
|
289
|
+
body: "Questo assistente \xE8 un sistema di IA, non una persona. Le sue risposte sono generate automaticamente e possono essere sbagliate."
|
|
290
|
+
},
|
|
291
|
+
generated: {
|
|
292
|
+
label: "Generato con IA",
|
|
293
|
+
title: "Contenuto generato con IA",
|
|
294
|
+
body: "Questo contenuto \xE8 stato generato da un sistema di IA."
|
|
295
|
+
},
|
|
296
|
+
edited: {
|
|
297
|
+
label: "Modificato con IA",
|
|
298
|
+
title: "Modificato con l\u2019IA",
|
|
299
|
+
body: "Questo contenuto \xE8 stato modificato o alterato con un sistema di IA."
|
|
300
|
+
},
|
|
301
|
+
deepfake: {
|
|
302
|
+
label: "Generato artificialmente",
|
|
303
|
+
title: "Generato o manipolato artificialmente",
|
|
304
|
+
body: "Questo contenuto \xE8 stato generato o manipolato con l\u2019IA. Pu\xF2 mostrare persone, luoghi o eventi in modo diverso da come sono stati realmente."
|
|
305
|
+
},
|
|
306
|
+
"emotion-recognition": {
|
|
307
|
+
label: "Riconoscimento delle emozioni",
|
|
308
|
+
title: "Il riconoscimento delle emozioni \xE8 attivo",
|
|
309
|
+
body: "Questo servizio utilizza un sistema di IA che riconosce emozioni o intenzioni a partire dai tuoi dati."
|
|
310
|
+
},
|
|
311
|
+
"biometric-categorisation": {
|
|
312
|
+
label: "Categorizzazione biometrica",
|
|
313
|
+
title: "La categorizzazione biometrica \xE8 attiva",
|
|
314
|
+
body: "Questo servizio utilizza un sistema di IA che assegna le persone a categorie in base a dati biometrici."
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
ui: {
|
|
318
|
+
acknowledge: "Ho capito",
|
|
319
|
+
details: "Informazioni su questa etichetta",
|
|
320
|
+
generatedWith: "Generato con {generator}",
|
|
321
|
+
createdOn: "Creato il {date}",
|
|
322
|
+
humanReviewed: "Verificato da una persona",
|
|
323
|
+
moreInfo: "Come usiamo l\u2019IA",
|
|
324
|
+
close: "Chiudi"
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
var registry = /* @__PURE__ */ new Map([
|
|
328
|
+
["en", en],
|
|
329
|
+
["de", de],
|
|
330
|
+
["fr", fr],
|
|
331
|
+
["it", it]
|
|
332
|
+
]);
|
|
333
|
+
var BUILT_IN_LOCALES = ["en", "de", "fr", "it"];
|
|
334
|
+
function registerLocale(code, messages) {
|
|
335
|
+
const key = code.toLowerCase();
|
|
336
|
+
const base = registry.get(key) ?? registry.get(key.split("-")[0] ?? "") ?? en;
|
|
337
|
+
registry.set(key, mergeMessages(base, messages));
|
|
338
|
+
}
|
|
339
|
+
function registeredLocales() {
|
|
340
|
+
return [...registry.keys()];
|
|
341
|
+
}
|
|
342
|
+
function resolveLocale(input) {
|
|
343
|
+
const tags = input == null ? [] : typeof input === "string" ? [input] : input;
|
|
344
|
+
for (const tag of tags) {
|
|
345
|
+
const lower = tag.toLowerCase();
|
|
346
|
+
if (registry.has(lower)) return lower;
|
|
347
|
+
const language = lower.split("-")[0] ?? "";
|
|
348
|
+
if (registry.has(language)) return language;
|
|
349
|
+
}
|
|
350
|
+
return "en";
|
|
351
|
+
}
|
|
352
|
+
function getMessages(locale, override) {
|
|
353
|
+
const messages = registry.get(resolveLocale(locale)) ?? en;
|
|
354
|
+
return override ? mergeMessages(messages, override) : messages;
|
|
355
|
+
}
|
|
356
|
+
function disclosureText(kind, locale, override) {
|
|
357
|
+
return getMessages(locale, override).kinds[kind];
|
|
358
|
+
}
|
|
359
|
+
function format(template, values) {
|
|
360
|
+
return template.replace(/\{(\w+)\}/g, (match, name) => values[name] ?? match);
|
|
361
|
+
}
|
|
362
|
+
function mergeMessages(base, override) {
|
|
363
|
+
const kinds = { ...base.kinds };
|
|
364
|
+
for (const [kind, text] of Object.entries(override.kinds ?? {})) {
|
|
365
|
+
const key = kind;
|
|
366
|
+
kinds[key] = { ...base.kinds[key], ...text };
|
|
367
|
+
}
|
|
368
|
+
const ui = { ...base.ui, ...override.ui ?? {} };
|
|
369
|
+
return { kinds, ui };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// src/inflate.ts
|
|
373
|
+
var LENGTH_BASE = [
|
|
374
|
+
3,
|
|
375
|
+
4,
|
|
376
|
+
5,
|
|
377
|
+
6,
|
|
378
|
+
7,
|
|
379
|
+
8,
|
|
380
|
+
9,
|
|
381
|
+
10,
|
|
382
|
+
11,
|
|
383
|
+
13,
|
|
384
|
+
15,
|
|
385
|
+
17,
|
|
386
|
+
19,
|
|
387
|
+
23,
|
|
388
|
+
27,
|
|
389
|
+
31,
|
|
390
|
+
35,
|
|
391
|
+
43,
|
|
392
|
+
51,
|
|
393
|
+
59,
|
|
394
|
+
67,
|
|
395
|
+
83,
|
|
396
|
+
99,
|
|
397
|
+
115,
|
|
398
|
+
131,
|
|
399
|
+
163,
|
|
400
|
+
195,
|
|
401
|
+
227,
|
|
402
|
+
258
|
|
403
|
+
];
|
|
404
|
+
var LENGTH_EXTRA = [
|
|
405
|
+
0,
|
|
406
|
+
0,
|
|
407
|
+
0,
|
|
408
|
+
0,
|
|
409
|
+
0,
|
|
410
|
+
0,
|
|
411
|
+
0,
|
|
412
|
+
0,
|
|
413
|
+
1,
|
|
414
|
+
1,
|
|
415
|
+
1,
|
|
416
|
+
1,
|
|
417
|
+
2,
|
|
418
|
+
2,
|
|
419
|
+
2,
|
|
420
|
+
2,
|
|
421
|
+
3,
|
|
422
|
+
3,
|
|
423
|
+
3,
|
|
424
|
+
3,
|
|
425
|
+
4,
|
|
426
|
+
4,
|
|
427
|
+
4,
|
|
428
|
+
4,
|
|
429
|
+
5,
|
|
430
|
+
5,
|
|
431
|
+
5,
|
|
432
|
+
5,
|
|
433
|
+
0
|
|
434
|
+
];
|
|
435
|
+
var DIST_BASE = [
|
|
436
|
+
1,
|
|
437
|
+
2,
|
|
438
|
+
3,
|
|
439
|
+
4,
|
|
440
|
+
5,
|
|
441
|
+
7,
|
|
442
|
+
9,
|
|
443
|
+
13,
|
|
444
|
+
17,
|
|
445
|
+
25,
|
|
446
|
+
33,
|
|
447
|
+
49,
|
|
448
|
+
65,
|
|
449
|
+
97,
|
|
450
|
+
129,
|
|
451
|
+
193,
|
|
452
|
+
257,
|
|
453
|
+
385,
|
|
454
|
+
513,
|
|
455
|
+
769,
|
|
456
|
+
1025,
|
|
457
|
+
1537,
|
|
458
|
+
2049,
|
|
459
|
+
3073,
|
|
460
|
+
4097,
|
|
461
|
+
6145,
|
|
462
|
+
8193,
|
|
463
|
+
12289,
|
|
464
|
+
16385,
|
|
465
|
+
24577
|
|
466
|
+
];
|
|
467
|
+
var DIST_EXTRA = [
|
|
468
|
+
0,
|
|
469
|
+
0,
|
|
470
|
+
0,
|
|
471
|
+
0,
|
|
472
|
+
1,
|
|
473
|
+
1,
|
|
474
|
+
2,
|
|
475
|
+
2,
|
|
476
|
+
3,
|
|
477
|
+
3,
|
|
478
|
+
4,
|
|
479
|
+
4,
|
|
480
|
+
5,
|
|
481
|
+
5,
|
|
482
|
+
6,
|
|
483
|
+
6,
|
|
484
|
+
7,
|
|
485
|
+
7,
|
|
486
|
+
8,
|
|
487
|
+
8,
|
|
488
|
+
9,
|
|
489
|
+
9,
|
|
490
|
+
10,
|
|
491
|
+
10,
|
|
492
|
+
11,
|
|
493
|
+
11,
|
|
494
|
+
12,
|
|
495
|
+
12,
|
|
496
|
+
13,
|
|
497
|
+
13
|
|
498
|
+
];
|
|
499
|
+
var CODE_LENGTH_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
|
|
500
|
+
function buildTree(lengths, offset, count) {
|
|
501
|
+
const tree = { counts: new Uint16Array(16), symbols: new Uint16Array(count) };
|
|
502
|
+
for (let i = 0; i < count; i++) {
|
|
503
|
+
const length = lengths[offset + i] ?? 0;
|
|
504
|
+
tree.counts[length] = (tree.counts[length] ?? 0) + 1;
|
|
505
|
+
}
|
|
506
|
+
tree.counts[0] = 0;
|
|
507
|
+
const offsets = new Uint16Array(16);
|
|
508
|
+
let sum = 0;
|
|
509
|
+
for (let i = 0; i < 16; i++) {
|
|
510
|
+
offsets[i] = sum;
|
|
511
|
+
sum += tree.counts[i] ?? 0;
|
|
512
|
+
}
|
|
513
|
+
for (let i = 0; i < count; i++) {
|
|
514
|
+
const length = lengths[offset + i] ?? 0;
|
|
515
|
+
if (length) {
|
|
516
|
+
const at = offsets[length] ?? 0;
|
|
517
|
+
tree.symbols[at] = i;
|
|
518
|
+
offsets[length] = at + 1;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return tree;
|
|
522
|
+
}
|
|
523
|
+
var fixed = null;
|
|
524
|
+
function fixedTrees() {
|
|
525
|
+
if (!fixed) {
|
|
526
|
+
const lengths = new Uint8Array(288);
|
|
527
|
+
lengths.fill(8, 0, 144);
|
|
528
|
+
lengths.fill(9, 144, 256);
|
|
529
|
+
lengths.fill(7, 256, 280);
|
|
530
|
+
lengths.fill(8, 280, 288);
|
|
531
|
+
fixed = { lit: buildTree(lengths, 0, 288), dist: buildTree(new Uint8Array(30).fill(5), 0, 30) };
|
|
532
|
+
}
|
|
533
|
+
return fixed;
|
|
534
|
+
}
|
|
535
|
+
var Reader = class {
|
|
536
|
+
constructor(data) {
|
|
537
|
+
this.data = data;
|
|
538
|
+
this.pos = 0;
|
|
539
|
+
this.bit = 0;
|
|
540
|
+
this.bits = 0;
|
|
541
|
+
}
|
|
542
|
+
getBit() {
|
|
543
|
+
if (this.bits === 0) {
|
|
544
|
+
if (this.pos >= this.data.length) throw new Error("inflate: unexpected end of data");
|
|
545
|
+
this.bit = this.data[this.pos++] ?? 0;
|
|
546
|
+
this.bits = 8;
|
|
547
|
+
}
|
|
548
|
+
const value = this.bit & 1;
|
|
549
|
+
this.bit >>>= 1;
|
|
550
|
+
this.bits--;
|
|
551
|
+
return value;
|
|
552
|
+
}
|
|
553
|
+
read(count, base = 0) {
|
|
554
|
+
let value = 0;
|
|
555
|
+
for (let i = 0; i < count; i++) value |= this.getBit() << i;
|
|
556
|
+
return value + base;
|
|
557
|
+
}
|
|
558
|
+
decode(tree) {
|
|
559
|
+
let sum = 0;
|
|
560
|
+
let cur = 0;
|
|
561
|
+
let len = 0;
|
|
562
|
+
do {
|
|
563
|
+
cur = 2 * cur + this.getBit();
|
|
564
|
+
len++;
|
|
565
|
+
if (len > 15) throw new Error("inflate: invalid code");
|
|
566
|
+
sum += tree.counts[len] ?? 0;
|
|
567
|
+
cur -= tree.counts[len] ?? 0;
|
|
568
|
+
} while (cur >= 0);
|
|
569
|
+
return tree.symbols[sum + cur] ?? 0;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
function dynamicTrees(r) {
|
|
573
|
+
const hlit = r.read(5, 257);
|
|
574
|
+
const hdist = r.read(5, 1);
|
|
575
|
+
const hclen = r.read(4, 4);
|
|
576
|
+
const codeLengths = new Uint8Array(19);
|
|
577
|
+
for (let i = 0; i < hclen; i++) codeLengths[CODE_LENGTH_ORDER[i] ?? 0] = r.read(3);
|
|
578
|
+
const codeTree = buildTree(codeLengths, 0, 19);
|
|
579
|
+
const lengths = new Uint8Array(hlit + hdist);
|
|
580
|
+
for (let n = 0; n < hlit + hdist; ) {
|
|
581
|
+
const sym = r.decode(codeTree);
|
|
582
|
+
if (sym < 16) {
|
|
583
|
+
lengths[n++] = sym;
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
let repeat;
|
|
587
|
+
let value = 0;
|
|
588
|
+
if (sym === 16) {
|
|
589
|
+
if (n === 0) throw new Error("inflate: invalid repeat");
|
|
590
|
+
value = lengths[n - 1] ?? 0;
|
|
591
|
+
repeat = r.read(2, 3);
|
|
592
|
+
} else if (sym === 17) {
|
|
593
|
+
repeat = r.read(3, 3);
|
|
594
|
+
} else {
|
|
595
|
+
repeat = r.read(7, 11);
|
|
596
|
+
}
|
|
597
|
+
while (repeat-- > 0) lengths[n++] = value;
|
|
598
|
+
}
|
|
599
|
+
return { lit: buildTree(lengths, 0, hlit), dist: buildTree(lengths, hlit, hdist) };
|
|
600
|
+
}
|
|
601
|
+
function inflateZlib(data, maxLength = 16 * 1024 * 1024) {
|
|
602
|
+
if (data.length < 2 || ((data[0] ?? 0) & 15) !== 8 || ((data[0] ?? 0) << 8 | (data[1] ?? 0)) % 31 !== 0) {
|
|
603
|
+
throw new Error("inflate: not a zlib stream");
|
|
604
|
+
}
|
|
605
|
+
const r = new Reader(data);
|
|
606
|
+
r.pos = 2;
|
|
607
|
+
let out = new Uint8Array(Math.max(1024, data.length * 4));
|
|
608
|
+
let length = 0;
|
|
609
|
+
const push = (byte) => {
|
|
610
|
+
if (length >= out.length) {
|
|
611
|
+
if (out.length >= maxLength) throw new Error("inflate: output too large");
|
|
612
|
+
const next = new Uint8Array(Math.min(maxLength, out.length * 2));
|
|
613
|
+
next.set(out);
|
|
614
|
+
out = next;
|
|
615
|
+
}
|
|
616
|
+
out[length++] = byte;
|
|
617
|
+
};
|
|
618
|
+
let final = 0;
|
|
619
|
+
do {
|
|
620
|
+
final = r.getBit();
|
|
621
|
+
const type = r.read(2);
|
|
622
|
+
if (type === 0) {
|
|
623
|
+
r.bits = 0;
|
|
624
|
+
if (r.pos + 4 > data.length) throw new Error("inflate: unexpected end of data");
|
|
625
|
+
const len = (data[r.pos] ?? 0) | (data[r.pos + 1] ?? 0) << 8;
|
|
626
|
+
r.pos += 4;
|
|
627
|
+
if (r.pos + len > data.length) throw new Error("inflate: unexpected end of data");
|
|
628
|
+
for (let i = 0; i < len; i++) push(data[r.pos++] ?? 0);
|
|
629
|
+
} else if (type === 1 || type === 2) {
|
|
630
|
+
const { lit, dist } = type === 1 ? fixedTrees() : dynamicTrees(r);
|
|
631
|
+
for (; ; ) {
|
|
632
|
+
const sym = r.decode(lit);
|
|
633
|
+
if (sym === 256) break;
|
|
634
|
+
if (sym < 256) {
|
|
635
|
+
push(sym);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
const index = sym - 257;
|
|
639
|
+
const len = r.read(LENGTH_EXTRA[index] ?? 0, LENGTH_BASE[index] ?? 0);
|
|
640
|
+
const distSym = r.decode(dist);
|
|
641
|
+
const offset = r.read(DIST_EXTRA[distSym] ?? 0, DIST_BASE[distSym] ?? 0);
|
|
642
|
+
if (offset > length) throw new Error("inflate: invalid distance");
|
|
643
|
+
for (let i = 0; i < len; i++) push(out[length - offset] ?? 0);
|
|
644
|
+
}
|
|
645
|
+
} else {
|
|
646
|
+
throw new Error("inflate: invalid block type");
|
|
647
|
+
}
|
|
648
|
+
} while (!final);
|
|
649
|
+
return out.subarray(0, length);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/marking.ts
|
|
653
|
+
var IPTC_BASE = "http://cv.iptc.org/newscodes/digitalsourcetype/";
|
|
654
|
+
var SCHEMA_ORG = {
|
|
655
|
+
trainedAlgorithmicMedia: "https://schema.org/TrainedAlgorithmicMediaDigitalSource",
|
|
656
|
+
compositeWithTrainedAlgorithmicMedia: "https://schema.org/CompositeWithTrainedAlgorithmicMediaDigitalSource",
|
|
657
|
+
algorithmicallyEnhanced: "https://schema.org/AlgorithmicallyEnhancedDigitalSource",
|
|
658
|
+
algorithmicMedia: "https://schema.org/AlgorithmicMediaDigitalSource",
|
|
659
|
+
compositeSynthetic: "https://schema.org/CompositeSyntheticDigitalSource"
|
|
660
|
+
};
|
|
661
|
+
var DEFAULT_SOURCE = {
|
|
662
|
+
generated: "trainedAlgorithmicMedia",
|
|
663
|
+
edited: "compositeWithTrainedAlgorithmicMedia",
|
|
664
|
+
deepfake: "trainedAlgorithmicMedia"
|
|
665
|
+
};
|
|
666
|
+
function iptcSourceType(type) {
|
|
667
|
+
return IPTC_BASE + type;
|
|
668
|
+
}
|
|
669
|
+
function schemaOrgSourceType(type) {
|
|
670
|
+
return SCHEMA_ORG[type];
|
|
671
|
+
}
|
|
672
|
+
function parseSourceType(value) {
|
|
673
|
+
if (!value) return void 0;
|
|
674
|
+
const trimmed = value.trim();
|
|
675
|
+
const name = trimmed.startsWith(IPTC_BASE) ? trimmed.slice(IPTC_BASE.length) : trimmed;
|
|
676
|
+
if (name in SCHEMA_ORG) return name;
|
|
677
|
+
for (const [type, uri] of Object.entries(SCHEMA_ORG)) {
|
|
678
|
+
if (uri === trimmed) return type;
|
|
679
|
+
}
|
|
680
|
+
return void 0;
|
|
681
|
+
}
|
|
682
|
+
function isAiSourceType(type) {
|
|
683
|
+
return type === "trainedAlgorithmicMedia" || type === "compositeWithTrainedAlgorithmicMedia" || type === "algorithmicallyEnhanced";
|
|
684
|
+
}
|
|
685
|
+
function createMarking(input = {}) {
|
|
686
|
+
const kind = input.kind ?? "generated";
|
|
687
|
+
const createdAt = input.createdAt instanceof Date ? input.createdAt.toISOString() : input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
688
|
+
const marking = {
|
|
689
|
+
kind,
|
|
690
|
+
sourceType: input.sourceType ?? DEFAULT_SOURCE[kind],
|
|
691
|
+
createdAt,
|
|
692
|
+
humanReviewed: input.humanReviewed ?? false
|
|
693
|
+
};
|
|
694
|
+
if (input.generator) marking.generator = input.generator;
|
|
695
|
+
if (input.generatorVersion) marking.generatorVersion = input.generatorVersion;
|
|
696
|
+
if (input.provider) marking.provider = input.provider;
|
|
697
|
+
if (input.disclosureUrl) marking.disclosureUrl = input.disclosureUrl;
|
|
698
|
+
if (input.description) marking.description = input.description;
|
|
699
|
+
return marking;
|
|
700
|
+
}
|
|
701
|
+
function markingAttributes(input) {
|
|
702
|
+
const m = normalise(input);
|
|
703
|
+
const attributes = {
|
|
704
|
+
"data-ai-generated": "true",
|
|
705
|
+
"data-ai-kind": m.kind,
|
|
706
|
+
"data-ai-source-type": iptcSourceType(m.sourceType),
|
|
707
|
+
"data-ai-created": m.createdAt
|
|
708
|
+
};
|
|
709
|
+
if (m.generator) attributes["data-ai-generator"] = generatorName(m);
|
|
710
|
+
if (m.provider) attributes["data-ai-provider"] = m.provider;
|
|
711
|
+
if (m.humanReviewed) attributes["data-ai-reviewed"] = "true";
|
|
712
|
+
return attributes;
|
|
713
|
+
}
|
|
714
|
+
function markingMetaTags(input) {
|
|
715
|
+
const m = normalise(input);
|
|
716
|
+
const tags = [
|
|
717
|
+
{ name: "ai-generated", content: m.kind },
|
|
718
|
+
{ name: "ai-source-type", content: iptcSourceType(m.sourceType) }
|
|
719
|
+
];
|
|
720
|
+
if (m.generator) tags.push({ name: "ai-generator", content: generatorName(m) });
|
|
721
|
+
if (m.humanReviewed) tags.push({ name: "ai-reviewed", content: "true" });
|
|
722
|
+
return tags;
|
|
723
|
+
}
|
|
724
|
+
function renderMetaTags(input) {
|
|
725
|
+
return markingMetaTags(input).map((tag) => `<meta name="${escapeHtml(tag.name)}" content="${escapeHtml(tag.content)}">`).join("\n");
|
|
726
|
+
}
|
|
727
|
+
function nextMetadata(input) {
|
|
728
|
+
return {
|
|
729
|
+
other: Object.fromEntries(markingMetaTags(input).map((tag) => [tag.name, tag.content]))
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function markingJsonLd(input, extra = {}) {
|
|
733
|
+
const m = normalise(input);
|
|
734
|
+
const data = {
|
|
735
|
+
"@context": "https://schema.org",
|
|
736
|
+
"@type": "CreativeWork",
|
|
737
|
+
digitalSourceType: schemaOrgSourceType(m.sourceType),
|
|
738
|
+
dateCreated: m.createdAt
|
|
739
|
+
};
|
|
740
|
+
if (m.generator) {
|
|
741
|
+
data.creator = {
|
|
742
|
+
"@type": "SoftwareApplication",
|
|
743
|
+
name: m.generator,
|
|
744
|
+
...m.generatorVersion ? { softwareVersion: m.generatorVersion } : {}
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
if (m.provider) data.publisher = { "@type": "Organization", name: m.provider };
|
|
748
|
+
if (m.description) data.description = m.description;
|
|
749
|
+
return { ...data, ...extra };
|
|
750
|
+
}
|
|
751
|
+
function renderJsonLd(input, extra = {}) {
|
|
752
|
+
const json = JSON.stringify(markingJsonLd(input, extra)).replace(/</g, "\\u003c");
|
|
753
|
+
return `<script type="application/ld+json">${json}</script>`;
|
|
754
|
+
}
|
|
755
|
+
function labelHtml(html, input, options = {}) {
|
|
756
|
+
const m = normalise(input);
|
|
757
|
+
const tag = options.tag ?? "div";
|
|
758
|
+
const attributes = Object.entries(markingAttributes(m)).map(([name, value]) => ` ${name}="${escapeHtml(value)}"`).join("");
|
|
759
|
+
const label = renderLabelLine(m, options);
|
|
760
|
+
const body = options.position === "bottom" ? `${html}${label}` : `${label}${html}`;
|
|
761
|
+
return `<${tag} class="witness-content"${attributes}>${body}</${tag}>`;
|
|
762
|
+
}
|
|
763
|
+
function labelText(input, options = {}) {
|
|
764
|
+
const m = normalise(input);
|
|
765
|
+
const messages = getMessages(options.locale, options.messages);
|
|
766
|
+
const parts = [messages.kinds[m.kind].label];
|
|
767
|
+
if (m.generator) {
|
|
768
|
+
parts.push(format(messages.ui.generatedWith, { generator: generatorName(m) }));
|
|
769
|
+
}
|
|
770
|
+
if (m.humanReviewed) parts.push(messages.ui.humanReviewed);
|
|
771
|
+
return parts.join(" \xB7 ");
|
|
772
|
+
}
|
|
773
|
+
function renderLabelLine(m, options) {
|
|
774
|
+
const messages = getMessages(options.locale, options.messages);
|
|
775
|
+
const text = escapeHtml(labelText(m, options));
|
|
776
|
+
const link = m.disclosureUrl ? ` <a class="witness-label__link" href="${escapeHtml(m.disclosureUrl)}">${escapeHtml(messages.ui.moreInfo)}</a>` : "";
|
|
777
|
+
return `<p class="witness-label" role="note"><span class="witness-label__icon" aria-hidden="true">AI</span> <span class="witness-label__text">${text}</span>${link}</p>`;
|
|
778
|
+
}
|
|
779
|
+
function generatorName(m) {
|
|
780
|
+
if (!m.generator) return "";
|
|
781
|
+
return m.generatorVersion ? `${m.generator} ${m.generatorVersion}` : m.generator;
|
|
782
|
+
}
|
|
783
|
+
function normalise(input) {
|
|
784
|
+
return isMarking(input) ? input : createMarking(input);
|
|
785
|
+
}
|
|
786
|
+
function isMarking(input) {
|
|
787
|
+
return typeof input.kind === "string" && typeof input.sourceType === "string" && typeof input.createdAt === "string" && typeof input.humanReviewed === "boolean";
|
|
788
|
+
}
|
|
789
|
+
function escapeHtml(value) {
|
|
790
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/image.ts
|
|
794
|
+
var XMP_NS = "http://ns.adobe.com/xap/1.0/\0";
|
|
795
|
+
var WITNESS_NS = "https://packages.sweber.dev/witness/ns/1.0/";
|
|
796
|
+
function detectImageFormat(bytes) {
|
|
797
|
+
if (bytes.length >= 8 && bytes[0] === 137 && ascii(bytes, 1, 3) === "PNG") return "png";
|
|
798
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
799
|
+
return "jpeg";
|
|
800
|
+
}
|
|
801
|
+
if (bytes.length >= 12 && ascii(bytes, 0, 4) === "RIFF" && ascii(bytes, 8, 4) === "WEBP") {
|
|
802
|
+
return "webp";
|
|
803
|
+
}
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
function markImage(bytes, input = {}, options = {}) {
|
|
807
|
+
const format2 = detectImageFormat(bytes);
|
|
808
|
+
if (!format2) return { bytes, status: "unsupported", format: format2 };
|
|
809
|
+
if (options.c2pa !== "overwrite" && hasC2pa(bytes, format2)) {
|
|
810
|
+
return { bytes, status: "skipped-c2pa", format: format2 };
|
|
811
|
+
}
|
|
812
|
+
const marking = "sourceType" in input && "humanReviewed" in input ? input : createMarking(input);
|
|
813
|
+
const existing = readXmp(bytes, format2);
|
|
814
|
+
const xmp = buildXmp(marking, existing);
|
|
815
|
+
const out = format2 === "png" ? writePng(bytes, xmp) : format2 === "jpeg" ? writeJpeg(bytes, xmp) : writeWebp(bytes, xmp);
|
|
816
|
+
return { bytes: out, status: "marked", format: format2 };
|
|
817
|
+
}
|
|
818
|
+
function readImageMarking(bytes) {
|
|
819
|
+
const format2 = detectImageFormat(bytes);
|
|
820
|
+
if (!format2) return { format: format2, xmp: null, aiGenerated: false, witness: false, c2pa: false };
|
|
821
|
+
const xmp = readXmp(bytes, format2);
|
|
822
|
+
const sourceType = xmp ? parseSourceType(xmpProperty(xmp, "Iptc4xmpExt:DigitalSourceType")) : void 0;
|
|
823
|
+
const generator = xmp ? xmpProperty(xmp, "Iptc4xmpExt:AISystemUsed") ?? xmpProperty(xmp, "xmp:CreatorTool") : void 0;
|
|
824
|
+
const info = {
|
|
825
|
+
format: format2,
|
|
826
|
+
xmp,
|
|
827
|
+
aiGenerated: isAiSourceType(sourceType),
|
|
828
|
+
witness: xmp?.includes(WITNESS_NS) ?? false,
|
|
829
|
+
c2pa: hasC2pa(bytes, format2)
|
|
830
|
+
};
|
|
831
|
+
if (sourceType) info.sourceType = sourceType;
|
|
832
|
+
if (generator) info.generator = generator;
|
|
833
|
+
return info;
|
|
834
|
+
}
|
|
835
|
+
function buildXmp(marking, existing) {
|
|
836
|
+
const description = buildDescription(marking);
|
|
837
|
+
if (existing?.includes("</rdf:RDF>")) {
|
|
838
|
+
const cleaned = existing.replace(/<rdf:Description\b[^>]*witness:marker="1"[^>]*\/>\s*/g, "").replace(
|
|
839
|
+
/<rdf:Description\b[^>]*witness:marker="1"[^>]*>[\s\S]*?<\/rdf:Description>\s*/g,
|
|
840
|
+
""
|
|
841
|
+
);
|
|
842
|
+
return cleaned.replace("</rdf:RDF>", `${description}
|
|
843
|
+
</rdf:RDF>`);
|
|
844
|
+
}
|
|
845
|
+
return [
|
|
846
|
+
'<?xpacket begin="\uFEFF" id="W5M0MpCehiHzreSzNTczkc9d"?>',
|
|
847
|
+
'<x:xmpmeta xmlns:x="adobe:ns:meta/">',
|
|
848
|
+
'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">',
|
|
849
|
+
description,
|
|
850
|
+
"</rdf:RDF>",
|
|
851
|
+
"</x:xmpmeta>",
|
|
852
|
+
'<?xpacket end="w"?>'
|
|
853
|
+
].join("\n");
|
|
854
|
+
}
|
|
855
|
+
function buildDescription(m) {
|
|
856
|
+
const attributes = [
|
|
857
|
+
["rdf:about", ""],
|
|
858
|
+
["xmlns:Iptc4xmpExt", "http://iptc.org/std/Iptc4xmpExt/2008-02-29/"],
|
|
859
|
+
["xmlns:xmp", "http://ns.adobe.com/xap/1.0/"],
|
|
860
|
+
["xmlns:dc", "http://purl.org/dc/elements/1.1/"],
|
|
861
|
+
["xmlns:witness", WITNESS_NS],
|
|
862
|
+
["witness:marker", "1"],
|
|
863
|
+
["witness:kind", m.kind],
|
|
864
|
+
["Iptc4xmpExt:DigitalSourceType", iptcSourceType(m.sourceType)],
|
|
865
|
+
["xmp:CreateDate", m.createdAt]
|
|
866
|
+
];
|
|
867
|
+
if (m.generator) {
|
|
868
|
+
attributes.push(["Iptc4xmpExt:AISystemUsed", m.generator]);
|
|
869
|
+
attributes.push(["xmp:CreatorTool", generatorName(m)]);
|
|
870
|
+
}
|
|
871
|
+
if (m.generatorVersion) attributes.push(["Iptc4xmpExt:AISystemVersionUsed", m.generatorVersion]);
|
|
872
|
+
if (m.provider) attributes.push(["witness:provider", m.provider]);
|
|
873
|
+
if (m.disclosureUrl) attributes.push(["witness:disclosureUrl", m.disclosureUrl]);
|
|
874
|
+
if (m.humanReviewed) attributes.push(["witness:humanReviewed", "True"]);
|
|
875
|
+
const attrs = attributes.map(([name, value]) => `
|
|
876
|
+
${name}="${escapeXml(value)}"`).join("");
|
|
877
|
+
const description = m.description ? `
|
|
878
|
+
<dc:description><rdf:Alt><rdf:li xml:lang="x-default">${escapeXml(m.description)}</rdf:li></rdf:Alt></dc:description>
|
|
879
|
+
` : "";
|
|
880
|
+
return description ? `<rdf:Description${attrs}>${description}</rdf:Description>` : `<rdf:Description${attrs}/>`;
|
|
881
|
+
}
|
|
882
|
+
function xmpProperty(xmp, name) {
|
|
883
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
884
|
+
const attribute = new RegExp(`${escaped}="([^"]*)"`).exec(xmp);
|
|
885
|
+
if (attribute?.[1] !== void 0) return unescapeXml(attribute[1]);
|
|
886
|
+
const element = new RegExp(
|
|
887
|
+
`<${escaped}(?:\\s+rdf:resource="([^"]*)")?\\s*(?:/>|>([\\s\\S]*?)</${escaped}>)`
|
|
888
|
+
).exec(xmp);
|
|
889
|
+
if (!element) return void 0;
|
|
890
|
+
if (element[1] !== void 0) return unescapeXml(element[1]);
|
|
891
|
+
const inner = element[2] ?? "";
|
|
892
|
+
const li = /<rdf:li[^>]*>([\s\S]*?)<\/rdf:li>/.exec(inner);
|
|
893
|
+
const value = (li ? li[1] : inner)?.trim();
|
|
894
|
+
return value ? unescapeXml(value) : void 0;
|
|
895
|
+
}
|
|
896
|
+
function writePng(bytes, xmp) {
|
|
897
|
+
const chunks = pngChunks(bytes).filter((chunk) => !isPngXmp(bytes, chunk));
|
|
898
|
+
const itxt = pngChunk(
|
|
899
|
+
"iTXt",
|
|
900
|
+
concat(latin1("XML:com.adobe.xmp"), new Uint8Array([0, 0, 0, 0, 0]), utf8(xmp))
|
|
901
|
+
);
|
|
902
|
+
const parts = [bytes.subarray(0, 8)];
|
|
903
|
+
for (const chunk of chunks) {
|
|
904
|
+
parts.push(bytes.subarray(chunk.offset, chunk.end));
|
|
905
|
+
if (chunk.type === "IHDR") parts.push(itxt);
|
|
906
|
+
}
|
|
907
|
+
return concat(...parts);
|
|
908
|
+
}
|
|
909
|
+
function pngChunks(bytes) {
|
|
910
|
+
const chunks = [];
|
|
911
|
+
let offset = 8;
|
|
912
|
+
while (offset + 12 <= bytes.length) {
|
|
913
|
+
const length = readU32BE(bytes, offset);
|
|
914
|
+
const type = ascii(bytes, offset + 4, 4);
|
|
915
|
+
const dataStart = offset + 8;
|
|
916
|
+
const dataEnd = dataStart + length;
|
|
917
|
+
const end = dataEnd + 4;
|
|
918
|
+
if (end > bytes.length) throw new Error("Witness: truncated PNG chunk");
|
|
919
|
+
chunks.push({ type, offset, dataStart, dataEnd, end });
|
|
920
|
+
offset = end;
|
|
921
|
+
if (type === "IEND") break;
|
|
922
|
+
}
|
|
923
|
+
return chunks;
|
|
924
|
+
}
|
|
925
|
+
function isPngXmp(bytes, chunk) {
|
|
926
|
+
return (chunk.type === "iTXt" || chunk.type === "zTXt") && ascii(bytes, chunk.dataStart, 18) === "XML:com.adobe.xmp\0";
|
|
927
|
+
}
|
|
928
|
+
function readPngXmp(bytes) {
|
|
929
|
+
for (const chunk of pngChunks(bytes)) {
|
|
930
|
+
if (!isPngXmp(bytes, chunk)) continue;
|
|
931
|
+
let p = chunk.dataStart + 18;
|
|
932
|
+
let compressed;
|
|
933
|
+
if (chunk.type === "zTXt") {
|
|
934
|
+
compressed = true;
|
|
935
|
+
p += 1;
|
|
936
|
+
} else {
|
|
937
|
+
compressed = bytes[p] === 1;
|
|
938
|
+
p += 2;
|
|
939
|
+
while (p < chunk.dataEnd && bytes[p] !== 0) p++;
|
|
940
|
+
p++;
|
|
941
|
+
while (p < chunk.dataEnd && bytes[p] !== 0) p++;
|
|
942
|
+
p++;
|
|
943
|
+
}
|
|
944
|
+
const data = bytes.subarray(p, chunk.dataEnd);
|
|
945
|
+
try {
|
|
946
|
+
return new TextDecoder().decode(compressed ? inflateZlib(data) : data);
|
|
947
|
+
} catch {
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
function pngChunk(type, data) {
|
|
954
|
+
const out = new Uint8Array(12 + data.length);
|
|
955
|
+
writeU32BE(out, 0, data.length);
|
|
956
|
+
out.set(latin1(type), 4);
|
|
957
|
+
out.set(data, 8);
|
|
958
|
+
writeU32BE(out, 8 + data.length, crc32(out.subarray(4, 8 + data.length)));
|
|
959
|
+
return out;
|
|
960
|
+
}
|
|
961
|
+
function jpegSegments(bytes) {
|
|
962
|
+
const segments = [];
|
|
963
|
+
let offset = 2;
|
|
964
|
+
while (offset + 4 <= bytes.length) {
|
|
965
|
+
if (bytes[offset] !== 255) throw new Error("Witness: invalid JPEG marker");
|
|
966
|
+
let markerOffset = offset;
|
|
967
|
+
while (bytes[markerOffset + 1] === 255) markerOffset++;
|
|
968
|
+
const marker = bytes[markerOffset + 1] ?? 0;
|
|
969
|
+
if (marker === 218 || marker === 217) return { segments, scanStart: offset };
|
|
970
|
+
const length = readU16BE(bytes, markerOffset + 2);
|
|
971
|
+
const end = markerOffset + 2 + length;
|
|
972
|
+
if (end > bytes.length) throw new Error("Witness: truncated JPEG segment");
|
|
973
|
+
segments.push({ marker, offset, dataStart: markerOffset + 4, end });
|
|
974
|
+
offset = end;
|
|
975
|
+
}
|
|
976
|
+
return { segments, scanStart: offset };
|
|
977
|
+
}
|
|
978
|
+
function isJpegXmp(bytes, segment) {
|
|
979
|
+
return segment.marker === 225 && ascii(bytes, segment.dataStart, XMP_NS.length) === XMP_NS;
|
|
980
|
+
}
|
|
981
|
+
function writeJpeg(bytes, xmp) {
|
|
982
|
+
const { segments, scanStart } = jpegSegments(bytes);
|
|
983
|
+
const payload = concat(latin1(XMP_NS), utf8(xmp));
|
|
984
|
+
if (payload.length + 2 > 65535) throw new Error("Witness: XMP packet too large for JPEG");
|
|
985
|
+
const app1 = new Uint8Array(4 + payload.length);
|
|
986
|
+
app1[0] = 255;
|
|
987
|
+
app1[1] = 225;
|
|
988
|
+
app1[2] = payload.length + 2 >> 8 & 255;
|
|
989
|
+
app1[3] = payload.length + 2 & 255;
|
|
990
|
+
app1.set(payload, 4);
|
|
991
|
+
const kept = segments.filter((segment) => !isJpegXmp(bytes, segment));
|
|
992
|
+
let insertAt = 0;
|
|
993
|
+
while (insertAt < kept.length) {
|
|
994
|
+
const segment = kept[insertAt];
|
|
995
|
+
if (!segment) break;
|
|
996
|
+
const isJfif = segment.marker === 224;
|
|
997
|
+
const isExif = segment.marker === 225 && ascii(bytes, segment.dataStart, 4) === "Exif";
|
|
998
|
+
if (!isJfif && !isExif) break;
|
|
999
|
+
insertAt++;
|
|
1000
|
+
}
|
|
1001
|
+
const parts = [bytes.subarray(0, 2)];
|
|
1002
|
+
kept.forEach((segment, index) => {
|
|
1003
|
+
if (index === insertAt) parts.push(app1);
|
|
1004
|
+
parts.push(bytes.subarray(segment.offset, segment.end));
|
|
1005
|
+
});
|
|
1006
|
+
if (insertAt >= kept.length) parts.push(app1);
|
|
1007
|
+
parts.push(bytes.subarray(scanStart));
|
|
1008
|
+
return concat(...parts);
|
|
1009
|
+
}
|
|
1010
|
+
function readJpegXmp(bytes) {
|
|
1011
|
+
for (const segment of jpegSegments(bytes).segments) {
|
|
1012
|
+
if (isJpegXmp(bytes, segment)) {
|
|
1013
|
+
return new TextDecoder().decode(
|
|
1014
|
+
bytes.subarray(segment.dataStart + XMP_NS.length, segment.end)
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
function webpChunks(bytes) {
|
|
1021
|
+
const chunks = [];
|
|
1022
|
+
const riffEnd = Math.min(bytes.length, 8 + readU32LE(bytes, 4));
|
|
1023
|
+
let offset = 12;
|
|
1024
|
+
while (offset + 8 <= riffEnd) {
|
|
1025
|
+
const fourcc = ascii(bytes, offset, 4);
|
|
1026
|
+
const size = readU32LE(bytes, offset + 4);
|
|
1027
|
+
const dataStart = offset + 8;
|
|
1028
|
+
const end = dataStart + size + size % 2;
|
|
1029
|
+
if (dataStart + size > bytes.length) throw new Error("Witness: truncated WebP chunk");
|
|
1030
|
+
chunks.push({ fourcc, offset, dataStart, size, end: Math.min(end, bytes.length) });
|
|
1031
|
+
offset = end;
|
|
1032
|
+
}
|
|
1033
|
+
return chunks;
|
|
1034
|
+
}
|
|
1035
|
+
function writeWebp(bytes, xmp) {
|
|
1036
|
+
const chunks = webpChunks(bytes).filter((chunk) => chunk.fourcc !== "XMP ");
|
|
1037
|
+
const first = chunks[0];
|
|
1038
|
+
if (!first) throw new Error("Witness: empty WebP file");
|
|
1039
|
+
const parts = [];
|
|
1040
|
+
if (first.fourcc === "VP8X") {
|
|
1041
|
+
const vp8x = bytes.slice(first.offset, first.end);
|
|
1042
|
+
vp8x[8] = (vp8x[8] ?? 0) | 4;
|
|
1043
|
+
parts.push(vp8x);
|
|
1044
|
+
for (const chunk of chunks.slice(1)) parts.push(bytes.subarray(chunk.offset, chunk.end));
|
|
1045
|
+
} else {
|
|
1046
|
+
const { width, height, alpha } = webpCanvas(bytes, first);
|
|
1047
|
+
const data = new Uint8Array(10);
|
|
1048
|
+
data[0] = 4 | (alpha ? 16 : 0);
|
|
1049
|
+
writeU24LE(data, 4, width - 1);
|
|
1050
|
+
writeU24LE(data, 7, height - 1);
|
|
1051
|
+
parts.push(riffChunk("VP8X", data));
|
|
1052
|
+
for (const chunk of chunks) parts.push(bytes.subarray(chunk.offset, chunk.end));
|
|
1053
|
+
}
|
|
1054
|
+
parts.push(riffChunk("XMP ", utf8(xmp)));
|
|
1055
|
+
const body = concat(...parts);
|
|
1056
|
+
const out = new Uint8Array(12 + body.length);
|
|
1057
|
+
out.set(latin1("RIFF"), 0);
|
|
1058
|
+
writeU32LE(out, 4, 4 + body.length);
|
|
1059
|
+
out.set(latin1("WEBP"), 8);
|
|
1060
|
+
out.set(body, 12);
|
|
1061
|
+
return out;
|
|
1062
|
+
}
|
|
1063
|
+
function webpCanvas(bytes, chunk) {
|
|
1064
|
+
const d = chunk.dataStart;
|
|
1065
|
+
if (chunk.fourcc === "VP8 ") {
|
|
1066
|
+
if (bytes[d + 3] !== 157 || bytes[d + 4] !== 1 || bytes[d + 5] !== 42) {
|
|
1067
|
+
throw new Error("Witness: invalid VP8 frame");
|
|
1068
|
+
}
|
|
1069
|
+
return {
|
|
1070
|
+
width: readU16LE(bytes, d + 6) & 16383,
|
|
1071
|
+
height: readU16LE(bytes, d + 8) & 16383,
|
|
1072
|
+
alpha: false
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
if (chunk.fourcc === "VP8L") {
|
|
1076
|
+
if (bytes[d] !== 47) throw new Error("Witness: invalid VP8L frame");
|
|
1077
|
+
const bits = readU32LE(bytes, d + 1);
|
|
1078
|
+
return {
|
|
1079
|
+
width: (bits & 16383) + 1,
|
|
1080
|
+
height: (bits >>> 14 & 16383) + 1,
|
|
1081
|
+
alpha: (bits >>> 28 & 1) === 1
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
throw new Error(`Witness: unexpected WebP chunk ${chunk.fourcc}`);
|
|
1085
|
+
}
|
|
1086
|
+
function readWebpXmp(bytes) {
|
|
1087
|
+
const chunk = webpChunks(bytes).find((c) => c.fourcc === "XMP ");
|
|
1088
|
+
return chunk ? new TextDecoder().decode(bytes.subarray(chunk.dataStart, chunk.dataStart + chunk.size)) : null;
|
|
1089
|
+
}
|
|
1090
|
+
function riffChunk(fourcc, data) {
|
|
1091
|
+
const out = new Uint8Array(8 + data.length + data.length % 2);
|
|
1092
|
+
out.set(latin1(fourcc), 0);
|
|
1093
|
+
writeU32LE(out, 4, data.length);
|
|
1094
|
+
out.set(data, 8);
|
|
1095
|
+
return out;
|
|
1096
|
+
}
|
|
1097
|
+
function readXmp(bytes, format2) {
|
|
1098
|
+
if (format2 === "png") return readPngXmp(bytes);
|
|
1099
|
+
if (format2 === "jpeg") return readJpegXmp(bytes);
|
|
1100
|
+
return readWebpXmp(bytes);
|
|
1101
|
+
}
|
|
1102
|
+
function hasC2pa(bytes, format2) {
|
|
1103
|
+
if (format2 === "png") return pngChunks(bytes).some((chunk) => chunk.type === "caBX");
|
|
1104
|
+
if (format2 === "webp") return webpChunks(bytes).some((chunk) => chunk.fourcc === "C2PA");
|
|
1105
|
+
return jpegSegments(bytes).segments.some(
|
|
1106
|
+
(segment) => segment.marker === 235 && ascii(bytes, segment.dataStart, Math.min(64, segment.end - segment.dataStart)).includes(
|
|
1107
|
+
"c2pa"
|
|
1108
|
+
)
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
function ascii(bytes, offset, length) {
|
|
1112
|
+
let out = "";
|
|
1113
|
+
for (let i = offset; i < offset + length && i < bytes.length; i++) {
|
|
1114
|
+
out += String.fromCharCode(bytes[i] ?? 0);
|
|
1115
|
+
}
|
|
1116
|
+
return out;
|
|
1117
|
+
}
|
|
1118
|
+
function latin1(text) {
|
|
1119
|
+
const out = new Uint8Array(text.length);
|
|
1120
|
+
for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
|
|
1121
|
+
return out;
|
|
1122
|
+
}
|
|
1123
|
+
function utf8(text) {
|
|
1124
|
+
return new TextEncoder().encode(text);
|
|
1125
|
+
}
|
|
1126
|
+
function concat(...parts) {
|
|
1127
|
+
const out = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0));
|
|
1128
|
+
let offset = 0;
|
|
1129
|
+
for (const part of parts) {
|
|
1130
|
+
out.set(part, offset);
|
|
1131
|
+
offset += part.length;
|
|
1132
|
+
}
|
|
1133
|
+
return out;
|
|
1134
|
+
}
|
|
1135
|
+
function readU16BE(b, o) {
|
|
1136
|
+
return (b[o] ?? 0) << 8 | (b[o + 1] ?? 0);
|
|
1137
|
+
}
|
|
1138
|
+
function readU16LE(b, o) {
|
|
1139
|
+
return (b[o] ?? 0) | (b[o + 1] ?? 0) << 8;
|
|
1140
|
+
}
|
|
1141
|
+
function readU32BE(b, o) {
|
|
1142
|
+
return ((b[o] ?? 0) << 24 | (b[o + 1] ?? 0) << 16 | (b[o + 2] ?? 0) << 8 | (b[o + 3] ?? 0)) >>> 0;
|
|
1143
|
+
}
|
|
1144
|
+
function readU32LE(b, o) {
|
|
1145
|
+
return ((b[o] ?? 0) | (b[o + 1] ?? 0) << 8 | (b[o + 2] ?? 0) << 16 | (b[o + 3] ?? 0) << 24) >>> 0;
|
|
1146
|
+
}
|
|
1147
|
+
function writeU32BE(b, o, v) {
|
|
1148
|
+
b[o] = v >>> 24 & 255;
|
|
1149
|
+
b[o + 1] = v >>> 16 & 255;
|
|
1150
|
+
b[o + 2] = v >>> 8 & 255;
|
|
1151
|
+
b[o + 3] = v & 255;
|
|
1152
|
+
}
|
|
1153
|
+
function writeU32LE(b, o, v) {
|
|
1154
|
+
b[o] = v & 255;
|
|
1155
|
+
b[o + 1] = v >>> 8 & 255;
|
|
1156
|
+
b[o + 2] = v >>> 16 & 255;
|
|
1157
|
+
b[o + 3] = v >>> 24 & 255;
|
|
1158
|
+
}
|
|
1159
|
+
function writeU24LE(b, o, v) {
|
|
1160
|
+
b[o] = v & 255;
|
|
1161
|
+
b[o + 1] = v >>> 8 & 255;
|
|
1162
|
+
b[o + 2] = v >>> 16 & 255;
|
|
1163
|
+
}
|
|
1164
|
+
var crcTable = null;
|
|
1165
|
+
function crc32(data) {
|
|
1166
|
+
if (!crcTable) {
|
|
1167
|
+
crcTable = new Uint32Array(256);
|
|
1168
|
+
for (let n = 0; n < 256; n++) {
|
|
1169
|
+
let c = n;
|
|
1170
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
1171
|
+
crcTable[n] = c >>> 0;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
let crc = 4294967295;
|
|
1175
|
+
for (const byte of data) crc = (crcTable[(crc ^ byte) & 255] ?? 0) ^ crc >>> 8;
|
|
1176
|
+
return (crc ^ 4294967295) >>> 0;
|
|
1177
|
+
}
|
|
1178
|
+
function escapeXml(value) {
|
|
1179
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1180
|
+
}
|
|
1181
|
+
function unescapeXml(value) {
|
|
1182
|
+
return value.replace(/"/g, '"').replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'").replace(/&/g, "&");
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/types.ts
|
|
1186
|
+
var DISCLOSURE_KINDS = [
|
|
1187
|
+
"chatbot",
|
|
1188
|
+
"generated",
|
|
1189
|
+
"edited",
|
|
1190
|
+
"deepfake",
|
|
1191
|
+
"emotion-recognition",
|
|
1192
|
+
"biometric-categorisation"
|
|
1193
|
+
];
|
|
1194
|
+
|
|
1195
|
+
// src/watermark.ts
|
|
1196
|
+
var MAGIC = [87, 49];
|
|
1197
|
+
var MAX_PAYLOAD = 255;
|
|
1198
|
+
function watermarkText(text, mark = {}) {
|
|
1199
|
+
if (text.length === 0) return text;
|
|
1200
|
+
return stripTextWatermark(text) + watermarkSuffix(mark);
|
|
1201
|
+
}
|
|
1202
|
+
function watermarkSuffix(mark = {}) {
|
|
1203
|
+
const payload = encodePayload(mark);
|
|
1204
|
+
return [...MAGIC, payload.length, ...payload].map(byteToSelector).join("");
|
|
1205
|
+
}
|
|
1206
|
+
function readTextWatermark(text) {
|
|
1207
|
+
for (const run of selectorRuns(text)) {
|
|
1208
|
+
const found = findInRun(run.bytes);
|
|
1209
|
+
if (found) {
|
|
1210
|
+
const raw = new TextDecoder().decode(new Uint8Array(found.payload));
|
|
1211
|
+
return { ...decodePayload(raw), raw };
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
function hasTextWatermark(text) {
|
|
1217
|
+
return readTextWatermark(text) !== null;
|
|
1218
|
+
}
|
|
1219
|
+
function stripTextWatermark(text) {
|
|
1220
|
+
const chars = [...text];
|
|
1221
|
+
const remove = /* @__PURE__ */ new Set();
|
|
1222
|
+
for (const run of selectorRuns(text)) {
|
|
1223
|
+
let offset = 0;
|
|
1224
|
+
while (offset < run.bytes.length) {
|
|
1225
|
+
const found = findInRun(run.bytes.slice(offset));
|
|
1226
|
+
if (!found) break;
|
|
1227
|
+
const start = offset + found.start;
|
|
1228
|
+
const end = offset + found.end;
|
|
1229
|
+
for (let i = start; i < end; i++) remove.add(run.start + i);
|
|
1230
|
+
offset = end;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (remove.size === 0) return text;
|
|
1234
|
+
return chars.filter((_, index) => !remove.has(index)).join("");
|
|
1235
|
+
}
|
|
1236
|
+
function encodePayload(mark) {
|
|
1237
|
+
const fields = [];
|
|
1238
|
+
if (mark.generator) fields.push(`g=${encodeURIComponent(mark.generator)}`);
|
|
1239
|
+
if (mark.createdAt) fields.push(`t=${encodeURIComponent(mark.createdAt)}`);
|
|
1240
|
+
if (mark.id) fields.push(`i=${encodeURIComponent(mark.id)}`);
|
|
1241
|
+
let bytes = [...new TextEncoder().encode(fields.join(";"))];
|
|
1242
|
+
if (bytes.length > MAX_PAYLOAD) {
|
|
1243
|
+
while (fields.length > 0 && bytes.length > MAX_PAYLOAD) {
|
|
1244
|
+
fields.pop();
|
|
1245
|
+
bytes = [...new TextEncoder().encode(fields.join(";"))];
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
return bytes;
|
|
1249
|
+
}
|
|
1250
|
+
function decodePayload(raw) {
|
|
1251
|
+
const result = {};
|
|
1252
|
+
for (const field of raw.split(";")) {
|
|
1253
|
+
const index = field.indexOf("=");
|
|
1254
|
+
if (index < 1) continue;
|
|
1255
|
+
const key = field.slice(0, index);
|
|
1256
|
+
let value;
|
|
1257
|
+
try {
|
|
1258
|
+
value = decodeURIComponent(field.slice(index + 1));
|
|
1259
|
+
} catch {
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
if (key === "g") result.generator = value;
|
|
1263
|
+
else if (key === "t") result.createdAt = value;
|
|
1264
|
+
else if (key === "i") result.id = value;
|
|
1265
|
+
}
|
|
1266
|
+
return result;
|
|
1267
|
+
}
|
|
1268
|
+
function byteToSelector(byte) {
|
|
1269
|
+
return byte < 16 ? String.fromCodePoint(65024 + byte) : String.fromCodePoint(917760 + (byte - 16));
|
|
1270
|
+
}
|
|
1271
|
+
function selectorToByte(codePoint) {
|
|
1272
|
+
if (codePoint >= 65024 && codePoint <= 65039) return codePoint - 65024;
|
|
1273
|
+
if (codePoint >= 917760 && codePoint <= 917999) return codePoint - 917760 + 16;
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1276
|
+
function selectorRuns(text) {
|
|
1277
|
+
const runs = [];
|
|
1278
|
+
let current = null;
|
|
1279
|
+
let index = 0;
|
|
1280
|
+
for (const char of text) {
|
|
1281
|
+
const byte = selectorToByte(char.codePointAt(0) ?? 0);
|
|
1282
|
+
if (byte === null) {
|
|
1283
|
+
current = null;
|
|
1284
|
+
} else {
|
|
1285
|
+
if (!current) {
|
|
1286
|
+
current = { start: index, bytes: [] };
|
|
1287
|
+
runs.push(current);
|
|
1288
|
+
}
|
|
1289
|
+
current.bytes.push(byte);
|
|
1290
|
+
}
|
|
1291
|
+
index++;
|
|
1292
|
+
}
|
|
1293
|
+
return runs;
|
|
1294
|
+
}
|
|
1295
|
+
function findInRun(bytes) {
|
|
1296
|
+
for (let i = 0; i + MAGIC.length < bytes.length; i++) {
|
|
1297
|
+
if (bytes[i] !== MAGIC[0] || bytes[i + 1] !== MAGIC[1]) continue;
|
|
1298
|
+
const length = bytes[i + 2];
|
|
1299
|
+
if (length === void 0) continue;
|
|
1300
|
+
const start = i + 3;
|
|
1301
|
+
const end = start + length;
|
|
1302
|
+
if (end > bytes.length) continue;
|
|
1303
|
+
return { start: i, end, payload: bytes.slice(start, end) };
|
|
1304
|
+
}
|
|
1305
|
+
return null;
|
|
1306
|
+
}
|
|
1307
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1308
|
+
0 && (module.exports = {
|
|
1309
|
+
BUILT_IN_LOCALES,
|
|
1310
|
+
DISCLOSURE_KINDS,
|
|
1311
|
+
buildXmp,
|
|
1312
|
+
createDisclosure,
|
|
1313
|
+
createMarking,
|
|
1314
|
+
detectImageFormat,
|
|
1315
|
+
disclosureText,
|
|
1316
|
+
escapeHtml,
|
|
1317
|
+
format,
|
|
1318
|
+
generatorName,
|
|
1319
|
+
getMessages,
|
|
1320
|
+
hasTextWatermark,
|
|
1321
|
+
iptcSourceType,
|
|
1322
|
+
isAiSourceType,
|
|
1323
|
+
labelHtml,
|
|
1324
|
+
labelText,
|
|
1325
|
+
markImage,
|
|
1326
|
+
markingAttributes,
|
|
1327
|
+
markingJsonLd,
|
|
1328
|
+
markingMetaTags,
|
|
1329
|
+
memoryStorage,
|
|
1330
|
+
nextMetadata,
|
|
1331
|
+
parseSourceType,
|
|
1332
|
+
readImageMarking,
|
|
1333
|
+
readTextWatermark,
|
|
1334
|
+
registerLocale,
|
|
1335
|
+
registeredLocales,
|
|
1336
|
+
renderJsonLd,
|
|
1337
|
+
renderMetaTags,
|
|
1338
|
+
resolveLocale,
|
|
1339
|
+
schemaOrgSourceType,
|
|
1340
|
+
stripTextWatermark,
|
|
1341
|
+
watermarkSuffix,
|
|
1342
|
+
watermarkText
|
|
1343
|
+
});
|
|
1344
|
+
//# sourceMappingURL=index.cjs.map
|