@affectively/synthetic-watermark 1.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [1.0.0] - 2026-01-25
6
+
7
+ ### Added
8
+
9
+ - Initial release
10
+ - PNG image watermarking using iTXt chunks
11
+ - MP3 audio watermarking using ID3v2 COMM frames
12
+ - Watermark detection for both formats
13
+ - Convenience functions for common AI sources (DALL-E, Gemini, etc.)
14
+ - Full TypeScript support
15
+ - Zero external dependencies
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AFFECTIVELY
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # @affectively/synthetic-watermark
2
+
3
+ Invisible watermarking for AI-generated content. Embeds synthetic origin markers into PNG images and MP3 audio files for authenticity verification and AI safety compliance.
4
+
5
+ ## Features
6
+
7
+ - **Image Watermarking** - Embeds invisible metadata into PNG files using iTXt chunks
8
+ - **Audio Watermarking** - Embeds metadata into MP3 files using ID3v2 tags
9
+ - **Detection** - Verify if content has synthetic origin markers
10
+ - **Zero Dependencies** - Uses only Node.js Buffer API
11
+ - **Non-Destructive** - Watermarks are stored in metadata, not visible/audible content
12
+ - **AI Safety** - Helps identify AI-generated content for authenticity verification
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @affectively/synthetic-watermark
18
+ # or
19
+ bun add @affectively/synthetic-watermark
20
+ # or
21
+ yarn add @affectively/synthetic-watermark
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### Image Watermarking
27
+
28
+ ```typescript
29
+ import {
30
+ embedImageWatermark,
31
+ detectSyntheticImageWatermark,
32
+ } from '@affectively/synthetic-watermark';
33
+
34
+ // Watermark an AI-generated image
35
+ const pngBuffer = await fs.readFile('ai-generated-image.png');
36
+ const watermarkedImage = embedImageWatermark(pngBuffer, 'png', {
37
+ source: 'dalle',
38
+ model: 'dall-e-3',
39
+ platform: 'MyApp',
40
+ });
41
+
42
+ // Save the watermarked image
43
+ await fs.writeFile('watermarked-image.png', watermarkedImage);
44
+
45
+ // Detect watermark
46
+ const detected = detectSyntheticImageWatermark(watermarkedImage);
47
+ if (detected) {
48
+ console.log('Synthetic origin detected:', detected);
49
+ // { platform: 'MyApp', source: 'dalle', model: 'dall-e-3', timestamp: 1234567890 }
50
+ }
51
+ ```
52
+
53
+ ### Audio Watermarking
54
+
55
+ ```typescript
56
+ import {
57
+ embedAudioWatermark,
58
+ detectSyntheticWatermark,
59
+ } from '@affectively/synthetic-watermark';
60
+
61
+ // Watermark AI-generated audio
62
+ const mp3Buffer = await fs.readFile('tts-audio.mp3');
63
+ const watermarkedAudio = embedAudioWatermark(mp3Buffer, 'mp3', {
64
+ source: 'tts',
65
+ platform: 'MyApp',
66
+ });
67
+
68
+ // Save the watermarked audio
69
+ await fs.writeFile('watermarked-audio.mp3', watermarkedAudio);
70
+
71
+ // Detect watermark
72
+ const detected = detectSyntheticWatermark(watermarkedAudio);
73
+ if (detected) {
74
+ console.log('Synthetic audio detected:', detected);
75
+ // { platform: 'MyApp', source: 'tts', timestamp: 1234567890 }
76
+ }
77
+ ```
78
+
79
+ ## Convenience Functions
80
+
81
+ ### For Images
82
+
83
+ ```typescript
84
+ import {
85
+ watermarkDallEImage,
86
+ watermarkGeminiImage,
87
+ watermarkDigitalTwinImage,
88
+ watermarkCyranoImage,
89
+ } from '@affectively/synthetic-watermark';
90
+
91
+ // Pre-configured for specific AI image generators
92
+ const watermarked = watermarkDallEImage(imageBuffer, 'png', optionalUserIdHash);
93
+ ```
94
+
95
+ ### For Audio
96
+
97
+ ```typescript
98
+ import {
99
+ watermarkDigitalTwinAudio,
100
+ watermarkHologramAudio,
101
+ watermarkCyranoAudio,
102
+ } from '@affectively/synthetic-watermark';
103
+
104
+ // Pre-configured for specific TTS systems
105
+ const watermarked = watermarkCyranoAudio(audioBuffer, 'mp3');
106
+ ```
107
+
108
+ ## Watermark Format
109
+
110
+ ### Image Watermark (PNG iTXt chunk)
111
+
112
+ ```
113
+ SyntheticOrigin: SYNTHETIC_IMAGE|platform|source|timestamp|userIdHash|model
114
+ ```
115
+
116
+ ### Audio Watermark (ID3v2 COMM frame)
117
+
118
+ ```
119
+ SYNTHETIC_ORIGIN: SYNTHETIC_AUDIO|platform|source|timestamp|userIdHash
120
+ ```
121
+
122
+ ## API Reference
123
+
124
+ ### Image Functions
125
+
126
+ #### `embedImageWatermark(buffer, format, config?)`
127
+
128
+ Embeds an invisible watermark into a PNG image.
129
+
130
+ - `buffer` - Image data (Uint8Array)
131
+ - `format` - Image format (currently only 'png' supported)
132
+ - `config` - Optional configuration:
133
+ - `platform` - Platform identifier (default: 'SYNTHETIC')
134
+ - `source` - Source system (e.g., 'dalle', 'gemini')
135
+ - `model` - Model name (e.g., 'dall-e-3')
136
+ - `userIdHash` - Hashed user ID for audit trail
137
+
138
+ Returns: Watermarked image buffer
139
+
140
+ #### `detectSyntheticImageWatermark(buffer)`
141
+
142
+ Detects and extracts watermark from PNG image.
143
+
144
+ Returns: `ImageWatermarkConfig | null`
145
+
146
+ ### Audio Functions
147
+
148
+ #### `embedAudioWatermark(buffer, format, config?)`
149
+
150
+ Embeds an invisible watermark into MP3 audio.
151
+
152
+ - `buffer` - Audio data (Buffer)
153
+ - `format` - Audio format (currently only 'mp3' supported)
154
+ - `config` - Optional configuration:
155
+ - `platform` - Platform identifier (default: 'SYNTHETIC')
156
+ - `source` - Source system (e.g., 'tts', 'hologram')
157
+ - `userIdHash` - Hashed user ID for audit trail
158
+
159
+ Returns: Watermarked audio buffer
160
+
161
+ #### `detectSyntheticWatermark(buffer)`
162
+
163
+ Detects and extracts watermark from MP3 audio.
164
+
165
+ Returns: `WatermarkConfig | null`
166
+
167
+ ## Use Cases
168
+
169
+ 1. **AI Safety Compliance** - Mark AI-generated content for authenticity verification
170
+ 2. **Content Authentication** - Verify the origin of synthetic media
171
+ 3. **Audit Trail** - Track when and by whom content was generated
172
+ 4. **Platform Trust** - Help users identify AI-generated content
173
+ 5. **Regulatory Compliance** - Support emerging AI content disclosure requirements
174
+
175
+ ## Technical Details
176
+
177
+ ### PNG Watermarking
178
+
179
+ - Uses iTXt (International Text) chunk per PNG specification
180
+ - Inserted before IEND chunk
181
+ - Includes proper CRC32 checksum
182
+ - Non-destructive to image data
183
+
184
+ ### MP3 Watermarking
185
+
186
+ - Uses ID3v2.3 COMM (Comment) frame
187
+ - Replaces existing ID3 tag if present
188
+ - Syncsafe integer encoding for size
189
+ - Compatible with standard ID3 readers
190
+
191
+ ## Browser Support
192
+
193
+ Works in Node.js and browser environments (requires Buffer polyfill in browser).
194
+
195
+ ## License
196
+
197
+ MIT © AFFECTIVELY
@@ -0,0 +1,130 @@
1
+ import { Buffer } from 'buffer';
2
+
3
+ /**
4
+ * Image Watermarking Service
5
+ *
6
+ * Embeds INVISIBLE metadata into generated images to indicate
7
+ * "Synthetic Origin" - marking AI-generated images for safety/authenticity.
8
+ *
9
+ * These watermarks are completely invisible to users.
10
+ * They are stored in the PNG file's metadata chunks (iTXt/tEXt),
11
+ * not rendered on the image itself. Similar to EXIF data in photos.
12
+ *
13
+ * For PNG format: Uses ancillary iTXt chunk (international text)
14
+ * For JPEG format: Could use EXIF/XMP metadata (future)
15
+ * For WebP format: Could use XMP metadata (future)
16
+ */
17
+
18
+ /**
19
+ * Watermark configuration
20
+ */
21
+ interface ImageWatermarkConfig {
22
+ /** Platform identifier */
23
+ platform: string;
24
+ /** Timestamp of generation */
25
+ timestamp: number;
26
+ /** Source system (e.g., 'dalle', 'imagen', 'gemini', 'digital_twin') */
27
+ source: string;
28
+ /** Optional user ID (hashed) for audit trail */
29
+ userIdHash?: string;
30
+ /** Optional model name */
31
+ model?: string;
32
+ }
33
+ /**
34
+ * Embed INVISIBLE watermark into image buffer
35
+ *
36
+ * For PNG: Inserts iTXt chunk before IEND
37
+ * For other formats: Passes through unchanged (future: EXIF/XMP)
38
+ *
39
+ * The watermark is stored in metadata, NOT visible on the image.
40
+ *
41
+ * @param imageBuffer - Raw image data
42
+ * @param format - Image format (png, jpeg, webp)
43
+ * @param config - Watermark configuration
44
+ * @returns Watermarked image buffer
45
+ */
46
+ declare function embedImageWatermark(imageBuffer: Uint8Array, format?: string, config?: Partial<ImageWatermarkConfig>): Buffer;
47
+ /**
48
+ * Detect synthetic origin watermark from image
49
+ *
50
+ * @param imageBuffer - Image data to check
51
+ * @returns Watermark info if found, null otherwise
52
+ */
53
+ declare function detectSyntheticImageWatermark(imageBuffer: Buffer): ImageWatermarkConfig | null;
54
+ /**
55
+ * Convenience function for DALL-E generated images
56
+ */
57
+ declare function watermarkDallEImage(imageBuffer: Uint8Array, format?: string, userIdHash?: string): Buffer;
58
+ /**
59
+ * Convenience function for Gemini Imagen images
60
+ */
61
+ declare function watermarkGeminiImage(imageBuffer: Uint8Array, format?: string, userIdHash?: string): Buffer;
62
+ /**
63
+ * Convenience function for Digital Twin generated images
64
+ */
65
+ declare function watermarkDigitalTwinImage(imageBuffer: Uint8Array, format?: string, userIdHash?: string): Buffer;
66
+ /**
67
+ * Convenience function for Cyrano-generated images
68
+ */
69
+ declare function watermarkCyranoImage(imageBuffer: Uint8Array, format?: string, userIdHash?: string): Buffer;
70
+
71
+ /**
72
+ * Audio Watermarking Service
73
+ *
74
+ * Embeds invisible/inaudible metadata into synthesized audio to indicate
75
+ * "Synthetic Origin" - marking AI-generated audio for safety and authenticity.
76
+ *
77
+ * Approach: Adds a metadata comment to the MP3 ID3 tag or embeds a
78
+ * high-frequency inaudible tone pattern (above 18kHz) that serves as
79
+ * a digital fingerprint.
80
+ *
81
+ * For MP3 format: Uses ID3 tag metadata injection
82
+ * For raw formats: Could use frequency-domain watermarking (future)
83
+ */
84
+
85
+ /**
86
+ * Watermark configuration
87
+ */
88
+ interface WatermarkConfig {
89
+ /** Platform identifier */
90
+ platform: string;
91
+ /** Timestamp of generation */
92
+ timestamp: number;
93
+ /** Source system (e.g., 'cyrano', 'hologram', 'twin') */
94
+ source: string;
95
+ /** Optional user ID (hashed) for audit trail */
96
+ userIdHash?: string;
97
+ }
98
+ /**
99
+ * Embed watermark into audio buffer
100
+ *
101
+ * For MP3: Prepends ID3v2 tag with synthetic origin metadata
102
+ * For other formats: Passes through unchanged (future: frequency watermarking)
103
+ *
104
+ * @param audioBuffer - Raw audio data
105
+ * @param format - Audio format (mp3, wav, etc.)
106
+ * @param config - Watermark configuration
107
+ * @returns Watermarked audio buffer
108
+ */
109
+ declare function embedAudioWatermark(audioBuffer: Buffer, format?: string, config?: Partial<WatermarkConfig>): Buffer;
110
+ /**
111
+ * Check if audio has synthetic origin watermark
112
+ *
113
+ * @param audioBuffer - Audio data to check
114
+ * @returns Watermark info if found, null otherwise
115
+ */
116
+ declare function detectSyntheticWatermark(audioBuffer: Buffer): WatermarkConfig | null;
117
+ /**
118
+ * Convenience function for Digital Twin audio
119
+ */
120
+ declare function watermarkDigitalTwinAudio(audioBuffer: Buffer, format?: string, userIdHash?: string): Buffer;
121
+ /**
122
+ * Convenience function for Hologram audio
123
+ */
124
+ declare function watermarkHologramAudio(audioBuffer: Buffer, format?: string): Buffer;
125
+ /**
126
+ * Convenience function for Cyrano TTS audio
127
+ */
128
+ declare function watermarkCyranoAudio(audioBuffer: Buffer, format?: string): Buffer;
129
+
130
+ export { type ImageWatermarkConfig, type WatermarkConfig, detectSyntheticImageWatermark, detectSyntheticWatermark, embedAudioWatermark, embedImageWatermark, watermarkCyranoAudio, watermarkCyranoImage, watermarkDallEImage, watermarkDigitalTwinAudio, watermarkDigitalTwinImage, watermarkGeminiImage, watermarkHologramAudio };
@@ -0,0 +1,130 @@
1
+ import { Buffer } from 'buffer';
2
+
3
+ /**
4
+ * Image Watermarking Service
5
+ *
6
+ * Embeds INVISIBLE metadata into generated images to indicate
7
+ * "Synthetic Origin" - marking AI-generated images for safety/authenticity.
8
+ *
9
+ * These watermarks are completely invisible to users.
10
+ * They are stored in the PNG file's metadata chunks (iTXt/tEXt),
11
+ * not rendered on the image itself. Similar to EXIF data in photos.
12
+ *
13
+ * For PNG format: Uses ancillary iTXt chunk (international text)
14
+ * For JPEG format: Could use EXIF/XMP metadata (future)
15
+ * For WebP format: Could use XMP metadata (future)
16
+ */
17
+
18
+ /**
19
+ * Watermark configuration
20
+ */
21
+ interface ImageWatermarkConfig {
22
+ /** Platform identifier */
23
+ platform: string;
24
+ /** Timestamp of generation */
25
+ timestamp: number;
26
+ /** Source system (e.g., 'dalle', 'imagen', 'gemini', 'digital_twin') */
27
+ source: string;
28
+ /** Optional user ID (hashed) for audit trail */
29
+ userIdHash?: string;
30
+ /** Optional model name */
31
+ model?: string;
32
+ }
33
+ /**
34
+ * Embed INVISIBLE watermark into image buffer
35
+ *
36
+ * For PNG: Inserts iTXt chunk before IEND
37
+ * For other formats: Passes through unchanged (future: EXIF/XMP)
38
+ *
39
+ * The watermark is stored in metadata, NOT visible on the image.
40
+ *
41
+ * @param imageBuffer - Raw image data
42
+ * @param format - Image format (png, jpeg, webp)
43
+ * @param config - Watermark configuration
44
+ * @returns Watermarked image buffer
45
+ */
46
+ declare function embedImageWatermark(imageBuffer: Uint8Array, format?: string, config?: Partial<ImageWatermarkConfig>): Buffer;
47
+ /**
48
+ * Detect synthetic origin watermark from image
49
+ *
50
+ * @param imageBuffer - Image data to check
51
+ * @returns Watermark info if found, null otherwise
52
+ */
53
+ declare function detectSyntheticImageWatermark(imageBuffer: Buffer): ImageWatermarkConfig | null;
54
+ /**
55
+ * Convenience function for DALL-E generated images
56
+ */
57
+ declare function watermarkDallEImage(imageBuffer: Uint8Array, format?: string, userIdHash?: string): Buffer;
58
+ /**
59
+ * Convenience function for Gemini Imagen images
60
+ */
61
+ declare function watermarkGeminiImage(imageBuffer: Uint8Array, format?: string, userIdHash?: string): Buffer;
62
+ /**
63
+ * Convenience function for Digital Twin generated images
64
+ */
65
+ declare function watermarkDigitalTwinImage(imageBuffer: Uint8Array, format?: string, userIdHash?: string): Buffer;
66
+ /**
67
+ * Convenience function for Cyrano-generated images
68
+ */
69
+ declare function watermarkCyranoImage(imageBuffer: Uint8Array, format?: string, userIdHash?: string): Buffer;
70
+
71
+ /**
72
+ * Audio Watermarking Service
73
+ *
74
+ * Embeds invisible/inaudible metadata into synthesized audio to indicate
75
+ * "Synthetic Origin" - marking AI-generated audio for safety and authenticity.
76
+ *
77
+ * Approach: Adds a metadata comment to the MP3 ID3 tag or embeds a
78
+ * high-frequency inaudible tone pattern (above 18kHz) that serves as
79
+ * a digital fingerprint.
80
+ *
81
+ * For MP3 format: Uses ID3 tag metadata injection
82
+ * For raw formats: Could use frequency-domain watermarking (future)
83
+ */
84
+
85
+ /**
86
+ * Watermark configuration
87
+ */
88
+ interface WatermarkConfig {
89
+ /** Platform identifier */
90
+ platform: string;
91
+ /** Timestamp of generation */
92
+ timestamp: number;
93
+ /** Source system (e.g., 'cyrano', 'hologram', 'twin') */
94
+ source: string;
95
+ /** Optional user ID (hashed) for audit trail */
96
+ userIdHash?: string;
97
+ }
98
+ /**
99
+ * Embed watermark into audio buffer
100
+ *
101
+ * For MP3: Prepends ID3v2 tag with synthetic origin metadata
102
+ * For other formats: Passes through unchanged (future: frequency watermarking)
103
+ *
104
+ * @param audioBuffer - Raw audio data
105
+ * @param format - Audio format (mp3, wav, etc.)
106
+ * @param config - Watermark configuration
107
+ * @returns Watermarked audio buffer
108
+ */
109
+ declare function embedAudioWatermark(audioBuffer: Buffer, format?: string, config?: Partial<WatermarkConfig>): Buffer;
110
+ /**
111
+ * Check if audio has synthetic origin watermark
112
+ *
113
+ * @param audioBuffer - Audio data to check
114
+ * @returns Watermark info if found, null otherwise
115
+ */
116
+ declare function detectSyntheticWatermark(audioBuffer: Buffer): WatermarkConfig | null;
117
+ /**
118
+ * Convenience function for Digital Twin audio
119
+ */
120
+ declare function watermarkDigitalTwinAudio(audioBuffer: Buffer, format?: string, userIdHash?: string): Buffer;
121
+ /**
122
+ * Convenience function for Hologram audio
123
+ */
124
+ declare function watermarkHologramAudio(audioBuffer: Buffer, format?: string): Buffer;
125
+ /**
126
+ * Convenience function for Cyrano TTS audio
127
+ */
128
+ declare function watermarkCyranoAudio(audioBuffer: Buffer, format?: string): Buffer;
129
+
130
+ export { type ImageWatermarkConfig, type WatermarkConfig, detectSyntheticImageWatermark, detectSyntheticWatermark, embedAudioWatermark, embedImageWatermark, watermarkCyranoAudio, watermarkCyranoImage, watermarkDallEImage, watermarkDigitalTwinAudio, watermarkDigitalTwinImage, watermarkGeminiImage, watermarkHologramAudio };
package/dist/index.js ADDED
@@ -0,0 +1,364 @@
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
+ detectSyntheticImageWatermark: () => detectSyntheticImageWatermark,
24
+ detectSyntheticWatermark: () => detectSyntheticWatermark,
25
+ embedAudioWatermark: () => embedAudioWatermark,
26
+ embedImageWatermark: () => embedImageWatermark,
27
+ watermarkCyranoAudio: () => watermarkCyranoAudio,
28
+ watermarkCyranoImage: () => watermarkCyranoImage,
29
+ watermarkDallEImage: () => watermarkDallEImage,
30
+ watermarkDigitalTwinAudio: () => watermarkDigitalTwinAudio,
31
+ watermarkDigitalTwinImage: () => watermarkDigitalTwinImage,
32
+ watermarkGeminiImage: () => watermarkGeminiImage,
33
+ watermarkHologramAudio: () => watermarkHologramAudio
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/imageWatermark.ts
38
+ var import_buffer = require("buffer");
39
+ var DEFAULT_WATERMARK = {
40
+ platform: "SYNTHETIC",
41
+ source: "ai_generated"
42
+ };
43
+ var PNG_SIGNATURE = import_buffer.Buffer.from([
44
+ 137,
45
+ 80,
46
+ 78,
47
+ 71,
48
+ 13,
49
+ 10,
50
+ 26,
51
+ 10
52
+ ]);
53
+ function isPNG(buffer) {
54
+ if (buffer.length < 8) return false;
55
+ for (let i = 0; i < 8; i++) {
56
+ if (buffer[i] !== PNG_SIGNATURE[i]) return false;
57
+ }
58
+ return true;
59
+ }
60
+ function crc32(data) {
61
+ const table = [];
62
+ for (let n = 0; n < 256; n++) {
63
+ let c = n;
64
+ for (let k = 0; k < 8; k++) {
65
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
66
+ }
67
+ table[n] = c >>> 0;
68
+ }
69
+ let crc = 4294967295;
70
+ for (let i = 0; i < data.length; i++) {
71
+ crc = table[(crc ^ data[i]) & 255] ^ crc >>> 8;
72
+ }
73
+ return (crc ^ 4294967295) >>> 0;
74
+ }
75
+ function createiTXtChunk(config) {
76
+ const keyword = "SyntheticOrigin";
77
+ const text = `SYNTHETIC_IMAGE|${config.platform}|${config.source}|${config.timestamp}|${config.userIdHash || ""}|${config.model || ""}`;
78
+ const keywordBuf = import_buffer.Buffer.from(keyword + "\0");
79
+ const compressionFlag = import_buffer.Buffer.from([0]);
80
+ const compressionMethod = import_buffer.Buffer.from([0]);
81
+ const languageTag = import_buffer.Buffer.from("en\0");
82
+ const translatedKeyword = import_buffer.Buffer.from("\0");
83
+ const textBuf = import_buffer.Buffer.from(text);
84
+ const chunkData = import_buffer.Buffer.concat([
85
+ keywordBuf,
86
+ compressionFlag,
87
+ compressionMethod,
88
+ languageTag,
89
+ translatedKeyword,
90
+ textBuf
91
+ ]);
92
+ const chunkType = import_buffer.Buffer.from("iTXt");
93
+ const typeAndData = import_buffer.Buffer.concat([
94
+ chunkType,
95
+ chunkData
96
+ ]);
97
+ const crcValue = crc32(new Uint8Array(typeAndData));
98
+ const crcBuf = import_buffer.Buffer.alloc(4);
99
+ crcBuf.writeUInt32BE(crcValue, 0);
100
+ const lengthBuf = import_buffer.Buffer.alloc(4);
101
+ lengthBuf.writeUInt32BE(chunkData.length, 0);
102
+ const parts = [
103
+ lengthBuf,
104
+ chunkType,
105
+ chunkData,
106
+ crcBuf
107
+ ];
108
+ return import_buffer.Buffer.concat(parts);
109
+ }
110
+ function findIENDPosition(buffer) {
111
+ const iendMarker = import_buffer.Buffer.from("IEND");
112
+ for (let i = 8; i < buffer.length - 8; i++) {
113
+ const slice = buffer.subarray(i + 4, i + 8);
114
+ if (import_buffer.Buffer.from(slice).equals(iendMarker)) {
115
+ return i;
116
+ }
117
+ }
118
+ return -1;
119
+ }
120
+ function embedImageWatermark(imageBuffer, format = "png", config = {}) {
121
+ const fullConfig = {
122
+ ...DEFAULT_WATERMARK,
123
+ timestamp: Date.now(),
124
+ ...config
125
+ };
126
+ const normalizedFormat = format.toLowerCase().replace("image/", "");
127
+ if (normalizedFormat !== "png") {
128
+ return import_buffer.Buffer.from(imageBuffer);
129
+ }
130
+ if (!isPNG(imageBuffer)) {
131
+ return import_buffer.Buffer.from(imageBuffer);
132
+ }
133
+ try {
134
+ const iendPos = findIENDPosition(imageBuffer);
135
+ if (iendPos === -1) {
136
+ return import_buffer.Buffer.from(imageBuffer);
137
+ }
138
+ const watermarkChunk = createiTXtChunk(fullConfig);
139
+ const beforeIEND = imageBuffer.subarray(0, iendPos);
140
+ const iendAndAfter = imageBuffer.subarray(iendPos);
141
+ const parts = [
142
+ import_buffer.Buffer.from(beforeIEND),
143
+ watermarkChunk,
144
+ import_buffer.Buffer.from(iendAndAfter)
145
+ ];
146
+ return import_buffer.Buffer.concat(parts);
147
+ } catch {
148
+ return import_buffer.Buffer.from(imageBuffer);
149
+ }
150
+ }
151
+ function detectSyntheticImageWatermark(imageBuffer) {
152
+ const bufferArray = new Uint8Array(imageBuffer);
153
+ if (!isPNG(bufferArray) || imageBuffer.length < 20) {
154
+ return null;
155
+ }
156
+ try {
157
+ const itxtMarker = import_buffer.Buffer.from("iTXt");
158
+ let pos = 8;
159
+ while (pos < imageBuffer.length - 12) {
160
+ const chunkLength = imageBuffer.readUInt32BE(pos);
161
+ const chunkType = imageBuffer.subarray(pos + 4, pos + 8).toString();
162
+ if (chunkType === "iTXt") {
163
+ const chunkData = imageBuffer.subarray(pos + 8, pos + 8 + chunkLength);
164
+ const chunkStr = chunkData.toString();
165
+ if (chunkStr.includes("SyntheticOrigin") && chunkStr.includes("SYNTHETIC_IMAGE|")) {
166
+ const markerIdx = chunkStr.indexOf("SYNTHETIC_IMAGE|");
167
+ const text = chunkStr.substring(markerIdx);
168
+ const parts = text.split("|");
169
+ if (parts.length >= 4) {
170
+ return {
171
+ platform: parts[1],
172
+ source: parts[2],
173
+ timestamp: parseInt(parts[3], 10),
174
+ userIdHash: parts[4] || void 0,
175
+ model: parts[5] || void 0
176
+ };
177
+ }
178
+ }
179
+ }
180
+ pos += 4 + 4 + chunkLength + 4;
181
+ if (chunkType === "IEND") break;
182
+ }
183
+ } catch {
184
+ }
185
+ return null;
186
+ }
187
+ function watermarkDallEImage(imageBuffer, format = "png", userIdHash) {
188
+ return embedImageWatermark(imageBuffer, format, {
189
+ source: "dalle",
190
+ model: "dall-e",
191
+ userIdHash
192
+ });
193
+ }
194
+ function watermarkGeminiImage(imageBuffer, format = "png", userIdHash) {
195
+ return embedImageWatermark(imageBuffer, format, {
196
+ source: "gemini",
197
+ model: "imagen",
198
+ userIdHash
199
+ });
200
+ }
201
+ function watermarkDigitalTwinImage(imageBuffer, format = "png", userIdHash) {
202
+ return embedImageWatermark(imageBuffer, format, {
203
+ source: "digital_twin",
204
+ userIdHash
205
+ });
206
+ }
207
+ function watermarkCyranoImage(imageBuffer, format = "png", userIdHash) {
208
+ return embedImageWatermark(imageBuffer, format, {
209
+ source: "cyrano",
210
+ userIdHash
211
+ });
212
+ }
213
+
214
+ // src/audioWatermark.ts
215
+ var import_buffer2 = require("buffer");
216
+ var DEFAULT_WATERMARK2 = {
217
+ platform: "SYNTHETIC",
218
+ source: "tts"
219
+ };
220
+ function createID3WatermarkTag(config) {
221
+ const comment = `SYNTHETIC_AUDIO|${config.platform}|${config.source}|${config.timestamp}${config.userIdHash ? `|${config.userIdHash}` : ""}`;
222
+ const encoding = import_buffer2.Buffer.from([0]);
223
+ const language = import_buffer2.Buffer.from("eng");
224
+ const shortDesc = import_buffer2.Buffer.from("SYNTHETIC_ORIGIN\0", "binary");
225
+ const text = import_buffer2.Buffer.concat([
226
+ import_buffer2.Buffer.from(comment, "utf-8"),
227
+ import_buffer2.Buffer.from([0])
228
+ ]);
229
+ const frameContent = import_buffer2.Buffer.concat([
230
+ encoding,
231
+ language,
232
+ shortDesc,
233
+ text
234
+ ]);
235
+ const frameId = import_buffer2.Buffer.from("COMM");
236
+ const frameSize = import_buffer2.Buffer.alloc(4);
237
+ frameSize.writeUInt32BE(frameContent.length, 0);
238
+ const frameFlags = import_buffer2.Buffer.from([0, 0]);
239
+ const frame = import_buffer2.Buffer.concat([
240
+ frameId,
241
+ frameSize,
242
+ frameFlags,
243
+ frameContent
244
+ ]);
245
+ const id3Header = import_buffer2.Buffer.from([
246
+ 73,
247
+ 68,
248
+ 51,
249
+ // "ID3"
250
+ 3,
251
+ 0,
252
+ // Version 2.3
253
+ 0
254
+ // Flags
255
+ ]);
256
+ const size = frame.length;
257
+ const syncsafeSize = import_buffer2.Buffer.alloc(4);
258
+ syncsafeSize[0] = size >> 21 & 127;
259
+ syncsafeSize[1] = size >> 14 & 127;
260
+ syncsafeSize[2] = size >> 7 & 127;
261
+ syncsafeSize[3] = size & 127;
262
+ return import_buffer2.Buffer.concat([
263
+ id3Header,
264
+ syncsafeSize,
265
+ frame
266
+ ]);
267
+ }
268
+ function hasID3Tag(audioBuffer) {
269
+ if (audioBuffer.length < 10) return false;
270
+ return audioBuffer[0] === 73 && // 'I'
271
+ audioBuffer[1] === 68 && // 'D'
272
+ audioBuffer[2] === 51;
273
+ }
274
+ function getID3TagSize(audioBuffer) {
275
+ if (!hasID3Tag(audioBuffer)) return 0;
276
+ const size = (audioBuffer[6] & 127) << 21 | (audioBuffer[7] & 127) << 14 | (audioBuffer[8] & 127) << 7 | audioBuffer[9] & 127;
277
+ return size + 10;
278
+ }
279
+ function embedAudioWatermark(audioBuffer, format = "mp3", config = {}) {
280
+ const fullConfig = {
281
+ ...DEFAULT_WATERMARK2,
282
+ timestamp: Date.now(),
283
+ ...config
284
+ };
285
+ const normalizedFormat = format.toLowerCase();
286
+ if (normalizedFormat !== "mp3") {
287
+ return audioBuffer;
288
+ }
289
+ try {
290
+ let audioData = audioBuffer;
291
+ const bufferArray = new Uint8Array(audioBuffer);
292
+ if (hasID3Tag(bufferArray)) {
293
+ const existingTagSize = getID3TagSize(bufferArray);
294
+ if (existingTagSize <= audioBuffer.length) {
295
+ audioData = audioBuffer.subarray(existingTagSize);
296
+ }
297
+ }
298
+ const watermarkTag = createID3WatermarkTag(fullConfig);
299
+ return import_buffer2.Buffer.concat([watermarkTag, audioData]);
300
+ } catch {
301
+ return audioBuffer;
302
+ }
303
+ }
304
+ function detectSyntheticWatermark(audioBuffer) {
305
+ const bufferArray = new Uint8Array(audioBuffer);
306
+ if (!hasID3Tag(bufferArray)) {
307
+ return null;
308
+ }
309
+ try {
310
+ const searchStr = "SYNTHETIC_ORIGIN";
311
+ const idx = audioBuffer.indexOf(searchStr);
312
+ if (idx === -1) return null;
313
+ const markerEnd = idx + searchStr.length + 1;
314
+ if (markerEnd >= audioBuffer.length) return null;
315
+ const markerStart = audioBuffer.indexOf("SYNTHETIC_AUDIO|", markerEnd);
316
+ if (markerStart === -1) return null;
317
+ let end = markerStart;
318
+ while (end < audioBuffer.length && audioBuffer[end] !== 0) {
319
+ end++;
320
+ }
321
+ const comment = audioBuffer.subarray(markerStart, end).toString("utf-8");
322
+ const parts = comment.split("|");
323
+ if (parts.length >= 4) {
324
+ return {
325
+ platform: parts[1] || "",
326
+ source: parts[2] || "",
327
+ timestamp: parseInt(parts[3] || "0", 10),
328
+ userIdHash: parts[4]
329
+ };
330
+ }
331
+ } catch {
332
+ }
333
+ return null;
334
+ }
335
+ function watermarkDigitalTwinAudio(audioBuffer, format = "mp3", userIdHash) {
336
+ return embedAudioWatermark(audioBuffer, format, {
337
+ source: "digital_twin",
338
+ userIdHash
339
+ });
340
+ }
341
+ function watermarkHologramAudio(audioBuffer, format = "mp3") {
342
+ return embedAudioWatermark(audioBuffer, format, {
343
+ source: "hologram"
344
+ });
345
+ }
346
+ function watermarkCyranoAudio(audioBuffer, format = "mp3") {
347
+ return embedAudioWatermark(audioBuffer, format, {
348
+ source: "cyrano"
349
+ });
350
+ }
351
+ // Annotate the CommonJS export names for ESM import in node:
352
+ 0 && (module.exports = {
353
+ detectSyntheticImageWatermark,
354
+ detectSyntheticWatermark,
355
+ embedAudioWatermark,
356
+ embedImageWatermark,
357
+ watermarkCyranoAudio,
358
+ watermarkCyranoImage,
359
+ watermarkDallEImage,
360
+ watermarkDigitalTwinAudio,
361
+ watermarkDigitalTwinImage,
362
+ watermarkGeminiImage,
363
+ watermarkHologramAudio
364
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,327 @@
1
+ // src/imageWatermark.ts
2
+ import { Buffer } from "buffer";
3
+ var DEFAULT_WATERMARK = {
4
+ platform: "SYNTHETIC",
5
+ source: "ai_generated"
6
+ };
7
+ var PNG_SIGNATURE = Buffer.from([
8
+ 137,
9
+ 80,
10
+ 78,
11
+ 71,
12
+ 13,
13
+ 10,
14
+ 26,
15
+ 10
16
+ ]);
17
+ function isPNG(buffer) {
18
+ if (buffer.length < 8) return false;
19
+ for (let i = 0; i < 8; i++) {
20
+ if (buffer[i] !== PNG_SIGNATURE[i]) return false;
21
+ }
22
+ return true;
23
+ }
24
+ function crc32(data) {
25
+ const table = [];
26
+ for (let n = 0; n < 256; n++) {
27
+ let c = n;
28
+ for (let k = 0; k < 8; k++) {
29
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
30
+ }
31
+ table[n] = c >>> 0;
32
+ }
33
+ let crc = 4294967295;
34
+ for (let i = 0; i < data.length; i++) {
35
+ crc = table[(crc ^ data[i]) & 255] ^ crc >>> 8;
36
+ }
37
+ return (crc ^ 4294967295) >>> 0;
38
+ }
39
+ function createiTXtChunk(config) {
40
+ const keyword = "SyntheticOrigin";
41
+ const text = `SYNTHETIC_IMAGE|${config.platform}|${config.source}|${config.timestamp}|${config.userIdHash || ""}|${config.model || ""}`;
42
+ const keywordBuf = Buffer.from(keyword + "\0");
43
+ const compressionFlag = Buffer.from([0]);
44
+ const compressionMethod = Buffer.from([0]);
45
+ const languageTag = Buffer.from("en\0");
46
+ const translatedKeyword = Buffer.from("\0");
47
+ const textBuf = Buffer.from(text);
48
+ const chunkData = Buffer.concat([
49
+ keywordBuf,
50
+ compressionFlag,
51
+ compressionMethod,
52
+ languageTag,
53
+ translatedKeyword,
54
+ textBuf
55
+ ]);
56
+ const chunkType = Buffer.from("iTXt");
57
+ const typeAndData = Buffer.concat([
58
+ chunkType,
59
+ chunkData
60
+ ]);
61
+ const crcValue = crc32(new Uint8Array(typeAndData));
62
+ const crcBuf = Buffer.alloc(4);
63
+ crcBuf.writeUInt32BE(crcValue, 0);
64
+ const lengthBuf = Buffer.alloc(4);
65
+ lengthBuf.writeUInt32BE(chunkData.length, 0);
66
+ const parts = [
67
+ lengthBuf,
68
+ chunkType,
69
+ chunkData,
70
+ crcBuf
71
+ ];
72
+ return Buffer.concat(parts);
73
+ }
74
+ function findIENDPosition(buffer) {
75
+ const iendMarker = Buffer.from("IEND");
76
+ for (let i = 8; i < buffer.length - 8; i++) {
77
+ const slice = buffer.subarray(i + 4, i + 8);
78
+ if (Buffer.from(slice).equals(iendMarker)) {
79
+ return i;
80
+ }
81
+ }
82
+ return -1;
83
+ }
84
+ function embedImageWatermark(imageBuffer, format = "png", config = {}) {
85
+ const fullConfig = {
86
+ ...DEFAULT_WATERMARK,
87
+ timestamp: Date.now(),
88
+ ...config
89
+ };
90
+ const normalizedFormat = format.toLowerCase().replace("image/", "");
91
+ if (normalizedFormat !== "png") {
92
+ return Buffer.from(imageBuffer);
93
+ }
94
+ if (!isPNG(imageBuffer)) {
95
+ return Buffer.from(imageBuffer);
96
+ }
97
+ try {
98
+ const iendPos = findIENDPosition(imageBuffer);
99
+ if (iendPos === -1) {
100
+ return Buffer.from(imageBuffer);
101
+ }
102
+ const watermarkChunk = createiTXtChunk(fullConfig);
103
+ const beforeIEND = imageBuffer.subarray(0, iendPos);
104
+ const iendAndAfter = imageBuffer.subarray(iendPos);
105
+ const parts = [
106
+ Buffer.from(beforeIEND),
107
+ watermarkChunk,
108
+ Buffer.from(iendAndAfter)
109
+ ];
110
+ return Buffer.concat(parts);
111
+ } catch {
112
+ return Buffer.from(imageBuffer);
113
+ }
114
+ }
115
+ function detectSyntheticImageWatermark(imageBuffer) {
116
+ const bufferArray = new Uint8Array(imageBuffer);
117
+ if (!isPNG(bufferArray) || imageBuffer.length < 20) {
118
+ return null;
119
+ }
120
+ try {
121
+ const itxtMarker = Buffer.from("iTXt");
122
+ let pos = 8;
123
+ while (pos < imageBuffer.length - 12) {
124
+ const chunkLength = imageBuffer.readUInt32BE(pos);
125
+ const chunkType = imageBuffer.subarray(pos + 4, pos + 8).toString();
126
+ if (chunkType === "iTXt") {
127
+ const chunkData = imageBuffer.subarray(pos + 8, pos + 8 + chunkLength);
128
+ const chunkStr = chunkData.toString();
129
+ if (chunkStr.includes("SyntheticOrigin") && chunkStr.includes("SYNTHETIC_IMAGE|")) {
130
+ const markerIdx = chunkStr.indexOf("SYNTHETIC_IMAGE|");
131
+ const text = chunkStr.substring(markerIdx);
132
+ const parts = text.split("|");
133
+ if (parts.length >= 4) {
134
+ return {
135
+ platform: parts[1],
136
+ source: parts[2],
137
+ timestamp: parseInt(parts[3], 10),
138
+ userIdHash: parts[4] || void 0,
139
+ model: parts[5] || void 0
140
+ };
141
+ }
142
+ }
143
+ }
144
+ pos += 4 + 4 + chunkLength + 4;
145
+ if (chunkType === "IEND") break;
146
+ }
147
+ } catch {
148
+ }
149
+ return null;
150
+ }
151
+ function watermarkDallEImage(imageBuffer, format = "png", userIdHash) {
152
+ return embedImageWatermark(imageBuffer, format, {
153
+ source: "dalle",
154
+ model: "dall-e",
155
+ userIdHash
156
+ });
157
+ }
158
+ function watermarkGeminiImage(imageBuffer, format = "png", userIdHash) {
159
+ return embedImageWatermark(imageBuffer, format, {
160
+ source: "gemini",
161
+ model: "imagen",
162
+ userIdHash
163
+ });
164
+ }
165
+ function watermarkDigitalTwinImage(imageBuffer, format = "png", userIdHash) {
166
+ return embedImageWatermark(imageBuffer, format, {
167
+ source: "digital_twin",
168
+ userIdHash
169
+ });
170
+ }
171
+ function watermarkCyranoImage(imageBuffer, format = "png", userIdHash) {
172
+ return embedImageWatermark(imageBuffer, format, {
173
+ source: "cyrano",
174
+ userIdHash
175
+ });
176
+ }
177
+
178
+ // src/audioWatermark.ts
179
+ import { Buffer as Buffer2 } from "buffer";
180
+ var DEFAULT_WATERMARK2 = {
181
+ platform: "SYNTHETIC",
182
+ source: "tts"
183
+ };
184
+ function createID3WatermarkTag(config) {
185
+ const comment = `SYNTHETIC_AUDIO|${config.platform}|${config.source}|${config.timestamp}${config.userIdHash ? `|${config.userIdHash}` : ""}`;
186
+ const encoding = Buffer2.from([0]);
187
+ const language = Buffer2.from("eng");
188
+ const shortDesc = Buffer2.from("SYNTHETIC_ORIGIN\0", "binary");
189
+ const text = Buffer2.concat([
190
+ Buffer2.from(comment, "utf-8"),
191
+ Buffer2.from([0])
192
+ ]);
193
+ const frameContent = Buffer2.concat([
194
+ encoding,
195
+ language,
196
+ shortDesc,
197
+ text
198
+ ]);
199
+ const frameId = Buffer2.from("COMM");
200
+ const frameSize = Buffer2.alloc(4);
201
+ frameSize.writeUInt32BE(frameContent.length, 0);
202
+ const frameFlags = Buffer2.from([0, 0]);
203
+ const frame = Buffer2.concat([
204
+ frameId,
205
+ frameSize,
206
+ frameFlags,
207
+ frameContent
208
+ ]);
209
+ const id3Header = Buffer2.from([
210
+ 73,
211
+ 68,
212
+ 51,
213
+ // "ID3"
214
+ 3,
215
+ 0,
216
+ // Version 2.3
217
+ 0
218
+ // Flags
219
+ ]);
220
+ const size = frame.length;
221
+ const syncsafeSize = Buffer2.alloc(4);
222
+ syncsafeSize[0] = size >> 21 & 127;
223
+ syncsafeSize[1] = size >> 14 & 127;
224
+ syncsafeSize[2] = size >> 7 & 127;
225
+ syncsafeSize[3] = size & 127;
226
+ return Buffer2.concat([
227
+ id3Header,
228
+ syncsafeSize,
229
+ frame
230
+ ]);
231
+ }
232
+ function hasID3Tag(audioBuffer) {
233
+ if (audioBuffer.length < 10) return false;
234
+ return audioBuffer[0] === 73 && // 'I'
235
+ audioBuffer[1] === 68 && // 'D'
236
+ audioBuffer[2] === 51;
237
+ }
238
+ function getID3TagSize(audioBuffer) {
239
+ if (!hasID3Tag(audioBuffer)) return 0;
240
+ const size = (audioBuffer[6] & 127) << 21 | (audioBuffer[7] & 127) << 14 | (audioBuffer[8] & 127) << 7 | audioBuffer[9] & 127;
241
+ return size + 10;
242
+ }
243
+ function embedAudioWatermark(audioBuffer, format = "mp3", config = {}) {
244
+ const fullConfig = {
245
+ ...DEFAULT_WATERMARK2,
246
+ timestamp: Date.now(),
247
+ ...config
248
+ };
249
+ const normalizedFormat = format.toLowerCase();
250
+ if (normalizedFormat !== "mp3") {
251
+ return audioBuffer;
252
+ }
253
+ try {
254
+ let audioData = audioBuffer;
255
+ const bufferArray = new Uint8Array(audioBuffer);
256
+ if (hasID3Tag(bufferArray)) {
257
+ const existingTagSize = getID3TagSize(bufferArray);
258
+ if (existingTagSize <= audioBuffer.length) {
259
+ audioData = audioBuffer.subarray(existingTagSize);
260
+ }
261
+ }
262
+ const watermarkTag = createID3WatermarkTag(fullConfig);
263
+ return Buffer2.concat([watermarkTag, audioData]);
264
+ } catch {
265
+ return audioBuffer;
266
+ }
267
+ }
268
+ function detectSyntheticWatermark(audioBuffer) {
269
+ const bufferArray = new Uint8Array(audioBuffer);
270
+ if (!hasID3Tag(bufferArray)) {
271
+ return null;
272
+ }
273
+ try {
274
+ const searchStr = "SYNTHETIC_ORIGIN";
275
+ const idx = audioBuffer.indexOf(searchStr);
276
+ if (idx === -1) return null;
277
+ const markerEnd = idx + searchStr.length + 1;
278
+ if (markerEnd >= audioBuffer.length) return null;
279
+ const markerStart = audioBuffer.indexOf("SYNTHETIC_AUDIO|", markerEnd);
280
+ if (markerStart === -1) return null;
281
+ let end = markerStart;
282
+ while (end < audioBuffer.length && audioBuffer[end] !== 0) {
283
+ end++;
284
+ }
285
+ const comment = audioBuffer.subarray(markerStart, end).toString("utf-8");
286
+ const parts = comment.split("|");
287
+ if (parts.length >= 4) {
288
+ return {
289
+ platform: parts[1] || "",
290
+ source: parts[2] || "",
291
+ timestamp: parseInt(parts[3] || "0", 10),
292
+ userIdHash: parts[4]
293
+ };
294
+ }
295
+ } catch {
296
+ }
297
+ return null;
298
+ }
299
+ function watermarkDigitalTwinAudio(audioBuffer, format = "mp3", userIdHash) {
300
+ return embedAudioWatermark(audioBuffer, format, {
301
+ source: "digital_twin",
302
+ userIdHash
303
+ });
304
+ }
305
+ function watermarkHologramAudio(audioBuffer, format = "mp3") {
306
+ return embedAudioWatermark(audioBuffer, format, {
307
+ source: "hologram"
308
+ });
309
+ }
310
+ function watermarkCyranoAudio(audioBuffer, format = "mp3") {
311
+ return embedAudioWatermark(audioBuffer, format, {
312
+ source: "cyrano"
313
+ });
314
+ }
315
+ export {
316
+ detectSyntheticImageWatermark,
317
+ detectSyntheticWatermark,
318
+ embedAudioWatermark,
319
+ embedImageWatermark,
320
+ watermarkCyranoAudio,
321
+ watermarkCyranoImage,
322
+ watermarkDallEImage,
323
+ watermarkDigitalTwinAudio,
324
+ watermarkDigitalTwinImage,
325
+ watermarkGeminiImage,
326
+ watermarkHologramAudio
327
+ };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@affectively/synthetic-watermark",
3
+ "version": "1.0.0",
4
+ "description": "Invisible watermarking for AI-generated content. Embeds synthetic origin markers into PNG images (iTXt chunks) and MP3 audio (ID3v2 tags) for authenticity verification.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "LICENSE",
18
+ "README.md",
19
+ "CHANGELOG.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup src/index.ts --format cjs,esm --dts",
23
+ "prepublishOnly": "npm run build",
24
+ "test": "bun test"
25
+ },
26
+ "keywords": [
27
+ "watermark",
28
+ "ai-generated",
29
+ "synthetic",
30
+ "authenticity",
31
+ "png",
32
+ "mp3",
33
+ "id3",
34
+ "metadata",
35
+ "ai-safety",
36
+ "content-authenticity",
37
+ "digital-watermark",
38
+ "steganography"
39
+ ],
40
+ "author": "AFFECTIVELY <opensource@affectively.ai>",
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/affectively-ai/synthetic-watermark.git"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/affectively-ai/synthetic-watermark/issues"
48
+ },
49
+ "homepage": "https://github.com/affectively-ai/synthetic-watermark#readme",
50
+ "devDependencies": {
51
+ "@types/node": "^22.0.0",
52
+ "tsup": "^8.0.0",
53
+ "typescript": "^5.9.0"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ }
58
+ }