@sweberdev/witness 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/ai-sdk.cjs.map +1 -1
- package/dist/ai-sdk.js +1 -1
- package/dist/bin.cjs +294 -6
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +3 -2
- package/dist/bin.js.map +1 -1
- package/dist/chunk-F7OWVTCS.js +307 -0
- package/dist/chunk-F7OWVTCS.js.map +1 -0
- package/dist/{chunk-APXU3MTX.js → chunk-GXMGYIOK.js} +3 -1
- package/dist/chunk-GXMGYIOK.js.map +1 -0
- package/dist/{chunk-DHX7SNWE.js → chunk-O6TADCND.js} +10 -3
- package/dist/chunk-O6TADCND.js.map +1 -0
- package/dist/{chunk-UBRK4CTM.js → chunk-PT57EYD7.js} +14 -10
- package/dist/chunk-PT57EYD7.js.map +1 -0
- package/dist/cli.cjs +294 -6
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -2
- package/dist/elements-auto.cjs +9 -2
- package/dist/elements-auto.cjs.map +1 -1
- package/dist/elements-auto.js +1 -1
- package/dist/elements.cjs +9 -2
- package/dist/elements.cjs.map +1 -1
- package/dist/elements.d.cts +1 -1
- package/dist/elements.d.ts +1 -1
- package/dist/elements.js +1 -1
- package/dist/index.cjs +297 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +13 -1
- package/package.json +2 -2
- package/src/cli.ts +9 -7
- package/src/elements.ts +9 -2
- package/src/image.ts +4 -3
- package/src/index.ts +13 -0
- package/src/media.ts +460 -0
- package/dist/chunk-APXU3MTX.js.map +0 -1
- package/dist/chunk-DHX7SNWE.js.map +0 -1
- package/dist/chunk-UBRK4CTM.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { D as Disclosure, a as DisclosureEvent, b as DisclosureOptions, c as DisclosureStorage, d as createDisclosure, m as memoryStorage } from './disclosure-C7Y_uY73.js';
|
|
2
2
|
import { D as DisclosureKind, L as LocaleOverride, b as DisclosureText, c as LocaleMessages, a as MarkingInput, M as Marking, S as SourceType } from './types-WWHzrNlc.js';
|
|
3
3
|
export { C as ContentKind, d as DISCLOSURE_KINDS, U as UiText } from './types-WWHzrNlc.js';
|
|
4
|
-
|
|
4
|
+
import { I as ImageFormat, M as MarkImageOptions, a as ImageMarkingInfo } from './image-B7YvdITx.js';
|
|
5
|
+
export { b as MarkImageResult, c as buildXmp, d as detectImageFormat, m as markImage, r as readImageMarking } from './image-B7YvdITx.js';
|
|
5
6
|
|
|
6
7
|
/** The locales bundled with the free package. */
|
|
7
8
|
declare const BUILT_IN_LOCALES: readonly ["en", "de", "fr", "it"];
|
|
@@ -80,6 +81,48 @@ declare function labelText(input: Marking | MarkingInput, options?: Pick<LabelHt
|
|
|
80
81
|
declare function generatorName(m: Pick<Marking, "generator" | "generatorVersion">): string;
|
|
81
82
|
declare function escapeHtml(value: string): string;
|
|
82
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Machine-readable AI marking for audio and video: the same XMP packet as for images, with the
|
|
86
|
+
* IPTC Digital Source Type, written where the XMP specification puts it for each container.
|
|
87
|
+
*
|
|
88
|
+
* - MP3: an ID3v2 `PRIV` frame with the owner `XMP`.
|
|
89
|
+
* - WAV: a RIFF `_PMX` chunk.
|
|
90
|
+
* - MP4, MOV and M4A: a top-level `uuid` box with the XMP UUID, appended at the end of the file
|
|
91
|
+
* so no sample offsets move.
|
|
92
|
+
*
|
|
93
|
+
* The audio and video data is not touched. Files that carry a C2PA manifest are left unchanged
|
|
94
|
+
* by default, as for images.
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
type MediaFormat = "mp3" | "wav" | "mp4";
|
|
98
|
+
type MarkMediaOptions = MarkImageOptions;
|
|
99
|
+
interface MarkMediaResult {
|
|
100
|
+
bytes: Uint8Array;
|
|
101
|
+
status: "marked" | "skipped-c2pa" | "unsupported";
|
|
102
|
+
format: MediaFormat | null;
|
|
103
|
+
}
|
|
104
|
+
interface MediaMarkingInfo extends Omit<ImageMarkingInfo, "format"> {
|
|
105
|
+
format: MediaFormat | null;
|
|
106
|
+
}
|
|
107
|
+
interface MarkFileResult {
|
|
108
|
+
bytes: Uint8Array;
|
|
109
|
+
status: "marked" | "skipped-c2pa" | "unsupported";
|
|
110
|
+
format: ImageFormat | MediaFormat | null;
|
|
111
|
+
}
|
|
112
|
+
interface MarkingInfo extends Omit<ImageMarkingInfo, "format"> {
|
|
113
|
+
format: ImageFormat | MediaFormat | null;
|
|
114
|
+
}
|
|
115
|
+
/** Detects MP3, WAV or an ISO media file (MP4, MOV, M4A) from the first bytes. */
|
|
116
|
+
declare function detectMediaFormat(bytes: Uint8Array): MediaFormat | null;
|
|
117
|
+
/** Writes the marking as XMP into an MP3, WAV, MP4, MOV or M4A file. */
|
|
118
|
+
declare function markMedia(bytes: Uint8Array, input?: Marking | MarkingInput, options?: MarkMediaOptions): MarkMediaResult;
|
|
119
|
+
/** Reads the AI marking of an audio or video file, whoever wrote it. */
|
|
120
|
+
declare function readMediaMarking(bytes: Uint8Array): MediaMarkingInfo;
|
|
121
|
+
/** Marks an image, audio or video file, whichever it is. */
|
|
122
|
+
declare function markFile(bytes: Uint8Array, input?: Marking | MarkingInput, options?: MarkMediaOptions): MarkFileResult;
|
|
123
|
+
/** Reads the AI marking of an image, audio or video file. */
|
|
124
|
+
declare function readMarking(bytes: Uint8Array): MarkingInfo;
|
|
125
|
+
|
|
83
126
|
/**
|
|
84
127
|
* An invisible, machine-readable marker for AI-generated text.
|
|
85
128
|
*
|
|
@@ -121,4 +164,4 @@ declare function hasTextWatermark(text: string): boolean;
|
|
|
121
164
|
*/
|
|
122
165
|
declare function stripTextWatermark(text: string): string;
|
|
123
166
|
|
|
124
|
-
export { BUILT_IN_LOCALES, DisclosureKind, DisclosureText, type LabelHtmlOptions, LocaleMessages, LocaleOverride, Marking, MarkingInput, type ReadTextWatermark, SourceType, type TextWatermark, createMarking, disclosureText, escapeHtml, format, generatorName, getMessages, hasTextWatermark, iptcSourceType, isAiSourceType, labelHtml, labelText, markingAttributes, markingJsonLd, markingMetaTags, nextMetadata, parseSourceType, readTextWatermark, registerLocale, registeredLocales, renderJsonLd, renderMetaTags, resolveLocale, schemaOrgSourceType, stripTextWatermark, watermarkSuffix, watermarkText };
|
|
167
|
+
export { BUILT_IN_LOCALES, DisclosureKind, DisclosureText, ImageFormat, ImageMarkingInfo, type LabelHtmlOptions, LocaleMessages, LocaleOverride, type MarkFileResult, MarkImageOptions, type MarkMediaOptions, type MarkMediaResult, Marking, type MarkingInfo, MarkingInput, type MediaFormat, type MediaMarkingInfo, type ReadTextWatermark, SourceType, type TextWatermark, createMarking, detectMediaFormat, disclosureText, escapeHtml, format, generatorName, getMessages, hasTextWatermark, iptcSourceType, isAiSourceType, labelHtml, labelText, markFile, markMedia, markingAttributes, markingJsonLd, markingMetaTags, nextMetadata, parseSourceType, readMarking, readMediaMarking, readTextWatermark, registerLocale, registeredLocales, renderJsonLd, renderMetaTags, resolveLocale, schemaOrgSourceType, stripTextWatermark, watermarkSuffix, watermarkText };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectMediaFormat,
|
|
3
|
+
markFile,
|
|
4
|
+
markMedia,
|
|
5
|
+
readMarking,
|
|
6
|
+
readMediaMarking
|
|
7
|
+
} from "./chunk-F7OWVTCS.js";
|
|
1
8
|
import {
|
|
2
9
|
buildXmp,
|
|
3
10
|
createMarking,
|
|
@@ -23,7 +30,7 @@ import {
|
|
|
23
30
|
stripTextWatermark,
|
|
24
31
|
watermarkSuffix,
|
|
25
32
|
watermarkText
|
|
26
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-GXMGYIOK.js";
|
|
27
34
|
import {
|
|
28
35
|
DISCLOSURE_KINDS,
|
|
29
36
|
createDisclosure,
|
|
@@ -45,6 +52,7 @@ export {
|
|
|
45
52
|
createDisclosure,
|
|
46
53
|
createMarking,
|
|
47
54
|
detectImageFormat,
|
|
55
|
+
detectMediaFormat,
|
|
48
56
|
disclosureText,
|
|
49
57
|
escapeHtml,
|
|
50
58
|
format,
|
|
@@ -55,7 +63,9 @@ export {
|
|
|
55
63
|
isAiSourceType,
|
|
56
64
|
labelHtml,
|
|
57
65
|
labelText,
|
|
66
|
+
markFile,
|
|
58
67
|
markImage,
|
|
68
|
+
markMedia,
|
|
59
69
|
markingAttributes,
|
|
60
70
|
markingJsonLd,
|
|
61
71
|
markingMetaTags,
|
|
@@ -63,6 +73,8 @@ export {
|
|
|
63
73
|
nextMetadata,
|
|
64
74
|
parseSourceType,
|
|
65
75
|
readImageMarking,
|
|
76
|
+
readMarking,
|
|
77
|
+
readMediaMarking,
|
|
66
78
|
readTextWatermark,
|
|
67
79
|
registerLocale,
|
|
68
80
|
registeredLocales,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sweberdev/witness",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AI transparency kit for Article 50 of the EU AI Act: chatbot notices, AI labels in DE/EN/FR/IT, IPTC/XMP marking for images, an invisible text watermark and Vercel AI SDK middleware.",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "AI transparency kit for Article 50 of the EU AI Act: chatbot notices, AI labels in DE/EN/FR/IT, IPTC/XMP marking for images, audio and video, an invisible text watermark and Vercel AI SDK middleware.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Seya Weber (https://github.com/sxwxbxr)",
|
|
7
7
|
"homepage": "https://packages.sweber.dev/witness/docs",
|
package/src/cli.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, extname, join } from "node:path";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
|
-
import { detectImageFormat
|
|
4
|
+
import { detectImageFormat } from "./image.js";
|
|
5
5
|
import { iptcSourceType } from "./marking.js";
|
|
6
|
+
import { detectMediaFormat, markFile, readMarking } from "./media.js";
|
|
6
7
|
import type { ContentKind, MarkingInput } from "./types.js";
|
|
7
8
|
import { readTextWatermark } from "./watermark.js";
|
|
8
9
|
|
|
@@ -11,7 +12,7 @@ export interface CliIo {
|
|
|
11
12
|
err: (line: string) => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
const HELP = `witness: AI Act Article 50 marking for images and text
|
|
15
|
+
const HELP = `witness: AI Act Article 50 marking for images, audio, video and text
|
|
15
16
|
|
|
16
17
|
Usage
|
|
17
18
|
witness mark <files...> (--out <dir> | --in-place) [options]
|
|
@@ -30,7 +31,8 @@ inspect options
|
|
|
30
31
|
--json one JSON object per file
|
|
31
32
|
--require exit 1 if a file carries no AI marking
|
|
32
33
|
|
|
33
|
-
Images: PNG, JPEG, WebP
|
|
34
|
+
Images: PNG, JPEG, WebP. Audio and video: MP3, WAV, MP4, MOV, M4A.
|
|
35
|
+
All get XMP with the IPTC Digital Source Type.
|
|
34
36
|
Text files are checked for the Witness text watermark.`;
|
|
35
37
|
|
|
36
38
|
const KINDS: ContentKind[] = ["generated", "edited", "deepfake"];
|
|
@@ -86,11 +88,11 @@ async function mark(argv: string[], io: CliIo): Promise<number> {
|
|
|
86
88
|
let failed = 0;
|
|
87
89
|
for (const file of positionals) {
|
|
88
90
|
const bytes = new Uint8Array(await readFile(file));
|
|
89
|
-
const result =
|
|
91
|
+
const result = markFile(bytes, input, {
|
|
90
92
|
c2pa: values["overwrite-c2pa"] ? "overwrite" : "skip",
|
|
91
93
|
});
|
|
92
94
|
if (result.status === "unsupported") {
|
|
93
|
-
io.err(`${file}: not a PNG, JPEG or
|
|
95
|
+
io.err(`${file}: not a PNG, JPEG, WebP, MP3, WAV or MP4 file, skipped`);
|
|
94
96
|
failed++;
|
|
95
97
|
continue;
|
|
96
98
|
}
|
|
@@ -115,9 +117,9 @@ async function inspect(argv: string[], io: CliIo): Promise<number> {
|
|
|
115
117
|
let unmarked = 0;
|
|
116
118
|
for (const file of positionals) {
|
|
117
119
|
const bytes = new Uint8Array(await readFile(file));
|
|
118
|
-
const format = detectImageFormat(bytes);
|
|
120
|
+
const format = detectImageFormat(bytes) ?? detectMediaFormat(bytes);
|
|
119
121
|
if (format) {
|
|
120
|
-
const info =
|
|
122
|
+
const info = readMarking(bytes);
|
|
121
123
|
const marked = info.aiGenerated || info.c2pa;
|
|
122
124
|
if (!marked) unmarked++;
|
|
123
125
|
if (values.json) {
|
package/src/elements.ts
CHANGED
|
@@ -103,6 +103,8 @@ export class WitnessNoticeElement extends BaseElement {
|
|
|
103
103
|
#disclosure: Disclosure | null = null;
|
|
104
104
|
#unsubscribe: (() => void) | null = null;
|
|
105
105
|
#expanded = false;
|
|
106
|
+
/** `id@version` of the disclosure a `shown` event was sent for, so attribute changes don't repeat it. */
|
|
107
|
+
#shownFor: string | null = null;
|
|
106
108
|
|
|
107
109
|
connectedCallback(): void {
|
|
108
110
|
if (!this.shadowRoot) this.attachShadow({ mode: "open" });
|
|
@@ -114,7 +116,8 @@ export class WitnessNoticeElement extends BaseElement {
|
|
|
114
116
|
this.#unsubscribe = null;
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
attributeChangedCallback(): void {
|
|
119
|
+
attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void {
|
|
120
|
+
if (oldValue === newValue) return;
|
|
118
121
|
if (this.isConnected && this.shadowRoot) this.#setup();
|
|
119
122
|
}
|
|
120
123
|
|
|
@@ -189,7 +192,11 @@ export class WitnessNoticeElement extends BaseElement {
|
|
|
189
192
|
),
|
|
190
193
|
);
|
|
191
194
|
root.replaceChildren(style, box);
|
|
192
|
-
disclosure.
|
|
195
|
+
const shownFor = `${disclosure.id}@${disclosure.version}`;
|
|
196
|
+
if (this.#shownFor !== shownFor) {
|
|
197
|
+
this.#shownFor = shownFor;
|
|
198
|
+
disclosure.markShown();
|
|
199
|
+
}
|
|
193
200
|
} else {
|
|
194
201
|
const reopen = h(
|
|
195
202
|
doc,
|
package/src/image.ts
CHANGED
|
@@ -50,7 +50,8 @@ export interface ImageMarkingInfo {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const XMP_NS = "http://ns.adobe.com/xap/1.0/\0";
|
|
53
|
-
|
|
53
|
+
/** @internal */
|
|
54
|
+
export const WITNESS_NS = "https://packages.sweber.dev/witness/ns/1.0/";
|
|
54
55
|
|
|
55
56
|
/** Detects PNG, JPEG or WebP from the first bytes. */
|
|
56
57
|
export function detectImageFormat(bytes: Uint8Array): ImageFormat | null {
|
|
@@ -165,8 +166,8 @@ function buildDescription(m: Marking): string {
|
|
|
165
166
|
: `<rdf:Description${attrs}/>`;
|
|
166
167
|
}
|
|
167
168
|
|
|
168
|
-
/** Reads a simple property in attribute or element form. */
|
|
169
|
-
function xmpProperty(xmp: string, name: string): string | undefined {
|
|
169
|
+
/** Reads a simple property in attribute or element form. @internal */
|
|
170
|
+
export function xmpProperty(xmp: string, name: string): string | undefined {
|
|
170
171
|
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
171
172
|
const attribute = new RegExp(`${escaped}="([^"]*)"`).exec(xmp);
|
|
172
173
|
if (attribute?.[1] !== undefined) return unescapeXml(attribute[1]);
|
package/src/index.ts
CHANGED
|
@@ -43,6 +43,19 @@ export {
|
|
|
43
43
|
renderMetaTags,
|
|
44
44
|
schemaOrgSourceType,
|
|
45
45
|
} from "./marking.js";
|
|
46
|
+
export {
|
|
47
|
+
detectMediaFormat,
|
|
48
|
+
type MarkFileResult,
|
|
49
|
+
type MarkingInfo,
|
|
50
|
+
type MarkMediaOptions,
|
|
51
|
+
type MarkMediaResult,
|
|
52
|
+
type MediaFormat,
|
|
53
|
+
type MediaMarkingInfo,
|
|
54
|
+
markFile,
|
|
55
|
+
markMedia,
|
|
56
|
+
readMarking,
|
|
57
|
+
readMediaMarking,
|
|
58
|
+
} from "./media.js";
|
|
46
59
|
export {
|
|
47
60
|
type ContentKind,
|
|
48
61
|
DISCLOSURE_KINDS,
|