@enslo/sd-metadata 2.2.0 → 3.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 CHANGED
@@ -5,6 +5,56 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.0.0] - 2026-06-07
9
+
10
+ ### Added
11
+
12
+ - **C2PA Content Credentials detection** (#234): Detect AI provenance metadata
13
+ embedded by commercial tools (ChatGPT / OpenAI, Gemini / Google). When an
14
+ image carries no recognized generation parameters but has a signed C2PA
15
+ manifest, `read()` now returns a `c2pa` status with coarse vendor attribution
16
+ and the declared AI-generated flag.
17
+ - New `C2paMetadata` and `C2paVendor` types
18
+ - The manifest is read as declared; its cryptographic signature is **not**
19
+ verified, so presence is not proof of authenticity and absence is not proof
20
+ of human origin
21
+
22
+ ### Potentially Breaking Changes
23
+
24
+ - **New `ParseResult` variant** (#234): `{ status: 'c2pa'; c2pa: C2paMetadata }`
25
+ added. TypeScript users with an exhaustive `switch` on `result.status` (or a
26
+ `Record<...>` keyed by the status union) will need to handle the new `'c2pa'`
27
+ case.
28
+ - **`read()` behavior change** (#234): images that carry a C2PA manifest but no
29
+ recognized generation metadata now return `{ status: 'c2pa' }` instead of
30
+ `{ status: 'unrecognized' }` or `{ status: 'empty' }`.
31
+
32
+ ### Security
33
+
34
+ - **`read()` hardening** (#236): Fix two HIGH-severity issues when reading
35
+ malformed input — a ReDoS via unbounded regex backtracking in XMP parsing,
36
+ and an out-of-bounds `RangeError` while reading image dimensions.
37
+
38
+ ### Maintenance
39
+
40
+ - Update development dependencies
41
+
42
+ ## [2.3.0] - 2026-05-30
43
+
44
+ ### Improved
45
+
46
+ - **ComfyUI metadata parsing** (#219): Improved accuracy and coverage for
47
+ non-standard workflow topologies and custom nodes (`ShowText|pysssss`,
48
+ `PromptStashSaver`). Several edge cases that previously caused parse failures
49
+ or incorrect results — including workflows with `NaN`-containing JSON, node
50
+ references in model fields, and workflow-only PNG files — are now handled
51
+ correctly.
52
+
53
+ ### Maintenance
54
+
55
+ - Add `index.d.ts.map` (declaration sourcemaps) to the published output (#216)
56
+ - Update development dependencies
57
+
8
58
  ## [2.2.0] - 2026-02-27
9
59
 
10
60
  ### Added
package/README.ja.md CHANGED
@@ -14,6 +14,7 @@ AI生成画像に埋め込まれたメタデータを読み書きするための
14
14
 
15
15
  - **マルチフォーマット対応**: PNG (tEXt / iTXt)、JPEG (COM / Exif)、WebP (Exif)
16
16
  - **シンプルAPI**: `read()`、`write()`、`embed()`、`stringify()` — 4つの関数で全ユースケースをカバー
17
+ - **AI生成元の検出**: C2PA Content Credentials を持つ画像(OpenAI ChatGPT、Google Gemini)を識別 — 検出のみ、署名検証なし
17
18
  - **TypeScriptネイティブ**: TypeScriptで書かれており、型定義を完全同梱
18
19
  - **ゼロ依存**: Node.jsとブラウザで外部依存なしで動作
19
20
  - **フォーマット変換**: PNG、JPEG、WebP間でメタデータをシームレスに変換
@@ -148,7 +149,7 @@ fileInput.addEventListener('change', async (e) => {
148
149
  // ==UserScript==
149
150
  // @name My Script
150
151
  // @namespace https://example.com
151
- // @require https://cdn.jsdelivr.net/npm/@enslo/sd-metadata@2.2.0/dist/index.global.js
152
+ // @require https://cdn.jsdelivr.net/npm/@enslo/sd-metadata@3.0.0/dist/index.global.js
152
153
  // ==/UserScript==
153
154
 
154
155
  const response = await fetch(imageUrl);
@@ -211,6 +212,13 @@ switch (result.status) {
211
212
  console.log(`Prompt: ${result.metadata.prompt}`);
212
213
  break;
213
214
 
215
+ case 'c2pa':
216
+ // C2PA Content Credentials を検出(例:OpenAI ChatGPT、Google Gemini)。
217
+ // 検出のみ — 署名は検証されず、読み取れる生成パラメータ(プロンプト/シード/モデル)もありません。
218
+ console.log(`AI生成元(未検証): ${result.c2pa.vendor}`);
219
+ console.log(`Claim generator: ${result.c2pa.claimGenerator ?? 'unknown'}`);
220
+ break;
221
+
214
222
  case 'unrecognized':
215
223
  // メタデータは存在するがフォーマットが認識できない
216
224
  console.log('不明なメタデータフォーマット');
@@ -258,6 +266,27 @@ if (result.ok) {
258
266
 
259
267
  </details>
260
268
 
269
+ <details>
270
+ <summary>AI生成元の検出(C2PA Content Credentials)</summary>
271
+
272
+ 一部の商用ツール(OpenAI ChatGPT、Google Gemini)は、生成パラメータの代わりに C2PA Content Credentials を埋め込みます。これらの画像に対して `read()` は `{ status: 'c2pa', c2pa }` を返します:
273
+
274
+ ```typescript
275
+ import { read, c2paVendorLabels } from '@enslo/sd-metadata';
276
+
277
+ const result = read(imageData);
278
+
279
+ if (result.status === 'c2pa') {
280
+ console.log('Vendor:', c2paVendorLabels[result.c2pa.vendor]);
281
+ console.log('Declared AI-generated:', result.c2pa.aiGenerated);
282
+ console.log('Claim generator:', result.c2pa.claimGenerator ?? 'unknown');
283
+ }
284
+ ```
285
+
286
+ > **検出のみ — 証明にはなりません。** C2PA署名を検証しないため、`c2pa` の結果は偽造可能で、真正性の証明にはなりません。また Content Credentials は再アップロードや再エンコードで失われやすいため、`c2pa` にならないことも「AI生成ではない」ことの証明にはなりません。
287
+
288
+ </details>
289
+
261
290
  <details>
262
291
  <summary>メタデータの削除</summary>
263
292
 
@@ -365,13 +394,16 @@ if (text) {
365
394
  **パラメータ:**
366
395
 
367
396
  - `input` - 画像ファイルデータ(PNG、JPEG、またはWebP)
368
- - `options` - オプションの読み込み設定(詳細は[型ドキュメント](./docs/types.ja.md)を参照)
397
+ - `options` - オプションの読み込み設定
398
+ - `strict?: boolean`(デフォルト: `false`)— `true` の場合、寸法(`width` / `height`)はメタデータからのみ取得します。`false` の場合、メタデータに寸法がなければ画像ヘッダーから取得します。
369
399
 
370
400
  **戻り値:**
371
401
 
372
402
  - `{ status: 'success', metadata, raw }` - パース成功
373
403
  - `metadata`: 統一されたメタデータオブジェクト(`GenerationMetadata`を参照)
374
404
  - `raw`: 元のフォーマット固有のデータ(chunks/segments)
405
+ - `{ status: 'c2pa', c2pa }` - 画像がC2PA Content Credentials(例:OpenAI ChatGPT、Google Gemini)を持つが、パース可能な生成メタデータがない
406
+ - `c2pa`: 未検証の Content Credentials(`C2paMetadata`を参照)。検出のみ — 署名は検証されません。
375
407
  - `{ status: 'unrecognized', raw }` - 画像にメタデータがあるが既知のAIツールからではない
376
408
  - `raw`: 変換用に保持された元のメタデータ
377
409
  - `{ status: 'empty' }` - 画像にメタデータが見つからない
@@ -396,7 +428,7 @@ if (text) {
396
428
  - `{ ok: false, error: { type, message? } }` - 失敗。`type` は以下のいずれか:
397
429
  - `'unsupportedFormat'`: 対象画像がPNG、JPEG、WebP以外の場合
398
430
  - `'conversionFailed'`: メタデータ変換に失敗(例:互換性のないフォーマット)
399
- - `'writeFailed'`: 画像へのメタデータ埋め込みに失敗
431
+ - `'writeFailed'`: 画像へのメタデータ埋め込みに失敗、または再書き込みが不可能な場合
400
432
 
401
433
  ### `embed(input: Uint8Array | ArrayBuffer, metadata: EmbedMetadata | GenerationMetadata): WriteResult`
402
434
 
@@ -430,7 +462,11 @@ SD WebUI (A1111) フォーマットでカスタムメタデータを画像に埋
430
462
 
431
463
  **戻り値:**
432
464
 
433
- - `ParseResult` の場合: `success` → WebUIフォーマット、`unrecognized` → 生テキスト、`empty`/`invalid` → 空文字列
465
+ - `ParseResult` の場合:
466
+ - `success` → WebUIフォーマット
467
+ - `c2pa` → claim generator 名(`claimGenerator ?? ''`)
468
+ - `unrecognized` → 生テキスト
469
+ - `empty` / `invalid` → 空文字列
434
470
  - `EmbedMetadata` / `GenerationMetadata` の場合: WebUIフォーマットのテキスト
435
471
 
436
472
  **ユースケース:**
@@ -454,6 +490,20 @@ if (result.status === 'success') {
454
490
  }
455
491
  ```
456
492
 
493
+ ### `c2paVendorLabels: Record<C2paVendor, string>`
494
+
495
+ `C2paVendor` の識別子から表示用の名前への読み取り専用マッピング。
496
+
497
+ ```typescript
498
+ import { c2paVendorLabels } from '@enslo/sd-metadata';
499
+
500
+ const result = read(imageData);
501
+ if (result.status === 'c2pa') {
502
+ console.log(c2paVendorLabels[result.c2pa.vendor]);
503
+ // => "OpenAI (ChatGPT)", "Google (Gemini)", "AI-generated (Content Credentials)"
504
+ }
505
+ ```
506
+
457
507
  ## 型リファレンス
458
508
 
459
509
  このセクションでは主要な型の概要を説明します。完全な型定義については[型ドキュメント](./docs/types.ja.md)を参照してください。
@@ -465,6 +515,7 @@ if (result.status === 'success') {
465
515
  ```typescript
466
516
  type ParseResult =
467
517
  | { status: 'success'; metadata: GenerationMetadata; raw: RawMetadata }
518
+ | { status: 'c2pa'; c2pa: C2paMetadata }
468
519
  | { status: 'unrecognized'; raw: RawMetadata }
469
520
  | { status: 'empty' }
470
521
  | { status: 'invalid'; message?: string };
package/README.md CHANGED
@@ -14,6 +14,7 @@ A TypeScript library to read and write metadata embedded in AI-generated images.
14
14
 
15
15
  - **Multi-format Support**: PNG (tEXt / iTXt), JPEG (COM / Exif), WebP (Exif)
16
16
  - **Simple API**: `read()`, `write()`, `embed()`, `stringify()` — four functions cover all use cases
17
+ - **AI Provenance Detection**: identifies images carrying C2PA Content Credentials (OpenAI ChatGPT, Google Gemini) — detection only, no signature verification
17
18
  - **TypeScript Native**: Written in TypeScript with full type definitions included
18
19
  - **Zero Dependencies**: Works in Node.js and browsers without any external dependencies
19
20
  - **Format Conversion**: Seamlessly convert metadata between PNG, JPEG, and WebP
@@ -148,7 +149,7 @@ For userscripts (Tampermonkey, Violentmonkey, etc.), load the IIFE build via `@r
148
149
  // ==UserScript==
149
150
  // @name My Script
150
151
  // @namespace https://example.com
151
- // @require https://cdn.jsdelivr.net/npm/@enslo/sd-metadata@2.2.0/dist/index.global.js
152
+ // @require https://cdn.jsdelivr.net/npm/@enslo/sd-metadata@3.0.0/dist/index.global.js
152
153
  // ==/UserScript==
153
154
 
154
155
  const response = await fetch(imageUrl);
@@ -211,6 +212,14 @@ switch (result.status) {
211
212
  console.log(`Prompt: ${result.metadata.prompt}`);
212
213
  break;
213
214
 
215
+ case 'c2pa':
216
+ // C2PA Content Credentials detected (e.g. OpenAI ChatGPT, Google Gemini).
217
+ // Detection only — the signature is NOT verified, and there are no
218
+ // generation parameters (prompt/seed/model) to read.
219
+ console.log(`AI provenance: ${result.c2pa.vendor} (unverified)`);
220
+ console.log(`Claim generator: ${result.c2pa.claimGenerator ?? 'unknown'}`);
221
+ break;
222
+
214
223
  case 'unrecognized':
215
224
  // Metadata exists but format is not recognized
216
225
  console.log('Unknown metadata format');
@@ -258,6 +267,27 @@ if (result.ok) {
258
267
 
259
268
  </details>
260
269
 
270
+ <details>
271
+ <summary>AI Provenance Detection (C2PA Content Credentials)</summary>
272
+
273
+ Some commercial tools (OpenAI ChatGPT, Google Gemini) embed a signed C2PA "Content Credentials" provenance manifest instead of generation parameters. `read()` returns `{ status: 'c2pa', c2pa }` for these images:
274
+
275
+ ```typescript
276
+ import { read, c2paVendorLabels } from '@enslo/sd-metadata';
277
+
278
+ const result = read(imageData);
279
+
280
+ if (result.status === 'c2pa') {
281
+ console.log('Vendor:', c2paVendorLabels[result.c2pa.vendor]);
282
+ console.log('Declared AI-generated:', result.c2pa.aiGenerated);
283
+ console.log('Claim generator:', result.c2pa.claimGenerator ?? 'unknown');
284
+ }
285
+ ```
286
+
287
+ > **Detection only — not proof.** The C2PA signature is not verified, so a `c2pa` result is forgeable and cannot prove authenticity. Content Credentials are also easily stripped (screenshots, re-uploads, re-encoding), so the absence of a `c2pa` result does not prove an image is *not* AI-generated.
288
+
289
+ </details>
290
+
261
291
  <details>
262
292
  <summary>Removing Metadata</summary>
263
293
 
@@ -362,13 +392,16 @@ Reads and parses metadata from an image file.
362
392
  **Parameters:**
363
393
 
364
394
  - `input` - Image file data (PNG, JPEG, or WebP)
365
- - `options` - Optional read options (see [Type Documentation](./docs/types.md) for details)
395
+ - `options` - Optional read options
396
+ - `strict?: boolean` (default: `false`) — When `true`, dimensions (`width` / `height`) are taken strictly from metadata only. When `false`, missing dimensions are extracted from image headers.
366
397
 
367
398
  **Returns:**
368
399
 
369
400
  - `{ status: 'success', metadata, raw }` - Successfully parsed
370
401
  - `metadata`: Unified metadata object (see `GenerationMetadata`)
371
402
  - `raw`: Original format-specific data (chunks/segments)
403
+ - `{ status: 'c2pa', c2pa }` - Image carries C2PA Content Credentials (e.g. OpenAI ChatGPT, Google Gemini) but no parseable generation metadata
404
+ - `c2pa`: Declared (unverified) AI provenance (see `C2paMetadata`). Detection only — the signature is not verified.
372
405
  - `{ status: 'unrecognized', raw }` - Image has metadata but not from a known AI tool
373
406
  - `raw`: Original metadata preserved for conversion
374
407
  - `{ status: 'empty' }` - No metadata found in the image
@@ -393,7 +426,7 @@ Writes metadata to an image file.
393
426
  - `{ ok: false, error: { type, message? } }` - Failed. `type` is one of:
394
427
  - `'unsupportedFormat'`: Target image is not PNG, JPEG, or WebP
395
428
  - `'conversionFailed'`: Metadata conversion failed (e.g., incompatible format)
396
- - `'writeFailed'`: Failed to embed metadata into the image
429
+ - `'writeFailed'`: Failed to embed metadata into the image, or the input cannot be written back
397
430
 
398
431
  ### `embed(input: Uint8Array | ArrayBuffer, metadata: EmbedMetadata | GenerationMetadata): WriteResult`
399
432
 
@@ -429,9 +462,11 @@ Converts metadata to a human-readable string.
429
462
 
430
463
  **Returns:**
431
464
 
432
- - `ParseResult` with `success` → Human-readable text in WebUI format
433
- - `ParseResult` with `unrecognized` Raw metadata as plain text
434
- - `ParseResult` with `empty` / `invalid` Empty string
465
+ - `ParseResult`:
466
+ - `success` → Human-readable text in WebUI format
467
+ - `c2pa` The claim generator name (`claimGenerator ?? ''`)
468
+ - `unrecognized` → Raw metadata as plain text
469
+ - `empty` / `invalid` → Empty string
435
470
  - `EmbedMetadata` / `GenerationMetadata` → Human-readable text in WebUI format
436
471
 
437
472
  **Use cases:**
@@ -439,6 +474,7 @@ Converts metadata to a human-readable string.
439
474
  - Displaying generation parameters in image viewers or galleries
440
475
  - Copying metadata to clipboard as readable text
441
476
  - Logging or debugging parsed metadata
477
+ - Previewing `EmbedMetadata` before embedding
442
478
 
443
479
  ### `softwareLabels: Record<GenerationSoftware, string>`
444
480
 
@@ -454,6 +490,20 @@ if (result.status === 'success') {
454
490
  }
455
491
  ```
456
492
 
493
+ ### `c2paVendorLabels: Record<C2paVendor, string>`
494
+
495
+ A read-only mapping from `C2paVendor` identifiers to their human-readable display names.
496
+
497
+ ```typescript
498
+ import { c2paVendorLabels } from '@enslo/sd-metadata';
499
+
500
+ const result = read(imageData);
501
+ if (result.status === 'c2pa') {
502
+ console.log(c2paVendorLabels[result.c2pa.vendor]);
503
+ // => "OpenAI (ChatGPT)", "Google (Gemini)", "AI-generated (Content Credentials)"
504
+ }
505
+ ```
506
+
457
507
  ## Type Reference
458
508
 
459
509
  This section provides an overview of the main types. For complete type definitions, see [Type Documentation](./docs/types.md).
@@ -465,6 +515,7 @@ The result of the `read()` function. It uses a discriminated union with a `statu
465
515
  ```typescript
466
516
  type ParseResult =
467
517
  | { status: 'success'; metadata: GenerationMetadata; raw: RawMetadata }
518
+ | { status: 'c2pa'; c2pa: C2paMetadata }
468
519
  | { status: 'unrecognized'; raw: RawMetadata }
469
520
  | { status: 'empty' }
470
521
  | { status: 'invalid'; message?: string };