From b12cdf99c97446aac2e25352393e00ffb2625bfd Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 12 Nov 2024 10:51:57 -0800 Subject: [PATCH 01/58] Update sidebars.js Add manifest tasks to sidebar nav. --- sidebars.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sidebars.js b/sidebars.js index 76fc0530..cfab4f09 100644 --- a/sidebars.js +++ b/sidebars.js @@ -44,6 +44,10 @@ const sidebars = { type: 'doc', id: 'manifest/signing-manifests', }, + { + type: 'doc', + id: 'manifest/tasks', + }, { type: 'doc', id: 'manifest/manifest-examples', From 79228a2208b84191c6325cdf4d067534cb2b6b7e Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 12 Nov 2024 11:08:27 -0800 Subject: [PATCH 02/58] Update manifest-tasks.mdx Add Python --- docs/manifest/manifest-tasks.mdx | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/manifest/manifest-tasks.mdx b/docs/manifest/manifest-tasks.mdx index d24fb6a9..af974ea5 100644 --- a/docs/manifest/manifest-tasks.mdx +++ b/docs/manifest/manifest-tasks.mdx @@ -15,11 +15,37 @@ import TabItem from '@theme/TabItem'; - This is how to read a manifest using Python. + +Use the `Reader` object to read manifest data from a file or stream and perform validation on the manifest store. Use the `json()` method to return a JSON manifest report; If there are validation errors, the report includes a `validation_status` field. + +Use the `resource_to_file` and `resource_to_stream` methods to write resources to a file or stream, respectively. + +An asset file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map. Retrieve binary resources such as thumbnails from the manifest data, use the `resource_to_stream` or `resource_to_file` methods using the associated `identifier` field values and a `uri`. + +```py +try: + # Create a reader from a file path + reader = c2pa.Reader.from_file("path/to/media_file.jpg") + + # Print the JSON for a manifest. + print("manifest store:", reader.json()) + + # Get the active manifest. + manifest = reader.get_active_manifest() + if manifest != None: + + # Get the URI to the manifest's thumbnail and write it to a file + uri = manifest["thumbnail"]["identifier"] + reader.resource_to_file(uri, "thumbnail_v2.jpg") + +except Exception as err: + print(err) +``` + - This is how to read a manifest using C++. Do we want also want C? + This is how to read a manifest using C++. From fc7ecbebdc320bbb3a0843c3791c511a32a41b21 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 12 Nov 2024 11:42:27 -0800 Subject: [PATCH 03/58] Update manifest-tasks.mdx Add JavaScript --- docs/manifest/manifest-tasks.mdx | 38 +++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/manifest/manifest-tasks.mdx b/docs/manifest/manifest-tasks.mdx index af974ea5..3c1af4bb 100644 --- a/docs/manifest/manifest-tasks.mdx +++ b/docs/manifest/manifest-tasks.mdx @@ -11,7 +11,43 @@ import TabItem from '@theme/TabItem'; - This is how to read a manifest using JavaScript. + +If the input provided to [`c2pa.read`](../../js-sdk/api/c2pa.c2pa#methods) has a C2PA manifest and was processed without errors, the returned [`c2paReadResult`](../../js-sdk/api/c2pa.c2pareadresult) contains a [`manifestStore`](../../js-sdk/api/c2pa.c2pareadresult.manifeststore). + +The [`manifestStore`](../../js-sdk/api/c2pa.c2pareadresult.manifeststore) object contains a few properties: + +- **manifests**: An object containing _all_ manifests found in an asset, keyed by UUID. +- **activeManifest**: A pointer to the latest [`manifest`](../../js-sdk/api/c2pa.manifest) in the manifest store. Effectively the "parent" manifest, this is the likely starting point when inspecting an asset's C2PA data. +- **validationStatus**: A list of any validation errors the library generated when processing an asset. See [Validation](./validation) for more information. + +[`Manifest`](../../js-sdk/api/c2pa.manifest) objects contain properties pertaining to an asset's provenance, along with convenient interfaces for [accessing assertion data](../../js-sdk/api/c2pa.assertionaccessor) and [generating a thumbnail](../../js-sdk/api/c2pa.thumbnail). + +```js + const { manifestStore, source } = await c2pa.read(sampleImage); + const activeManifest = manifestStore?.activeManifest; + if (activeManifest) { + // Get thumbnail + // Note: You would normally call `dispose()` when working with a + // component-based UI library (e.g. on component un-mount) + // @ts-expect-error noUnusedLocals + const { url, dispose } = source.thumbnail.getUrl(); + + // Get properties + const properties: Record = { + title: activeManifest.title, + format: activeManifest.format, + claimGenerator: activeManifest.claimGenerator.split('(')[0]?.trim(), + producer: selectProducer(activeManifest)?.name ?? 'Unknown', + thumbnail: ``, + ingredients: (activeManifest.ingredients ?? []) + .map((i) => i.title) + .join(', '), + signatureIssuer: activeManifest.signatureInfo?.issuer, + signatureDate: activeManifest.signatureInfo?.time + ? parseISO(activeManifest.signatureInfo.time).toString() + : 'No date available', + }; +``` From c66b543ed4167394bdc702a56b635aa5b683e899 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 12 Nov 2024 11:45:05 -0800 Subject: [PATCH 04/58] Update manifest-tasks.mdx Add Node.js --- docs/manifest/manifest-tasks.mdx | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/docs/manifest/manifest-tasks.mdx b/docs/manifest/manifest-tasks.mdx index 3c1af4bb..aeb333ac 100644 --- a/docs/manifest/manifest-tasks.mdx +++ b/docs/manifest/manifest-tasks.mdx @@ -78,15 +78,37 @@ except Exception as err: print(err) ``` - - - - This is how to read a manifest using C++. - This is how to read a manifest using Node.js. +Use the `c2pa.read()` function to read a manifest; for example: + +```ts +import { createC2pa } from 'c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const c2pa = createC2pa(); + +async function read(path, mimeType) { + const buffer = await readFile(path); + const result = await c2pa.read({ buffer, mimeType }); + + if (result) { + const { active_manifest, manifests, validation_status } = result; + console.log(active_manifest); + } else { + console.log('No claim found'); + } +} + +await read('my-c2pa-file.jpg', 'image/jpeg'); +``` + + + This is how to read a manifest using C++. + + ## Getting resources from a manifest From 44a5dd57bbdcd37e64d6c1883104f084bbc93c5a Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 12 Nov 2024 11:46:19 -0800 Subject: [PATCH 05/58] Update manifest-tasks.mdx Add cpp. --- docs/manifest/manifest-tasks.mdx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/manifest/manifest-tasks.mdx b/docs/manifest/manifest-tasks.mdx index aeb333ac..e39b5527 100644 --- a/docs/manifest/manifest-tasks.mdx +++ b/docs/manifest/manifest-tasks.mdx @@ -106,7 +106,23 @@ await read('my-c2pa-file.jpg', 'image/jpeg'); - This is how to read a manifest using C++. +Use the `read_file` function to read C2PA data from the specified file. This function examines the specified asset file for C2PA data and its return value is a JSON report if it finds C2PA data. If there are validation errors, the report includes a `validation_status` field. Exceptions are thrown on errors. + +```cpp +auto json_store = C2pa::read_file("", "") +``` + +Where: + +- ``- The asset file to read; The file must be one of the [supported file formats](#supported-file-formats). +- `` - Optional path to data output directory; If provided, the function extracts any binary resources, such as thumbnails, icons, and C2PA data into that directory. These files are referenced by the identifier fields in the manifest store report. + +For example: + +```cpp +auto json_store = C2pa::read_file("work/media_file.jpg", "output/data_dir") +``` + From 305b50c8a7098c9677b772ca1a37631da7016db6 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Thu, 14 Nov 2024 16:52:27 -0800 Subject: [PATCH 06/58] Move into separate tasks dir/sidebar branch --- docs/manifest/manifest-tasks.mdx | 197 ------------------------------- docs/tasks/build.mdx | 22 ++++ docs/tasks/get-resources.mdx | 48 ++++++++ docs/tasks/index.md | 12 ++ docs/tasks/read.mdx | 157 ++++++++++++++++++++++++ docs/tasks/sign.mdx | 21 ++++ docs/tasks/write.mdx | 21 ++++ sidebars.js | 33 +++++- 8 files changed, 310 insertions(+), 201 deletions(-) delete mode 100644 docs/manifest/manifest-tasks.mdx create mode 100644 docs/tasks/build.mdx create mode 100644 docs/tasks/get-resources.mdx create mode 100644 docs/tasks/index.md create mode 100644 docs/tasks/read.mdx create mode 100644 docs/tasks/sign.mdx create mode 100644 docs/tasks/write.mdx diff --git a/docs/manifest/manifest-tasks.mdx b/docs/manifest/manifest-tasks.mdx deleted file mode 100644 index e39b5527..00000000 --- a/docs/manifest/manifest-tasks.mdx +++ /dev/null @@ -1,197 +0,0 @@ ---- -id: tasks -title: Common tasks working with manifest data ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -## Reading a manifest - - - - - -If the input provided to [`c2pa.read`](../../js-sdk/api/c2pa.c2pa#methods) has a C2PA manifest and was processed without errors, the returned [`c2paReadResult`](../../js-sdk/api/c2pa.c2pareadresult) contains a [`manifestStore`](../../js-sdk/api/c2pa.c2pareadresult.manifeststore). - -The [`manifestStore`](../../js-sdk/api/c2pa.c2pareadresult.manifeststore) object contains a few properties: - -- **manifests**: An object containing _all_ manifests found in an asset, keyed by UUID. -- **activeManifest**: A pointer to the latest [`manifest`](../../js-sdk/api/c2pa.manifest) in the manifest store. Effectively the "parent" manifest, this is the likely starting point when inspecting an asset's C2PA data. -- **validationStatus**: A list of any validation errors the library generated when processing an asset. See [Validation](./validation) for more information. - -[`Manifest`](../../js-sdk/api/c2pa.manifest) objects contain properties pertaining to an asset's provenance, along with convenient interfaces for [accessing assertion data](../../js-sdk/api/c2pa.assertionaccessor) and [generating a thumbnail](../../js-sdk/api/c2pa.thumbnail). - -```js - const { manifestStore, source } = await c2pa.read(sampleImage); - const activeManifest = manifestStore?.activeManifest; - if (activeManifest) { - // Get thumbnail - // Note: You would normally call `dispose()` when working with a - // component-based UI library (e.g. on component un-mount) - // @ts-expect-error noUnusedLocals - const { url, dispose } = source.thumbnail.getUrl(); - - // Get properties - const properties: Record = { - title: activeManifest.title, - format: activeManifest.format, - claimGenerator: activeManifest.claimGenerator.split('(')[0]?.trim(), - producer: selectProducer(activeManifest)?.name ?? 'Unknown', - thumbnail: ``, - ingredients: (activeManifest.ingredients ?? []) - .map((i) => i.title) - .join(', '), - signatureIssuer: activeManifest.signatureInfo?.issuer, - signatureDate: activeManifest.signatureInfo?.time - ? parseISO(activeManifest.signatureInfo.time).toString() - : 'No date available', - }; -``` - - - - -Use the `Reader` object to read manifest data from a file or stream and perform validation on the manifest store. Use the `json()` method to return a JSON manifest report; If there are validation errors, the report includes a `validation_status` field. - -Use the `resource_to_file` and `resource_to_stream` methods to write resources to a file or stream, respectively. - -An asset file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map. Retrieve binary resources such as thumbnails from the manifest data, use the `resource_to_stream` or `resource_to_file` methods using the associated `identifier` field values and a `uri`. - -```py -try: - # Create a reader from a file path - reader = c2pa.Reader.from_file("path/to/media_file.jpg") - - # Print the JSON for a manifest. - print("manifest store:", reader.json()) - - # Get the active manifest. - manifest = reader.get_active_manifest() - if manifest != None: - - # Get the URI to the manifest's thumbnail and write it to a file - uri = manifest["thumbnail"]["identifier"] - reader.resource_to_file(uri, "thumbnail_v2.jpg") - -except Exception as err: - print(err) -``` - - - - -Use the `c2pa.read()` function to read a manifest; for example: - -```ts -import { createC2pa } from 'c2pa-node'; -import { readFile } from 'node:fs/promises'; - -const c2pa = createC2pa(); - -async function read(path, mimeType) { - const buffer = await readFile(path); - const result = await c2pa.read({ buffer, mimeType }); - - if (result) { - const { active_manifest, manifests, validation_status } = result; - console.log(active_manifest); - } else { - console.log('No claim found'); - } -} - -await read('my-c2pa-file.jpg', 'image/jpeg'); -``` - - - -Use the `read_file` function to read C2PA data from the specified file. This function examines the specified asset file for C2PA data and its return value is a JSON report if it finds C2PA data. If there are validation errors, the report includes a `validation_status` field. Exceptions are thrown on errors. - -```cpp -auto json_store = C2pa::read_file("", "") -``` - -Where: - -- ``- The asset file to read; The file must be one of the [supported file formats](#supported-file-formats). -- `` - Optional path to data output directory; If provided, the function extracts any binary resources, such as thumbnails, icons, and C2PA data into that directory. These files are referenced by the identifier fields in the manifest store report. - -For example: - -```cpp -auto json_store = C2pa::read_file("work/media_file.jpg", "output/data_dir") -``` - - - - - -## Getting resources from a manifest - - - - This is how to get resources from a manifest using JavaScript. - - - - This is how to get resources from a manifest using Python. - - - - This is how to get resources from a manifest using C++. - - - - This is how to get resources from a manifest using Node.js. - - - -## Building a manifest - - - - - This is how to build a manifest using Python. - - - - This is how to build a manifest using C++. - - - - This is how to build a manifest using Node.js. - - - -## Writing a manifest - - - - This is how to write a manifest using Python. - - - - This is how to write a manifest using C++. - - - - This is how to write a manifest using Node.js. - - - -## Signing a manifest - - - - This is how to sign a manifest using Python. - - - - This is how to sign a manifest using C++. - - - - This is how to sign a manifest using Node.js. - - diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx new file mode 100644 index 00000000..2d7a3a1a --- /dev/null +++ b/docs/tasks/build.mdx @@ -0,0 +1,22 @@ +--- +id: build +title: Building a manifest +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + + This is how to build a manifest using Python. + + + + This is how to build a manifest using C++. + + + + This is how to build a manifest using Node.js. + + diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx new file mode 100644 index 00000000..60456bc7 --- /dev/null +++ b/docs/tasks/get-resources.mdx @@ -0,0 +1,48 @@ +--- +id: get-resources +title: Getting resources from a manifest +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Manifest data can include binary resources such as thumbnail and icon images which are referenced by JUMBF URIs in manifest data. + + + + This is how to get resources from a manifest using JavaScript. + + + + +Retrieve binary resources such as thumbnails from the manifest data, use the `resource_to_stream` or `resource_to_file` methods using the associated `identifier` field values and a `uri`. + +NOTE: Need to add example of using `reader.resource_to_stream()`. + +```python +try: +# Create a reader from a file path +reader = c2pa.Reader.from_file("path/to/media_file.jpg") + +# Get the active manifest. +manifest = reader.get_active_manifest() +if manifest != None: + + # get the uri to the manifest's thumbnail and write it to a file + uri = manifest["thumbnail"]["identifier"] + reader.resource_to_file(uri, "thumbnail_v2.jpg") + +except Exception as err: + print(err) +``` + + + + + This is how to get resources from a manifest using C++. + + + + This is how to get resources from a manifest using Node.js. + + diff --git a/docs/tasks/index.md b/docs/tasks/index.md new file mode 100644 index 00000000..0ec0dcad --- /dev/null +++ b/docs/tasks/index.md @@ -0,0 +1,12 @@ +--- +id: common-tasks +title: Common tasks +--- + +There are a number of common tasks when working with manifests: + +- [Reading manifest data](./read.mdx) +- [Getting resources from a manifest](./get-resources.mdx) +- [Building a manifest](./build.mdx) +- [Writing a manifest](./write.mdx) +- [Signing a manifest](./sign.mdx) \ No newline at end of file diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx new file mode 100644 index 00000000..5bfae4cd --- /dev/null +++ b/docs/tasks/read.mdx @@ -0,0 +1,157 @@ +--- +id: read +title: Reading manifest data +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + + +Use [`c2pa.read`](../../docs/js-sdk/api/c2pa.c2pa#methods) to read manifest data from an asset; if the asset has a C2PA manifest and was processed without errors, the returned [`c2paReadResult`](../../docs/js-sdk/api/c2pa.c2pareadresult) contains a [`manifestStore`](../../docs/js-sdk/api/c2pa.c2pareadresult.manifeststore) object with several useful properties: + +- **manifests**: An object containing all the asset's manifests ([`Manifest`](../../docs/js-sdk/api/c2pa.manifest) objects), keyed by UUID. +- **activeManifest**: A pointer to the latest [`manifest`](../../docs/js-sdk/api/c2pa.manifest) in the manifest store. Effectively the "parent" manifest, this is the likely starting point when inspecting an asset's C2PA data. +- **validationStatus**: A list of any validation errors encountered. See [Validation](../../docs/js-sdk/guides/validation) for more information. + +```js +import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@0.17.2/+esm'; + +const sampleImage = ''; + +(async () => { + // Initialize the c2pa-js SDK + const c2pa = await createC2pa({ + wasmSrc: + 'https://cdn.jsdelivr.net/npm/c2pa@0.17.2/dist/assets/wasm/toolkit_bg.wasm', + workerSrc: + 'https://cdn.jsdelivr.net/npm/c2pa@0.17.2/dist/c2pa.worker.min.js', + }); + + try { + // Read in image and get a manifest store + const { manifestStore } = await c2pa.read(sampleImage); + console.log('manifestStore', manifestStore); + + // Get the active manifest + const activeManifest = manifestStore?.activeManifest; + console.log('activeManifest', activeManifest); + } catch (err) { + console.error('Error reading image:', err); + } +})(); +``` + + + + + +Use the `Reader` object to read manifest data from a file or stream and perform validation on the manifest store. Use the `json()` method to return a JSON manifest report; If there are validation errors, the report includes a `validation_status` field. + +An asset file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map. + +```py +try: + # Create a reader from a file path + reader = c2pa.Reader.from_file("path/to/media_file.jpg") + + # Print the JSON for a manifest. + print("manifest store:", reader.json()) + + # Get the active manifest. + manifest = reader.get_active_manifest() + if manifest != None: + + # Get the URI to the manifest's thumbnail and write it to a file + uri = manifest["thumbnail"]["identifier"] + reader.resource_to_file(uri, "thumbnail_v2.jpg") + +except Exception as err: + print(err) +``` + + + + + +Use the `c2pa.read()` function to read a manifest; for example: + +```ts +import { createC2pa } from 'c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const c2pa = createC2pa(); + +async function read(path, mimeType) { + const buffer = await readFile(path); + const result = await c2pa.read({ buffer, mimeType }); + + if (result) { + const { active_manifest, manifests, validation_status } = result; + console.log(active_manifest); + } else { + console.log('No claim found'); + } +} + +await read('my-c2pa-file.jpg', 'image/jpeg'); +``` + + + + + +Use the `read_file` function to read C2PA data from the specified file. This function examines the specified asset file for C2PA data and its return value is a JSON report if it finds C2PA data. If there are validation errors, the report includes a `validation_status` field. Exceptions are thrown on errors. + +```cpp +auto json_store = C2pa::read_file("", "") +``` + +Where: + +- ``- The asset file to read. +- `` - Optional path to data output directory; If provided, the function extracts any binary resources, such as thumbnails, icons, and C2PA data into that directory. These files are referenced by the identifier fields in the manifest store report. + +For example: + +```cpp +auto json_store = C2pa::read_file("work/media_file.jpg", "output/data_dir") +``` + + + + + +Use the [`Reader`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html) struct to read manifest data from a file or stream. + +### Reading from a file + +Use [`from_file`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file) to read manifest data from a file: + +```rs +use c2pa::Reader; +let reader = Reader::from_file("path/to/file.jpg").unwrap(); +``` + +There is also an asynchronous version of this method, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file_async). + +### Reading from a stream + +Use [`from_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream) to read manifest data from a stream: + +```rs +use std::io::Cursor; + +use c2pa::Reader; +let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg")); +let reader = Reader::from_stream("image/jpeg", stream).unwrap(); +println!("{}", reader.json()); +``` + +There is also an asynchronous version of this method, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream_async). + + + + diff --git a/docs/tasks/sign.mdx b/docs/tasks/sign.mdx new file mode 100644 index 00000000..9ac76f4c --- /dev/null +++ b/docs/tasks/sign.mdx @@ -0,0 +1,21 @@ +--- +id: sign +title: Signing manifest data +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + This is how to sign a manifest using Python. + + + + This is how to sign a manifest using C++. + + + + This is how to sign a manifest using Node.js. + + diff --git a/docs/tasks/write.mdx b/docs/tasks/write.mdx new file mode 100644 index 00000000..38aaa3a4 --- /dev/null +++ b/docs/tasks/write.mdx @@ -0,0 +1,21 @@ +--- +id: write +title: Writing a manifest +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + This is how to write a manifest using Python. + + + + This is how to write a manifest using C++. + + + + This is how to write a manifest using Node.js. + + diff --git a/sidebars.js b/sidebars.js index cfab4f09..a4962e56 100644 --- a/sidebars.js +++ b/sidebars.js @@ -44,10 +44,6 @@ const sidebars = { type: 'doc', id: 'manifest/signing-manifests', }, - { - type: 'doc', - id: 'manifest/tasks', - }, { type: 'doc', id: 'manifest/manifest-examples', @@ -65,6 +61,35 @@ const sidebars = { ], }, + { + type: 'category', + label: 'Common tasks', + link: { type: 'doc', id: 'tasks/common-tasks' }, + collapsed: true, + items: [ + { + type: 'doc', + id: 'tasks/read', + }, + { + type: 'doc', + id: 'tasks/get-resources', + }, + { + type: 'doc', + id: 'tasks/build', + }, + { + type: 'doc', + id: 'tasks/write', + }, + { + type: 'doc', + id: 'tasks/sign', + }, + ], + }, + { type: 'category', label: 'C2PA Tool', From 99fcfc90bf19787ee8084c80692514798f755e88 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 20 Nov 2024 16:04:04 -0800 Subject: [PATCH 07/58] Add rust --- docs/tasks/build.mdx | 10 ++++++++-- docs/tasks/get-resources.mdx | 13 ++++++++++--- docs/tasks/read.mdx | 15 ++++++--------- docs/tasks/sign.mdx | 10 ++++++++-- docs/tasks/write.mdx | 9 +++++++-- 5 files changed, 39 insertions(+), 18 deletions(-) diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index 2d7a3a1a..7c478036 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -16,7 +16,13 @@ import TabItem from '@theme/TabItem'; This is how to build a manifest using C++. - - This is how to build a manifest using Node.js. +{' '} + + This is how to build a manifest using Node.js. + + + + This is how to build a manifest using Rust. + diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index 60456bc7..7a3ffd43 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -42,7 +42,14 @@ except Exception as err: This is how to get resources from a manifest using C++. - - This is how to get resources from a manifest using Node.js. - +{' '} + + This is how to get resources from a manifest using Node.js. + + +{' '} + + This is how to get resources using Rust. + + diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index 5bfae4cd..163c3f4b 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -1,6 +1,7 @@ --- id: read title: Reading manifest data +hide_table_of_contents: true --- import Tabs from '@theme/Tabs'; @@ -57,17 +58,13 @@ try: # Create a reader from a file path reader = c2pa.Reader.from_file("path/to/media_file.jpg") + # Alternatively, create a reader from a stream + stream = open("path/to/media_file.jpg", "rb") + reader = c2pa.Reader("image/jpeg", stream) + # Print the JSON for a manifest. print("manifest store:", reader.json()) - # Get the active manifest. - manifest = reader.get_active_manifest() - if manifest != None: - - # Get the URI to the manifest's thumbnail and write it to a file - uri = manifest["thumbnail"]["identifier"] - reader.resource_to_file(uri, "thumbnail_v2.jpg") - except Exception as err: print(err) ``` @@ -103,7 +100,7 @@ await read('my-c2pa-file.jpg', 'image/jpeg'); -Use the `read_file` function to read C2PA data from the specified file. This function examines the specified asset file for C2PA data and its return value is a JSON report if it finds C2PA data. If there are validation errors, the report includes a `validation_status` field. Exceptions are thrown on errors. +Use the `read_file` function to read C2PA data from the specified file. This function examines the specified asset file for C2PA data and returns a JSON report if it finds any; it throws exceptions on errors. If there are validation errors, the report includes a `validation_status` field. ```cpp auto json_store = C2pa::read_file("", "") diff --git a/docs/tasks/sign.mdx b/docs/tasks/sign.mdx index 9ac76f4c..bf57db89 100644 --- a/docs/tasks/sign.mdx +++ b/docs/tasks/sign.mdx @@ -15,7 +15,13 @@ import TabItem from '@theme/TabItem'; This is how to sign a manifest using C++. - - This is how to sign a manifest using Node.js. +{' '} + + This is how to sign a manifest using Node.js. + + + + This is how to sign a manifest using Rust. + diff --git a/docs/tasks/write.mdx b/docs/tasks/write.mdx index 38aaa3a4..3ea55402 100644 --- a/docs/tasks/write.mdx +++ b/docs/tasks/write.mdx @@ -15,7 +15,12 @@ import TabItem from '@theme/TabItem'; This is how to write a manifest using C++. - - This is how to write a manifest using Node.js. +{' '} + + This is how to write a manifest using Node.js. + + + + This is how to write a manifest using Rust. From 4b9e7d52abcb7cacd72127a11c5dc58a31f77b8a Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 4 Dec 2024 13:05:49 -0800 Subject: [PATCH 08/58] Add Rust everywhere, add some examples, update .gitignore --- .gitignore | 15 +++- docs/tasks/build.mdx | 131 ++++++++++++++++++++++++++++++++--- docs/tasks/get-resources.mdx | 74 ++++++++++++++++++-- docs/tasks/sign.mdx | 9 +-- docs/tasks/write.mdx | 9 +-- sidebars.js | 4 +- 6 files changed, 212 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index da86b312..d07e58e0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,17 @@ .docusaurus .cache-loader /docs/js-sdk/api -/docs/c2patool -/docs/rust-sdk +/docs/c2patool/*.md +/docs/c2patool/docs/*.md +/docs/c2pa-node/*.md +/docs/c2pa-node-example/*.md +/docs/c2pa-node/docs/*.md +/docs/c2pa-python/*.md +/docs/c2pa-python/docs/*.md +/docs/c2pa-c/*.md +/docs/c2pa-c/docs/*.md +/docs/rust-sdk/*.md +/docs/rust-sdk/docs/*.md /docs/**/readme.md # Misc @@ -23,4 +32,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.dccache +.dccache \ No newline at end of file diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index 7c478036..166fadf2 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -1,6 +1,7 @@ --- id: build title: Building a manifest +hide_table_of_contents: true --- import Tabs from '@theme/Tabs'; @@ -8,18 +9,128 @@ import TabItem from '@theme/TabItem'; - - This is how to build a manifest using Python. - + - - This is how to build a manifest using C++. - +```python +try: + # Define a function to sign the claim bytes + # In this case we are using a pre-defined sign_ps256 method, passing in our private cert + # Normally this cert would be kept safe in some other location + def private_sign(data: bytes) -> bytes: + return sign_ps256(data, "tests/fixtures/ps256.pem") -{' '} - - This is how to build a manifest using Node.js. - + # read our public certs into memory + certs = open(data_dir + "ps256.pub", "rb").read() + + # Create a signer from the private signer, certs and a time stamp service url + signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") + + # Create a builder add a thumbnail resource and an ingredient file. + builder = Builder(manifest_json) + + # Add the resource from a stream + a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb") + builder.add_resource("image/jpeg", a_thumbnail_jpg_stream) + + # Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail + ingredient_json = { + "title": "A.jpg", + "relationship": "parentOf", # "parentOf", "componentOf" or "inputTo" + "thumbnail": { + "identifier": "thumbnail", + "format": "image/jpeg" + } + } + + # Add the ingredient from a stream + a_jpg_stream = open("tests/fixtures/A.jpg", "rb") + builder.add_ingredient("image/jpeg", a_jpg_stream) + + # At this point we could archive or unarchive our Builder to continue later. + # In this example we use a bytearray for the archive stream. + # all ingredients and resources will be saved in the archive + archive = io.BytesIO(bytearray()) + builder.to_archive(archive) + archive.seek() + builder = builder.from_archive(archive) + + # Sign the builder with a stream and output it to a stream + # This returns the binary manifest data that could be uploaded to cloud storage. + input_stream = open("tests/fixtures/A.jpg", "rb") + output_stream = open("target/out.jpg", "wb") + c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream) + +except Exception as err: + print(err) +``` + + + + + +```ts +import { ManifestBuilder } from 'c2pa-node'; + +const manifest = new ManifestBuilder({ + claim_generator: 'my-app/1.0.0', + format: 'image/jpeg', + title: 'node_test_local_signer.jpg', + assertions: [ + { + label: 'c2pa.actions', + data: { + actions: [ + { + action: 'c2pa.created', + }, + ], + }, + }, + { + label: 'com.custom.my-assertion', + data: { + description: 'My custom test assertion', + version: '1.0.0', + }, + }, + ], +}); +``` + + + + + +```cpp +const std::string manifest_json = R"{ + "claim_generator": "c2pa_c_test/0.1", + "claim_generator_info": [ + { + "name": "c2pa-c test", + "version": "0.1" + } + ], + "assertions": [ + { + "label": "c2pa.training-mining", + "data": { + "entries": { + "c2pa.ai_generative_training": { "use": "notAllowed" }, + "c2pa.ai_inference": { "use": "notAllowed" }, + "c2pa.ai_training": { "use": "notAllowed" }, + "c2pa.data_mining": { "use": "notAllowed" } + } + } + } + ] + }; + +auto builder = Builder(manifest_json); + + +``` + + This is how to build a manifest using Rust. diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index 7a3ffd43..6570d788 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -10,7 +10,69 @@ Manifest data can include binary resources such as thumbnail and icon images whi - This is how to get resources from a manifest using JavaScript. + +```JavaScript +import { createC2pa, selectProducer } from 'c2pa'; +import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?url'; +import workerSrc from 'c2pa/dist/c2pa.worker.js?url'; +import { parseISO } from 'date-fns'; + +const sampleImage = + 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; + +(async () => { + let output: string[] = []; + + const c2pa = await createC2pa({ + wasmSrc, + workerSrc, + }); + + const { manifestStore, source } = await c2pa.read(sampleImage); + const activeManifest = manifestStore?.activeManifest; + if (activeManifest) { + // Get thumbnail + // Note: You would normally call `dispose()` when working with a + // component-based UI library (e.g. on component un-mount) + // @ts-expect-error noUnusedLocals + const { url, dispose } = source.thumbnail.getUrl(); + + // Get properties + const properties: Record = { + title: activeManifest.title, + format: activeManifest.format, + claimGenerator: activeManifest.claimGenerator.split('(')[0]?.trim(), + producer: selectProducer(activeManifest)?.name ?? 'Unknown', + thumbnail: ``, + ingredients: (activeManifest.ingredients ?? []) + .map((i) => i.title) + .join(', '), + signatureIssuer: activeManifest.signatureInfo?.issuer, + signatureDate: activeManifest.signatureInfo?.time + ? parseISO(activeManifest.signatureInfo.time).toString() + : 'No date available', + }; + + output = Object.keys(properties).map((key) => { + return ` + + ${key} + ${properties[key]} + + `; + }); + } else { + output.push(` + + No provenance data found + + `); + } + + document.querySelector('#results tbody')!.innerHTML = output.join(''); +})(); +``` + @@ -38,16 +100,14 @@ except Exception as err: - - This is how to get resources from a manifest using C++. - - -{' '} This is how to get resources from a manifest using Node.js. -{' '} + + This is how to get resources from a manifest using C++. + + This is how to get resources using Rust. diff --git a/docs/tasks/sign.mdx b/docs/tasks/sign.mdx index bf57db89..e34ec4ea 100644 --- a/docs/tasks/sign.mdx +++ b/docs/tasks/sign.mdx @@ -11,15 +11,16 @@ import TabItem from '@theme/TabItem'; This is how to sign a manifest using Python. - - This is how to sign a manifest using C++. - - {' '} + This is how to sign a manifest using Node.js. + + This is how to sign a manifest using C++. + + This is how to sign a manifest using Rust. diff --git a/docs/tasks/write.mdx b/docs/tasks/write.mdx index 3ea55402..e776b2cd 100644 --- a/docs/tasks/write.mdx +++ b/docs/tasks/write.mdx @@ -11,15 +11,16 @@ import TabItem from '@theme/TabItem'; This is how to write a manifest using Python. - - This is how to write a manifest using C++. - - {' '} This is how to write a manifest using Node.js. +{' '} + + This is how to write a manifest using C++. + + This is how to write a manifest using Rust. diff --git a/sidebars.js b/sidebars.js index a4962e56..832c166e 100644 --- a/sidebars.js +++ b/sidebars.js @@ -98,12 +98,12 @@ const sidebars = { items: [ { type: 'doc', - id: 'c2patool/manifest', + id: 'c2patool/docs/manifest', label: 'Using a manifest file', }, { type: 'doc', - id: 'c2patool/x_509', + id: 'c2patool/docs/x_509', label: 'Creating and using a certificate', }, { From 131ac2b9b33951b01e0ee97e49a4bdd35dcbace7 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 4 Dec 2024 16:03:24 -0800 Subject: [PATCH 09/58] Change page titles --- docs/manifest/understanding.md | 2 +- docs/tasks/index.md | 7 ++++--- docs/tasks/read.mdx | 9 +++++---- docs/tasks/write.mdx | 2 -- sidebars.js | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/manifest/understanding.md b/docs/manifest/understanding.md index 4202b445..594b6476 100644 --- a/docs/manifest/understanding.md +++ b/docs/manifest/understanding.md @@ -1,6 +1,6 @@ --- id: understanding-manifest -title: Working with manifests +title: Understanding manifests --- ## Overview diff --git a/docs/tasks/index.md b/docs/tasks/index.md index 0ec0dcad..dbc57f21 100644 --- a/docs/tasks/index.md +++ b/docs/tasks/index.md @@ -1,9 +1,10 @@ --- -id: common-tasks -title: Common tasks +id: working-manifests +title: Working with manifests --- -There are a number of common tasks when working with manifests: +There are a number of common tasks when working with manifests. +Although the tasks and APIs are similar at a high level, the way you accomplish each task is specific to the language being used. - [Reading manifest data](./read.mdx) - [Getting resources from a manifest](./get-resources.mdx) diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index 163c3f4b..d75ef2eb 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -18,17 +18,18 @@ Use [`c2pa.read`](../../docs/js-sdk/api/c2pa.c2pa#methods) to read manifest data - **validationStatus**: A list of any validation errors encountered. See [Validation](../../docs/js-sdk/guides/validation) for more information. ```js -import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@0.17.2/+esm'; - +const version = '0.17.2'; const sampleImage = ''; +import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; + (async () => { // Initialize the c2pa-js SDK const c2pa = await createC2pa({ wasmSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@0.17.2/dist/assets/wasm/toolkit_bg.wasm', + 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm', workerSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@0.17.2/dist/c2pa.worker.min.js', + 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', }); try { diff --git a/docs/tasks/write.mdx b/docs/tasks/write.mdx index e776b2cd..571fb175 100644 --- a/docs/tasks/write.mdx +++ b/docs/tasks/write.mdx @@ -11,12 +11,10 @@ import TabItem from '@theme/TabItem'; This is how to write a manifest using Python. -{' '} This is how to write a manifest using Node.js. -{' '} This is how to write a manifest using C++. diff --git a/sidebars.js b/sidebars.js index 00fbd544..021768ef 100644 --- a/sidebars.js +++ b/sidebars.js @@ -24,7 +24,7 @@ const sidebars = { }, { type: 'category', - label: 'Working with manifests', + label: 'Understanding manifests', link: { type: 'doc', id: 'manifest/understanding-manifest' }, collapsed: true, items: [ @@ -63,8 +63,8 @@ const sidebars = { { type: 'category', - label: 'Common tasks', - link: { type: 'doc', id: 'tasks/common-tasks' }, + label: 'Working with manifests', + link: { type: 'doc', id: 'tasks/working-manifests' }, collapsed: true, items: [ { From 27bd8a7fb9709bfe6cc8dc533a00acd74205ee02 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Thu, 19 Dec 2024 15:04:44 -0800 Subject: [PATCH 10/58] reword --- docs/tasks/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tasks/index.md b/docs/tasks/index.md index dbc57f21..bcf676ee 100644 --- a/docs/tasks/index.md +++ b/docs/tasks/index.md @@ -4,7 +4,7 @@ title: Working with manifests --- There are a number of common tasks when working with manifests. -Although the tasks and APIs are similar at a high level, the way you accomplish each task is specific to the language being used. +The way you accomplish each task is specific to the language you're using, although at a high level the process is similar. - [Reading manifest data](./read.mdx) - [Getting resources from a manifest](./get-resources.mdx) From fbc60518f6efc54d608882d8541af909a2943434 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 20 Dec 2024 11:35:38 -0800 Subject: [PATCH 11/58] Add setup --- docs/tasks/index.md | 3 +- docs/tasks/setup.mdx | 93 ++++++++++++++++++++++++++++++++++++++++++++ docs/tasks/sign.mdx | 2 - sidebars.js | 4 ++ 4 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 docs/tasks/setup.mdx diff --git a/docs/tasks/index.md b/docs/tasks/index.md index bcf676ee..2c830f2c 100644 --- a/docs/tasks/index.md +++ b/docs/tasks/index.md @@ -6,8 +6,9 @@ title: Working with manifests There are a number of common tasks when working with manifests. The way you accomplish each task is specific to the language you're using, although at a high level the process is similar. +- [Preliminary setup](./setup.mdx) - [Reading manifest data](./read.mdx) - [Getting resources from a manifest](./get-resources.mdx) - [Building a manifest](./build.mdx) - [Writing a manifest](./write.mdx) -- [Signing a manifest](./sign.mdx) \ No newline at end of file +- [Signing a manifest](./sign.mdx) diff --git a/docs/tasks/setup.mdx b/docs/tasks/setup.mdx new file mode 100644 index 00000000..ab2705c8 --- /dev/null +++ b/docs/tasks/setup.mdx @@ -0,0 +1,93 @@ +--- +id: setup +title: Setup +hide_table_of_contents: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + +{' '} + + This is how to setup the JavaScript library. + + + + This is how to setup the Python library. + +```python +# Import the C2PA Python package +from c2pa import * + +# Import standard general-purpose packages +import os +import io +import logging +import json +import base64 + +# Import web packages used in example implementation +from flask import Flask, request, abort +from flask_cors import CORS +from waitress import serve + +# Import AWS SDK package (to use KMS) +import boto3 +``` + + + + + This is how to setup the Node.js library. + +```js +import { createC2pa } from 'c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const c2pa = createC2pa(); +``` + + + + + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include "c2pa.hpp" +#include "test_signer.hpp" +#include + +// this example uses nlohmann json for parsing the manifest +using json = nlohmann::json; +using namespace std; +namespace fs = std::filesystem; +using namespace c2pa; +``` + + + + + This is how to setup the Rust library. + +```rust +use std::{ + io::{Cursor, Write}, + process::{Command, Stdio}, +}; + +use anyhow::Result; +use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, SigningAlg}; +``` + + + + diff --git a/docs/tasks/sign.mdx b/docs/tasks/sign.mdx index e34ec4ea..1ec2cb57 100644 --- a/docs/tasks/sign.mdx +++ b/docs/tasks/sign.mdx @@ -11,8 +11,6 @@ import TabItem from '@theme/TabItem'; This is how to sign a manifest using Python. -{' '} - This is how to sign a manifest using Node.js. diff --git a/sidebars.js b/sidebars.js index 021768ef..8d0f593a 100644 --- a/sidebars.js +++ b/sidebars.js @@ -67,6 +67,10 @@ const sidebars = { link: { type: 'doc', id: 'tasks/working-manifests' }, collapsed: true, items: [ + { + type: 'doc', + id: 'tasks/setup', + }, { type: 'doc', id: 'tasks/read', From 9aa2deb03e7ad26e55ee5b8608d60cc48b8643db Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 10 Jan 2025 11:43:53 -0800 Subject: [PATCH 12/58] Add more example code --- docs/tasks/read.mdx | 14 --- docs/tasks/setup.mdx | 28 ++++- docs/tasks/sign.mdx | 252 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+), 17 deletions(-) diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index d75ef2eb..e92eeb40 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -18,20 +18,6 @@ Use [`c2pa.read`](../../docs/js-sdk/api/c2pa.c2pa#methods) to read manifest data - **validationStatus**: A list of any validation errors encountered. See [Validation](../../docs/js-sdk/guides/validation) for more information. ```js -const version = '0.17.2'; -const sampleImage = ''; - -import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; - -(async () => { - // Initialize the c2pa-js SDK - const c2pa = await createC2pa({ - wasmSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm', - workerSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', - }); - try { // Read in image and get a manifest store const { manifestStore } = await c2pa.read(sampleImage); diff --git a/docs/tasks/setup.mdx b/docs/tasks/setup.mdx index ab2705c8..f37f3651 100644 --- a/docs/tasks/setup.mdx +++ b/docs/tasks/setup.mdx @@ -9,9 +9,24 @@ import TabItem from '@theme/TabItem'; -{' '} - This is how to setup the JavaScript library. + +```js +const version = '0.24.2'; +const sampleImage = ''; + +import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; + +(async () => { + // Initialize the c2pa-js SDK + const c2pa = await createC2pa({ + wasmSrc: + 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm', + workerSrc: + 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', + }); +``` + @@ -85,7 +100,14 @@ use std::{ }; use anyhow::Result; -use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, SigningAlg}; +use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader}; + +use c2pa_crypto::raw_signature::SigningAlg; +use serde_json::json; + +const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg"); +const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub"); +const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem"); ``` diff --git a/docs/tasks/sign.mdx b/docs/tasks/sign.mdx index 1ec2cb57..925f9b76 100644 --- a/docs/tasks/sign.mdx +++ b/docs/tasks/sign.mdx @@ -9,10 +9,262 @@ import TabItem from '@theme/TabItem'; This is how to sign a manifest using Python. + +```python +request_data = ... # This is the asset being signed +content_type = ... # MIME type of the asset + +manifest = json.dumps({ + "title": "image.jpg", + "format": "image/jpeg", + "claim_generator_info": [ + { + "name": "Documentation example", + "version": "0.0.1" + } + ], + "assertions": [ + { + "label": "c2pa.actions", + "data": { + "actions": [ + { + "action": "c2pa.edited", + "softwareAgent": { + "name": "C2PA Python Example", + "version": "0.1.0" + } + } + ] + } + } + ] +}) + +try: + builder = Builder(manifest) + + signer = create_signer(kms_sign, signing_alg, + cert_chain, timestamp_url) + + result = io.BytesIO(b"") + builder.sign(signer, content_type, io.BytesIO(request_data), result) + + return result.getvalue() +except Exception as e: + logging.error(e) + abort(500, description=e) +``` + This is how to sign a manifest using Node.js. + +Use the `c2pa.sign()` method to sign an ingredient, either locally if you have a signing certificate and key available, or by using a remote signing API. + +## Signing buffers + +If you have an asset file's data loaded into memory, you can sign the the asset using the loaded buffer. + +**NOTE**: Signing using a buffer is currently supported only for `image/jpeg` and `image/png` data. For all other file types, use the [file-based approach](#signing-files) . + +```ts +import { readFile } from 'node:fs/promises'; +import { createC2pa, createTestSigner } from 'c2pa-node'; + +// read an asset into a buffer +const buffer = await readFile('to-be-signed.jpg'); +const asset: Asset = { buffer, mimeType: 'image/jpeg' }; + +// build a manifest to use for signing +const manifest = new ManifestBuilder( + { + claim_generator: 'my-app/1.0.0', + format: 'image/jpeg', + title: 'buffer_signer.jpg', + assertions: [ + { + label: 'c2pa.actions', + data: { + actions: [ + { + action: 'c2pa.created', + }, + ], + }, + }, + { + label: 'com.custom.my-assertion', + data: { + description: 'My custom test assertion', + version: '1.0.0', + }, + }, + ], + }, + { vendor: 'cai' }, +); + +// create a signing function +async function sign(asset, manifest) { + const signer = await createTestSigner(); + const c2pa = createC2pa({ + signer, + }); + + const { signedAsset, signedManifest } = await c2pa.sign({ + asset, + manifest, + }); +} + +// sign +await sign(asset, manifest); +``` + +## Signing files + +To avoid loading the entire asset into memory (or for file types other than JPEG and PNG that don't support in-memory signing), pass in the file path to the asset file to sign it; for example: + +```ts +import { resolve } from 'node:path'; +import { createC2pa, createTestSigner } from 'c2pa-node'; + +// get the asset full path +const asset = { + path: resolve('to-be-signed.jpg'), +}; +// define a location where to place the signed asset +const outputPath = resolve('signed.jpg'); + +// create a signing function +async function sign(asset, manifest) { + const signer = await createTestSigner(); + const c2pa = createC2pa({ + signer, + }); + + const { signedAsset, signedManifest } = await c2pa.sign({ + manifest, + asset, + options: { + outputPath, + }, + }); +} + +// build a manifest to use for signing +const manifest = new ManifestBuilder( + { + claim_generator: 'my-app/1.0.0', + format: 'image/jpeg', + title: 'buffer_signer.jpg', + assertions: [ + { + label: 'c2pa.actions', + data: { + actions: [ + { + action: 'c2pa.created', + }, + ], + }, + }, + { + label: 'com.custom.my-assertion', + data: { + description: 'My custom test assertion', + version: '1.0.0', + }, + }, + ], + }, + { vendor: 'cai' }, +); + +// sign +await sign(asset, manifest); +``` + +## Remote signing + +If you have access to a web service that performs signing, you can use it to sign remotely; for example: + +```ts +import { readFile } from 'node:fs/promises'; +import { fetch, Headers } from 'node-fetch'; +import { createC2pa, SigningAlgorithm } from 'c2pa-node'; + +function createRemoteSigner() { + return { + type: 'remote', + async reserveSize() { + const url = `https://my.signing.service/box-size`; + const res = await fetch(url); + const data = (await res.json()) as { boxSize: number }; + return data.boxSize; + }, + async sign({ reserveSize, toBeSigned }) { + const url = `https://my.signing.service/sign?boxSize=${reserveSize}`; + const res = await fetch(url, { + method: 'POST', + headers: new Headers({ + 'Content-Type': 'application/octet-stream', + }), + body: toBeSigned, + }); + return res.buffer(); + }, + }; +} + +async function sign(asset, manifest) { + const signer = createRemoteSigner(); + const c2pa = createC2pa({ + signer, + }); + + const { signedAsset, signedManifest } = await c2pa.sign({ + asset, + manifest, + }); +} + +const buffer = await readFile('to-be-signed.jpg'); +const asset: Asset = { buffer, mimeType: 'image/jpeg' }; + +const manifest = new ManifestBuilder( + { + claim_generator: 'my-app/1.0.0', + format: 'image/jpeg', + title: 'buffer_signer.jpg', + assertions: [ + { + label: 'c2pa.actions', + data: { + actions: [ + { + action: 'c2pa.created', + }, + ], + }, + }, + { + label: 'com.custom.my-assertion', + data: { + description: 'My custom test assertion', + version: '1.0.0', + }, + }, + ], + }, + { vendor: 'cai' }, +); + +await sign(asset, manifest); +``` + From 0ec851f39cd82f56ad3d9e6041c106c7356dcf45 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 14 Jan 2025 15:57:13 -0800 Subject: [PATCH 13/58] Consolidate tasks, add cpp examples --- .../js-sdk/examples/quickstart/rollup-main.ts | 26 ++ docs/js-sdk/examples/quickstart/vite-main.ts | 27 ++ .../examples/quickstart/webpack-main.ts | 30 ++ docs/js-sdk/examples/view-manifest/index.html | 22 ++ docs/js-sdk/examples/view-manifest/main.ts | 59 ++++ docs/tasks/build.mdx | 201 ++++++++++++- docs/tasks/get-resources.mdx | 71 ++++- docs/tasks/index.md | 4 +- docs/tasks/sign.mdx | 278 ------------------ docs/tasks/write.mdx | 25 -- sidebars.js | 8 - 11 files changed, 433 insertions(+), 318 deletions(-) create mode 100644 docs/js-sdk/examples/quickstart/rollup-main.ts create mode 100644 docs/js-sdk/examples/quickstart/vite-main.ts create mode 100644 docs/js-sdk/examples/quickstart/webpack-main.ts create mode 100644 docs/js-sdk/examples/view-manifest/index.html create mode 100644 docs/js-sdk/examples/view-manifest/main.ts delete mode 100644 docs/tasks/sign.mdx delete mode 100644 docs/tasks/write.mdx diff --git a/docs/js-sdk/examples/quickstart/rollup-main.ts b/docs/js-sdk/examples/quickstart/rollup-main.ts new file mode 100644 index 00000000..f92207aa --- /dev/null +++ b/docs/js-sdk/examples/quickstart/rollup-main.ts @@ -0,0 +1,26 @@ +import { createC2pa } from 'c2pa'; +import wasmModule from 'c2pa/dist/assets/wasm/toolkit_bg.wasm'; + +const sampleImage = + 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; + +(async () => { + // Initialize the c2pa-js SDK + const wasmSrc = await wasmModule(); + const c2pa = await createC2pa({ + wasmSrc, + workerSrc: 'c2pa.worker.min.js', + }); + + try { + // Read in our sample image and get a manifest store + const { manifestStore } = await c2pa.read(sampleImage); + console.log('manifestStore', manifestStore); + + // Get the active manifest + const activeManifest = manifestStore?.activeManifest; + console.log('activeManifest', activeManifest); + } catch (err) { + console.error('Error reading image:', err); + } +})(); diff --git a/docs/js-sdk/examples/quickstart/vite-main.ts b/docs/js-sdk/examples/quickstart/vite-main.ts new file mode 100644 index 00000000..e86371ea --- /dev/null +++ b/docs/js-sdk/examples/quickstart/vite-main.ts @@ -0,0 +1,27 @@ +const sampleImage = + 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; + +(async () => { + // Information about where to fetch the library + const version = '0.17.2'; + const libraryUrl = `https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm`; + + // Initialize the c2pa-js SDK + const { createC2pa } = await import(libraryUrl); + const c2pa = await createC2pa({ + wasmSrc: `https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm`, + workerSrc: `https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js`, + }); + + // Read in our sample image and get a manifest store + try { + const { manifestStore } = await c2pa.read(sampleImage); + console.log('manifestStore', manifestStore); + + // Get the active manifest + const activeManifest = manifestStore?.activeManifest; + console.log('activeManifest', activeManifest); + } catch (err) { + console.error('Error reading image:', err); + } +})(); diff --git a/docs/js-sdk/examples/quickstart/webpack-main.ts b/docs/js-sdk/examples/quickstart/webpack-main.ts new file mode 100644 index 00000000..31a5898b --- /dev/null +++ b/docs/js-sdk/examples/quickstart/webpack-main.ts @@ -0,0 +1,30 @@ +import { createC2pa } from 'c2pa'; +import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?file'; +import workerSrc from 'c2pa/dist/c2pa.worker.min.js?file'; + +const element = document.createElement('div'); +element.innerHTML = `Please view the console`; +document.body.appendChild(element); + +const sampleImage = + 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; + +(async () => { + // Initialize the c2pa-js SDK + const c2pa = await createC2pa({ + wasmSrc, + workerSrc, + }); + + try { + // Read in our sample image and get a manifest store + const { manifestStore } = await c2pa.read(sampleImage); + console.log('manifestStore', manifestStore); + + // Get the active manifest + const activeManifest = manifestStore?.activeManifest; + console.log('activeManifest', activeManifest); + } catch (err) { + console.error('Error reading image:', err); + } +})(); diff --git a/docs/js-sdk/examples/view-manifest/index.html b/docs/js-sdk/examples/view-manifest/index.html new file mode 100644 index 00000000..4e910bc7 --- /dev/null +++ b/docs/js-sdk/examples/view-manifest/index.html @@ -0,0 +1,22 @@ + + + + + + + + active-manifest + + + + + + + + + + +
PropertyValue
Loading…
+ + + diff --git a/docs/js-sdk/examples/view-manifest/main.ts b/docs/js-sdk/examples/view-manifest/main.ts new file mode 100644 index 00000000..94c8d745 --- /dev/null +++ b/docs/js-sdk/examples/view-manifest/main.ts @@ -0,0 +1,59 @@ +import { createC2pa, selectProducer } from 'c2pa'; +import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?url'; +import workerSrc from 'c2pa/dist/c2pa.worker.js?url'; +import { parseISO } from 'date-fns'; + +const sampleImage = + 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; + +(async () => { + let output: string[] = []; + + const c2pa = await createC2pa({ + wasmSrc, + workerSrc, + }); + + const { manifestStore, source } = await c2pa.read(sampleImage); + const activeManifest = manifestStore?.activeManifest; + if (activeManifest) { + // Get thumbnail + // Note: You would normally call `dispose()` when working with a + // component-based UI library (e.g. on component un-mount) + // @ts-expect-error noUnusedLocals + const { url, dispose } = source.thumbnail.getUrl(); + + // Get properties + const properties: Record = { + title: activeManifest.title, + format: activeManifest.format, + claimGenerator: activeManifest.claimGenerator.split('(')[0]?.trim(), + producer: selectProducer(activeManifest)?.name ?? 'Unknown', + thumbnail: ``, + ingredients: (activeManifest.ingredients ?? []) + .map((i) => i.title) + .join(', '), + signatureIssuer: activeManifest.signatureInfo?.issuer, + signatureDate: activeManifest.signatureInfo?.time + ? parseISO(activeManifest.signatureInfo.time).toString() + : 'No date available', + }; + + output = Object.keys(properties).map((key) => { + return ` + + ${key} + ${properties[key]} + + `; + }); + } else { + output.push(` + + No provenance data found + + `); + } + + document.querySelector('#results tbody')!.innerHTML = output.join(''); +})(); diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index 166fadf2..734e7d92 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -1,6 +1,6 @@ --- id: build -title: Building a manifest +title: Attaching and signing a manifest hide_table_of_contents: true --- @@ -11,6 +11,8 @@ import TabItem from '@theme/TabItem'; +Use a `Builder` object to add a manifest to an asset. + ```python try: # Define a function to sign the claim bytes @@ -32,6 +34,10 @@ try: a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb") builder.add_resource("image/jpeg", a_thumbnail_jpg_stream) + # Add the resource from a file + # The URI provided here, "thumbnail", must match an identifier in the manifest definition. + builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg") + # Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail ingredient_json = { "title": "A.jpg", @@ -64,6 +70,54 @@ except Exception as err: print(err) ``` +FROM SIGNING: + +```python +request_data = ... # This is the asset being signed +content_type = ... # MIME type of the asset + +manifest = json.dumps({ + "title": "image.jpg", + "format": "image/jpeg", + "claim_generator_info": [ + { + "name": "Documentation example", + "version": "0.0.1" + } + ], + "assertions": [ + { + "label": "c2pa.actions", + "data": { + "actions": [ + { + "action": "c2pa.edited", + "softwareAgent": { + "name": "C2PA Python Example", + "version": "0.1.0" + } + } + ] + } + } + ] +}) + +try: + builder = Builder(manifest) + + signer = create_signer(kms_sign, signing_alg, + cert_chain, timestamp_url) + + result = io.BytesIO(b"") + builder.sign(signer, content_type, io.BytesIO(request_data), result) + + return result.getvalue() +except Exception as e: + logging.error(e) + abort(500, description=e) +``` + @@ -95,6 +149,148 @@ const manifest = new ManifestBuilder({ }, ], }); +``` + +FROM SIGNING: + +Use the `c2pa.sign()` method to sign an ingredient, either locally if you have a signing certificate and key available, or by using a remote signing API. + +## Signing a stream + +If you have an asset file's data loaded into memory, you can sign the the asset using the loaded stream (buffer). + +**NOTE**: Signing using a stream is currently supported only for `image/jpeg` and `image/png` data. For all other file types, use the [file-based approach](#signing-files) . + +```ts +import { readFile } from 'node:fs/promises'; +import { createC2pa, createTestSigner } from 'c2pa-node'; + +// read an asset into a buffer +const buffer = await readFile('to-be-signed.jpg'); +const asset: Asset = { buffer, mimeType: 'image/jpeg' }; + +// build a manifest to use for signing +const manifest = new ManifestBuilder( + { + claim_generator: 'my-app/1.0.0', + format: 'image/jpeg', + title: 'buffer_signer.jpg', + assertions: [ + { + label: 'c2pa.actions', + data: { + actions: [ + { + action: 'c2pa.created', + }, + ], + }, + }, + { + label: 'com.custom.my-assertion', + data: { + description: 'My custom test assertion', + version: '1.0.0', + }, + }, + ], + }, + { vendor: 'cai' }, +); + +// create a signing function +async function sign(asset, manifest) { + const signer = await createTestSigner(); + const c2pa = createC2pa({ + signer, + }); + + const { signedAsset, signedManifest } = await c2pa.sign({ + asset, + manifest, + }); +} + +// sign +await sign(asset, manifest); +``` + +**Remote signing** + +If you have access to a web service that performs signing, you can use it to sign remotely; for example: + +```ts +import { readFile } from 'node:fs/promises'; +import { fetch, Headers } from 'node-fetch'; +import { createC2pa, SigningAlgorithm } from 'c2pa-node'; + +function createRemoteSigner() { + return { + type: 'remote', + async reserveSize() { + const url = `https://my.signing.service/box-size`; + const res = await fetch(url); + const data = (await res.json()) as { boxSize: number }; + return data.boxSize; + }, + async sign({ reserveSize, toBeSigned }) { + const url = `https://my.signing.service/sign?boxSize=${reserveSize}`; + const res = await fetch(url, { + method: 'POST', + headers: new Headers({ + 'Content-Type': 'application/octet-stream', + }), + body: toBeSigned, + }); + return res.buffer(); + }, + }; +} + +async function sign(asset, manifest) { + const signer = createRemoteSigner(); + const c2pa = createC2pa({ + signer, + }); + + const { signedAsset, signedManifest } = await c2pa.sign({ + asset, + manifest, + }); +} + +const buffer = await readFile('to-be-signed.jpg'); +const asset: Asset = { buffer, mimeType: 'image/jpeg' }; + +const manifest = new ManifestBuilder( + { + claim_generator: 'my-app/1.0.0', + format: 'image/jpeg', + title: 'buffer_signer.jpg', + assertions: [ + { + label: 'c2pa.actions', + data: { + actions: [ + { + action: 'c2pa.created', + }, + ], + }, + }, + { + label: 'com.custom.my-assertion', + data: { + description: 'My custom test assertion', + version: '1.0.0', + }, + }, + ], + }, + { vendor: 'cai' }, +); + +await sign(asset, manifest); ``` @@ -127,13 +323,12 @@ const std::string manifest_json = R"{ auto builder = Builder(manifest_json); - ```
- This is how to build a manifest using Rust. + This is how to attach and sign a manifest using Rust.
diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index 6570d788..3ef5425a 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -11,7 +11,7 @@ Manifest data can include binary resources such as thumbnail and icon images whi -```JavaScript +```js import { createC2pa, selectProducer } from 'c2pa'; import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?url'; import workerSrc from 'c2pa/dist/c2pa.worker.js?url'; @@ -106,6 +106,75 @@ except Exception as err: This is how to get resources from a manifest using C++. + +```cpp +fs::path manifest_path = current_dir / "../tests/fixtures/training.json"; +//fs::path certs_path = current_dir / "../tests/fixtures/es256_certs.pem"; +//fs::path image_path = current_dir / "../tests/fixtures/A.jpg"; +fs::path output_path = current_dir / "../target/example/training.jpg"; +fs::path thumbnail_path = current_dir / "../target/example/thumbnail.jpg"; + +string read_text_file(const fs::path &path) +{ + ifstream file(path); + if (!file.is_open()) + { + throw runtime_error("Could not open file " + string(path)); + } + string contents((istreambuf_iterator(file)), istreambuf_iterator()); + file.close(); + return contents.data(); +} + +try + { + // load the manifest, certs, and private key + /* Commenting out, because not part of resource reading + string manifest_json = read_text_file(manifest_path).data(); + + string certs = read_text_file(certs_path).data(); + + // create a signer + Signer signer = Signer(&test_signer, Es256, certs, "http://timestamp.digicert.com"); + + auto builder = Builder(manifest_json); + auto manifest_data = builder.sign(image_path, output_path, signer); + */ + + // read the new manifest and display the JSON + auto reader = Reader(output_path); + + auto manifest_store_json = reader.json(); + cout << "The new manifest is " << manifest_store_json << endl; + + // get the active manifest + json manifest_store = json::parse(manifest_store_json); + if (manifest_store.contains("active_manifest")) + { + string active_manifest = manifest_store["active_manifest"]; + json &manifest = manifest_store["manifests"][active_manifest]; + + string identifer = manifest["thumbnail"]["identifier"]; + + reader.get_resource(identifer, thumbnail_path); + + cout << "thumbnail written to" << thumbnail_path << endl; + } + } + catch (c2pa::Exception const &e) + { + cout << "C2PA Error: " << e.what() << endl; + } + catch (runtime_error const &e) + { + cout << "setup error" << e.what() << endl; + } + catch (json::parse_error const &e) + { + cout << "parse error " << e.what() << endl; + } +``` + diff --git a/docs/tasks/index.md b/docs/tasks/index.md index 2c830f2c..bedb3c62 100644 --- a/docs/tasks/index.md +++ b/docs/tasks/index.md @@ -9,6 +9,4 @@ The way you accomplish each task is specific to the language you're using, altho - [Preliminary setup](./setup.mdx) - [Reading manifest data](./read.mdx) - [Getting resources from a manifest](./get-resources.mdx) -- [Building a manifest](./build.mdx) -- [Writing a manifest](./write.mdx) -- [Signing a manifest](./sign.mdx) +- [Attaching a manifest to an asset and signing it](./build.mdx) diff --git a/docs/tasks/sign.mdx b/docs/tasks/sign.mdx deleted file mode 100644 index 925f9b76..00000000 --- a/docs/tasks/sign.mdx +++ /dev/null @@ -1,278 +0,0 @@ ---- -id: sign -title: Signing manifest data ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - - - - This is how to sign a manifest using Python. - -```python -request_data = ... # This is the asset being signed -content_type = ... # MIME type of the asset - -manifest = json.dumps({ - "title": "image.jpg", - "format": "image/jpeg", - "claim_generator_info": [ - { - "name": "Documentation example", - "version": "0.0.1" - } - ], - "assertions": [ - { - "label": "c2pa.actions", - "data": { - "actions": [ - { - "action": "c2pa.edited", - "softwareAgent": { - "name": "C2PA Python Example", - "version": "0.1.0" - } - } - ] - } - } - ] -}) - -try: - builder = Builder(manifest) - - signer = create_signer(kms_sign, signing_alg, - cert_chain, timestamp_url) - - result = io.BytesIO(b"") - builder.sign(signer, content_type, io.BytesIO(request_data), result) - - return result.getvalue() -except Exception as e: - logging.error(e) - abort(500, description=e) -``` - - - - - This is how to sign a manifest using Node.js. - -Use the `c2pa.sign()` method to sign an ingredient, either locally if you have a signing certificate and key available, or by using a remote signing API. - -## Signing buffers - -If you have an asset file's data loaded into memory, you can sign the the asset using the loaded buffer. - -**NOTE**: Signing using a buffer is currently supported only for `image/jpeg` and `image/png` data. For all other file types, use the [file-based approach](#signing-files) . - -```ts -import { readFile } from 'node:fs/promises'; -import { createC2pa, createTestSigner } from 'c2pa-node'; - -// read an asset into a buffer -const buffer = await readFile('to-be-signed.jpg'); -const asset: Asset = { buffer, mimeType: 'image/jpeg' }; - -// build a manifest to use for signing -const manifest = new ManifestBuilder( - { - claim_generator: 'my-app/1.0.0', - format: 'image/jpeg', - title: 'buffer_signer.jpg', - assertions: [ - { - label: 'c2pa.actions', - data: { - actions: [ - { - action: 'c2pa.created', - }, - ], - }, - }, - { - label: 'com.custom.my-assertion', - data: { - description: 'My custom test assertion', - version: '1.0.0', - }, - }, - ], - }, - { vendor: 'cai' }, -); - -// create a signing function -async function sign(asset, manifest) { - const signer = await createTestSigner(); - const c2pa = createC2pa({ - signer, - }); - - const { signedAsset, signedManifest } = await c2pa.sign({ - asset, - manifest, - }); -} - -// sign -await sign(asset, manifest); -``` - -## Signing files - -To avoid loading the entire asset into memory (or for file types other than JPEG and PNG that don't support in-memory signing), pass in the file path to the asset file to sign it; for example: - -```ts -import { resolve } from 'node:path'; -import { createC2pa, createTestSigner } from 'c2pa-node'; - -// get the asset full path -const asset = { - path: resolve('to-be-signed.jpg'), -}; -// define a location where to place the signed asset -const outputPath = resolve('signed.jpg'); - -// create a signing function -async function sign(asset, manifest) { - const signer = await createTestSigner(); - const c2pa = createC2pa({ - signer, - }); - - const { signedAsset, signedManifest } = await c2pa.sign({ - manifest, - asset, - options: { - outputPath, - }, - }); -} - -// build a manifest to use for signing -const manifest = new ManifestBuilder( - { - claim_generator: 'my-app/1.0.0', - format: 'image/jpeg', - title: 'buffer_signer.jpg', - assertions: [ - { - label: 'c2pa.actions', - data: { - actions: [ - { - action: 'c2pa.created', - }, - ], - }, - }, - { - label: 'com.custom.my-assertion', - data: { - description: 'My custom test assertion', - version: '1.0.0', - }, - }, - ], - }, - { vendor: 'cai' }, -); - -// sign -await sign(asset, manifest); -``` - -## Remote signing - -If you have access to a web service that performs signing, you can use it to sign remotely; for example: - -```ts -import { readFile } from 'node:fs/promises'; -import { fetch, Headers } from 'node-fetch'; -import { createC2pa, SigningAlgorithm } from 'c2pa-node'; - -function createRemoteSigner() { - return { - type: 'remote', - async reserveSize() { - const url = `https://my.signing.service/box-size`; - const res = await fetch(url); - const data = (await res.json()) as { boxSize: number }; - return data.boxSize; - }, - async sign({ reserveSize, toBeSigned }) { - const url = `https://my.signing.service/sign?boxSize=${reserveSize}`; - const res = await fetch(url, { - method: 'POST', - headers: new Headers({ - 'Content-Type': 'application/octet-stream', - }), - body: toBeSigned, - }); - return res.buffer(); - }, - }; -} - -async function sign(asset, manifest) { - const signer = createRemoteSigner(); - const c2pa = createC2pa({ - signer, - }); - - const { signedAsset, signedManifest } = await c2pa.sign({ - asset, - manifest, - }); -} - -const buffer = await readFile('to-be-signed.jpg'); -const asset: Asset = { buffer, mimeType: 'image/jpeg' }; - -const manifest = new ManifestBuilder( - { - claim_generator: 'my-app/1.0.0', - format: 'image/jpeg', - title: 'buffer_signer.jpg', - assertions: [ - { - label: 'c2pa.actions', - data: { - actions: [ - { - action: 'c2pa.created', - }, - ], - }, - }, - { - label: 'com.custom.my-assertion', - data: { - description: 'My custom test assertion', - version: '1.0.0', - }, - }, - ], - }, - { vendor: 'cai' }, -); - -await sign(asset, manifest); -``` - - - - - This is how to sign a manifest using C++. - - - - This is how to sign a manifest using Rust. - - - diff --git a/docs/tasks/write.mdx b/docs/tasks/write.mdx deleted file mode 100644 index 571fb175..00000000 --- a/docs/tasks/write.mdx +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: write -title: Writing a manifest ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - - - - This is how to write a manifest using Python. - - - - This is how to write a manifest using Node.js. - - - - This is how to write a manifest using C++. - - - - This is how to write a manifest using Rust. - - diff --git a/sidebars.js b/sidebars.js index 8d0f593a..82e18580 100644 --- a/sidebars.js +++ b/sidebars.js @@ -83,14 +83,6 @@ const sidebars = { type: 'doc', id: 'tasks/build', }, - { - type: 'doc', - id: 'tasks/write', - }, - { - type: 'doc', - id: 'tasks/sign', - }, ], }, From 6774774255951926313334f5eb929af65f480148 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 15 Jan 2025 13:29:59 -0800 Subject: [PATCH 14/58] Add Rust examples, fix syntax highlighting for Rust --- docs/tasks/build.mdx | 52 ++++++++++++++++++++++++++++++++++++ docs/tasks/get-resources.mdx | 31 +++++++++++++++++++++ docs/tasks/read.mdx | 4 +-- docs/tasks/setup.mdx | 2 +- docusaurus.config.js | 1 + 5 files changed, 87 insertions(+), 3 deletions(-) diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index 734e7d92..e1f596fb 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -329,6 +329,58 @@ auto builder = Builder(manifest_json); This is how to attach and sign a manifest using Rust. + +From [`c2pa-rs/sdk/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L88C5-L134C1): + +```rust +let json = manifest_def(title, format); + +let mut builder = Builder::from_json(&json)?; +builder.add_ingredient_from_stream( + json!({ + "title": parent_name, + "relationship": "parentOf" + }) + .to_string(), + format, + &mut source, +)?; + +let thumb_uri = builder + .definition + .thumbnail + .as_ref() + .map(|t| t.identifier.clone()); + +// add a manifest thumbnail ( just reuse the image for now ) +if let Some(uri) = thumb_uri { + if !uri.starts_with("self#jumbf") { + source.rewind()?; + builder.add_resource(&uri, &mut source)?; + } +} + +// write the manifest builder to a zipped stream +let mut zipped = Cursor::new(Vec::new()); +builder.to_archive(&mut zipped)?; + +// write the zipped stream to a file for debugging +//let debug_path = format!("{}/../target/test.zip", env!("CARGO_MANIFEST_DIR")); +// std::fs::write(debug_path, zipped.get_ref())?; + +// unzip the manifest builder from the zipped stream +zipped.rewind()?; + +let ed_signer = + |_context: *const (), data: &[u8]| CallbackSigner::ed25519_sign(data, PRIVATE_KEY); +let signer = CallbackSigner::new(ed_signer, SigningAlg::Ed25519, CERTS); + +let mut builder = Builder::from_archive(&mut zipped)?; +// sign the ManifestStoreBuilder and write it to the output stream +let mut dest = Cursor::new(Vec::new()); +builder.sign(&signer, format, &mut source, &mut dest)?; +``` + diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index 3ef5425a..d666d89c 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -179,6 +179,37 @@ try This is how to get resources using Rust. + +This is from [`resource_to_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.resource_to_stream) API doc: + +```rust +use c2pa::Reader; +let stream = std::io::Cursor::new(Vec::new()); +let reader = Reader::from_file("path/to/file.jpg").unwrap(); +let manifest = reader.active_manifest().unwrap(); +let uri = &manifest.thumbnail_ref().unwrap().identifier; +let bytes_written = reader.resource_to_stream(uri, stream).unwrap(); +``` + +This is from [`c2pa-rs/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L138): + +```rust +let reader = Reader::from_stream(format, &mut dest)?; + +// extract a thumbnail image from the ManifestStore +let mut thumbnail = Cursor::new(Vec::new()); +if let Some(manifest) = reader.active_manifest() { + if let Some(thumbnail_ref) = manifest.thumbnail_ref() { + reader.resource_to_stream(&thumbnail_ref.identifier, &mut thumbnail)?; + println!( + "wrote thumbnail {} of size {}", + thumbnail_ref.format, + thumbnail.get_ref().len() + ); + } +} +``` +
diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index e92eeb40..0a8adb99 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -114,7 +114,7 @@ Use the [`Reader`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html) struct t Use [`from_file`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file) to read manifest data from a file: -```rs +```rust use c2pa::Reader; let reader = Reader::from_file("path/to/file.jpg").unwrap(); ``` @@ -125,7 +125,7 @@ There is also an asynchronous version of this method, [`from_stream_async`](http Use [`from_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream) to read manifest data from a stream: -```rs +```rust use std::io::Cursor; use c2pa::Reader; diff --git a/docs/tasks/setup.mdx b/docs/tasks/setup.mdx index f37f3651..e6c6dcbb 100644 --- a/docs/tasks/setup.mdx +++ b/docs/tasks/setup.mdx @@ -91,7 +91,7 @@ using namespace c2pa; - This is how to setup the Rust library. + This is how to setup your code to use the Rust library. ```rust use std::{ diff --git a/docusaurus.config.js b/docusaurus.config.js index d60197ec..44f6af07 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -140,6 +140,7 @@ const config = { prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, + additionalLanguages: ['rust'], }, algolia: { // The application ID provided by Algolia From cb64e8d545d2924293e93efc9d07baca0df5c1c3 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 15 Jan 2025 14:23:16 -0800 Subject: [PATCH 15/58] More example code --- docs/tasks/get-resources.mdx | 109 ++++++++++++++++++----------------- docs/tasks/read.mdx | 1 + 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index d666d89c..e957461a 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -108,12 +108,6 @@ except Exception as err: This is how to get resources from a manifest using C++. ```cpp -fs::path manifest_path = current_dir / "../tests/fixtures/training.json"; -//fs::path certs_path = current_dir / "../tests/fixtures/es256_certs.pem"; -//fs::path image_path = current_dir / "../tests/fixtures/A.jpg"; -fs::path output_path = current_dir / "../target/example/training.jpg"; -fs::path thumbnail_path = current_dir / "../target/example/thumbnail.jpg"; - string read_text_file(const fs::path &path) { ifstream file(path); @@ -126,53 +120,62 @@ string read_text_file(const fs::path &path) return contents.data(); } -try - { - // load the manifest, certs, and private key - /* Commenting out, because not part of resource reading - string manifest_json = read_text_file(manifest_path).data(); - - string certs = read_text_file(certs_path).data(); - - // create a signer - Signer signer = Signer(&test_signer, Es256, certs, "http://timestamp.digicert.com"); - - auto builder = Builder(manifest_json); - auto manifest_data = builder.sign(image_path, output_path, signer); - */ - - // read the new manifest and display the JSON - auto reader = Reader(output_path); - - auto manifest_store_json = reader.json(); - cout << "The new manifest is " << manifest_store_json << endl; - - // get the active manifest - json manifest_store = json::parse(manifest_store_json); - if (manifest_store.contains("active_manifest")) - { - string active_manifest = manifest_store["active_manifest"]; - json &manifest = manifest_store["manifests"][active_manifest]; - - string identifer = manifest["thumbnail"]["identifier"]; - - reader.get_resource(identifer, thumbnail_path); - - cout << "thumbnail written to" << thumbnail_path << endl; - } - } - catch (c2pa::Exception const &e) - { - cout << "C2PA Error: " << e.what() << endl; - } - catch (runtime_error const &e) - { - cout << "setup error" << e.what() << endl; - } - catch (json::parse_error const &e) - { - cout << "parse error " << e.what() << endl; - } +int main() +{ + fs::path manifest_path = current_dir / "../tests/fixtures/training.json"; + //fs::path certs_path = current_dir / "../tests/fixtures/es256_certs.pem"; + //fs::path image_path = current_dir / "../tests/fixtures/A.jpg"; + fs::path output_path = current_dir / "../target/example/training.jpg"; + fs::path thumbnail_path = current_dir / "../target/example/thumbnail.jpg"; + + try + { + // load the manifest, certs, and private key + /* Commenting out, because not part of resource reading + string manifest_json = read_text_file(manifest_path).data(); + + string certs = read_text_file(certs_path).data(); + + // create a signer + Signer signer = Signer(&test_signer, Es256, certs, "http://timestamp.digicert.com"); + + auto builder = Builder(manifest_json); + auto manifest_data = builder.sign(image_path, output_path, signer); + */ + + // read the new manifest and display the JSON + auto reader = Reader(output_path); + + auto manifest_store_json = reader.json(); + cout << "The new manifest is " << manifest_store_json << endl; + + // get the active manifest + json manifest_store = json::parse(manifest_store_json); + if (manifest_store.contains("active_manifest")) + { + string active_manifest = manifest_store["active_manifest"]; + json &manifest = manifest_store["manifests"][active_manifest]; + + string identifer = manifest["thumbnail"]["identifier"]; + + reader.get_resource(identifer, thumbnail_path); + + cout << "thumbnail written to" << thumbnail_path << endl; + } + } + catch (c2pa::Exception const &e) + { + cout << "C2PA Error: " << e.what() << endl; + } + catch (runtime_error const &e) + { + cout << "setup error" << e.what() << endl; + } + catch (json::parse_error const &e) + { + cout << "parse error " << e.what() << endl; + } +} ``` diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index 0a8adb99..d53dfa36 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -78,6 +78,7 @@ async function read(path, mimeType) { } else { console.log('No claim found'); } + // If there are no validation errors, then validation_status will be an empty array } await read('my-c2pa-file.jpg', 'image/jpeg'); From b34aa0d616be670d30661f4c1803181f088a5d43 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 12 Feb 2025 14:36:26 -0800 Subject: [PATCH 16/58] Move example task code into includes to make reviewing by developers easier. --- docs/tasks/build.mdx | 396 ++----------------- docs/tasks/get-resources.mdx | 214 +--------- docs/tasks/includes/_cpp-build.md | 28 ++ docs/tasks/includes/_cpp-get-resources.md | 72 ++++ docs/tasks/includes/_cpp-read.md | 17 + docs/tasks/includes/_cpp-setup.md | 21 + docs/tasks/includes/_js-get-resources.md | 61 +++ docs/tasks/includes/_js-read.md | 21 + docs/tasks/includes/_js-setup.md | 17 + docs/tasks/includes/_node-build.md | 171 ++++++++ docs/tasks/includes/_node-get-resources.md | 5 + docs/tasks/includes/_node-read.md | 24 ++ docs/tasks/includes/_node-setup.md | 8 + docs/tasks/includes/_python-build.md | 107 +++++ docs/tasks/includes/_python-get-resources.md | 21 + docs/tasks/includes/_python-read.md | 20 + docs/tasks/includes/_python-setup.md | 21 + docs/tasks/includes/_rust-build.md | 48 +++ docs/tasks/includes/_rust-get-resources.md | 31 ++ docs/tasks/includes/_rust-read.md | 28 ++ docs/tasks/includes/_rust-setup.md | 18 + docs/tasks/includes/touch.sh | 17 + docs/tasks/read.mdx | 133 +------ docs/tasks/setup.mdx | 114 +----- 24 files changed, 839 insertions(+), 774 deletions(-) create mode 100644 docs/tasks/includes/_cpp-build.md create mode 100644 docs/tasks/includes/_cpp-get-resources.md create mode 100644 docs/tasks/includes/_cpp-read.md create mode 100644 docs/tasks/includes/_cpp-setup.md create mode 100644 docs/tasks/includes/_js-get-resources.md create mode 100644 docs/tasks/includes/_js-read.md create mode 100644 docs/tasks/includes/_js-setup.md create mode 100644 docs/tasks/includes/_node-build.md create mode 100644 docs/tasks/includes/_node-get-resources.md create mode 100644 docs/tasks/includes/_node-read.md create mode 100644 docs/tasks/includes/_node-setup.md create mode 100644 docs/tasks/includes/_python-build.md create mode 100644 docs/tasks/includes/_python-get-resources.md create mode 100644 docs/tasks/includes/_python-read.md create mode 100644 docs/tasks/includes/_python-setup.md create mode 100644 docs/tasks/includes/_rust-build.md create mode 100644 docs/tasks/includes/_rust-get-resources.md create mode 100644 docs/tasks/includes/_rust-read.md create mode 100644 docs/tasks/includes/_rust-setup.md create mode 100755 docs/tasks/includes/touch.sh diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index e1f596fb..6c5776d4 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -7,380 +7,36 @@ hide_table_of_contents: true import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; - - - - -Use a `Builder` object to add a manifest to an asset. - -```python -try: - # Define a function to sign the claim bytes - # In this case we are using a pre-defined sign_ps256 method, passing in our private cert - # Normally this cert would be kept safe in some other location - def private_sign(data: bytes) -> bytes: - return sign_ps256(data, "tests/fixtures/ps256.pem") - - # read our public certs into memory - certs = open(data_dir + "ps256.pub", "rb").read() - - # Create a signer from the private signer, certs and a time stamp service url - signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") - - # Create a builder add a thumbnail resource and an ingredient file. - builder = Builder(manifest_json) - - # Add the resource from a stream - a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb") - builder.add_resource("image/jpeg", a_thumbnail_jpg_stream) - - # Add the resource from a file - # The URI provided here, "thumbnail", must match an identifier in the manifest definition. - builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg") - - # Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail - ingredient_json = { - "title": "A.jpg", - "relationship": "parentOf", # "parentOf", "componentOf" or "inputTo" - "thumbnail": { - "identifier": "thumbnail", - "format": "image/jpeg" - } - } - - # Add the ingredient from a stream - a_jpg_stream = open("tests/fixtures/A.jpg", "rb") - builder.add_ingredient("image/jpeg", a_jpg_stream) - - # At this point we could archive or unarchive our Builder to continue later. - # In this example we use a bytearray for the archive stream. - # all ingredients and resources will be saved in the archive - archive = io.BytesIO(bytearray()) - builder.to_archive(archive) - archive.seek() - builder = builder.from_archive(archive) - - # Sign the builder with a stream and output it to a stream - # This returns the binary manifest data that could be uploaded to cloud storage. - input_stream = open("tests/fixtures/A.jpg", "rb") - output_stream = open("target/out.jpg", "wb") - c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream) - -except Exception as err: - print(err) -``` - -FROM SIGNING: - -```python -request_data = ... # This is the asset being signed -content_type = ... # MIME type of the asset - -manifest = json.dumps({ - "title": "image.jpg", - "format": "image/jpeg", - "claim_generator_info": [ - { - "name": "Documentation example", - "version": "0.0.1" - } - ], - "assertions": [ - { - "label": "c2pa.actions", - "data": { - "actions": [ - { - "action": "c2pa.edited", - "softwareAgent": { - "name": "C2PA Python Example", - "version": "0.1.0" - } - } - ] - } - } - ] -}) - -try: - builder = Builder(manifest) - - signer = create_signer(kms_sign, signing_alg, - cert_chain, timestamp_url) - - result = io.BytesIO(b"") - builder.sign(signer, content_type, io.BytesIO(request_data), result) - - return result.getvalue() -except Exception as e: - logging.error(e) - abort(500, description=e) -``` - - - - - -```ts -import { ManifestBuilder } from 'c2pa-node'; - -const manifest = new ManifestBuilder({ - claim_generator: 'my-app/1.0.0', - format: 'image/jpeg', - title: 'node_test_local_signer.jpg', - assertions: [ - { - label: 'c2pa.actions', - data: { - actions: [ - { - action: 'c2pa.created', - }, - ], - }, - }, - { - label: 'com.custom.my-assertion', - data: { - description: 'My custom test assertion', - version: '1.0.0', - }, - }, - ], -}); -``` - -FROM SIGNING: - -Use the `c2pa.sign()` method to sign an ingredient, either locally if you have a signing certificate and key available, or by using a remote signing API. - -## Signing a stream - -If you have an asset file's data loaded into memory, you can sign the the asset using the loaded stream (buffer). +import PythonBuild from './includes/_python-build.md'; +import NodeBuild from './includes/_node-build.md'; +import CppBuild from './includes/_cpp-build.md'; +import RustBuild from './includes/_rust-build.md'; -**NOTE**: Signing using a stream is currently supported only for `image/jpeg` and `image/png` data. For all other file types, use the [file-based approach](#signing-files) . - -```ts -import { readFile } from 'node:fs/promises'; -import { createC2pa, createTestSigner } from 'c2pa-node'; - -// read an asset into a buffer -const buffer = await readFile('to-be-signed.jpg'); -const asset: Asset = { buffer, mimeType: 'image/jpeg' }; - -// build a manifest to use for signing -const manifest = new ManifestBuilder( - { - claim_generator: 'my-app/1.0.0', - format: 'image/jpeg', - title: 'buffer_signer.jpg', - assertions: [ - { - label: 'c2pa.actions', - data: { - actions: [ - { - action: 'c2pa.created', - }, - ], - }, - }, - { - label: 'com.custom.my-assertion', - data: { - description: 'My custom test assertion', - version: '1.0.0', - }, - }, - ], - }, - { vendor: 'cai' }, -); - -// create a signing function -async function sign(asset, manifest) { - const signer = await createTestSigner(); - const c2pa = createC2pa({ - signer, - }); - - const { signedAsset, signedManifest } = await c2pa.sign({ - asset, - manifest, - }); -} - -// sign -await sign(asset, manifest); -``` - -**Remote signing** - -If you have access to a web service that performs signing, you can use it to sign remotely; for example: - -```ts -import { readFile } from 'node:fs/promises'; -import { fetch, Headers } from 'node-fetch'; -import { createC2pa, SigningAlgorithm } from 'c2pa-node'; - -function createRemoteSigner() { - return { - type: 'remote', - async reserveSize() { - const url = `https://my.signing.service/box-size`; - const res = await fetch(url); - const data = (await res.json()) as { boxSize: number }; - return data.boxSize; - }, - async sign({ reserveSize, toBeSigned }) { - const url = `https://my.signing.service/sign?boxSize=${reserveSize}`; - const res = await fetch(url, { - method: 'POST', - headers: new Headers({ - 'Content-Type': 'application/octet-stream', - }), - body: toBeSigned, - }); - return res.buffer(); - }, - }; -} - -async function sign(asset, manifest) { - const signer = createRemoteSigner(); - const c2pa = createC2pa({ - signer, - }); - - const { signedAsset, signedManifest } = await c2pa.sign({ - asset, - manifest, - }); -} - -const buffer = await readFile('to-be-signed.jpg'); -const asset: Asset = { buffer, mimeType: 'image/jpeg' }; - -const manifest = new ManifestBuilder( - { - claim_generator: 'my-app/1.0.0', - format: 'image/jpeg', - title: 'buffer_signer.jpg', - assertions: [ - { - label: 'c2pa.actions', - data: { - actions: [ - { - action: 'c2pa.created', - }, - ], - }, - }, - { - label: 'com.custom.my-assertion', - data: { - description: 'My custom test assertion', - version: '1.0.0', - }, - }, - ], - }, - { vendor: 'cai' }, -); - -await sign(asset, manifest); -``` - - - - - -```cpp -const std::string manifest_json = R"{ - "claim_generator": "c2pa_c_test/0.1", - "claim_generator_info": [ - { - "name": "c2pa-c test", - "version": "0.1" - } - ], - "assertions": [ - { - "label": "c2pa.training-mining", - "data": { - "entries": { - "c2pa.ai_generative_training": { "use": "notAllowed" }, - "c2pa.ai_inference": { "use": "notAllowed" }, - "c2pa.ai_training": { "use": "notAllowed" }, - "c2pa.data_mining": { "use": "notAllowed" } - } - } - } - ] - }; - -auto builder = Builder(manifest_json); - -``` - - - - - This is how to attach and sign a manifest using Rust. - -From [`c2pa-rs/sdk/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L88C5-L134C1): - -```rust -let json = manifest_def(title, format); - -let mut builder = Builder::from_json(&json)?; -builder.add_ingredient_from_stream( - json!({ - "title": parent_name, - "relationship": "parentOf" - }) - .to_string(), - format, - &mut source, -)?; - -let thumb_uri = builder - .definition - .thumbnail - .as_ref() - .map(|t| t.identifier.clone()); - -// add a manifest thumbnail ( just reuse the image for now ) -if let Some(uri) = thumb_uri { - if !uri.starts_with("self#jumbf") { - source.rewind()?; - builder.add_resource(&uri, &mut source)?; - } -} + -// write the manifest builder to a zipped stream -let mut zipped = Cursor::new(Vec::new()); -builder.to_archive(&mut zipped)?; +{' '} + + You can't currently attach and sign a manifest using the JavaScript library. + -// write the zipped stream to a file for debugging -//let debug_path = format!("{}/../target/test.zip", env!("CARGO_MANIFEST_DIR")); -// std::fs::write(debug_path, zipped.get_ref())?; +{' '} + + + -// unzip the manifest builder from the zipped stream -zipped.rewind()?; +{' '} + + + -let ed_signer = - |_context: *const (), data: &[u8]| CallbackSigner::ed25519_sign(data, PRIVATE_KEY); -let signer = CallbackSigner::new(ed_signer, SigningAlg::Ed25519, CERTS); +{' '} + + + -let mut builder = Builder::from_archive(&mut zipped)?; -// sign the ManifestStoreBuilder and write it to the output stream -let mut dest = Cursor::new(Vec::new()); -builder.sign(&signer, format, &mut source, &mut dest)?; -``` +{' '} + + + - - - + diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index e957461a..8a274370 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -6,213 +6,39 @@ title: Getting resources from a manifest import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Manifest data can include binary resources such as thumbnail and icon images which are referenced by JUMBF URIs in manifest data. - - - - -```js -import { createC2pa, selectProducer } from 'c2pa'; -import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?url'; -import workerSrc from 'c2pa/dist/c2pa.worker.js?url'; -import { parseISO } from 'date-fns'; - -const sampleImage = - 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; - -(async () => { - let output: string[] = []; - - const c2pa = await createC2pa({ - wasmSrc, - workerSrc, - }); - - const { manifestStore, source } = await c2pa.read(sampleImage); - const activeManifest = manifestStore?.activeManifest; - if (activeManifest) { - // Get thumbnail - // Note: You would normally call `dispose()` when working with a - // component-based UI library (e.g. on component un-mount) - // @ts-expect-error noUnusedLocals - const { url, dispose } = source.thumbnail.getUrl(); - - // Get properties - const properties: Record = { - title: activeManifest.title, - format: activeManifest.format, - claimGenerator: activeManifest.claimGenerator.split('(')[0]?.trim(), - producer: selectProducer(activeManifest)?.name ?? 'Unknown', - thumbnail: ``, - ingredients: (activeManifest.ingredients ?? []) - .map((i) => i.title) - .join(', '), - signatureIssuer: activeManifest.signatureInfo?.issuer, - signatureDate: activeManifest.signatureInfo?.time - ? parseISO(activeManifest.signatureInfo.time).toString() - : 'No date available', - }; - - output = Object.keys(properties).map((key) => { - return ` - - ${key} - ${properties[key]} - - `; - }); - } else { - output.push(` - - No provenance data found - - `); - } +import JSGetResources from './includes/_js-get-resources.md'; +import PythonGetResources from './includes/_python-get-resources.md'; +import NodeGetResources from './includes/_node-get-resources.md'; +import CppGetResources from './includes/_cpp-get-resources.md'; +import RustGetResources from './includes/_rust-get-resources.md'; - document.querySelector('#results tbody')!.innerHTML = output.join(''); -})(); -``` - - - - - -Retrieve binary resources such as thumbnails from the manifest data, use the `resource_to_stream` or `resource_to_file` methods using the associated `identifier` field values and a `uri`. - -NOTE: Need to add example of using `reader.resource_to_stream()`. - -```python -try: -# Create a reader from a file path -reader = c2pa.Reader.from_file("path/to/media_file.jpg") - -# Get the active manifest. -manifest = reader.get_active_manifest() -if manifest != None: +Manifest data can include binary resources such as thumbnail and icon images which are referenced by JUMBF URIs in manifest data. - # get the uri to the manifest's thumbnail and write it to a file - uri = manifest["thumbnail"]["identifier"] - reader.resource_to_file(uri, "thumbnail_v2.jpg") + -except Exception as err: - print(err) -``` +{' '} + + + +{' '} + + +{' '} - This is how to get resources from a manifest using Node.js. + +{' '} - This is how to get resources from a manifest using C++. - -```cpp -string read_text_file(const fs::path &path) -{ - ifstream file(path); - if (!file.is_open()) - { - throw runtime_error("Could not open file " + string(path)); - } - string contents((istreambuf_iterator(file)), istreambuf_iterator()); - file.close(); - return contents.data(); -} - -int main() -{ - fs::path manifest_path = current_dir / "../tests/fixtures/training.json"; - //fs::path certs_path = current_dir / "../tests/fixtures/es256_certs.pem"; - //fs::path image_path = current_dir / "../tests/fixtures/A.jpg"; - fs::path output_path = current_dir / "../target/example/training.jpg"; - fs::path thumbnail_path = current_dir / "../target/example/thumbnail.jpg"; - - try - { - // load the manifest, certs, and private key - /* Commenting out, because not part of resource reading - string manifest_json = read_text_file(manifest_path).data(); - - string certs = read_text_file(certs_path).data(); - - // create a signer - Signer signer = Signer(&test_signer, Es256, certs, "http://timestamp.digicert.com"); - - auto builder = Builder(manifest_json); - auto manifest_data = builder.sign(image_path, output_path, signer); - */ - - // read the new manifest and display the JSON - auto reader = Reader(output_path); - - auto manifest_store_json = reader.json(); - cout << "The new manifest is " << manifest_store_json << endl; - - // get the active manifest - json manifest_store = json::parse(manifest_store_json); - if (manifest_store.contains("active_manifest")) - { - string active_manifest = manifest_store["active_manifest"]; - json &manifest = manifest_store["manifests"][active_manifest]; - - string identifer = manifest["thumbnail"]["identifier"]; - - reader.get_resource(identifer, thumbnail_path); - - cout << "thumbnail written to" << thumbnail_path << endl; - } - } - catch (c2pa::Exception const &e) - { - cout << "C2PA Error: " << e.what() << endl; - } - catch (runtime_error const &e) - { - cout << "setup error" << e.what() << endl; - } - catch (json::parse_error const &e) - { - cout << "parse error " << e.what() << endl; - } -} -``` - + +{' '} - This is how to get resources using Rust. - -This is from [`resource_to_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.resource_to_stream) API doc: - -```rust -use c2pa::Reader; -let stream = std::io::Cursor::new(Vec::new()); -let reader = Reader::from_file("path/to/file.jpg").unwrap(); -let manifest = reader.active_manifest().unwrap(); -let uri = &manifest.thumbnail_ref().unwrap().identifier; -let bytes_written = reader.resource_to_stream(uri, stream).unwrap(); -``` - -This is from [`c2pa-rs/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L138): - -```rust -let reader = Reader::from_stream(format, &mut dest)?; - -// extract a thumbnail image from the ManifestStore -let mut thumbnail = Cursor::new(Vec::new()); -if let Some(manifest) = reader.active_manifest() { - if let Some(thumbnail_ref) = manifest.thumbnail_ref() { - reader.resource_to_stream(&thumbnail_ref.identifier, &mut thumbnail)?; - println!( - "wrote thumbnail {} of size {}", - thumbnail_ref.format, - thumbnail.get_ref().len() - ); - } -} -``` - + diff --git a/docs/tasks/includes/_cpp-build.md b/docs/tasks/includes/_cpp-build.md new file mode 100644 index 00000000..b3d82de8 --- /dev/null +++ b/docs/tasks/includes/_cpp-build.md @@ -0,0 +1,28 @@ + +```cpp +const std::string manifest_json = R"{ + "claim_generator": "c2pa_c_test/0.1", + "claim_generator_info": [ + { + "name": "c2pa-c test", + "version": "0.1" + } + ], + "assertions": [ + { + "label": "c2pa.training-mining", + "data": { + "entries": { + "c2pa.ai_generative_training": { "use": "notAllowed" }, + "c2pa.ai_inference": { "use": "notAllowed" }, + "c2pa.ai_training": { "use": "notAllowed" }, + "c2pa.data_mining": { "use": "notAllowed" } + } + } + } + ] + }; + +auto builder = Builder(manifest_json); + +``` \ No newline at end of file diff --git a/docs/tasks/includes/_cpp-get-resources.md b/docs/tasks/includes/_cpp-get-resources.md new file mode 100644 index 00000000..85fa94ed --- /dev/null +++ b/docs/tasks/includes/_cpp-get-resources.md @@ -0,0 +1,72 @@ +This is how to get resources from a manifest using C++. + +```cpp +string read_text_file(const fs::path &path) +{ + ifstream file(path); + if (!file.is_open()) + { + throw runtime_error("Could not open file " + string(path)); + } + string contents((istreambuf_iterator(file)), istreambuf_iterator()); + file.close(); + return contents.data(); +} + +int main() +{ + fs::path manifest_path = current_dir / "../tests/fixtures/training.json"; + //fs::path certs_path = current_dir / "../tests/fixtures/es256_certs.pem"; + //fs::path image_path = current_dir / "../tests/fixtures/A.jpg"; + fs::path output_path = current_dir / "../target/example/training.jpg"; + fs::path thumbnail_path = current_dir / "../target/example/thumbnail.jpg"; + + try + { + // load the manifest, certs, and private key + /* Commenting out, because not part of resource reading + string manifest_json = read_text_file(manifest_path).data(); + + string certs = read_text_file(certs_path).data(); + + // create a signer + Signer signer = Signer(&test_signer, Es256, certs, "http://timestamp.digicert.com"); + + auto builder = Builder(manifest_json); + auto manifest_data = builder.sign(image_path, output_path, signer); + */ + + // read the new manifest and display the JSON + auto reader = Reader(output_path); + + auto manifest_store_json = reader.json(); + cout << "The new manifest is " << manifest_store_json << endl; + + // get the active manifest + json manifest_store = json::parse(manifest_store_json); + if (manifest_store.contains("active_manifest")) + { + string active_manifest = manifest_store["active_manifest"]; + json &manifest = manifest_store["manifests"][active_manifest]; + + string identifer = manifest["thumbnail"]["identifier"]; + + reader.get_resource(identifer, thumbnail_path); + + cout << "thumbnail written to" << thumbnail_path << endl; + } + } + catch (c2pa::Exception const &e) + { + cout << "C2PA Error: " << e.what() << endl; + } + catch (runtime_error const &e) + { + cout << "setup error" << e.what() << endl; + } + catch (json::parse_error const &e) + { + cout << "parse error " << e.what() << endl; + } +} +``` \ No newline at end of file diff --git a/docs/tasks/includes/_cpp-read.md b/docs/tasks/includes/_cpp-read.md new file mode 100644 index 00000000..95dac314 --- /dev/null +++ b/docs/tasks/includes/_cpp-read.md @@ -0,0 +1,17 @@ + +Use the `read_file` function to read C2PA data from the specified file. This function examines the specified asset file for C2PA data and returns a JSON report if it finds any; it throws exceptions on errors. If there are validation errors, the report includes a `validation_status` field. + +```cpp +auto json_store = C2pa::read_file("", "") +``` + +Where: + +- ``- The asset file to read. +- `` - Optional path to data output directory; If provided, the function extracts any binary resources, such as thumbnails, icons, and C2PA data into that directory. These files are referenced by the identifier fields in the manifest store report. + +For example: + +```cpp +auto json_store = C2pa::read_file("work/media_file.jpg", "output/data_dir") +``` \ No newline at end of file diff --git a/docs/tasks/includes/_cpp-setup.md b/docs/tasks/includes/_cpp-setup.md new file mode 100644 index 00000000..ccc92326 --- /dev/null +++ b/docs/tasks/includes/_cpp-setup.md @@ -0,0 +1,21 @@ +To setup the C++ library: + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include "c2pa.hpp" +#include "test_signer.hpp" +#include + +// this example uses nlohmann json for parsing the manifest +using json = nlohmann::json; +using namespace std; +namespace fs = std::filesystem; +using namespace c2pa; +``` \ No newline at end of file diff --git a/docs/tasks/includes/_js-get-resources.md b/docs/tasks/includes/_js-get-resources.md new file mode 100644 index 00000000..12b561df --- /dev/null +++ b/docs/tasks/includes/_js-get-resources.md @@ -0,0 +1,61 @@ +```js +import { createC2pa, selectProducer } from 'c2pa'; +import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?url'; +import workerSrc from 'c2pa/dist/c2pa.worker.js?url'; +import { parseISO } from 'date-fns'; + +const sampleImage = + 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; + +(async () => { + let output: string[] = []; + + const c2pa = await createC2pa({ + wasmSrc, + workerSrc, + }); + + const { manifestStore, source } = await c2pa.read(sampleImage); + const activeManifest = manifestStore?.activeManifest; + if (activeManifest) { + // Get thumbnail + // Note: You would normally call `dispose()` when working with a + // component-based UI library (e.g. on component un-mount) + // @ts-expect-error noUnusedLocals + const { url, dispose } = source.thumbnail.getUrl(); + + // Get properties + const properties: Record = { + title: activeManifest.title, + format: activeManifest.format, + claimGenerator: activeManifest.claimGenerator.split('(')[0]?.trim(), + producer: selectProducer(activeManifest)?.name ?? 'Unknown', + thumbnail: ``, + ingredients: (activeManifest.ingredients ?? []) + .map((i) => i.title) + .join(', '), + signatureIssuer: activeManifest.signatureInfo?.issuer, + signatureDate: activeManifest.signatureInfo?.time + ? parseISO(activeManifest.signatureInfo.time).toString() + : 'No date available', + }; + + output = Object.keys(properties).map((key) => { + return ` + + ${key} + ${properties[key]} + + `; + }); + } else { + output.push(` + + No provenance data found + + `); + } + + document.querySelector('#results tbody')!.innerHTML = output.join(''); +})(); +``` \ No newline at end of file diff --git a/docs/tasks/includes/_js-read.md b/docs/tasks/includes/_js-read.md new file mode 100644 index 00000000..e2d42ffd --- /dev/null +++ b/docs/tasks/includes/_js-read.md @@ -0,0 +1,21 @@ + +Use [`c2pa.read`](../../docs/js-sdk/api/c2pa.c2pa#methods) to read manifest data from an asset; if the asset has a C2PA manifest and was processed without errors, the returned [`c2paReadResult`](../../docs/js-sdk/api/c2pa.c2pareadresult) contains a [`manifestStore`](../../docs/js-sdk/api/c2pa.c2pareadresult.manifeststore) object with several useful properties: + +- **manifests**: An object containing all the asset's manifests ([`Manifest`](../../docs/js-sdk/api/c2pa.manifest) objects), keyed by UUID. +- **activeManifest**: A pointer to the latest [`manifest`](../../docs/js-sdk/api/c2pa.manifest) in the manifest store. Effectively the "parent" manifest, this is the likely starting point when inspecting an asset's C2PA data. +- **validationStatus**: A list of any validation errors encountered. See [Validation](../../docs/js-sdk/guides/validation) for more information. + +```js + try { + // Read in image and get a manifest store + const { manifestStore } = await c2pa.read(sampleImage); + console.log('manifestStore', manifestStore); + + // Get the active manifest + const activeManifest = manifestStore?.activeManifest; + console.log('activeManifest', activeManifest); + } catch (err) { + console.error('Error reading image:', err); + } +})(); +``` \ No newline at end of file diff --git a/docs/tasks/includes/_js-setup.md b/docs/tasks/includes/_js-setup.md new file mode 100644 index 00000000..6056f054 --- /dev/null +++ b/docs/tasks/includes/_js-setup.md @@ -0,0 +1,17 @@ +This is how to setup the JavaScript library: + +```js +const version = '0.24.2'; +const sampleImage = ''; + +import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; + +(async () => { + // Initialize the c2pa-js SDK + const c2pa = await createC2pa({ + wasmSrc: + 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm', + workerSrc: + 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', + }); +``` \ No newline at end of file diff --git a/docs/tasks/includes/_node-build.md b/docs/tasks/includes/_node-build.md new file mode 100644 index 00000000..e848396a --- /dev/null +++ b/docs/tasks/includes/_node-build.md @@ -0,0 +1,171 @@ + +```ts +import { ManifestBuilder } from 'c2pa-node'; + +const manifest = new ManifestBuilder({ + claim_generator: 'my-app/1.0.0', + format: 'image/jpeg', + title: 'node_test_local_signer.jpg', + assertions: [ + { + label: 'c2pa.actions', + data: { + actions: [ + { + action: 'c2pa.created', + }, + ], + }, + }, + { + label: 'com.custom.my-assertion', + data: { + description: 'My custom test assertion', + version: '1.0.0', + }, + }, + ], +}); +``` + +FROM SIGNING: + +Use the `c2pa.sign()` method to sign an ingredient, either locally if you have a signing certificate and key available, or by using a remote signing API. + +## Signing a stream + +If you have an asset file's data loaded into memory, you can sign the the asset using the loaded stream (buffer). + +**NOTE**: Signing using a stream is currently supported only for `image/jpeg` and `image/png` data. For all other file types, use the [file-based approach](#signing-files) . + +```ts +import { readFile } from 'node:fs/promises'; +import { createC2pa, createTestSigner } from 'c2pa-node'; + +// read an asset into a buffer +const buffer = await readFile('to-be-signed.jpg'); +const asset: Asset = { buffer, mimeType: 'image/jpeg' }; + +// build a manifest to use for signing +const manifest = new ManifestBuilder( + { + claim_generator: 'my-app/1.0.0', + format: 'image/jpeg', + title: 'buffer_signer.jpg', + assertions: [ + { + label: 'c2pa.actions', + data: { + actions: [ + { + action: 'c2pa.created', + }, + ], + }, + }, + { + label: 'com.custom.my-assertion', + data: { + description: 'My custom test assertion', + version: '1.0.0', + }, + }, + ], + }, + { vendor: 'cai' }, +); + +// create a signing function +async function sign(asset, manifest) { + const signer = await createTestSigner(); + const c2pa = createC2pa({ + signer, + }); + + const { signedAsset, signedManifest } = await c2pa.sign({ + asset, + manifest, + }); +} + +// sign +await sign(asset, manifest); +``` + +**Remote signing** + +If you have access to a web service that performs signing, you can use it to sign remotely; for example: + +```ts +import { readFile } from 'node:fs/promises'; +import { fetch, Headers } from 'node-fetch'; +import { createC2pa, SigningAlgorithm } from 'c2pa-node'; + +function createRemoteSigner() { + return { + type: 'remote', + async reserveSize() { + const url = `https://my.signing.service/box-size`; + const res = await fetch(url); + const data = (await res.json()) as { boxSize: number }; + return data.boxSize; + }, + async sign({ reserveSize, toBeSigned }) { + const url = `https://my.signing.service/sign?boxSize=${reserveSize}`; + const res = await fetch(url, { + method: 'POST', + headers: new Headers({ + 'Content-Type': 'application/octet-stream', + }), + body: toBeSigned, + }); + return res.buffer(); + }, + }; +} + +async function sign(asset, manifest) { + const signer = createRemoteSigner(); + const c2pa = createC2pa({ + signer, + }); + + const { signedAsset, signedManifest } = await c2pa.sign({ + asset, + manifest, + }); +} + +const buffer = await readFile('to-be-signed.jpg'); +const asset: Asset = { buffer, mimeType: 'image/jpeg' }; + +const manifest = new ManifestBuilder( + { + claim_generator: 'my-app/1.0.0', + format: 'image/jpeg', + title: 'buffer_signer.jpg', + assertions: [ + { + label: 'c2pa.actions', + data: { + actions: [ + { + action: 'c2pa.created', + }, + ], + }, + }, + { + label: 'com.custom.my-assertion', + data: { + description: 'My custom test assertion', + version: '1.0.0', + }, + }, + ], + }, + { vendor: 'cai' }, +); + +await sign(asset, manifest); +``` \ No newline at end of file diff --git a/docs/tasks/includes/_node-get-resources.md b/docs/tasks/includes/_node-get-resources.md new file mode 100644 index 00000000..109f33f0 --- /dev/null +++ b/docs/tasks/includes/_node-get-resources.md @@ -0,0 +1,5 @@ +This is how to get resources from a manifest using Node.js. + +```js +// TBD +``` \ No newline at end of file diff --git a/docs/tasks/includes/_node-read.md b/docs/tasks/includes/_node-read.md new file mode 100644 index 00000000..9c1a1973 --- /dev/null +++ b/docs/tasks/includes/_node-read.md @@ -0,0 +1,24 @@ + +Use the `c2pa.read()` function to read a manifest; for example: + +```ts +import { createC2pa } from 'c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const c2pa = createC2pa(); + +async function read(path, mimeType) { + const buffer = await readFile(path); + const result = await c2pa.read({ buffer, mimeType }); + + if (result) { + const { active_manifest, manifests, validation_status } = result; + console.log(active_manifest); + } else { + console.log('No claim found'); + } + // If there are no validation errors, then validation_status will be an empty array +} + +await read('my-c2pa-file.jpg', 'image/jpeg'); +``` diff --git a/docs/tasks/includes/_node-setup.md b/docs/tasks/includes/_node-setup.md new file mode 100644 index 00000000..2c69ca83 --- /dev/null +++ b/docs/tasks/includes/_node-setup.md @@ -0,0 +1,8 @@ +This is how to setup the Node.js library. + +```js +import { createC2pa } from 'c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const c2pa = createC2pa(); +``` \ No newline at end of file diff --git a/docs/tasks/includes/_python-build.md b/docs/tasks/includes/_python-build.md new file mode 100644 index 00000000..26045cdb --- /dev/null +++ b/docs/tasks/includes/_python-build.md @@ -0,0 +1,107 @@ + +Use a `Builder` object to add a manifest to an asset. + +```python +try: + # Define a function to sign the claim bytes + # In this case we are using a pre-defined sign_ps256 method, passing in our private cert + # Normally this cert would be kept safe in some other location + def private_sign(data: bytes) -> bytes: + return sign_ps256(data, "tests/fixtures/ps256.pem") + + # read our public certs into memory + certs = open(data_dir + "ps256.pub", "rb").read() + + # Create a signer from the private signer, certs and a time stamp service url + signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") + + # Create a builder add a thumbnail resource and an ingredient file. + builder = Builder(manifest_json) + + # Add the resource from a stream + a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb") + builder.add_resource("image/jpeg", a_thumbnail_jpg_stream) + + # Add the resource from a file + # The URI provided here, "thumbnail", must match an identifier in the manifest definition. + builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg") + + # Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail + ingredient_json = { + "title": "A.jpg", + "relationship": "parentOf", # "parentOf", "componentOf" or "inputTo" + "thumbnail": { + "identifier": "thumbnail", + "format": "image/jpeg" + } + } + + # Add the ingredient from a stream + a_jpg_stream = open("tests/fixtures/A.jpg", "rb") + builder.add_ingredient("image/jpeg", a_jpg_stream) + + # At this point we could archive or unarchive our Builder to continue later. + # In this example we use a bytearray for the archive stream. + # all ingredients and resources will be saved in the archive + archive = io.BytesIO(bytearray()) + builder.to_archive(archive) + archive.seek() + builder = builder.from_archive(archive) + + # Sign the builder with a stream and output it to a stream + # This returns the binary manifest data that could be uploaded to cloud storage. + input_stream = open("tests/fixtures/A.jpg", "rb") + output_stream = open("target/out.jpg", "wb") + c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream) + +except Exception as err: + print(err) +``` + +FROM SIGNING: + +```python +request_data = ... # This is the asset being signed +content_type = ... # MIME type of the asset + +manifest = json.dumps({ + "title": "image.jpg", + "format": "image/jpeg", + "claim_generator_info": [ + { + "name": "Documentation example", + "version": "0.0.1" + } + ], + "assertions": [ + { + "label": "c2pa.actions", + "data": { + "actions": [ + { + "action": "c2pa.edited", + "softwareAgent": { + "name": "C2PA Python Example", + "version": "0.1.0" + } + } + ] + } + } + ] +}) + +try: + builder = Builder(manifest) + + signer = create_signer(kms_sign, signing_alg, + cert_chain, timestamp_url) + + result = io.BytesIO(b"") + builder.sign(signer, content_type, io.BytesIO(request_data), result) + + return result.getvalue() +except Exception as e: + logging.error(e) + abort(500, description=e) +``` \ No newline at end of file diff --git a/docs/tasks/includes/_python-get-resources.md b/docs/tasks/includes/_python-get-resources.md new file mode 100644 index 00000000..d8d9d925 --- /dev/null +++ b/docs/tasks/includes/_python-get-resources.md @@ -0,0 +1,21 @@ + +Retrieve binary resources such as thumbnails from the manifest data, use the `resource_to_stream` or `resource_to_file` methods using the associated `identifier` field values and a `uri`. + +NOTE: Need to add example of using `reader.resource_to_stream()`. + +```python +try: +# Create a reader from a file path +reader = c2pa.Reader.from_file("path/to/media_file.jpg") + +# Get the active manifest. +manifest = reader.get_active_manifest() +if manifest != None: + + # get the uri to the manifest's thumbnail and write it to a file + uri = manifest["thumbnail"]["identifier"] + reader.resource_to_file(uri, "thumbnail_v2.jpg") + +except Exception as err: + print(err) +``` \ No newline at end of file diff --git a/docs/tasks/includes/_python-read.md b/docs/tasks/includes/_python-read.md new file mode 100644 index 00000000..ea990120 --- /dev/null +++ b/docs/tasks/includes/_python-read.md @@ -0,0 +1,20 @@ + +Use the `Reader` object to read manifest data from a file or stream and perform validation on the manifest store. Use the `json()` method to return a JSON manifest report; If there are validation errors, the report includes a `validation_status` field. + +An asset file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map. + +```py +try: + # Create a reader from a file path + reader = c2pa.Reader.from_file("path/to/media_file.jpg") + + # Alternatively, create a reader from a stream + stream = open("path/to/media_file.jpg", "rb") + reader = c2pa.Reader("image/jpeg", stream) + + # Print the JSON for a manifest. + print("manifest store:", reader.json()) + +except Exception as err: + print(err) +``` \ No newline at end of file diff --git a/docs/tasks/includes/_python-setup.md b/docs/tasks/includes/_python-setup.md new file mode 100644 index 00000000..fe695faf --- /dev/null +++ b/docs/tasks/includes/_python-setup.md @@ -0,0 +1,21 @@ +This is how to setup the Python library. + +```python +# Import the C2PA Python package +from c2pa import * + +# Import standard general-purpose packages +import os +import io +import logging +import json +import base64 + +# Import web packages used in example implementation +from flask import Flask, request, abort +from flask_cors import CORS +from waitress import serve + +# Import AWS SDK package (to use KMS) +import boto3 +``` diff --git a/docs/tasks/includes/_rust-build.md b/docs/tasks/includes/_rust-build.md new file mode 100644 index 00000000..4a2a0825 --- /dev/null +++ b/docs/tasks/includes/_rust-build.md @@ -0,0 +1,48 @@ +This is how to attach and sign a manifest using Rust. + +This example is from [`c2pa-rs/sdk/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L88C5-L134C1): + +```rust +let json = manifest_def(title, format); + +let mut builder = Builder::from_json(&json)?; +builder.add_ingredient_from_stream( + json!({ + "title": parent_name, + "relationship": "parentOf" + }) + .to_string(), + format, + &mut source, +)?; + +let thumb_uri = builder + .definition + .thumbnail + .as_ref() + .map(|t| t.identifier.clone()); + +// add a manifest thumbnail ( just reuse the image for now ) +if let Some(uri) = thumb_uri { + if !uri.starts_with("self#jumbf") { + source.rewind()?; + builder.add_resource(&uri, &mut source)?; + } +} + +// write the manifest builder to a zipped stream +let mut zipped = Cursor::new(Vec::new()); +builder.to_archive(&mut zipped)?; + +// unzip the manifest builder from the zipped stream +zipped.rewind()?; + +let ed_signer = + |_context: *const (), data: &[u8]| CallbackSigner::ed25519_sign(data, PRIVATE_KEY); +let signer = CallbackSigner::new(ed_signer, SigningAlg::Ed25519, CERTS); + +let mut builder = Builder::from_archive(&mut zipped)?; +// sign the ManifestStoreBuilder and write it to the output stream +let mut dest = Cursor::new(Vec::new()); +builder.sign(&signer, format, &mut source, &mut dest)?; +``` diff --git a/docs/tasks/includes/_rust-get-resources.md b/docs/tasks/includes/_rust-get-resources.md new file mode 100644 index 00000000..145d15b4 --- /dev/null +++ b/docs/tasks/includes/_rust-get-resources.md @@ -0,0 +1,31 @@ +This is how to get resources using Rust. + +This is from [`resource_to_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.resource_to_stream) API doc: + +```rust +use c2pa::Reader; +let stream = std::io::Cursor::new(Vec::new()); +let reader = Reader::from_file("path/to/file.jpg").unwrap(); +let manifest = reader.active_manifest().unwrap(); +let uri = &manifest.thumbnail_ref().unwrap().identifier; +let bytes_written = reader.resource_to_stream(uri, stream).unwrap(); +``` + +This is from [`c2pa-rs/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L138): + +```rust +let reader = Reader::from_stream(format, &mut dest)?; + +// extract a thumbnail image from the ManifestStore +let mut thumbnail = Cursor::new(Vec::new()); +if let Some(manifest) = reader.active_manifest() { + if let Some(thumbnail_ref) = manifest.thumbnail_ref() { + reader.resource_to_stream(&thumbnail_ref.identifier, &mut thumbnail)?; + println!( + "wrote thumbnail {} of size {}", + thumbnail_ref.format, + thumbnail.get_ref().len() + ); + } +} +``` \ No newline at end of file diff --git a/docs/tasks/includes/_rust-read.md b/docs/tasks/includes/_rust-read.md new file mode 100644 index 00000000..f164268a --- /dev/null +++ b/docs/tasks/includes/_rust-read.md @@ -0,0 +1,28 @@ + +Use the [`Reader`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html) struct to read manifest data from a file or stream. + +### Reading from a file + +Use [`from_file`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file) to read manifest data from a file: + +```rust +use c2pa::Reader; +let reader = Reader::from_file("path/to/file.jpg").unwrap(); +``` + +There is also an asynchronous version of this method, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file_async). + +### Reading from a stream + +Use [`from_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream) to read manifest data from a stream: + +```rust +use std::io::Cursor; + +use c2pa::Reader; +let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg")); +let reader = Reader::from_stream("image/jpeg", stream).unwrap(); +println!("{}", reader.json()); +``` + +There is also an asynchronous version of this method, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream_async). diff --git a/docs/tasks/includes/_rust-setup.md b/docs/tasks/includes/_rust-setup.md new file mode 100644 index 00000000..f0e8e9cd --- /dev/null +++ b/docs/tasks/includes/_rust-setup.md @@ -0,0 +1,18 @@ +This is how to setup your code to use the Rust library. + +```rust +use std::{ + io::{Cursor, Write}, + process::{Command, Stdio}, +}; + +use anyhow::Result; +use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader}; + +use c2pa_crypto::raw_signature::SigningAlg; +use serde_json::json; + +const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg"); +const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub"); +const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem"); +``` \ No newline at end of file diff --git a/docs/tasks/includes/touch.sh b/docs/tasks/includes/touch.sh new file mode 100755 index 00000000..41b5b530 --- /dev/null +++ b/docs/tasks/includes/touch.sh @@ -0,0 +1,17 @@ +touch _cpp-read.md +touch _node-read.md +touch _rust-read.md +touch _js-read.md +touch _python-read.md + +touch _cpp-get-resources.md +touch _node-get-resources.md +touch _rust-get-resources.md +touch _js-get-resources.md +touch _python-get-resources.md + +touch _cpp-sign.md +touch _node-sign.md +touch _rust-sign.md +touch _python-sign.md + diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index d53dfa36..6295e6a1 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -7,136 +7,37 @@ hide_table_of_contents: true import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import JSRead from './includes/_js-read.md'; +import PythonRead from './includes/_python-read.md'; +import NodeRead from './includes/_node-read.md'; +import CppRead from './includes/_cpp-read.md'; +import RustRead from './includes/_rust-read.md'; + +{' '} - -Use [`c2pa.read`](../../docs/js-sdk/api/c2pa.c2pa#methods) to read manifest data from an asset; if the asset has a C2PA manifest and was processed without errors, the returned [`c2paReadResult`](../../docs/js-sdk/api/c2pa.c2pareadresult) contains a [`manifestStore`](../../docs/js-sdk/api/c2pa.c2pareadresult.manifeststore) object with several useful properties: - -- **manifests**: An object containing all the asset's manifests ([`Manifest`](../../docs/js-sdk/api/c2pa.manifest) objects), keyed by UUID. -- **activeManifest**: A pointer to the latest [`manifest`](../../docs/js-sdk/api/c2pa.manifest) in the manifest store. Effectively the "parent" manifest, this is the likely starting point when inspecting an asset's C2PA data. -- **validationStatus**: A list of any validation errors encountered. See [Validation](../../docs/js-sdk/guides/validation) for more information. - -```js - try { - // Read in image and get a manifest store - const { manifestStore } = await c2pa.read(sampleImage); - console.log('manifestStore', manifestStore); - - // Get the active manifest - const activeManifest = manifestStore?.activeManifest; - console.log('activeManifest', activeManifest); - } catch (err) { - console.error('Error reading image:', err); - } -})(); -``` - + - - -Use the `Reader` object to read manifest data from a file or stream and perform validation on the manifest store. Use the `json()` method to return a JSON manifest report; If there are validation errors, the report includes a `validation_status` field. - -An asset file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map. - -```py -try: - # Create a reader from a file path - reader = c2pa.Reader.from_file("path/to/media_file.jpg") - - # Alternatively, create a reader from a stream - stream = open("path/to/media_file.jpg", "rb") - reader = c2pa.Reader("image/jpeg", stream) - - # Print the JSON for a manifest. - print("manifest store:", reader.json()) - -except Exception as err: - print(err) -``` - +{' '} + + +{' '} - -Use the `c2pa.read()` function to read a manifest; for example: - -```ts -import { createC2pa } from 'c2pa-node'; -import { readFile } from 'node:fs/promises'; - -const c2pa = createC2pa(); - -async function read(path, mimeType) { - const buffer = await readFile(path); - const result = await c2pa.read({ buffer, mimeType }); - - if (result) { - const { active_manifest, manifests, validation_status } = result; - console.log(active_manifest); - } else { - console.log('No claim found'); - } - // If there are no validation errors, then validation_status will be an empty array -} - -await read('my-c2pa-file.jpg', 'image/jpeg'); -``` - + +{' '} - -Use the `read_file` function to read C2PA data from the specified file. This function examines the specified asset file for C2PA data and returns a JSON report if it finds any; it throws exceptions on errors. If there are validation errors, the report includes a `validation_status` field. - -```cpp -auto json_store = C2pa::read_file("", "") -``` - -Where: - -- ``- The asset file to read. -- `` - Optional path to data output directory; If provided, the function extracts any binary resources, such as thumbnails, icons, and C2PA data into that directory. These files are referenced by the identifier fields in the manifest store report. - -For example: - -```cpp -auto json_store = C2pa::read_file("work/media_file.jpg", "output/data_dir") -``` - + +{' '} - -Use the [`Reader`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html) struct to read manifest data from a file or stream. - -### Reading from a file - -Use [`from_file`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file) to read manifest data from a file: - -```rust -use c2pa::Reader; -let reader = Reader::from_file("path/to/file.jpg").unwrap(); -``` - -There is also an asynchronous version of this method, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file_async). - -### Reading from a stream - -Use [`from_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream) to read manifest data from a stream: - -```rust -use std::io::Cursor; - -use c2pa::Reader; -let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg")); -let reader = Reader::from_stream("image/jpeg", stream).unwrap(); -println!("{}", reader.json()); -``` - -There is also an asynchronous version of this method, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream_async). - + diff --git a/docs/tasks/setup.mdx b/docs/tasks/setup.mdx index e6c6dcbb..a751cf56 100644 --- a/docs/tasks/setup.mdx +++ b/docs/tasks/setup.mdx @@ -6,110 +6,36 @@ hide_table_of_contents: true import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import JSSetup from './includes/_js-setup.md'; +import PythonSetup from './includes/_python-setup.md'; +import NodeSetup from './includes/_node-setup.md'; +import CppSetup from './includes/_cpp-setup.md'; +import RustSetup from './includes/_rust-setup.md'; +{' '} - -```js -const version = '0.24.2'; -const sampleImage = ''; - -import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; - -(async () => { - // Initialize the c2pa-js SDK - const c2pa = await createC2pa({ - wasmSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm', - workerSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', - }); -``` - + - - This is how to setup the Python library. - -```python -# Import the C2PA Python package -from c2pa import * - -# Import standard general-purpose packages -import os -import io -import logging -import json -import base64 - -# Import web packages used in example implementation -from flask import Flask, request, abort -from flask_cors import CORS -from waitress import serve - -# Import AWS SDK package (to use KMS) -import boto3 -``` - - - - - This is how to setup the Node.js library. - -```js -import { createC2pa } from 'c2pa-node'; -import { readFile } from 'node:fs/promises'; - -const c2pa = createC2pa(); -``` - - - - - -```cpp -#include -#include -#include -#include -#include -#include -#include -#include -#include "c2pa.hpp" -#include "test_signer.hpp" -#include +{' '} + + + -// this example uses nlohmann json for parsing the manifest -using json = nlohmann::json; -using namespace std; -namespace fs = std::filesystem; -using namespace c2pa; -``` +{' '} + + + - +{' '} + + + - This is how to setup your code to use the Rust library. - -```rust -use std::{ - io::{Cursor, Write}, - process::{Command, Stdio}, -}; - -use anyhow::Result; -use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader}; - -use c2pa_crypto::raw_signature::SigningAlg; -use serde_json::json; - -const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg"); -const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub"); -const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem"); -``` - + From 234b2d33ae7574db541bffb25a16ff864e353caa Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 12 Feb 2025 14:47:35 -0800 Subject: [PATCH 17/58] Fix spacing to avoid linting weirdness --- docs/tasks/build.mdx | 31 ++++++++++++++++++++----------- docs/tasks/get-resources.mdx | 27 ++++++++++++++++++--------- docs/tasks/read.mdx | 27 ++++++++++++++++++--------- docs/tasks/setup.mdx | 26 ++++++++++++++++++-------- 4 files changed, 74 insertions(+), 37 deletions(-) diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index 6c5776d4..3818263f 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -14,29 +14,38 @@ import RustBuild from './includes/_rust-build.md'; -{' '} - - You can't currently attach and sign a manifest using the JavaScript library. + + +You can't currently attach a manifest to an asset and sign the claim using the JavaScript library. + -{' '} - - {' '} - - + + + + {' '} - - + + + + {' '} + + + + - + +{' '} + + diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index 8a274370..1fa248a1 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -16,29 +16,38 @@ Manifest data can include binary resources such as thumbnail and icon images whi -{' '} - - {' '} - - + + + + {' '} - - + + + + {' '} - - + + + + {' '} + + + + + + diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index 6295e6a1..aed5139a 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -15,29 +15,38 @@ import RustRead from './includes/_rust-read.md'; -{' '} - - {' '} - - + + + + {' '} - - + + + + {' '} - - + + + + {' '} + + + + + + diff --git a/docs/tasks/setup.mdx b/docs/tasks/setup.mdx index a751cf56..f631ec91 100644 --- a/docs/tasks/setup.mdx +++ b/docs/tasks/setup.mdx @@ -14,28 +14,38 @@ import RustSetup from './includes/_rust-setup.md'; -{' '} - - {' '} - - + + + + {' '} - - + + + + {' '} + + + + - + +{' '} + + + + From 71148645567a14a301582cf3c8b9fe823f9eaeec Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 12 Feb 2025 14:51:56 -0800 Subject: [PATCH 18/58] Remove script committed by accident --- docs/tasks/includes/touch.sh | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100755 docs/tasks/includes/touch.sh diff --git a/docs/tasks/includes/touch.sh b/docs/tasks/includes/touch.sh deleted file mode 100755 index 41b5b530..00000000 --- a/docs/tasks/includes/touch.sh +++ /dev/null @@ -1,17 +0,0 @@ -touch _cpp-read.md -touch _node-read.md -touch _rust-read.md -touch _js-read.md -touch _python-read.md - -touch _cpp-get-resources.md -touch _node-get-resources.md -touch _rust-get-resources.md -touch _js-get-resources.md -touch _python-get-resources.md - -touch _cpp-sign.md -touch _node-sign.md -touch _rust-sign.md -touch _python-sign.md - From 0e9ccd1f77425dea0f5ec0e0c164dce3878b8a3c Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 12 Feb 2025 15:00:59 -0800 Subject: [PATCH 19/58] Remove files from JS example repo --- .../js-sdk/examples/quickstart/rollup-main.ts | 26 -------- docs/js-sdk/examples/quickstart/vite-main.ts | 27 --------- .../examples/quickstart/webpack-main.ts | 30 ---------- docs/js-sdk/examples/view-manifest/index.html | 22 ------- docs/js-sdk/examples/view-manifest/main.ts | 59 ------------------- 5 files changed, 164 deletions(-) delete mode 100644 docs/js-sdk/examples/quickstart/rollup-main.ts delete mode 100644 docs/js-sdk/examples/quickstart/vite-main.ts delete mode 100644 docs/js-sdk/examples/quickstart/webpack-main.ts delete mode 100644 docs/js-sdk/examples/view-manifest/index.html delete mode 100644 docs/js-sdk/examples/view-manifest/main.ts diff --git a/docs/js-sdk/examples/quickstart/rollup-main.ts b/docs/js-sdk/examples/quickstart/rollup-main.ts deleted file mode 100644 index f92207aa..00000000 --- a/docs/js-sdk/examples/quickstart/rollup-main.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createC2pa } from 'c2pa'; -import wasmModule from 'c2pa/dist/assets/wasm/toolkit_bg.wasm'; - -const sampleImage = - 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; - -(async () => { - // Initialize the c2pa-js SDK - const wasmSrc = await wasmModule(); - const c2pa = await createC2pa({ - wasmSrc, - workerSrc: 'c2pa.worker.min.js', - }); - - try { - // Read in our sample image and get a manifest store - const { manifestStore } = await c2pa.read(sampleImage); - console.log('manifestStore', manifestStore); - - // Get the active manifest - const activeManifest = manifestStore?.activeManifest; - console.log('activeManifest', activeManifest); - } catch (err) { - console.error('Error reading image:', err); - } -})(); diff --git a/docs/js-sdk/examples/quickstart/vite-main.ts b/docs/js-sdk/examples/quickstart/vite-main.ts deleted file mode 100644 index e86371ea..00000000 --- a/docs/js-sdk/examples/quickstart/vite-main.ts +++ /dev/null @@ -1,27 +0,0 @@ -const sampleImage = - 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; - -(async () => { - // Information about where to fetch the library - const version = '0.17.2'; - const libraryUrl = `https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm`; - - // Initialize the c2pa-js SDK - const { createC2pa } = await import(libraryUrl); - const c2pa = await createC2pa({ - wasmSrc: `https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm`, - workerSrc: `https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js`, - }); - - // Read in our sample image and get a manifest store - try { - const { manifestStore } = await c2pa.read(sampleImage); - console.log('manifestStore', manifestStore); - - // Get the active manifest - const activeManifest = manifestStore?.activeManifest; - console.log('activeManifest', activeManifest); - } catch (err) { - console.error('Error reading image:', err); - } -})(); diff --git a/docs/js-sdk/examples/quickstart/webpack-main.ts b/docs/js-sdk/examples/quickstart/webpack-main.ts deleted file mode 100644 index 31a5898b..00000000 --- a/docs/js-sdk/examples/quickstart/webpack-main.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { createC2pa } from 'c2pa'; -import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?file'; -import workerSrc from 'c2pa/dist/c2pa.worker.min.js?file'; - -const element = document.createElement('div'); -element.innerHTML = `Please view the console`; -document.body.appendChild(element); - -const sampleImage = - 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; - -(async () => { - // Initialize the c2pa-js SDK - const c2pa = await createC2pa({ - wasmSrc, - workerSrc, - }); - - try { - // Read in our sample image and get a manifest store - const { manifestStore } = await c2pa.read(sampleImage); - console.log('manifestStore', manifestStore); - - // Get the active manifest - const activeManifest = manifestStore?.activeManifest; - console.log('activeManifest', activeManifest); - } catch (err) { - console.error('Error reading image:', err); - } -})(); diff --git a/docs/js-sdk/examples/view-manifest/index.html b/docs/js-sdk/examples/view-manifest/index.html deleted file mode 100644 index 4e910bc7..00000000 --- a/docs/js-sdk/examples/view-manifest/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - active-manifest - - - - - - - - - - -
PropertyValue
Loading…
- - - diff --git a/docs/js-sdk/examples/view-manifest/main.ts b/docs/js-sdk/examples/view-manifest/main.ts deleted file mode 100644 index 94c8d745..00000000 --- a/docs/js-sdk/examples/view-manifest/main.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { createC2pa, selectProducer } from 'c2pa'; -import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?url'; -import workerSrc from 'c2pa/dist/c2pa.worker.js?url'; -import { parseISO } from 'date-fns'; - -const sampleImage = - 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; - -(async () => { - let output: string[] = []; - - const c2pa = await createC2pa({ - wasmSrc, - workerSrc, - }); - - const { manifestStore, source } = await c2pa.read(sampleImage); - const activeManifest = manifestStore?.activeManifest; - if (activeManifest) { - // Get thumbnail - // Note: You would normally call `dispose()` when working with a - // component-based UI library (e.g. on component un-mount) - // @ts-expect-error noUnusedLocals - const { url, dispose } = source.thumbnail.getUrl(); - - // Get properties - const properties: Record = { - title: activeManifest.title, - format: activeManifest.format, - claimGenerator: activeManifest.claimGenerator.split('(')[0]?.trim(), - producer: selectProducer(activeManifest)?.name ?? 'Unknown', - thumbnail: ``, - ingredients: (activeManifest.ingredients ?? []) - .map((i) => i.title) - .join(', '), - signatureIssuer: activeManifest.signatureInfo?.issuer, - signatureDate: activeManifest.signatureInfo?.time - ? parseISO(activeManifest.signatureInfo.time).toString() - : 'No date available', - }; - - output = Object.keys(properties).map((key) => { - return ` - - ${key} - ${properties[key]} - - `; - }); - } else { - output.push(` - - No provenance data found - - `); - } - - document.querySelector('#results tbody')!.innerHTML = output.join(''); -})(); From 3b136025d0b93fe6fe50270146bb3c8fefdb1190 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 12 Feb 2025 15:02:06 -0800 Subject: [PATCH 20/58] Add examples to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d07e58e0..492f5261 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ .docusaurus .cache-loader /docs/js-sdk/api +/docs/examples/quickstart/* +/docs/examples/view-manifest/* /docs/c2patool/*.md /docs/c2patool/docs/*.md /docs/c2pa-node/*.md From 171b336af66fce1e2f3e32cd7f6d4a13023d5619 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 12 Feb 2025 15:07:51 -0800 Subject: [PATCH 21/58] Try again to remove spurious {' '} characters --- docs/tasks/build.mdx | 4 ---- docs/tasks/get-resources.mdx | 4 ---- docs/tasks/read.mdx | 4 ---- docs/tasks/setup.mdx | 10 +++------- 4 files changed, 3 insertions(+), 19 deletions(-) diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index 3818263f..e63016f2 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -22,28 +22,24 @@ You can't currently attach a manifest to an asset and sign the claim using the J -{' '} -{' '} -{' '} -{' '} diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index 1fa248a1..94e2bcbc 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -18,28 +18,24 @@ Manifest data can include binary resources such as thumbnail and icon images whi -{' '} -{' '} -{' '} -{' '} diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index aed5139a..97ba381a 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -17,28 +17,24 @@ import RustRead from './includes/_rust-read.md'; -{' '} -{' '} -{' '} -{' '} diff --git a/docs/tasks/setup.mdx b/docs/tasks/setup.mdx index f631ec91..ceb38d0d 100644 --- a/docs/tasks/setup.mdx +++ b/docs/tasks/setup.mdx @@ -16,36 +16,32 @@ import RustSetup from './includes/_rust-setup.md'; -{' '} -{' '} -{' '} -{' '} - + - + - +
From 6cd10724ba97ae67647d96d9b14199d127f4a754 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 14 Feb 2025 10:25:09 -0800 Subject: [PATCH 22/58] Update gitignore to properly ignore external examples pulled down by fetch-readme script --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 492f5261..6fabebf6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,8 @@ .docusaurus .cache-loader /docs/js-sdk/api -/docs/examples/quickstart/* -/docs/examples/view-manifest/* +/docs/js-sdk/examples/* +/docs/js-sdk/examples/* /docs/c2patool/*.md /docs/c2patool/docs/*.md /docs/c2pa-node/*.md From f346293174cf77641d30299324863fce8770fe89 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 14 Feb 2025 10:48:30 -0800 Subject: [PATCH 23/58] Add query param, other small edits --- docs/tasks/build.mdx | 4 ++-- docs/tasks/get-resources.mdx | 2 +- docs/tasks/includes/_cpp-build.md | 1 + docs/tasks/includes/_cpp-setup.md | 2 +- docs/tasks/includes/_js-get-resources.md | 2 ++ docs/tasks/includes/_js-setup.md | 2 +- docs/tasks/includes/_node-build.md | 1 + docs/tasks/includes/_node-get-resources.md | 2 +- docs/tasks/includes/_node-setup.md | 2 +- docs/tasks/includes/_python-build.md | 2 ++ docs/tasks/includes/_python-get-resources.md | 4 +++- docs/tasks/includes/_python-setup.md | 2 +- docs/tasks/includes/_rust-build.md | 2 +- docs/tasks/includes/_rust-get-resources.md | 4 +++- docs/tasks/read.mdx | 2 +- docs/tasks/setup.mdx | 2 +- 16 files changed, 23 insertions(+), 13 deletions(-) diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index e63016f2..4095a3e4 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -12,11 +12,11 @@ import NodeBuild from './includes/_node-build.md'; import CppBuild from './includes/_cpp-build.md'; import RustBuild from './includes/_rust-build.md'; - + -You can't currently attach a manifest to an asset and sign the claim using the JavaScript library. +You can't currently attach a manifest to an asset and sign the claim using the JavaScript library. You need to use a language that runs on the "back-end," such as Python, Node.js, C++, or Rust. diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index 94e2bcbc..37c7547e 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -14,7 +14,7 @@ import RustGetResources from './includes/_rust-get-resources.md'; Manifest data can include binary resources such as thumbnail and icon images which are referenced by JUMBF URIs in manifest data. - + diff --git a/docs/tasks/includes/_cpp-build.md b/docs/tasks/includes/_cpp-build.md index b3d82de8..04f27f6e 100644 --- a/docs/tasks/includes/_cpp-build.md +++ b/docs/tasks/includes/_cpp-build.md @@ -1,3 +1,4 @@ +This is an example of how to assign a manifest to an asset and sign the claim using C++: ```cpp const std::string manifest_json = R"{ diff --git a/docs/tasks/includes/_cpp-setup.md b/docs/tasks/includes/_cpp-setup.md index ccc92326..c0c6853a 100644 --- a/docs/tasks/includes/_cpp-setup.md +++ b/docs/tasks/includes/_cpp-setup.md @@ -1,4 +1,4 @@ -To setup the C++ library: +This is how to set up your code to use the C++ library: ```cpp #include diff --git a/docs/tasks/includes/_js-get-resources.md b/docs/tasks/includes/_js-get-resources.md index 12b561df..4c5d7162 100644 --- a/docs/tasks/includes/_js-get-resources.md +++ b/docs/tasks/includes/_js-get-resources.md @@ -1,3 +1,5 @@ +The example below shows how to get resources from manifest data using the JavaScript library. + ```js import { createC2pa, selectProducer } from 'c2pa'; import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?url'; diff --git a/docs/tasks/includes/_js-setup.md b/docs/tasks/includes/_js-setup.md index 6056f054..850db022 100644 --- a/docs/tasks/includes/_js-setup.md +++ b/docs/tasks/includes/_js-setup.md @@ -1,4 +1,4 @@ -This is how to setup the JavaScript library: +This is how to set up your code to use the JavaScript library: ```js const version = '0.24.2'; diff --git a/docs/tasks/includes/_node-build.md b/docs/tasks/includes/_node-build.md index e848396a..14fa6e5e 100644 --- a/docs/tasks/includes/_node-build.md +++ b/docs/tasks/includes/_node-build.md @@ -1,3 +1,4 @@ +This is an example of how to assign a manifest to an asset and sign the claim using Node.js: ```ts import { ManifestBuilder } from 'c2pa-node'; diff --git a/docs/tasks/includes/_node-get-resources.md b/docs/tasks/includes/_node-get-resources.md index 109f33f0..422062ba 100644 --- a/docs/tasks/includes/_node-get-resources.md +++ b/docs/tasks/includes/_node-get-resources.md @@ -1,4 +1,4 @@ -This is how to get resources from a manifest using Node.js. +The example below shows how to get resources from manifest data using the Node.js library. ```js // TBD diff --git a/docs/tasks/includes/_node-setup.md b/docs/tasks/includes/_node-setup.md index 2c69ca83..85f8c9d4 100644 --- a/docs/tasks/includes/_node-setup.md +++ b/docs/tasks/includes/_node-setup.md @@ -1,4 +1,4 @@ -This is how to setup the Node.js library. +This is how to set up your code to use the Node.js library. ```js import { createC2pa } from 'c2pa-node'; diff --git a/docs/tasks/includes/_python-build.md b/docs/tasks/includes/_python-build.md index 26045cdb..d3569fd3 100644 --- a/docs/tasks/includes/_python-build.md +++ b/docs/tasks/includes/_python-build.md @@ -1,4 +1,6 @@ +This is an example of how to assign a manifest to an asset and sign the claim using Python. + Use a `Builder` object to add a manifest to an asset. ```python diff --git a/docs/tasks/includes/_python-get-resources.md b/docs/tasks/includes/_python-get-resources.md index d8d9d925..1fe26259 100644 --- a/docs/tasks/includes/_python-get-resources.md +++ b/docs/tasks/includes/_python-get-resources.md @@ -1,7 +1,9 @@ +The example below shows how to get resources from manifest data using the Python library. + Retrieve binary resources such as thumbnails from the manifest data, use the `resource_to_stream` or `resource_to_file` methods using the associated `identifier` field values and a `uri`. -NOTE: Need to add example of using `reader.resource_to_stream()`. +_NOTE: Need to add example of using `reader.resource_to_stream()`._ ```python try: diff --git a/docs/tasks/includes/_python-setup.md b/docs/tasks/includes/_python-setup.md index fe695faf..54b0a6d1 100644 --- a/docs/tasks/includes/_python-setup.md +++ b/docs/tasks/includes/_python-setup.md @@ -1,4 +1,4 @@ -This is how to setup the Python library. +This is how to set up your code to use the Python library. ```python # Import the C2PA Python package diff --git a/docs/tasks/includes/_rust-build.md b/docs/tasks/includes/_rust-build.md index 4a2a0825..4a569420 100644 --- a/docs/tasks/includes/_rust-build.md +++ b/docs/tasks/includes/_rust-build.md @@ -1,4 +1,4 @@ -This is how to attach and sign a manifest using Rust. +This is an example of how to assign a manifest to an asset and sign the claim using Rust. This example is from [`c2pa-rs/sdk/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L88C5-L134C1): diff --git a/docs/tasks/includes/_rust-get-resources.md b/docs/tasks/includes/_rust-get-resources.md index 145d15b4..52347b94 100644 --- a/docs/tasks/includes/_rust-get-resources.md +++ b/docs/tasks/includes/_rust-get-resources.md @@ -1,4 +1,6 @@ -This is how to get resources using Rust. +The example below shows how to get resources from manifest data using the Rust library. + +_NOTE: Need to clarify if/how these two code examples work together._ This is from [`resource_to_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.resource_to_stream) API doc: diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index 97ba381a..deb955d2 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -13,7 +13,7 @@ import NodeRead from './includes/_node-read.md'; import CppRead from './includes/_cpp-read.md'; import RustRead from './includes/_rust-read.md'; - + diff --git a/docs/tasks/setup.mdx b/docs/tasks/setup.mdx index ceb38d0d..9ca14193 100644 --- a/docs/tasks/setup.mdx +++ b/docs/tasks/setup.mdx @@ -12,7 +12,7 @@ import NodeSetup from './includes/_node-setup.md'; import CppSetup from './includes/_cpp-setup.md'; import RustSetup from './includes/_rust-setup.md'; - + From 561ac54f2c36268aa29deb21746b3715df60c8be Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 14 Feb 2025 14:42:47 -0800 Subject: [PATCH 24/58] Comments from Tania --- docs/tasks/includes/_python-build.md | 30 ++++++++++---------- docs/tasks/includes/_python-get-resources.md | 20 ++++++------- docs/tasks/includes/_python-read.md | 4 ++- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/docs/tasks/includes/_python-build.md b/docs/tasks/includes/_python-build.md index d3569fd3..6342fd14 100644 --- a/docs/tasks/includes/_python-build.md +++ b/docs/tasks/includes/_python-build.md @@ -5,30 +5,30 @@ Use a `Builder` object to add a manifest to an asset. ```python try: - # Define a function to sign the claim bytes - # In this case we are using a pre-defined sign_ps256 method, passing in our private cert - # Normally this cert would be kept safe in some other location + # Define a function to sign the claim bytes. + # In this case we are using a pre-defined sign_ps256 method, passing in our private cert. + # Normally this cert would be kept safe in some other location. def private_sign(data: bytes) -> bytes: return sign_ps256(data, "tests/fixtures/ps256.pem") - # read our public certs into memory + # Read our public certs into memory. certs = open(data_dir + "ps256.pub", "rb").read() - # Create a signer from the private signer, certs and a time stamp service url + # Create a signer from the private signer, certs and a time stamp service URL. signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") # Create a builder add a thumbnail resource and an ingredient file. builder = Builder(manifest_json) - # Add the resource from a stream + # Add the resource from a stream. a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb") builder.add_resource("image/jpeg", a_thumbnail_jpg_stream) - # Add the resource from a file + # Add the resource from a file. # The URI provided here, "thumbnail", must match an identifier in the manifest definition. builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg") - # Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail + # Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail. ingredient_json = { "title": "A.jpg", "relationship": "parentOf", # "parentOf", "componentOf" or "inputTo" @@ -38,19 +38,19 @@ try: } } - # Add the ingredient from a stream + # Add the ingredient from a stream. a_jpg_stream = open("tests/fixtures/A.jpg", "rb") builder.add_ingredient("image/jpeg", a_jpg_stream) - # At this point we could archive or unarchive our Builder to continue later. - # In this example we use a bytearray for the archive stream. - # all ingredients and resources will be saved in the archive + # At this point archive or unarchive Builder to continue later. + # This example uses a bytearray for the archive stream. + # All ingredients and resources are saved in the archive. archive = io.BytesIO(bytearray()) builder.to_archive(archive) archive.seek() builder = builder.from_archive(archive) - # Sign the builder with a stream and output it to a stream + # Sign the builder with a stream and output it to a stream. # This returns the binary manifest data that could be uploaded to cloud storage. input_stream = open("tests/fixtures/A.jpg", "rb") output_stream = open("target/out.jpg", "wb") @@ -63,8 +63,8 @@ except Exception as err: FROM SIGNING: ```python -request_data = ... # This is the asset being signed -content_type = ... # MIME type of the asset +request_data = ... # This is the asset being signed. +content_type = ... # MIME type of the asset. manifest = json.dumps({ "title": "image.jpg", diff --git a/docs/tasks/includes/_python-get-resources.md b/docs/tasks/includes/_python-get-resources.md index 1fe26259..773e05da 100644 --- a/docs/tasks/includes/_python-get-resources.md +++ b/docs/tasks/includes/_python-get-resources.md @@ -5,19 +5,19 @@ Retrieve binary resources such as thumbnails from the manifest data, use the `re _NOTE: Need to add example of using `reader.resource_to_stream()`._ -```python +```py try: -# Create a reader from a file path -reader = c2pa.Reader.from_file("path/to/media_file.jpg") + # Create a reader from a file path + reader = c2pa.Reader.from_file("path/to/media_file.jpg") -# Get the active manifest. -manifest = reader.get_active_manifest() -if manifest != None: + # Get the active manifest. + manifest = reader.get_active_manifest() + if manifest != None: - # get the uri to the manifest's thumbnail and write it to a file - uri = manifest["thumbnail"]["identifier"] - reader.resource_to_file(uri, "thumbnail_v2.jpg") + # get the uri to the manifest's thumbnail and write it to a file + uri = manifest["thumbnail"]["identifier"] + reader.resource_to_file(uri, "thumbnail_v2.jpg") except Exception as err: - print(err) + print(err) ``` \ No newline at end of file diff --git a/docs/tasks/includes/_python-read.md b/docs/tasks/includes/_python-read.md index ea990120..64c659f4 100644 --- a/docs/tasks/includes/_python-read.md +++ b/docs/tasks/includes/_python-read.md @@ -1,5 +1,7 @@ -Use the `Reader` object to read manifest data from a file or stream and perform validation on the manifest store. Use the `json()` method to return a JSON manifest report; If there are validation errors, the report includes a `validation_status` field. +Use the `Reader` object to read manifest data from a file or stream and perform validation on the manifest store. + +Use the `json()` method to return a JSON manifest report; If there are validation errors, the report includes a `validation_status` field. An asset file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map. From 00c670282b26d40be11101812267101a1ab574b2 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 14 Feb 2025 14:59:56 -0800 Subject: [PATCH 25/58] Comments from ahalle and tmathern --- docs/tasks/includes/_python-get-resources.md | 4 ++-- docs/tasks/includes/_python-read.md | 4 ++-- docs/tasks/includes/_python-setup.md | 8 ++++---- docs/tasks/includes/_rust-read.md | 2 +- docs/tasks/includes/_rust-setup.md | 5 ++++- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/tasks/includes/_python-get-resources.md b/docs/tasks/includes/_python-get-resources.md index 773e05da..f2200c4c 100644 --- a/docs/tasks/includes/_python-get-resources.md +++ b/docs/tasks/includes/_python-get-resources.md @@ -7,14 +7,14 @@ _NOTE: Need to add example of using `reader.resource_to_stream()`._ ```py try: - # Create a reader from a file path + # Create a reader from a file path. reader = c2pa.Reader.from_file("path/to/media_file.jpg") # Get the active manifest. manifest = reader.get_active_manifest() if manifest != None: - # get the uri to the manifest's thumbnail and write it to a file + # get the uri to the manifest's thumbnail and write it to a file. uri = manifest["thumbnail"]["identifier"] reader.resource_to_file(uri, "thumbnail_v2.jpg") diff --git a/docs/tasks/includes/_python-read.md b/docs/tasks/includes/_python-read.md index 64c659f4..7f7a416f 100644 --- a/docs/tasks/includes/_python-read.md +++ b/docs/tasks/includes/_python-read.md @@ -7,10 +7,10 @@ An asset file may contain many manifests in a manifest store. The most recent ma ```py try: - # Create a reader from a file path + # Create a reader from a file path. reader = c2pa.Reader.from_file("path/to/media_file.jpg") - # Alternatively, create a reader from a stream + # Alternatively, create a reader from a stream. stream = open("path/to/media_file.jpg", "rb") reader = c2pa.Reader("image/jpeg", stream) diff --git a/docs/tasks/includes/_python-setup.md b/docs/tasks/includes/_python-setup.md index 54b0a6d1..01388bfa 100644 --- a/docs/tasks/includes/_python-setup.md +++ b/docs/tasks/includes/_python-setup.md @@ -1,21 +1,21 @@ This is how to set up your code to use the Python library. ```python -# Import the C2PA Python package +# Import the C2PA Python package. from c2pa import * -# Import standard general-purpose packages +# Import standard general-purpose packages. import os import io import logging import json import base64 -# Import web packages used in example implementation +# Import web packages used in example implementation. from flask import Flask, request, abort from flask_cors import CORS from waitress import serve -# Import AWS SDK package (to use KMS) +# Import AWS SDK package (to use KMS). import boto3 ``` diff --git a/docs/tasks/includes/_rust-read.md b/docs/tasks/includes/_rust-read.md index f164268a..d44ea756 100644 --- a/docs/tasks/includes/_rust-read.md +++ b/docs/tasks/includes/_rust-read.md @@ -18,8 +18,8 @@ Use [`from_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.f ```rust use std::io::Cursor; - use c2pa::Reader; + let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg")); let reader = Reader::from_stream("image/jpeg", stream).unwrap(); println!("{}", reader.json()); diff --git a/docs/tasks/includes/_rust-setup.md b/docs/tasks/includes/_rust-setup.md index f0e8e9cd..017826cb 100644 --- a/docs/tasks/includes/_rust-setup.md +++ b/docs/tasks/includes/_rust-setup.md @@ -1,5 +1,9 @@ This is how to setup your code to use the Rust library. +:::tip +The files used in this example are in the C2PA Rust library [`sdk/tests/fixtures`](https://github.com/contentauth/c2pa-rs/tree/main/sdk/tests/fixtures) directory. +::: + ```rust use std::{ io::{Cursor, Write}, @@ -8,7 +12,6 @@ use std::{ use anyhow::Result; use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader}; - use c2pa_crypto::raw_signature::SigningAlg; use serde_json::json; From 18d1c4b27fd37d302d34742d05ee9d792fb7243b Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Thu, 20 Feb 2025 16:02:29 -0800 Subject: [PATCH 26/58] Remove duplicate example --- docs/tasks/includes/_python-build.md | 48 ---------------------------- 1 file changed, 48 deletions(-) diff --git a/docs/tasks/includes/_python-build.md b/docs/tasks/includes/_python-build.md index 6342fd14..19338a0b 100644 --- a/docs/tasks/includes/_python-build.md +++ b/docs/tasks/includes/_python-build.md @@ -58,52 +58,4 @@ try: except Exception as err: print(err) -``` - -FROM SIGNING: - -```python -request_data = ... # This is the asset being signed. -content_type = ... # MIME type of the asset. - -manifest = json.dumps({ - "title": "image.jpg", - "format": "image/jpeg", - "claim_generator_info": [ - { - "name": "Documentation example", - "version": "0.0.1" - } - ], - "assertions": [ - { - "label": "c2pa.actions", - "data": { - "actions": [ - { - "action": "c2pa.edited", - "softwareAgent": { - "name": "C2PA Python Example", - "version": "0.1.0" - } - } - ] - } - } - ] -}) - -try: - builder = Builder(manifest) - - signer = create_signer(kms_sign, signing_alg, - cert_chain, timestamp_url) - - result = io.BytesIO(b"") - builder.sign(signer, content_type, io.BytesIO(request_data), result) - - return result.getvalue() -except Exception as e: - logging.error(e) - abort(500, description=e) ``` \ No newline at end of file From 832b7e9cd5828ae2e731d9c7a1a5683195afa4e1 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 21 Feb 2025 12:33:56 -0800 Subject: [PATCH 27/58] Eli comments --- docs/tasks/includes/_js-setup.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/tasks/includes/_js-setup.md b/docs/tasks/includes/_js-setup.md index 850db022..f3857486 100644 --- a/docs/tasks/includes/_js-setup.md +++ b/docs/tasks/includes/_js-setup.md @@ -1,7 +1,7 @@ This is how to set up your code to use the JavaScript library: ```js -const version = '0.24.2'; +const version = '0.27.1'; const sampleImage = ''; import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; @@ -14,4 +14,31 @@ import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; workerSrc: 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', }); +``` + +If you installed the package locally, for example from npm, then its simply: + +```js +import { createC2pa } from 'c2pa'; +``` + +Additionally, the `wasmSrc` and `workerSrc` objects need to available as static assets that can be fetched at runtime. The best way to do that depends on the build system. For example, using [Vite](https://vite.dev/guide/assets#explicit-url-imports), as shown in the [minimal-ts-vite example](https://github.com/contentauth/c2pa-js/blob/main/examples/minimal-ts-vite/examples/active-manifest/main.ts): + + +```js +import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?url'; +import workerSrc from 'c2pa/dist/c2pa.worker.js?url'; +import { parseISO } from 'date-fns'; + +(async () => { + let output: string[] = []; + + const c2pa = await createC2pa({ + wasmSrc, + workerSrc, + }); + + ... + +})(); ``` \ No newline at end of file From 133d4ba9105370c6d5cb092db0dac05347836083 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 21 Feb 2025 12:35:31 -0800 Subject: [PATCH 28/58] Fix code example --- docs/tasks/includes/_js-setup.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/tasks/includes/_js-setup.md b/docs/tasks/includes/_js-setup.md index f3857486..ced981ee 100644 --- a/docs/tasks/includes/_js-setup.md +++ b/docs/tasks/includes/_js-setup.md @@ -14,6 +14,10 @@ import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; workerSrc: 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', }); + + ... + +})(); ``` If you installed the package locally, for example from npm, then its simply: @@ -39,6 +43,6 @@ import { parseISO } from 'date-fns'; }); ... - + })(); ``` \ No newline at end of file From 404b6b961e3928803b93df5d256577207190ba5c Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 10 Mar 2025 14:07:24 -0700 Subject: [PATCH 29/58] Remove content for Node.js and replace with WIP note --- docs/tasks/build.mdx | 5 ++++- docs/tasks/get-resources.mdx | 5 ++++- docs/tasks/includes/_node-wip.md | 3 +++ docs/tasks/read.mdx | 5 ++++- docs/tasks/setup.mdx | 5 ++++- 5 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 docs/tasks/includes/_node-wip.md diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index 4095a3e4..2d4d0e3c 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -8,7 +8,10 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import PythonBuild from './includes/_python-build.md'; +/* import NodeBuild from './includes/_node-build.md'; +*/ +import NodeWIP from './includes/_node-wip.md'; import CppBuild from './includes/_cpp-build.md'; import RustBuild from './includes/_rust-build.md'; @@ -28,7 +31,7 @@ You can't currently attach a manifest to an asset and sign the claim using the J - + diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index 37c7547e..cb55f1f8 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -8,7 +8,10 @@ import TabItem from '@theme/TabItem'; import JSGetResources from './includes/_js-get-resources.md'; import PythonGetResources from './includes/_python-get-resources.md'; +/* import NodeGetResources from './includes/_node-get-resources.md'; +*/ +import NodeWIP from './includes/_node-wip.md'; import CppGetResources from './includes/_cpp-get-resources.md'; import RustGetResources from './includes/_rust-get-resources.md'; @@ -30,7 +33,7 @@ Manifest data can include binary resources such as thumbnail and icon images whi - + diff --git a/docs/tasks/includes/_node-wip.md b/docs/tasks/includes/_node-wip.md new file mode 100644 index 00000000..ca2cd717 --- /dev/null +++ b/docs/tasks/includes/_node-wip.md @@ -0,0 +1,3 @@ +:::note +The Node.js library is being revised. The documentation will be updated as soon as possible with the latest changes. +::: \ No newline at end of file diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index deb955d2..9c20c7bf 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -9,7 +9,10 @@ import TabItem from '@theme/TabItem'; import JSRead from './includes/_js-read.md'; import PythonRead from './includes/_python-read.md'; +/* import NodeRead from './includes/_node-read.md'; +*/ +import NodeWIP from './includes/_node-wip.md'; import CppRead from './includes/_cpp-read.md'; import RustRead from './includes/_rust-read.md'; @@ -29,7 +32,7 @@ import RustRead from './includes/_rust-read.md'; - + diff --git a/docs/tasks/setup.mdx b/docs/tasks/setup.mdx index 9ca14193..b272ae94 100644 --- a/docs/tasks/setup.mdx +++ b/docs/tasks/setup.mdx @@ -8,7 +8,10 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import JSSetup from './includes/_js-setup.md'; import PythonSetup from './includes/_python-setup.md'; +/* import NodeSetup from './includes/_node-setup.md'; +*/ +import NodeWIP from './includes/_node-wip.md'; import CppSetup from './includes/_cpp-setup.md'; import RustSetup from './includes/_rust-setup.md'; @@ -28,7 +31,7 @@ import RustSetup from './includes/_rust-setup.md'; - + From 9ec18a5c29ad4b6214e500e9f51933e2f71a7915 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 10 Mar 2025 14:13:10 -0700 Subject: [PATCH 30/58] Add Tania's comments for future reference --- docs/tasks/includes/_node-build.md | 4 +--- docs/tasks/includes/_node-setup.md | 10 +++++++++- docs/tasks/includes/_python-read.md | 6 +++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/tasks/includes/_node-build.md b/docs/tasks/includes/_node-build.md index 14fa6e5e..72d1e2e5 100644 --- a/docs/tasks/includes/_node-build.md +++ b/docs/tasks/includes/_node-build.md @@ -29,13 +29,11 @@ const manifest = new ManifestBuilder({ }); ``` -FROM SIGNING: - Use the `c2pa.sign()` method to sign an ingredient, either locally if you have a signing certificate and key available, or by using a remote signing API. ## Signing a stream -If you have an asset file's data loaded into memory, you can sign the the asset using the loaded stream (buffer). +If you have an asset file's data loaded into a stream, you can use it to sign the asset **NOTE**: Signing using a stream is currently supported only for `image/jpeg` and `image/png` data. For all other file types, use the [file-based approach](#signing-files) . diff --git a/docs/tasks/includes/_node-setup.md b/docs/tasks/includes/_node-setup.md index 85f8c9d4..763985f6 100644 --- a/docs/tasks/includes/_node-setup.md +++ b/docs/tasks/includes/_node-setup.md @@ -5,4 +5,12 @@ import { createC2pa } from 'c2pa-node'; import { readFile } from 'node:fs/promises'; const c2pa = createC2pa(); -``` \ No newline at end of file +``` + + \ No newline at end of file diff --git a/docs/tasks/includes/_python-read.md b/docs/tasks/includes/_python-read.md index 7f7a416f..8f9b8055 100644 --- a/docs/tasks/includes/_python-read.md +++ b/docs/tasks/includes/_python-read.md @@ -19,4 +19,8 @@ try: except Exception as err: print(err) -``` \ No newline at end of file +``` + + \ No newline at end of file From 90333fedee9319e8ce4fdbada3889ee9d891a5d1 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 10 Mar 2025 14:42:42 -0700 Subject: [PATCH 31/58] Comments from Eli --- docs/tasks/includes/_js-get-resources.md | 4 ++++ docs/tasks/includes/_js-read.md | 3 +++ docs/tasks/includes/_js-setup.md | 6 +++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/tasks/includes/_js-get-resources.md b/docs/tasks/includes/_js-get-resources.md index 4c5d7162..4dd02de4 100644 --- a/docs/tasks/includes/_js-get-resources.md +++ b/docs/tasks/includes/_js-get-resources.md @@ -1,3 +1,7 @@ +:::note +That JavaScript library is being extensively revised so the APIs used here may change in the near future. +::: + The example below shows how to get resources from manifest data using the JavaScript library. ```js diff --git a/docs/tasks/includes/_js-read.md b/docs/tasks/includes/_js-read.md index e2d42ffd..7ddf4d98 100644 --- a/docs/tasks/includes/_js-read.md +++ b/docs/tasks/includes/_js-read.md @@ -1,3 +1,6 @@ +:::note +That JavaScript library is being extensively revised so the APIs used here may change in the near future. +::: Use [`c2pa.read`](../../docs/js-sdk/api/c2pa.c2pa#methods) to read manifest data from an asset; if the asset has a C2PA manifest and was processed without errors, the returned [`c2paReadResult`](../../docs/js-sdk/api/c2pa.c2pareadresult) contains a [`manifestStore`](../../docs/js-sdk/api/c2pa.c2pareadresult.manifeststore) object with several useful properties: diff --git a/docs/tasks/includes/_js-setup.md b/docs/tasks/includes/_js-setup.md index ced981ee..56885a08 100644 --- a/docs/tasks/includes/_js-setup.md +++ b/docs/tasks/includes/_js-setup.md @@ -1,3 +1,7 @@ +:::note +That JavaScript library is being extensively revised so the APIs used here may change in the near future. +::: + This is how to set up your code to use the JavaScript library: ```js @@ -20,7 +24,7 @@ import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; })(); ``` -If you installed the package locally, for example from npm, then its simply: +If you installed the package locally, for example from npm, then it's simply: ```js import { createC2pa } from 'c2pa'; From 92a823735b73c2f471b163b55a88c4f82c90678c Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Thu, 13 Mar 2025 14:50:00 -0700 Subject: [PATCH 32/58] Remove Python streaming example code, just use file i/o. --- docs/tasks/includes/_python-read.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/tasks/includes/_python-read.md b/docs/tasks/includes/_python-read.md index 8f9b8055..4020ef6e 100644 --- a/docs/tasks/includes/_python-read.md +++ b/docs/tasks/includes/_python-read.md @@ -10,10 +10,6 @@ try: # Create a reader from a file path. reader = c2pa.Reader.from_file("path/to/media_file.jpg") - # Alternatively, create a reader from a stream. - stream = open("path/to/media_file.jpg", "rb") - reader = c2pa.Reader("image/jpeg", stream) - # Print the JSON for a manifest. print("manifest store:", reader.json()) From ddd71f1ac644e7f38aa00de144cc76e9ee7f9598 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Thu, 24 Jul 2025 14:44:51 -0700 Subject: [PATCH 33/58] Kick to try to get a doc build From 44d3f61fc234f20269dddd3ef902e0a682fab821 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Thu, 18 Sep 2025 16:12:36 -0700 Subject: [PATCH 34/58] Switch around section titles --- docs/manifest/understanding.md | 11 +++++------ sidebars.js | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/manifest/understanding.md b/docs/manifest/understanding.md index 8831d335..0aed839a 100644 --- a/docs/manifest/understanding.md +++ b/docs/manifest/understanding.md @@ -1,13 +1,11 @@ --- id: understanding-manifest -title: Working with manifests +title: Understanding manifests --- -## Understanding manifests - The concept of a _manifest_ is central to how Content Credentials work. -A collection of manifests (known as a _manifest store_) is attached to an asset. Each manifest contains information about the provenance of the asset. Creating or editing an asset using a C2PA-compliant device or tool (for example Adobe Photoshop) adds a new manifest to the manifest store. +A collection of manifests (known as a _manifest store_) is attached to an asset. Each manifest contains information about the provenance of the asset. Creating or editing an asset using a C2PA-compliant device or tool (for example, Adobe Photoshop) adds a new manifest to the manifest store. The manifests in the manifest store are not ordered, but the most-recently added manifest is the _active manifest_. The active manifest has content bindings that can be validated with the asset—that is, it's hashed with the asset to ensure its validity. @@ -21,6 +19,7 @@ A manifest store can be either: In addition, an asset can have a [external manifest store](https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_external_manifests), linked from the asset's metadata, as detailed in the [C2PA specification](https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_embedding_a_reference_to_an_external_manifest). This is sometimes referred to as a _remote manifest_. To determine if an asset has Content Credentials, the SDK checks for the presence of a manifest store in this order: + 1. In the asset metadata 1. In a sidebar file. 1. In a remote manifest. @@ -28,10 +27,10 @@ To determine if an asset has Content Credentials, the SDK checks for the presenc So, for example to see if `foo.jpg` has Content Credentials, the SDK first checks if there's a manifest store in the file itself, then looks for a sidecar file (`foo.c2pa` in the same directory), and finally looks in the asset's metadata for a reference to a remote manifest store. :::info -Currently, only Adobe has implemented a Content Credentials cloud service to provide access to remote manifest stores, but in theory anyone could do so to provide a publicly-accessible network location for manifests. +Currently, only Adobe has implemented a Content Credentials cloud service to provide access to remote manifest stores, but in theory anyone could do so to provide a publicly-accessible network location for manifests. ::: -## Binary versus JSON manifest formats +## Binary versus JSON manifest The manifest as described in the C2PA specification is a binary structure in JPEG universal metadata box format ([JUMBF](https://www.iso.org/standard/84635.html)) that can include JSON and binary data for things like encryption keys and thumbnail images. diff --git a/sidebars.js b/sidebars.js index 57963a3e..5c945755 100644 --- a/sidebars.js +++ b/sidebars.js @@ -29,7 +29,7 @@ const sidebars = { }, { type: 'category', - label: 'Working with manifests', + label: 'Understanding manifests', link: { type: 'doc', id: 'manifest/understanding-manifest' }, collapsed: true, items: [ From c24e3f3ec2a0f623b35f794de253fe74003f4797 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Thu, 18 Sep 2025 16:56:12 -0700 Subject: [PATCH 35/58] Remove setup page and include that code in each example instead --- docs/tasks/includes/_cpp-build.md | 28 +++++++++--- docs/tasks/includes/_cpp-get-resources.md | 34 +++++++++------ docs/tasks/includes/_cpp-read.md | 27 ++++++++---- docs/tasks/includes/_js-get-resources.md | 45 ++++++++++++++++++-- docs/tasks/includes/_js-read.md | 16 ++++++- docs/tasks/includes/_python-build.md | 10 +++++ docs/tasks/includes/_python-get-resources.md | 9 ++++ docs/tasks/includes/_python-read.md | 9 ++++ docs/tasks/includes/_rust-build.md | 11 ++++- docs/tasks/includes/_rust-get-resources.md | 22 +++++++--- docs/tasks/includes/_rust-read.md | 29 ++++++++++++- docs/tasks/index.md | 1 - sidebars.js | 4 -- 13 files changed, 199 insertions(+), 46 deletions(-) diff --git a/docs/tasks/includes/_cpp-build.md b/docs/tasks/includes/_cpp-build.md index 04f27f6e..ece961f5 100644 --- a/docs/tasks/includes/_cpp-build.md +++ b/docs/tasks/includes/_cpp-build.md @@ -1,23 +1,38 @@ This is an example of how to assign a manifest to an asset and sign the claim using C++: ```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include "c2pa.hpp" +#include "test_signer.hpp" + +using namespace std; +namespace fs = std::filesystem; +using namespace c2pa; + const std::string manifest_json = R"{ "claim_generator": "c2pa_c_test/0.1", "claim_generator_info": [ { - "name": "c2pa-c test", + "name": "c2pa-c example", "version": "0.1" } ], "assertions": [ { - "label": "c2pa.training-mining", + "label": "cawg.training-mining", "data": { "entries": { - "c2pa.ai_generative_training": { "use": "notAllowed" }, - "c2pa.ai_inference": { "use": "notAllowed" }, - "c2pa.ai_training": { "use": "notAllowed" }, - "c2pa.data_mining": { "use": "notAllowed" } + "cawg.ai_generative_training": { "use": "notAllowed" }, + "cawg.ai_inference": { "use": "notAllowed" }, + "cawg.ai_training": { "use": "notAllowed" }, + "cawg.data_mining": { "use": "notAllowed" } } } } @@ -25,5 +40,4 @@ const std::string manifest_json = R"{ }; auto builder = Builder(manifest_json); - ``` \ No newline at end of file diff --git a/docs/tasks/includes/_cpp-get-resources.md b/docs/tasks/includes/_cpp-get-resources.md index 85fa94ed..60bb1591 100644 --- a/docs/tasks/includes/_cpp-get-resources.md +++ b/docs/tasks/includes/_cpp-get-resources.md @@ -1,6 +1,24 @@ This is how to get resources from a manifest using C++. ```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include "c2pa.hpp" +#include "test_signer.hpp" +#include + +// this example uses nlohmann json for parsing the manifest +using json = nlohmann::json; +using namespace std; +namespace fs = std::filesystem; +using namespace c2pa; + string read_text_file(const fs::path &path) { ifstream file(path); @@ -23,19 +41,6 @@ int main() try { - // load the manifest, certs, and private key - /* Commenting out, because not part of resource reading - string manifest_json = read_text_file(manifest_path).data(); - - string certs = read_text_file(certs_path).data(); - - // create a signer - Signer signer = Signer(&test_signer, Es256, certs, "http://timestamp.digicert.com"); - - auto builder = Builder(manifest_json); - auto manifest_data = builder.sign(image_path, output_path, signer); - */ - // read the new manifest and display the JSON auto reader = Reader(output_path); @@ -56,14 +61,17 @@ int main() cout << "thumbnail written to" << thumbnail_path << endl; } } + catch (c2pa::Exception const &e) { cout << "C2PA Error: " << e.what() << endl; } + catch (runtime_error const &e) { cout << "setup error" << e.what() << endl; } + catch (json::parse_error const &e) { cout << "parse error " << e.what() << endl; diff --git a/docs/tasks/includes/_cpp-read.md b/docs/tasks/includes/_cpp-read.md index 95dac314..e48e63f2 100644 --- a/docs/tasks/includes/_cpp-read.md +++ b/docs/tasks/includes/_cpp-read.md @@ -2,16 +2,25 @@ Use the `read_file` function to read C2PA data from the specified file. This function examines the specified asset file for C2PA data and returns a JSON report if it finds any; it throws exceptions on errors. If there are validation errors, the report includes a `validation_status` field. ```cpp -auto json_store = C2pa::read_file("", "") -``` +#include +#include +#include +#include +#include +#include +#include +#include +#include "c2pa.hpp" +#include "test_signer.hpp" -Where: +using namespace std; +namespace fs = std::filesystem; +using namespace c2pa; -- ``- The asset file to read. -- `` - Optional path to data output directory; If provided, the function extracts any binary resources, such as thumbnails, icons, and C2PA data into that directory. These files are referenced by the identifier fields in the manifest store report. +auto json_store = C2pa::read_file("work/media_file.jpg", "output/data_dir") +``` -For example: +Where: -```cpp -auto json_store = C2pa::read_file("work/media_file.jpg", "output/data_dir") -``` \ No newline at end of file +- `work/media_file.jpg` is the asset file to read. +- `output/data_dir` is the optional path to data output directory; If provided, the function extracts any binary resources, such as thumbnails, icons, and C2PA data into that directory. These files are referenced by the identifier fields in the manifest store report. diff --git a/docs/tasks/includes/_js-get-resources.md b/docs/tasks/includes/_js-get-resources.md index 4dd02de4..56a6bfc8 100644 --- a/docs/tasks/includes/_js-get-resources.md +++ b/docs/tasks/includes/_js-get-resources.md @@ -5,15 +5,25 @@ That JavaScript library is being extensively revised so the APIs used here may c The example below shows how to get resources from manifest data using the JavaScript library. ```js +const version = '0.27.1'; +const sampleImage = 'my_image.jpg' + import { createC2pa, selectProducer } from 'c2pa'; import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?url'; import workerSrc from 'c2pa/dist/c2pa.worker.js?url'; import { parseISO } from 'date-fns'; - -const sampleImage = - 'https://raw.githubusercontent.com/contentauth/c2pa-js/main/tools/testing/fixtures/images/CAICAI.jpg'; +import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; (async () => { + // Initialize the c2pa-js SDK + const c2pa = await createC2pa({ + wasmSrc: + 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm', + workerSrc: + 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', + }); + + let output: string[] = []; const c2pa = await createC2pa({ @@ -54,6 +64,7 @@ const sampleImage = `; }); + } else { output.push(` @@ -63,5 +74,33 @@ const sampleImage = } document.querySelector('#results tbody')!.innerHTML = output.join(''); + })(); +``` + +With an HTML page like this: + +```html + + + + + + + + active-manifest + + + + + + + + + + +
PropertyValue
Loading…
+ + + ``` \ No newline at end of file diff --git a/docs/tasks/includes/_js-read.md b/docs/tasks/includes/_js-read.md index 7ddf4d98..30abbabf 100644 --- a/docs/tasks/includes/_js-read.md +++ b/docs/tasks/includes/_js-read.md @@ -9,6 +9,20 @@ Use [`c2pa.read`](../../docs/js-sdk/api/c2pa.c2pa#methods) to read manifest data - **validationStatus**: A list of any validation errors encountered. See [Validation](../../docs/js-sdk/guides/validation) for more information. ```js +const version = '0.30.14'; +const sampleImage = ''; + +import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; + +(async () => { + // Initialize the c2pa-js SDK + const c2pa = await createC2pa({ + wasmSrc: + 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm', + workerSrc: + 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', + }); + try { // Read in image and get a manifest store const { manifestStore } = await c2pa.read(sampleImage); @@ -21,4 +35,4 @@ Use [`c2pa.read`](../../docs/js-sdk/api/c2pa.c2pa#methods) to read manifest data console.error('Error reading image:', err); } })(); -``` \ No newline at end of file +``` diff --git a/docs/tasks/includes/_python-build.md b/docs/tasks/includes/_python-build.md index 19338a0b..b90cfb11 100644 --- a/docs/tasks/includes/_python-build.md +++ b/docs/tasks/includes/_python-build.md @@ -4,6 +4,16 @@ This is an example of how to assign a manifest to an asset and sign the claim us Use a `Builder` object to add a manifest to an asset. ```python +# Import the C2PA Python package. +from c2pa import * + +# Import standard general-purpose packages. +import os +import io +import logging +import json +import base64 + try: # Define a function to sign the claim bytes. # In this case we are using a pre-defined sign_ps256 method, passing in our private cert. diff --git a/docs/tasks/includes/_python-get-resources.md b/docs/tasks/includes/_python-get-resources.md index f2200c4c..833d8660 100644 --- a/docs/tasks/includes/_python-get-resources.md +++ b/docs/tasks/includes/_python-get-resources.md @@ -6,6 +6,15 @@ Retrieve binary resources such as thumbnails from the manifest data, use the `re _NOTE: Need to add example of using `reader.resource_to_stream()`._ ```py +# Import the C2PA Python package. +from c2pa import * + +# Import standard general-purpose packages. +import os +import io +import logging +import json + try: # Create a reader from a file path. reader = c2pa.Reader.from_file("path/to/media_file.jpg") diff --git a/docs/tasks/includes/_python-read.md b/docs/tasks/includes/_python-read.md index 4020ef6e..edf52a4e 100644 --- a/docs/tasks/includes/_python-read.md +++ b/docs/tasks/includes/_python-read.md @@ -6,6 +6,15 @@ Use the `json()` method to return a JSON manifest report; If there are validatio An asset file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map. ```py +# Import the C2PA Python package. +from c2pa import * + +# Import standard general-purpose packages. +import os +import io +import logging +import json + try: # Create a reader from a file path. reader = c2pa.Reader.from_file("path/to/media_file.jpg") diff --git a/docs/tasks/includes/_rust-build.md b/docs/tasks/includes/_rust-build.md index 4a569420..6faadd69 100644 --- a/docs/tasks/includes/_rust-build.md +++ b/docs/tasks/includes/_rust-build.md @@ -3,9 +3,18 @@ This is an example of how to assign a manifest to an asset and sign the claim us This example is from [`c2pa-rs/sdk/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L88C5-L134C1): ```rust -let json = manifest_def(title, format); +use std::io::{Cursor, Seek}; + +use anyhow::Result; +use c2pa::{ + crypto::raw_signature::SigningAlg, settings::Settings, validation_results::ValidationState, + Builder, CallbackSigner, Reader, +}; +use serde_json::json; +let json = manifest_def(title, format); let mut builder = Builder::from_json(&json)?; + builder.add_ingredient_from_stream( json!({ "title": parent_name, diff --git a/docs/tasks/includes/_rust-get-resources.md b/docs/tasks/includes/_rust-get-resources.md index 52347b94..ade19f45 100644 --- a/docs/tasks/includes/_rust-get-resources.md +++ b/docs/tasks/includes/_rust-get-resources.md @@ -6,16 +6,28 @@ This is from [`resource_to_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Read ```rust use c2pa::Reader; -let stream = std::io::Cursor::new(Vec::new()); -let reader = Reader::from_file("path/to/file.jpg").unwrap(); -let manifest = reader.active_manifest().unwrap(); -let uri = &manifest.thumbnail_ref().unwrap().identifier; -let bytes_written = reader.resource_to_stream(uri, stream).unwrap(); +#[cfg(feature = "file_io")] +{ + let stream = std::io::Cursor::new(Vec::new()); + let reader = Reader::from_file("path/to/file.jpg").unwrap(); + let manifest = reader.active_manifest().unwrap(); + let uri = &manifest.thumbnail_ref().unwrap().identifier; + let bytes_written = reader.resource_to_stream(uri, stream).unwrap(); +} ``` This is from [`c2pa-rs/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L138): ```rust +use std::io::{Cursor, Seek}; + +use anyhow::Result; +use c2pa::{ + crypto::raw_signature::SigningAlg, settings::Settings, validation_results::ValidationState, + Builder, CallbackSigner, Reader, +}; +use serde_json::json; + let reader = Reader::from_stream(format, &mut dest)?; // extract a thumbnail image from the ManifestStore diff --git a/docs/tasks/includes/_rust-read.md b/docs/tasks/includes/_rust-read.md index d44ea756..9521fd07 100644 --- a/docs/tasks/includes/_rust-read.md +++ b/docs/tasks/includes/_rust-read.md @@ -6,6 +6,20 @@ Use the [`Reader`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html) struct t Use [`from_file`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file) to read manifest data from a file: ```rust +use std::{ + io::{Cursor, Write}, + process::{Command, Stdio}, +}; + +use anyhow::Result; +use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader}; +use c2pa_crypto::raw_signature::SigningAlg; +use serde_json::json; + +const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg"); +const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub"); +const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem"); + use c2pa::Reader; let reader = Reader::from_file("path/to/file.jpg").unwrap(); ``` @@ -17,8 +31,19 @@ There is also an asynchronous version of this method, [`from_stream_async`](http Use [`from_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream) to read manifest data from a stream: ```rust -use std::io::Cursor; -use c2pa::Reader; +use std::{ + io::{Cursor, Write}, + process::{Command, Stdio}, +}; + +use anyhow::Result; +use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader}; +use c2pa_crypto::raw_signature::SigningAlg; +use serde_json::json; + +const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg"); +const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub"); +const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem"); let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg")); let reader = Reader::from_stream("image/jpeg", stream).unwrap(); diff --git a/docs/tasks/index.md b/docs/tasks/index.md index bedb3c62..0def1c21 100644 --- a/docs/tasks/index.md +++ b/docs/tasks/index.md @@ -6,7 +6,6 @@ title: Working with manifests There are a number of common tasks when working with manifests. The way you accomplish each task is specific to the language you're using, although at a high level the process is similar. -- [Preliminary setup](./setup.mdx) - [Reading manifest data](./read.mdx) - [Getting resources from a manifest](./get-resources.mdx) - [Attaching a manifest to an asset and signing it](./build.mdx) diff --git a/sidebars.js b/sidebars.js index 5c945755..cf1336ca 100644 --- a/sidebars.js +++ b/sidebars.js @@ -94,10 +94,6 @@ const sidebars = { link: { type: 'doc', id: 'tasks/working-manifests' }, collapsed: true, items: [ - { - type: 'doc', - id: 'tasks/setup', - }, { type: 'doc', id: 'tasks/read', From 4e5e93364c91b98c5f7046826722bdfb0debf66e Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 1 Dec 2025 15:27:53 -0800 Subject: [PATCH 36/58] Minor edits --- docs/tasks/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tasks/index.md b/docs/tasks/index.md index 0def1c21..68c22fd8 100644 --- a/docs/tasks/index.md +++ b/docs/tasks/index.md @@ -1,11 +1,11 @@ --- id: working-manifests -title: Working with manifests +title: Working with the SDK --- -There are a number of common tasks when working with manifests. -The way you accomplish each task is specific to the language you're using, although at a high level the process is similar. +There are a number of common tasks when using the CAI open-source SDK. +At a high level the process is the same in every language, but how you accomplish each task is specific to the language you're using. - [Reading manifest data](./read.mdx) - [Getting resources from a manifest](./get-resources.mdx) -- [Attaching a manifest to an asset and signing it](./build.mdx) +- [Adding a manifest to an asset and signing it](./build.mdx) From 1818a5db6a842b2b399fe9ccf65809a89e661ae2 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 2 Dec 2025 10:46:00 -0800 Subject: [PATCH 37/58] reorder sidenav --- docs/tasks/build.mdx | 2 +- sidebars.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index 2d4d0e3c..5d21784a 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -1,6 +1,6 @@ --- id: build -title: Attaching and signing a manifest +title: Adding and signing a manifest hide_table_of_contents: true --- diff --git a/sidebars.js b/sidebars.js index 4837cdcd..30ae1496 100644 --- a/sidebars.js +++ b/sidebars.js @@ -108,7 +108,7 @@ const sidebars = { }, { type: 'category', - label: 'Working with manifests', + label: 'Working with the SDK', link: { type: 'doc', id: 'tasks/working-manifests' }, collapsed: true, items: [ From b51c0f41ee36eac53eafa115d0498db23440adca Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 3 Dec 2025 14:24:36 -0800 Subject: [PATCH 38/58] Replace sidebar elements removed by merge --- sidebars.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sidebars.js b/sidebars.js index 0fe4f68e..d419f3d2 100644 --- a/sidebars.js +++ b/sidebars.js @@ -134,6 +134,26 @@ const sidebars = { }, ], }, + { + type: 'category', + label: 'Working with the SDK', + link: { type: 'doc', id: 'tasks/working-manifests' }, + collapsed: true, + items: [ + { + type: 'doc', + id: 'tasks/read', + }, + { + type: 'doc', + id: 'tasks/get-resources', + }, + { + type: 'doc', + id: 'tasks/build', + }, + ], + }, { type: 'category', label: 'C2PA Tool', From 42b16da6c62b62da85e8363d1ffc3985345af9ec Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 5 Dec 2025 11:14:31 -0800 Subject: [PATCH 39/58] Major reorg --- docs/tasks/build.mdx | 10 +- docs/tasks/{includes => cpp}/_cpp-build.md | 0 .../{includes => cpp}/_cpp-get-resources.md | 0 docs/tasks/{includes => cpp}/_cpp-read.md | 0 docs/tasks/{includes => cpp}/_cpp-setup.md | 0 docs/tasks/get-resources.mdx | 12 +- docs/tasks/includes/_js-get-resources.md | 106 ------------------ docs/tasks/includes/_js-read.md | 38 ------- docs/tasks/includes/_js-setup.md | 52 --------- docs/tasks/js/_js-get-resources.md | 19 ++++ docs/tasks/js/_js-read.md | 25 +++++ docs/tasks/{includes => node}/_node-build.md | 0 .../{includes => node}/_node-get-resources.md | 0 docs/tasks/{includes => node}/_node-read.md | 0 docs/tasks/{includes => node}/_node-setup.md | 0 docs/tasks/{includes => node}/_node-wip.md | 0 .../{includes => python}/_python-build.md | 0 .../_python-get-resources.md | 0 .../{includes => python}/_python-read.md | 0 .../{includes => python}/_python-setup.md | 0 docs/tasks/read.mdx | 12 +- docs/tasks/{includes => rust}/_rust-build.md | 0 .../{includes => rust}/_rust-get-resources.md | 0 docs/tasks/{includes => rust}/_rust-read.md | 0 docs/tasks/{includes => rust}/_rust-setup.md | 0 docs/tasks/setup.mdx | 50 --------- 26 files changed, 61 insertions(+), 263 deletions(-) rename docs/tasks/{includes => cpp}/_cpp-build.md (100%) rename docs/tasks/{includes => cpp}/_cpp-get-resources.md (100%) rename docs/tasks/{includes => cpp}/_cpp-read.md (100%) rename docs/tasks/{includes => cpp}/_cpp-setup.md (100%) delete mode 100644 docs/tasks/includes/_js-get-resources.md delete mode 100644 docs/tasks/includes/_js-read.md delete mode 100644 docs/tasks/includes/_js-setup.md create mode 100644 docs/tasks/js/_js-get-resources.md create mode 100644 docs/tasks/js/_js-read.md rename docs/tasks/{includes => node}/_node-build.md (100%) rename docs/tasks/{includes => node}/_node-get-resources.md (100%) rename docs/tasks/{includes => node}/_node-read.md (100%) rename docs/tasks/{includes => node}/_node-setup.md (100%) rename docs/tasks/{includes => node}/_node-wip.md (100%) rename docs/tasks/{includes => python}/_python-build.md (100%) rename docs/tasks/{includes => python}/_python-get-resources.md (100%) rename docs/tasks/{includes => python}/_python-read.md (100%) rename docs/tasks/{includes => python}/_python-setup.md (100%) rename docs/tasks/{includes => rust}/_rust-build.md (100%) rename docs/tasks/{includes => rust}/_rust-get-resources.md (100%) rename docs/tasks/{includes => rust}/_rust-read.md (100%) rename docs/tasks/{includes => rust}/_rust-setup.md (100%) delete mode 100644 docs/tasks/setup.mdx diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index 5d21784a..dafac783 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -7,13 +7,13 @@ hide_table_of_contents: true import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import PythonBuild from './includes/_python-build.md'; +import PythonBuild from './python/_python-build.md'; /* -import NodeBuild from './includes/_node-build.md'; +import NodeBuild from './node/_node-build.md'; */ -import NodeWIP from './includes/_node-wip.md'; -import CppBuild from './includes/_cpp-build.md'; -import RustBuild from './includes/_rust-build.md'; +import NodeWIP from './node/_node-wip.md'; +import CppBuild from './cpp/_cpp-build.md'; +import RustBuild from './rust/_rust-build.md'; diff --git a/docs/tasks/includes/_cpp-build.md b/docs/tasks/cpp/_cpp-build.md similarity index 100% rename from docs/tasks/includes/_cpp-build.md rename to docs/tasks/cpp/_cpp-build.md diff --git a/docs/tasks/includes/_cpp-get-resources.md b/docs/tasks/cpp/_cpp-get-resources.md similarity index 100% rename from docs/tasks/includes/_cpp-get-resources.md rename to docs/tasks/cpp/_cpp-get-resources.md diff --git a/docs/tasks/includes/_cpp-read.md b/docs/tasks/cpp/_cpp-read.md similarity index 100% rename from docs/tasks/includes/_cpp-read.md rename to docs/tasks/cpp/_cpp-read.md diff --git a/docs/tasks/includes/_cpp-setup.md b/docs/tasks/cpp/_cpp-setup.md similarity index 100% rename from docs/tasks/includes/_cpp-setup.md rename to docs/tasks/cpp/_cpp-setup.md diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index cb55f1f8..ee351dfb 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -6,14 +6,14 @@ title: Getting resources from a manifest import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import JSGetResources from './includes/_js-get-resources.md'; -import PythonGetResources from './includes/_python-get-resources.md'; +import JSGetResources from './js/_js-get-resources.md'; +import PythonGetResources from './python/_python-get-resources.md'; /* -import NodeGetResources from './includes/_node-get-resources.md'; +import NodeGetResources from './node/_node-get-resources.md'; */ -import NodeWIP from './includes/_node-wip.md'; -import CppGetResources from './includes/_cpp-get-resources.md'; -import RustGetResources from './includes/_rust-get-resources.md'; +import NodeWIP from './node/_node-wip.md'; +import CppGetResources from './cpp/_cpp-get-resources.md'; +import RustGetResources from './rust/_rust-get-resources.md'; Manifest data can include binary resources such as thumbnail and icon images which are referenced by JUMBF URIs in manifest data. diff --git a/docs/tasks/includes/_js-get-resources.md b/docs/tasks/includes/_js-get-resources.md deleted file mode 100644 index 56a6bfc8..00000000 --- a/docs/tasks/includes/_js-get-resources.md +++ /dev/null @@ -1,106 +0,0 @@ -:::note -That JavaScript library is being extensively revised so the APIs used here may change in the near future. -::: - -The example below shows how to get resources from manifest data using the JavaScript library. - -```js -const version = '0.27.1'; -const sampleImage = 'my_image.jpg' - -import { createC2pa, selectProducer } from 'c2pa'; -import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?url'; -import workerSrc from 'c2pa/dist/c2pa.worker.js?url'; -import { parseISO } from 'date-fns'; -import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; - -(async () => { - // Initialize the c2pa-js SDK - const c2pa = await createC2pa({ - wasmSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm', - workerSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', - }); - - - let output: string[] = []; - - const c2pa = await createC2pa({ - wasmSrc, - workerSrc, - }); - - const { manifestStore, source } = await c2pa.read(sampleImage); - const activeManifest = manifestStore?.activeManifest; - if (activeManifest) { - // Get thumbnail - // Note: You would normally call `dispose()` when working with a - // component-based UI library (e.g. on component un-mount) - // @ts-expect-error noUnusedLocals - const { url, dispose } = source.thumbnail.getUrl(); - - // Get properties - const properties: Record = { - title: activeManifest.title, - format: activeManifest.format, - claimGenerator: activeManifest.claimGenerator.split('(')[0]?.trim(), - producer: selectProducer(activeManifest)?.name ?? 'Unknown', - thumbnail: ``, - ingredients: (activeManifest.ingredients ?? []) - .map((i) => i.title) - .join(', '), - signatureIssuer: activeManifest.signatureInfo?.issuer, - signatureDate: activeManifest.signatureInfo?.time - ? parseISO(activeManifest.signatureInfo.time).toString() - : 'No date available', - }; - - output = Object.keys(properties).map((key) => { - return ` - - ${key} - ${properties[key]} - - `; - }); - - } else { - output.push(` - - No provenance data found - - `); - } - - document.querySelector('#results tbody')!.innerHTML = output.join(''); - -})(); -``` - -With an HTML page like this: - -```html - - - - - - - - active-manifest - - - - - - - - - - -
PropertyValue
Loading…
- - - -``` \ No newline at end of file diff --git a/docs/tasks/includes/_js-read.md b/docs/tasks/includes/_js-read.md deleted file mode 100644 index 30abbabf..00000000 --- a/docs/tasks/includes/_js-read.md +++ /dev/null @@ -1,38 +0,0 @@ -:::note -That JavaScript library is being extensively revised so the APIs used here may change in the near future. -::: - -Use [`c2pa.read`](../../docs/js-sdk/api/c2pa.c2pa#methods) to read manifest data from an asset; if the asset has a C2PA manifest and was processed without errors, the returned [`c2paReadResult`](../../docs/js-sdk/api/c2pa.c2pareadresult) contains a [`manifestStore`](../../docs/js-sdk/api/c2pa.c2pareadresult.manifeststore) object with several useful properties: - -- **manifests**: An object containing all the asset's manifests ([`Manifest`](../../docs/js-sdk/api/c2pa.manifest) objects), keyed by UUID. -- **activeManifest**: A pointer to the latest [`manifest`](../../docs/js-sdk/api/c2pa.manifest) in the manifest store. Effectively the "parent" manifest, this is the likely starting point when inspecting an asset's C2PA data. -- **validationStatus**: A list of any validation errors encountered. See [Validation](../../docs/js-sdk/guides/validation) for more information. - -```js -const version = '0.30.14'; -const sampleImage = ''; - -import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; - -(async () => { - // Initialize the c2pa-js SDK - const c2pa = await createC2pa({ - wasmSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm', - workerSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', - }); - - try { - // Read in image and get a manifest store - const { manifestStore } = await c2pa.read(sampleImage); - console.log('manifestStore', manifestStore); - - // Get the active manifest - const activeManifest = manifestStore?.activeManifest; - console.log('activeManifest', activeManifest); - } catch (err) { - console.error('Error reading image:', err); - } -})(); -``` diff --git a/docs/tasks/includes/_js-setup.md b/docs/tasks/includes/_js-setup.md deleted file mode 100644 index 56885a08..00000000 --- a/docs/tasks/includes/_js-setup.md +++ /dev/null @@ -1,52 +0,0 @@ -:::note -That JavaScript library is being extensively revised so the APIs used here may change in the near future. -::: - -This is how to set up your code to use the JavaScript library: - -```js -const version = '0.27.1'; -const sampleImage = ''; - -import { createC2pa } from 'https://cdn.jsdelivr.net/npm/c2pa@${version}/+esm'; - -(async () => { - // Initialize the c2pa-js SDK - const c2pa = await createC2pa({ - wasmSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/assets/wasm/toolkit_bg.wasm', - workerSrc: - 'https://cdn.jsdelivr.net/npm/c2pa@${version}/dist/c2pa.worker.min.js', - }); - - ... - -})(); -``` - -If you installed the package locally, for example from npm, then it's simply: - -```js -import { createC2pa } from 'c2pa'; -``` - -Additionally, the `wasmSrc` and `workerSrc` objects need to available as static assets that can be fetched at runtime. The best way to do that depends on the build system. For example, using [Vite](https://vite.dev/guide/assets#explicit-url-imports), as shown in the [minimal-ts-vite example](https://github.com/contentauth/c2pa-js/blob/main/examples/minimal-ts-vite/examples/active-manifest/main.ts): - - -```js -import wasmSrc from 'c2pa/dist/assets/wasm/toolkit_bg.wasm?url'; -import workerSrc from 'c2pa/dist/c2pa.worker.js?url'; -import { parseISO } from 'date-fns'; - -(async () => { - let output: string[] = []; - - const c2pa = await createC2pa({ - wasmSrc, - workerSrc, - }); - - ... - -})(); -``` \ No newline at end of file diff --git a/docs/tasks/js/_js-get-resources.md b/docs/tasks/js/_js-get-resources.md new file mode 100644 index 00000000..31851c36 --- /dev/null +++ b/docs/tasks/js/_js-get-resources.md @@ -0,0 +1,19 @@ +The example below shows how to get resources from manifest data using the JavaScript library. + +```js +import { createC2pa } from '@contentauth/c2pa-web'; +import wasmSrc from '@contentauth/c2pa-web/resources/c2pa.wasm?url'; +const c2pa = createC2pa({ wasmSrc }); + +const response = await fetch( + 'https://contentauth.github.io/example-assets/images/Firefly_tabby_cat.jpg' +); + +const blob = await response.blob(); +const reader = await c2pa.reader.fromBlob(blob.type, blob); + +... +``` + +More TBD. + diff --git a/docs/tasks/js/_js-read.md b/docs/tasks/js/_js-read.md new file mode 100644 index 00000000..36942302 --- /dev/null +++ b/docs/tasks/js/_js-read.md @@ -0,0 +1,25 @@ + +Once you've used [`createC2pa`](https://contentauth.github.io/c2pa-js/functions/_contentauth_c2pa-web.index.createC2pa.html) to create an instance of c2pa-web (for example in `c2pa` in this example), use `c2pa.reader.fromBlob()` to create a [Reader](https://contentauth.github.io/c2pa-js/interfaces/_contentauth_c2pa-web.index.Reader.html) for an asset. + +Then use Reader's [`manifestStore()` method](https://contentauth.github.io/c2pa-js/interfaces/_contentauth_c2pa-web.index.Reader.html#manifeststore) to read manifest data (if any) from the asset. + +For example: + +```js +import { createC2pa } from '@contentauth/c2pa-web'; +import wasmSrc from '@contentauth/c2pa-web/resources/c2pa.wasm?url'; +const c2pa = createC2pa({ wasmSrc }); + +const response = await fetch( + 'https://contentauth.github.io/example-assets/images/Firefly_tabby_cat.jpg' +); + +const blob = await response.blob(); +const reader = await c2pa.reader.fromBlob(blob.type, blob); +const manifestStore = await reader.manifestStore(); + +console.log(manifestStore); + +// Free SDK objects when they are no longer needed to avoid memory leaks. +await reader.free(); +``` diff --git a/docs/tasks/includes/_node-build.md b/docs/tasks/node/_node-build.md similarity index 100% rename from docs/tasks/includes/_node-build.md rename to docs/tasks/node/_node-build.md diff --git a/docs/tasks/includes/_node-get-resources.md b/docs/tasks/node/_node-get-resources.md similarity index 100% rename from docs/tasks/includes/_node-get-resources.md rename to docs/tasks/node/_node-get-resources.md diff --git a/docs/tasks/includes/_node-read.md b/docs/tasks/node/_node-read.md similarity index 100% rename from docs/tasks/includes/_node-read.md rename to docs/tasks/node/_node-read.md diff --git a/docs/tasks/includes/_node-setup.md b/docs/tasks/node/_node-setup.md similarity index 100% rename from docs/tasks/includes/_node-setup.md rename to docs/tasks/node/_node-setup.md diff --git a/docs/tasks/includes/_node-wip.md b/docs/tasks/node/_node-wip.md similarity index 100% rename from docs/tasks/includes/_node-wip.md rename to docs/tasks/node/_node-wip.md diff --git a/docs/tasks/includes/_python-build.md b/docs/tasks/python/_python-build.md similarity index 100% rename from docs/tasks/includes/_python-build.md rename to docs/tasks/python/_python-build.md diff --git a/docs/tasks/includes/_python-get-resources.md b/docs/tasks/python/_python-get-resources.md similarity index 100% rename from docs/tasks/includes/_python-get-resources.md rename to docs/tasks/python/_python-get-resources.md diff --git a/docs/tasks/includes/_python-read.md b/docs/tasks/python/_python-read.md similarity index 100% rename from docs/tasks/includes/_python-read.md rename to docs/tasks/python/_python-read.md diff --git a/docs/tasks/includes/_python-setup.md b/docs/tasks/python/_python-setup.md similarity index 100% rename from docs/tasks/includes/_python-setup.md rename to docs/tasks/python/_python-setup.md diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index 9c20c7bf..e7288793 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -7,14 +7,14 @@ hide_table_of_contents: true import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import JSRead from './includes/_js-read.md'; -import PythonRead from './includes/_python-read.md'; +import JSRead from './js/_js-read.md'; +import PythonRead from './python/_python-read.md'; /* -import NodeRead from './includes/_node-read.md'; +import NodeRead from './node/_node-read.md'; */ -import NodeWIP from './includes/_node-wip.md'; -import CppRead from './includes/_cpp-read.md'; -import RustRead from './includes/_rust-read.md'; +import NodeWIP from './node/_node-wip.md'; +import CppRead from './cpp/_cpp-read.md'; +import RustRead from './rust/_rust-read.md'; diff --git a/docs/tasks/includes/_rust-build.md b/docs/tasks/rust/_rust-build.md similarity index 100% rename from docs/tasks/includes/_rust-build.md rename to docs/tasks/rust/_rust-build.md diff --git a/docs/tasks/includes/_rust-get-resources.md b/docs/tasks/rust/_rust-get-resources.md similarity index 100% rename from docs/tasks/includes/_rust-get-resources.md rename to docs/tasks/rust/_rust-get-resources.md diff --git a/docs/tasks/includes/_rust-read.md b/docs/tasks/rust/_rust-read.md similarity index 100% rename from docs/tasks/includes/_rust-read.md rename to docs/tasks/rust/_rust-read.md diff --git a/docs/tasks/includes/_rust-setup.md b/docs/tasks/rust/_rust-setup.md similarity index 100% rename from docs/tasks/includes/_rust-setup.md rename to docs/tasks/rust/_rust-setup.md diff --git a/docs/tasks/setup.mdx b/docs/tasks/setup.mdx deleted file mode 100644 index b272ae94..00000000 --- a/docs/tasks/setup.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -id: setup -title: Setup -hide_table_of_contents: true ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import JSSetup from './includes/_js-setup.md'; -import PythonSetup from './includes/_python-setup.md'; -/* -import NodeSetup from './includes/_node-setup.md'; -*/ -import NodeWIP from './includes/_node-wip.md'; -import CppSetup from './includes/_cpp-setup.md'; -import RustSetup from './includes/_rust-setup.md'; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From e311f17313b487bf2726d8a18c9c1bed7ebfe088 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 14 Jan 2026 14:20:40 -0800 Subject: [PATCH 40/58] Fix sidebar --- docs/tasks/index.md | 1 - sidebars.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/tasks/index.md b/docs/tasks/index.md index 68c22fd8..5fcf125c 100644 --- a/docs/tasks/index.md +++ b/docs/tasks/index.md @@ -1,5 +1,4 @@ --- -id: working-manifests title: Working with the SDK --- diff --git a/sidebars.js b/sidebars.js index d419f3d2..517b5d0e 100644 --- a/sidebars.js +++ b/sidebars.js @@ -137,7 +137,7 @@ const sidebars = { { type: 'category', label: 'Working with the SDK', - link: { type: 'doc', id: 'tasks/working-manifests' }, + link: { type: 'doc', id: 'tasks/index' }, collapsed: true, items: [ { From c5cd1abeccac104f58ca2019bec0274df387b9e5 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 14 Jan 2026 14:33:21 -0800 Subject: [PATCH 41/58] update sidebar --- sidebars.js | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/sidebars.js b/sidebars.js index 1f6100fd..b6dc9823 100644 --- a/sidebars.js +++ b/sidebars.js @@ -103,6 +103,29 @@ const sidebars = { }, ], }, + { + type: 'category', + label: 'Working with the SDK', + link: { type: 'doc', id: 'tasks/index' }, + collapsed: true, + items: [ + { + type: 'doc', + id: 'tasks/read', + label: 'Reading manifest data', + }, + { + type: 'doc', + id: 'tasks/get-resources', + label: 'Getting manifest resources', + }, + { + type: 'doc', + id: 'tasks/build', + label: 'Adding and signing amanifest', + }, + ], + }, { type: 'category', label: 'Signing and certificates', @@ -135,8 +158,6 @@ const sidebars = { type: 'category', label: 'C2PA conformance program', link: { type: 'doc', id: 'conformance/index' }, - label: 'Working with the SDK', - link: { type: 'doc', id: 'tasks/index' }, collapsed: true, items: [ { From aaaca304317a3184575dc75031947041602f218d Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 14 Jan 2026 16:04:00 -0800 Subject: [PATCH 42/58] Add settings example code: --- docs/tasks/build.mdx | 5 +- docs/tasks/cpp/_cpp-settings.md | 4 ++ docs/tasks/cpp/_cpp-setup.md | 21 -------- docs/tasks/get-resources.mdx | 5 +- docs/tasks/js/_js-settings.md | 1 + docs/tasks/node/_node-build.md | 53 +++++++++---------- docs/tasks/node/_node-get-resources.md | 5 ++ docs/tasks/node/_node-read.md | 54 +++++++++++++++----- docs/tasks/node/_node-settings.md | 51 ++++++++++++++++++ docs/tasks/node/_node-setup.md | 16 ------ docs/tasks/python/_python-read.md | 71 ++++++++++++++++---------- docs/tasks/python/_python-settings.md | 14 +++++ docs/tasks/python/_python-setup.md | 21 -------- docs/tasks/read.mdx | 5 +- docs/tasks/rust/_rust-settings.md | 13 +++++ docs/tasks/rust/_rust-setup.md | 21 -------- docs/tasks/settings.mdx | 48 +++++++++++++++++ sidebars.js | 7 ++- 18 files changed, 257 insertions(+), 158 deletions(-) create mode 100644 docs/tasks/cpp/_cpp-settings.md delete mode 100644 docs/tasks/cpp/_cpp-setup.md create mode 100644 docs/tasks/js/_js-settings.md create mode 100644 docs/tasks/node/_node-settings.md delete mode 100644 docs/tasks/node/_node-setup.md create mode 100644 docs/tasks/python/_python-settings.md delete mode 100644 docs/tasks/python/_python-setup.md create mode 100644 docs/tasks/rust/_rust-settings.md delete mode 100644 docs/tasks/rust/_rust-setup.md create mode 100644 docs/tasks/settings.mdx diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index dafac783..84287ec1 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -8,10 +8,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import PythonBuild from './python/_python-build.md'; -/* import NodeBuild from './node/_node-build.md'; -*/ -import NodeWIP from './node/_node-wip.md'; import CppBuild from './cpp/_cpp-build.md'; import RustBuild from './rust/_rust-build.md'; @@ -31,7 +28,7 @@ You can't currently attach a manifest to an asset and sign the claim using the J - + diff --git a/docs/tasks/cpp/_cpp-settings.md b/docs/tasks/cpp/_cpp-settings.md new file mode 100644 index 00000000..9a0a718d --- /dev/null +++ b/docs/tasks/cpp/_cpp-settings.md @@ -0,0 +1,4 @@ +```cpp +// set settings to not generate thumbnails +c2pa::load_settings("{\"builder\": { \"thumbnail\":{\"enabled\": false}}}", "json"); +``` \ No newline at end of file diff --git a/docs/tasks/cpp/_cpp-setup.md b/docs/tasks/cpp/_cpp-setup.md deleted file mode 100644 index c0c6853a..00000000 --- a/docs/tasks/cpp/_cpp-setup.md +++ /dev/null @@ -1,21 +0,0 @@ -This is how to set up your code to use the C++ library: - -```cpp -#include -#include -#include -#include -#include -#include -#include -#include -#include "c2pa.hpp" -#include "test_signer.hpp" -#include - -// this example uses nlohmann json for parsing the manifest -using json = nlohmann::json; -using namespace std; -namespace fs = std::filesystem; -using namespace c2pa; -``` \ No newline at end of file diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index ee351dfb..217b67b2 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -8,10 +8,7 @@ import TabItem from '@theme/TabItem'; import JSGetResources from './js/_js-get-resources.md'; import PythonGetResources from './python/_python-get-resources.md'; -/* import NodeGetResources from './node/_node-get-resources.md'; -*/ -import NodeWIP from './node/_node-wip.md'; import CppGetResources from './cpp/_cpp-get-resources.md'; import RustGetResources from './rust/_rust-get-resources.md'; @@ -33,7 +30,7 @@ Manifest data can include binary resources such as thumbnail and icon images whi - + diff --git a/docs/tasks/js/_js-settings.md b/docs/tasks/js/_js-settings.md new file mode 100644 index 00000000..c45cf5b6 --- /dev/null +++ b/docs/tasks/js/_js-settings.md @@ -0,0 +1 @@ +Config settings TBD \ No newline at end of file diff --git a/docs/tasks/node/_node-build.md b/docs/tasks/node/_node-build.md index 72d1e2e5..6271fe8d 100644 --- a/docs/tasks/node/_node-build.md +++ b/docs/tasks/node/_node-build.md @@ -1,32 +1,33 @@ This is an example of how to assign a manifest to an asset and sign the claim using Node.js: ```ts -import { ManifestBuilder } from 'c2pa-node'; - -const manifest = new ManifestBuilder({ - claim_generator: 'my-app/1.0.0', - format: 'image/jpeg', - title: 'node_test_local_signer.jpg', - assertions: [ - { - label: 'c2pa.actions', - data: { - actions: [ - { - action: 'c2pa.created', - }, - ], - }, - }, - { - label: 'com.custom.my-assertion', - data: { - description: 'My custom test assertion', - version: '1.0.0', - }, - }, - ], -}); +import { Builder } from '@contentauth/c2pa-node'; + +// Create a new builder +const builder = Builder.new(); + +// Create with custom settings +const settings = { + builder: { + generate_c2pa_archive: true + } +}; +const builder = Builder.new(settings); + +// Or create from an existing manifest definition +const builder = Builder.withJson(manifestDefinition); + +// Or create with both manifest and settings +const builder = Builder.withJson(manifestDefinition, settings); + +// Add assertions to the manifest +builder.addAssertion('c2pa.actions', actionsAssertion); + +// Add resources +await builder.addResource('resource://example', resourceAsset); + +// Sign the manifest +const manifest = builder.sign(signer, inputAsset, outputAsset); ``` Use the `c2pa.sign()` method to sign an ingredient, either locally if you have a signing certificate and key available, or by using a remote signing API. diff --git a/docs/tasks/node/_node-get-resources.md b/docs/tasks/node/_node-get-resources.md index 422062ba..68006e78 100644 --- a/docs/tasks/node/_node-get-resources.md +++ b/docs/tasks/node/_node-get-resources.md @@ -1,5 +1,10 @@ The example below shows how to get resources from manifest data using the Node.js library. ```js +import { createC2pa } from 'c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const c2pa = createC2pa(); + // TBD ``` \ No newline at end of file diff --git a/docs/tasks/node/_node-read.md b/docs/tasks/node/_node-read.md index 9c1a1973..d6bceb76 100644 --- a/docs/tasks/node/_node-read.md +++ b/docs/tasks/node/_node-read.md @@ -2,23 +2,51 @@ Use the `c2pa.read()` function to read a manifest; for example: ```ts -import { createC2pa } from 'c2pa-node'; -import { readFile } from 'node:fs/promises'; +// read-manifest.ts +import { Reader } from '@contentauth/c2pa-node'; -const c2pa = createC2pa(); +async function main(): Promise { + const inputPath = process.argv[2]; + if (!inputPath) { + console.error('Usage: ts-node read-manifest.ts '); + process.exit(1); + } + + const reader = await Reader.fromAsset({ path: inputPath }); + if (!reader) { + console.log('No C2PA manifest found.'); + return; + } -async function read(path, mimeType) { - const buffer = await readFile(path); - const result = await c2pa.read({ buffer, mimeType }); + const manifestStore = reader.json(); + console.log(JSON.stringify(manifestStore, null, 2)); - if (result) { - const { active_manifest, manifests, validation_status } = result; - console.log(active_manifest); - } else { - console.log('No claim found'); + // If you only want the active manifest: + const active = reader.getActive(); + if (active) { + console.log('Active manifest label:', manifestStore.active_manifest); } - // If there are no validation errors, then validation_status will be an empty array } -await read('my-c2pa-file.jpg', 'image/jpeg'); +main().catch((err) => { + console.error(err); + process.exit(1); +}); ``` + +Using a buffer + +```ts +import fs from 'node:fs/promises'; +import { Reader } from '@contentauth/c2pa-node'; + +async function readFromBuffer(filePath: string): Promise { + const buffer = await fs.readFile(filePath); + const reader = await Reader.fromAsset({ buffer, mimeType: 'jpeg' }); // adjust mimeType as needed + if (!reader) { + console.log('No C2PA manifest found.'); + return; + } + console.log(JSON.stringify(reader.json(), null, 2)); +} +``` \ No newline at end of file diff --git a/docs/tasks/node/_node-settings.md b/docs/tasks/node/_node-settings.md new file mode 100644 index 00000000..459689c1 --- /dev/null +++ b/docs/tasks/node/_node-settings.md @@ -0,0 +1,51 @@ +```ts +import { + createTrustSettings, + createCawgTrustSettings, + createVerifySettings, + mergeSettings, + settingsToJson, + loadSettingsFromFile, + loadSettingsFromUrl +} from '@contentauth/c2pa-node'; + +// Create trust settings +const trustSettings = createTrustSettings({ + verifyTrustList: true, + userAnchors: "path/to/user-anchors.pem", + trustAnchors: "path/to/trust-anchors.pem", + allowedList: "path/to/allowed-list.pem" +}); + +// Create CAWG trust settings +const cawgTrustSettings = createCawgTrustSettings({ + verifyTrustList: true, + trustAnchors: "path/to/cawg-anchors.pem" +}); + +// Create verify settings +const verifySettings = createVerifySettings({ + verifyAfterReading: false, + verifyAfterSign: false, + verifyTrust: true, + verifyTimestampTrust: true, + ocspFetch: true, + remoteManifestFetch: true, + skipIngredientConflictResolution: false, + strictV1Validation: false +}); + +// Merge multiple settings +const combinedSettings = mergeSettings(trustSettings, verifySettings); + +// Convert settings to JSON string +const jsonString = settingsToJson(combinedSettings); + +// Load settings from file (JSON or TOML) +const fileSettings = await loadSettingsFromFile('./c2pa-settings.toml'); +const reader = await Reader.fromAsset(inputAsset, fileSettings); + +// Load settings from URL +const urlSettings = await loadSettingsFromUrl('https://example.com/c2pa-settings.json'); +const builder = Builder.new(urlSettings); +``` \ No newline at end of file diff --git a/docs/tasks/node/_node-setup.md b/docs/tasks/node/_node-setup.md deleted file mode 100644 index 763985f6..00000000 --- a/docs/tasks/node/_node-setup.md +++ /dev/null @@ -1,16 +0,0 @@ -This is how to set up your code to use the Node.js library. - -```js -import { createC2pa } from 'c2pa-node'; -import { readFile } from 'node:fs/promises'; - -const c2pa = createC2pa(); -``` - - \ No newline at end of file diff --git a/docs/tasks/python/_python-read.md b/docs/tasks/python/_python-read.md index edf52a4e..604c7831 100644 --- a/docs/tasks/python/_python-read.md +++ b/docs/tasks/python/_python-read.md @@ -1,31 +1,48 @@ -Use the `Reader` object to read manifest data from a file or stream and perform validation on the manifest store. +Use the `c2pa.Reader` object to read manifest data from a file or stream and perform validation on the manifest store. -Use the `json()` method to return a JSON manifest report; If there are validation errors, the report includes a `validation_status` field. - -An asset file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map. +This example shows how to read a C2PA manifest embedded in a media file, and validate that it is trusted according to the official trust anchor certificate list. The output is printed as prettified JSON. ```py -# Import the C2PA Python package. -from c2pa import * - -# Import standard general-purpose packages. -import os -import io -import logging -import json - -try: - # Create a reader from a file path. - reader = c2pa.Reader.from_file("path/to/media_file.jpg") - - # Print the JSON for a manifest. - print("manifest store:", reader.json()) - -except Exception as err: - print(err) -``` - - \ No newline at end of file +import sys +import c2pa +import urllib.request + +TRUST_ANCHORS_URL = "https://contentcredentials.org/trust/anchors.pem" + +def load_trust_anchors(): + try: + with urllib.request.urlopen(TRUST_ANCHORS_URL) as response: + anchors = response.read().decode('utf-8') + settings = { + "verify": { + "verify_cert_anchors": True + }, + "trust": { + "trust_anchors": anchors + } + } + c2pa.load_settings(settings) + except Exception as e: + print(f"Warning: Could not load trust anchors from {TRUST_ANCHORS_URL}: {e}") + + +def read_c2pa_data(media_path: str): + print(f"Reading {media_path}") + try: + with c2pa.Reader(media_path) as reader: + print(reader.detailed_json()) + except Exception as e: + print(f"Error reading C2PA data from {media_path}: {e}") + sys.exit(1) + + +if __name__ == '__main__': + if len(sys.argv) < 2: + media_path = "tests/fixtures/cloud.jpg" + else: + media_path = sys.argv[1] + + load_trust_anchors() + read_c2pa_data(media_path) +``` \ No newline at end of file diff --git a/docs/tasks/python/_python-settings.md b/docs/tasks/python/_python-settings.md new file mode 100644 index 00000000..28af3472 --- /dev/null +++ b/docs/tasks/python/_python-settings.md @@ -0,0 +1,14 @@ +```py +try: + settings = { + "verify": { + "verify_cert_anchors": True + }, + "trust": { + "trust_anchors": "some url" + } + } + c2pa.load_settings(settings) +except Exception as e: + print(f"Exception loading settings: {e}") +``` \ No newline at end of file diff --git a/docs/tasks/python/_python-setup.md b/docs/tasks/python/_python-setup.md deleted file mode 100644 index 01388bfa..00000000 --- a/docs/tasks/python/_python-setup.md +++ /dev/null @@ -1,21 +0,0 @@ -This is how to set up your code to use the Python library. - -```python -# Import the C2PA Python package. -from c2pa import * - -# Import standard general-purpose packages. -import os -import io -import logging -import json -import base64 - -# Import web packages used in example implementation. -from flask import Flask, request, abort -from flask_cors import CORS -from waitress import serve - -# Import AWS SDK package (to use KMS). -import boto3 -``` diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index e7288793..0401b1aa 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -9,10 +9,7 @@ import TabItem from '@theme/TabItem'; import JSRead from './js/_js-read.md'; import PythonRead from './python/_python-read.md'; -/* import NodeRead from './node/_node-read.md'; -*/ -import NodeWIP from './node/_node-wip.md'; import CppRead from './cpp/_cpp-read.md'; import RustRead from './rust/_rust-read.md'; @@ -32,7 +29,7 @@ import RustRead from './rust/_rust-read.md'; - + diff --git a/docs/tasks/rust/_rust-settings.md b/docs/tasks/rust/_rust-settings.md new file mode 100644 index 00000000..3ca81dbc --- /dev/null +++ b/docs/tasks/rust/_rust-settings.md @@ -0,0 +1,13 @@ +```rs +use c2pa::{Context, Builder, Result}; + +fn main() -> Result<()> { + // Create a Context with settings from a file + let context = Context::new() + .with_settings(include_str!("settings.json"))?; + + let builder = Builder::from_context(context); + // ... use builder + Ok(()) +} +``` \ No newline at end of file diff --git a/docs/tasks/rust/_rust-setup.md b/docs/tasks/rust/_rust-setup.md deleted file mode 100644 index 017826cb..00000000 --- a/docs/tasks/rust/_rust-setup.md +++ /dev/null @@ -1,21 +0,0 @@ -This is how to setup your code to use the Rust library. - -:::tip -The files used in this example are in the C2PA Rust library [`sdk/tests/fixtures`](https://github.com/contentauth/c2pa-rs/tree/main/sdk/tests/fixtures) directory. -::: - -```rust -use std::{ - io::{Cursor, Write}, - process::{Command, Stdio}, -}; - -use anyhow::Result; -use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader}; -use c2pa_crypto::raw_signature::SigningAlg; -use serde_json::json; - -const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg"); -const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub"); -const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem"); -``` \ No newline at end of file diff --git a/docs/tasks/settings.mdx b/docs/tasks/settings.mdx new file mode 100644 index 00000000..16f8a08c --- /dev/null +++ b/docs/tasks/settings.mdx @@ -0,0 +1,48 @@ +--- +id: settings +title: Configuring settings +hide_table_of_contents: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import JSRead from './js/_js-settings.md'; +import PythonRead from './python/_python-settings.md'; +import NodeSettings from './node/_node-settings.md'; +import CppRead from './cpp/_cpp-settings.md'; +import RustRead from './rust/_rust-settings.md'; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sidebars.js b/sidebars.js index b6dc9823..9ba65fba 100644 --- a/sidebars.js +++ b/sidebars.js @@ -109,6 +109,11 @@ const sidebars = { link: { type: 'doc', id: 'tasks/index' }, collapsed: true, items: [ + { + type: 'doc', + id: 'tasks/settings', + label: 'Configuring settings', + }, { type: 'doc', id: 'tasks/read', @@ -122,7 +127,7 @@ const sidebars = { { type: 'doc', id: 'tasks/build', - label: 'Adding and signing amanifest', + label: 'Adding and signing a manifest', }, ], }, From f86a78300c1d7f5103e8870d494c3e96d675d03e Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 30 Jan 2026 12:53:15 -0800 Subject: [PATCH 43/58] Update schemas from main --- static/schemas/Reader.schema.json | 9 ++++++++- static/schemas/Settings.schema.json | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/static/schemas/Reader.schema.json b/static/schemas/Reader.schema.json index 33836419..e559ddb2 100644 --- a/static/schemas/Reader.schema.json +++ b/static/schemas/Reader.schema.json @@ -84,7 +84,7 @@ "instance_id": { "description": "Instance ID from `xmpMM:InstanceID` in XMP metadata.", "type": "string", - "default": "xmp:iid:5b3a16fa-7fb7-4d70-9332-ff5b8731e21e" + "default": "xmp:iid:f28df072-2337-47b2-8699-5cbe5643dae9" }, "thumbnail": { "anyOf": [ @@ -136,6 +136,13 @@ }, "label": { "type": ["string", "null"] + }, + "claim_version": { + "description": "The version of the claim, parsed from the claim label.\n\nFor example:\n- `c2pa.claim.v2` -> 2\n- `c2pa.claim` -> 1", + "type": ["integer", "null"], + "format": "uint8", + "minimum": 0, + "maximum": 255 } } }, diff --git a/static/schemas/Settings.schema.json b/static/schemas/Settings.schema.json index d318691b..476ff1e2 100644 --- a/static/schemas/Settings.schema.json +++ b/static/schemas/Settings.schema.json @@ -235,7 +235,7 @@ } }, "generate_c2pa_archive": { - "description": "Whether to generate a C2PA archive (instead of zip) when writing the manifest builder.\nThis will eventually become the default behavior.", + "description": "Whether to generate a C2PA archive (instead of zip) when writing the manifest builder.\nNow always defaults to true - the ability to disable it will be removed in the future.", "type": ["boolean", "null"] } }, From 7eb40433d767a49eee3cca30a4295c93ec72cb14 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 30 Jan 2026 12:58:46 -0800 Subject: [PATCH 44/58] Update schemas --- static/schemas/Reader.schema.json | 10 ++-------- static/schemas/Settings.schema.json | 5 +---- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/static/schemas/Reader.schema.json b/static/schemas/Reader.schema.json index 7218460b..e559ddb2 100644 --- a/static/schemas/Reader.schema.json +++ b/static/schemas/Reader.schema.json @@ -135,17 +135,11 @@ ] }, "label": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "claim_version": { "description": "The version of the claim, parsed from the claim label.\n\nFor example:\n- `c2pa.claim.v2` -> 2\n- `c2pa.claim` -> 1", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint8", "minimum": 0, "maximum": 255 diff --git a/static/schemas/Settings.schema.json b/static/schemas/Settings.schema.json index 8d69903b..476ff1e2 100644 --- a/static/schemas/Settings.schema.json +++ b/static/schemas/Settings.schema.json @@ -236,10 +236,7 @@ }, "generate_c2pa_archive": { "description": "Whether to generate a C2PA archive (instead of zip) when writing the manifest builder.\nNow always defaults to true - the ability to disable it will be removed in the future.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] } }, "required": ["thumbnail", "actions"] From 06fdc472db9f12c72bf75a76bcbe500e76865428 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 2 Feb 2026 14:25:04 -0800 Subject: [PATCH 45/58] Add JS and CPP example code --- docs/tasks/cpp/_cpp-settings.md | 52 +++++++++++++++++++++++++++ docs/tasks/js/_js-settings.md | 60 ++++++++++++++++++++++++++++++- docs/tasks/rust/_rust-settings.md | 2 +- docs/tasks/settings.mdx | 1 + 4 files changed, 113 insertions(+), 2 deletions(-) diff --git a/docs/tasks/cpp/_cpp-settings.md b/docs/tasks/cpp/_cpp-settings.md index 9a0a718d..e59d0194 100644 --- a/docs/tasks/cpp/_cpp-settings.md +++ b/docs/tasks/cpp/_cpp-settings.md @@ -1,4 +1,56 @@ ```cpp +#include "c2pa.hpp" // set settings to not generate thumbnails c2pa::load_settings("{\"builder\": { \"thumbnail\":{\"enabled\": false}}}", "json"); +``` + +Load from a file: + +```cpp +#include +#include +#include +#include "c2pa.hpp" + +int main(int argc, char** argv) { + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " /path/to/settings.json\n"; + return 1; + } + + const std::string settings_path = argv[1]; + + std::ifstream in(settings_path); + if (!in) { + std::cerr << "Failed to open settings file: " << settings_path << "\n"; + return 1; + } + + std::ostringstream buffer; + buffer << in.rdbuf(); + const std::string json = buffer.str(); + + try { + c2pa::load_settings(json, "json"); // format is "json" + std::cout << "Loaded settings successfully. c2pa version: " + << c2pa::version() << "\n"; + } catch (const c2pa::C2paException& e) { + std::cerr << "Failed to load settings: " << e.what() << "\n"; + return 1; + } + return 0; +} +``` + +Example `settings.json`: + +```json +{ + "builder": { + "thumbnail": { "enabled": false }, + "actions": { + "auto_placed_action": { "enabled": true } + } + } +} ``` \ No newline at end of file diff --git a/docs/tasks/js/_js-settings.md b/docs/tasks/js/_js-settings.md index c45cf5b6..54108721 100644 --- a/docs/tasks/js/_js-settings.md +++ b/docs/tasks/js/_js-settings.md @@ -1 +1,59 @@ -Config settings TBD \ No newline at end of file +Load settings as defined inline: + +```js +import { createC2pa } from '@contentauth/c2pa-web'; +// With Vite (or similar), this resolves to the hosted WASM binary URL: +import wasmSrc from '@contentauth/c2pa-web/resources/c2pa.wasm?url'; + +const settings = { + // Turn trust verification on/off (defaults to true) + verify: { verifyTrust: true }, + + // Configure trust (PEM text, a URL, or an array of URLs) + trust: { + // Example: load system trust anchors from a URL (PEM file) + trustAnchors: 'https://example.com/trust_anchors.pem', + // Optional user anchors (also PEM text or URLs) + // userAnchors: '-----BEGIN CERTIFICATE-----\n...' + }, + + // Optional builder settings + // builder: { generateC2paArchive: true }, +}; + +const c2pa = await createC2pa({ wasmSrc, settings }); + +// Use the SDK (example: read an asset) +const res = await fetch('https://contentauth.github.io/example-assets/images/cloudscape-ACA-Cr.jpeg'); +const blob = await res.blob(); + +const reader = await c2pa.reader.fromBlob(blob.type, blob); +const manifestStore = await reader.manifestStore(); +console.log(manifestStore); + +await reader.free(); +c2pa.dispose(); +``` + +To load settings from a JSON file: + +```js +import { createC2pa } from '@contentauth/c2pa-web'; +import wasmSrc from '@contentauth/c2pa-web/resources/c2pa.wasm?url'; + +const settings = await fetch('/c2pa-settings.json').then(r => r.json()); +const c2pa = await createC2pa({ wasmSrc, settings }); +``` + +Where `c2pa-settings.json` is: + +```js +{ + "verify": { "verifyTrust": true }, + "trust": { + "trustAnchors": "https://example.com/trust_anchors.pem", + "userAnchors": "https://example.com/user_anchors.pem" + }, + "builder": { "generateC2paArchive": true } +} +``` \ No newline at end of file diff --git a/docs/tasks/rust/_rust-settings.md b/docs/tasks/rust/_rust-settings.md index 3ca81dbc..8effb800 100644 --- a/docs/tasks/rust/_rust-settings.md +++ b/docs/tasks/rust/_rust-settings.md @@ -1,4 +1,4 @@ -```rs +```rust use c2pa::{Context, Builder, Result}; fn main() -> Result<()> { diff --git a/docs/tasks/settings.mdx b/docs/tasks/settings.mdx index 16f8a08c..e0f62fbf 100644 --- a/docs/tasks/settings.mdx +++ b/docs/tasks/settings.mdx @@ -16,6 +16,7 @@ import RustRead from './rust/_rust-settings.md'; +Settings supports: `trust`, `cawgTrust`, `verify`, and `builder`. From 60aa73fe2a49cec9a01dc843ef2026b9e100cd06 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 2 Feb 2026 14:34:39 -0800 Subject: [PATCH 46/58] Revert changes to schema files --- static/schemas/Builder.schema.json | 5 +---- static/schemas/Settings.schema.json | 17 +++-------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/static/schemas/Builder.schema.json b/static/schemas/Builder.schema.json index f7bb4e2c..5d7c99f1 100644 --- a/static/schemas/Builder.schema.json +++ b/static/schemas/Builder.schema.json @@ -121,10 +121,7 @@ } } }, - "required": [ - "no_embed", - "timestamp_manifest_labels" - ], + "required": ["no_embed", "timestamp_manifest_labels"], "$defs": { "ClaimGeneratorInfo": { "description": "Description of the claim generator, or the software used in generating the claim.\n\nThis structure is also used for actions softwareAgent", diff --git a/static/schemas/Settings.schema.json b/static/schemas/Settings.schema.json index 39d0265a..e84bb901 100644 --- a/static/schemas/Settings.schema.json +++ b/static/schemas/Settings.schema.json @@ -236,21 +236,14 @@ }, "generate_c2pa_archive": { "description": "Whether to generate a C2PA archive (instead of zip) when writing the manifest builder.\nNow always defaults to true - the ability to disable it will be removed in the future.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "auto_timestamp_assertion": { "description": "Settings for configuring auto-generation of the [`TimeStamp`] assertion.\n\n[`TimeStamp`]: crate::assertions::TimeStamp", "$ref": "#/$defs/TimeStampSettings" } }, - "required": [ - "thumbnail", - "actions", - "auto_timestamp_assertion" - ] + "required": ["thumbnail", "actions", "auto_timestamp_assertion"] }, "ClaimGeneratorInfoSettings": { "description": "Settings for the claim generator info.", @@ -735,11 +728,7 @@ "$ref": "#/$defs/TimeStampFetchScope" } }, - "required": [ - "enabled", - "skip_existing", - "fetch_scope" - ] + "required": ["enabled", "skip_existing", "fetch_scope"] }, "TimeStampFetchScope": { "description": "The scope of manifests to fetch timestamps for.\n\nSee [`TimeStampSettings`] for more information.", From 0c1d3c5dd6a7a26b5b0fd2d89b7f97805a5e9cbd Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 2 Feb 2026 16:53:52 -0800 Subject: [PATCH 47/58] Your descriptive commit message From 931bc31bdfc68cb53578692b31a1c5f35b914b61 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 3 Feb 2026 11:16:31 -0800 Subject: [PATCH 48/58] Add builder w remote signing to JS --- docs/tasks/build.mdx | 3 +- docs/tasks/index.md | 4 +- docs/tasks/js/_js-build.md | 80 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 docs/tasks/js/_js-build.md diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index 84287ec1..58ac1d56 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import JsBuild from './js/_js-build.md'; import PythonBuild from './python/_python-build.md'; import NodeBuild from './node/_node-build.md'; import CppBuild from './cpp/_cpp-build.md'; @@ -16,7 +17,7 @@ import RustBuild from './rust/_rust-build.md'; -You can't currently attach a manifest to an asset and sign the claim using the JavaScript library. You need to use a language that runs on the "back-end," such as Python, Node.js, C++, or Rust. + diff --git a/docs/tasks/index.md b/docs/tasks/index.md index 5fcf125c..623f3976 100644 --- a/docs/tasks/index.md +++ b/docs/tasks/index.md @@ -2,9 +2,9 @@ title: Working with the SDK --- -There are a number of common tasks when using the CAI open-source SDK. -At a high level the process is the same in every language, but how you accomplish each task is specific to the language you're using. +At a high level, using the CAI open-source SDK is the same in every language, but how you accomplish each task is specific to the language you're using. +- [Configuring SDK settings](./settings.mdx) - [Reading manifest data](./read.mdx) - [Getting resources from a manifest](./get-resources.mdx) - [Adding a manifest to an asset and signing it](./build.mdx) diff --git a/docs/tasks/js/_js-build.md b/docs/tasks/js/_js-build.md new file mode 100644 index 00000000..b58b8833 --- /dev/null +++ b/docs/tasks/js/_js-build.md @@ -0,0 +1,80 @@ +Notes: +- You provide the `Signer`. In production, this object wraps a service/HSM that returns a proper signature for your algorithm (`es256`, `ps256`, `ed25519`, etc.). Set `reserveSize` to a value large enough for timestamps/countersignatures your signer adds. +- To attach a remote manifest instead of embedding, use `builder.setRemoteUrl(url)` and `builder.setNoEmbed(true)` before signing. + +```js +import { createC2pa, type Signer } from '@contentauth/c2pa-web'; +import wasmSrc from '@contentauth/c2pa-web/resources/c2pa.wasm?url'; + +// 1) Create a Signer that calls your backend (which returns the signature bytes) +function createRemoteSigner(): Signer { + return { + alg: 'es256', + reserveSize: async () => 4096, // bytes to reserve for TSA/countersignature (tune as needed) + sign: async (toBeSigned: Uint8Array, _reserveSize: number) => { + const res = await fetch('/api/sign', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + alg: 'es256', + payload: Array.from(toBeSigned), + }), + }); + if (!res.ok) throw new Error('Signing failed'); + const sigBytes = new Uint8Array(await res.arrayBuffer()); + return sigBytes; + }, + }; +} + +async function run() { + // 2) Initialize the SDK + const c2pa = await createC2pa({ wasmSrc }); + + // 3) Fetch the asset to sign + const imgUrl = 'https://contentauth.github.io/example-assets/images/cloudscape-ACA-Cr.jpeg'; + const resp = await fetch(imgUrl); + const assetBlob = await resp.blob(); + + // 4) Build a simple manifest (add a created action and optional thumbnail) + const builder = await c2pa.builder.new(); + await builder.addAction({ + action: 'c2pa.created', + digitalSourceType: 'http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture', + }); + // Optional: include a thumbnail that represents the asset + await builder.setThumbnailFromBlob('image/jpeg', assetBlob); + + // 5) Sign and get a new asset with the manifest embedded + const signer = createRemoteSigner(); + const signedBytes = await builder.sign(signer, assetBlob.type, assetBlob); + + // 6) Use/save the signed asset + const signedBlob = new Blob([signedBytes], { type: assetBlob.type }); + const url = URL.createObjectURL(signedBlob); + // e.g., download + const a = document.createElement('a'); + a.href = url; + a.download = 'signed.jpg'; + a.click(); + URL.revokeObjectURL(url); + + // Cleanup + await builder.free(); + c2pa.dispose(); +} + +run().catch(console.error); +``` + +To retrieve manifest bytes alongside the signed asset: + +```js +const { asset, manifest } = await builder.signAndGetManifestBytes( + signer, + assetBlob.type, + assetBlob +); +// asset -> signed asset bytes +// manifest -> embedded manifest bytes +``` \ No newline at end of file From e15584f0535e406f030ea0fe2d76474d21b6c986 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 3 Feb 2026 11:27:37 -0800 Subject: [PATCH 49/58] Add more intro/context to JS builder example --- docs/tasks/js/_js-build.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/tasks/js/_js-build.md b/docs/tasks/js/_js-build.md index b58b8833..34ae5165 100644 --- a/docs/tasks/js/_js-build.md +++ b/docs/tasks/js/_js-build.md @@ -1,6 +1,17 @@ -Notes: -- You provide the `Signer`. In production, this object wraps a service/HSM that returns a proper signature for your algorithm (`es256`, `ps256`, `ed25519`, etc.). Set `reserveSize` to a value large enough for timestamps/countersignatures your signer adds. -- To attach a remote manifest instead of embedding, use `builder.setRemoteUrl(url)` and `builder.setNoEmbed(true)` before signing. +Using client-side JavaScript, you can: +- Create and compose manifests using the JavaScript library manifest `builder` API. +- Perform signing in the browser if you have a private key available to the client (using WebCrypto, an imported key, or an ephemeral/test key). The web library provides a `Signer` interface you can implement to call into whatever signing capability you have in the browser. +- Produce a manifest store or sidecar (`.c2pa`) file entirely in the browser (so you can download a signed sidecar next to the asset). + +Signing Content Credentials requires an end-entity X.509 certificate that fits the C2PA trust model to produce publicly verifiable signatures. Getting and protecting that certificate/private key in a browser is risky and should not be done in production. + +::: warning +Never put production private keys into client code! +::: + +Embedding into binaries: While you can build and sign manifests client-side, embedding a signed manifest properly into binary file formats (JPEG/PNG/MP4, etc.) is functionality that server-side libraries (Node.js, Python, C/C++, and Rust) explicitly provide. + +Security & auditability: Browser-stored keys (or keys entered by users) are exposed to theft, malware, or cross-site scripting (XSS) attacks. For production signing, best practice is to use a remote signer (KMS/HSM) or server-side signing where keys are protected and signing operations are auditable. ```js import { createC2pa, type Signer } from '@contentauth/c2pa-web'; @@ -77,4 +88,8 @@ const { asset, manifest } = await builder.signAndGetManifestBytes( ); // asset -> signed asset bytes // manifest -> embedded manifest bytes -``` \ No newline at end of file +``` + +Notes: +- You provide the `Signer` used in the example above. In production, this object wraps a service/HSM that returns a proper signature for your algorithm (`es256`, `ps256`, `ed25519`, etc.). Set `reserveSize` to a value large enough for timestamps/countersignatures your signer adds. +- To attach a remote manifest instead of embedding, use `builder.setRemoteUrl(url)` and `builder.setNoEmbed(true)` before signing. \ No newline at end of file From 1be4a1dfd5a1332a195a9e1b746fc06fb0572494 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 31 Mar 2026 16:47:18 -0700 Subject: [PATCH 50/58] Add new tasks, some updates --- docs/tasks/archives.mdx | 34 +++++ docs/tasks/build.mdx | 36 ++--- docs/tasks/cpp/_cpp-archives.md | 110 ++++++++++++++ docs/tasks/cpp/_cpp-build.md | 34 ++--- docs/tasks/cpp/_cpp-get-resources.md | 46 +----- docs/tasks/cpp/_cpp-intents.md | 99 +++++++++++++ docs/tasks/cpp/_cpp-read.md | 38 ++--- docs/tasks/cpp/_cpp-settings.md | 141 ++++++++++++------ docs/tasks/get-resources.mdx | 32 ++-- docs/tasks/index.md | 10 +- docs/tasks/intents.mdx | 34 +++++ docs/tasks/python/_python-archives.md | 162 +++++++++++++++++++++ docs/tasks/python/_python-build.md | 95 +++++------- docs/tasks/python/_python-get-resources.md | 35 ++--- docs/tasks/python/_python-intents.md | 93 ++++++++++++ docs/tasks/python/_python-read.md | 36 ++--- docs/tasks/python/_python-settings.md | 97 +++++++++++- docs/tasks/read.mdx | 34 ++--- docs/tasks/rust/_rust-archives.md | 129 ++++++++++++++++ docs/tasks/rust/_rust-build.md | 77 ++++------ docs/tasks/rust/_rust-get-resources.md | 61 +++----- docs/tasks/rust/_rust-intents.md | 74 ++++++++++ docs/tasks/rust/_rust-read.md | 63 +++----- docs/tasks/rust/_rust-settings.md | 125 +++++++++++++++- docs/tasks/settings.mdx | 88 ++++++++--- sidebars.js | 10 ++ 26 files changed, 1342 insertions(+), 451 deletions(-) create mode 100644 docs/tasks/archives.mdx create mode 100644 docs/tasks/cpp/_cpp-archives.md create mode 100644 docs/tasks/cpp/_cpp-intents.md create mode 100644 docs/tasks/intents.mdx create mode 100644 docs/tasks/python/_python-archives.md create mode 100644 docs/tasks/python/_python-intents.md create mode 100644 docs/tasks/rust/_rust-archives.md create mode 100644 docs/tasks/rust/_rust-intents.md diff --git a/docs/tasks/archives.mdx b/docs/tasks/archives.mdx new file mode 100644 index 00000000..e6cdfd9e --- /dev/null +++ b/docs/tasks/archives.mdx @@ -0,0 +1,34 @@ +--- +id: archives +title: Using working stores and archives +hide_table_of_contents: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import Rustarchives from './rust/_rust-archives.md'; +import Cpparchives from './cpp/_cpp-archives.md'; +import Pythonarchives from './python/_python-archives.md'; + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index 58ac1d56..c5db6648 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -7,42 +7,28 @@ hide_table_of_contents: true import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import JsBuild from './js/_js-build.md'; import PythonBuild from './python/_python-build.md'; -import NodeBuild from './node/_node-build.md'; import CppBuild from './cpp/_cpp-build.md'; import RustBuild from './rust/_rust-build.md'; - + - + - + - + - + - + - + - + - - - - - - - - - - - - - - - + + + diff --git a/docs/tasks/cpp/_cpp-archives.md b/docs/tasks/cpp/_cpp-archives.md new file mode 100644 index 00000000..729393e6 --- /dev/null +++ b/docs/tasks/cpp/_cpp-archives.md @@ -0,0 +1,110 @@ +_Working stores_ and _archives_ provide a standard way to save and restore the state of a `Builder`. + +A **working store** is the editable C2PA manifest state (claims, ingredients, assertions) that has not yet been bound to a final asset. An **archive** is a working store serialized to a file or stream (typically a `.c2pa` file) using the standard JUMBF `application/c2pa` format. + +### Saving a working store to an archive + +Use `to_archive()` to save a `Builder` to a file or stream: + +```cpp +#include "c2pa.hpp" +#include + +c2pa::Context context; +auto builder = c2pa::Builder(context, manifest_json); +builder.add_resource("thumbnail", "thumbnail.jpg"); +builder.add_ingredient(ingredient_json, "source.jpg"); + +// Save working store to archive file +builder.to_archive("manifest.c2pa"); + +// Or save to a stream +std::ofstream archive_stream("manifest.c2pa", std::ios::binary); +builder.to_archive(archive_stream); +archive_stream.close(); +``` + +### Restoring a working store from an archive + +Use `from_archive` to create a new `Builder` from an archive, or `with_archive` to load an archive into an existing `Builder` while preserving its `Context`: + +```cpp +#include "c2pa.hpp" +#include + +// Restore from file (default context) +auto builder = c2pa::Builder::from_archive("manifest.c2pa"); + +// Or restore with a custom context (preserves settings) +c2pa::Context context(R"({ + "builder": { + "thumbnail": {"enabled": false} + } +})"); +auto builder = c2pa::Builder(context); +std::ifstream archive_stream("manifest.c2pa", std::ios::binary); +builder.with_archive(archive_stream); +archive_stream.close(); + +// Sign with the restored working store +builder.sign("asset.jpg", "signed.jpg", signer); +``` + +### Two-phase workflow + +Prepare a manifest in one step, sign it later: + +**Phase 1: Prepare** + +```cpp +c2pa::Context context; +auto builder = c2pa::Builder(context, manifest_json); +builder.add_resource("thumbnail", "thumb.jpg"); +builder.add_ingredient( + R"({"title": "Sketch", "relationship": "parentOf"})", + "sketch.png" +); + +builder.to_archive("artwork_manifest.c2pa"); +``` + +**Phase 2: Sign** + +```cpp +auto builder = c2pa::Builder::from_archive("artwork_manifest.c2pa"); + +auto signer = c2pa::Signer("Es256", certs, private_key, tsa_url); +builder.sign("artwork.jpg", "signed_artwork.jpg", signer); +``` + +### Linking an ingredient archive to an action + +Use a `label` in the `add_ingredient` call on the signing builder, and reference it in `ingredientIds`: + +```cpp +c2pa::Context context; + +const std::string manifest_def = R"({ + "claim_generator_info": [{"name": "My App", "version": "0.1.0"}], + "assertions": [{ + "label": "c2pa.actions.v2", + "data": { + "actions": [{ + "action": "c2pa.placed", + "parameters": { + "ingredientIds": ["my-ingredient"] + } + }] + } + }] +})"; + +auto builder = c2pa::Builder(context, manifest_def); + +builder.add_ingredient( + R"({"title": "photo.jpg", "relationship": "componentOf", "label": "my-ingredient"})", + "ingredient.c2pa" +); + +builder.sign("source.jpg", "signed.jpg", signer); +``` diff --git a/docs/tasks/cpp/_cpp-build.md b/docs/tasks/cpp/_cpp-build.md index ece961f5..6f267696 100644 --- a/docs/tasks/cpp/_cpp-build.md +++ b/docs/tasks/cpp/_cpp-build.md @@ -2,42 +2,36 @@ This is an example of how to assign a manifest to an asset and sign the claim us ```cpp #include -#include #include -#include -#include -#include -#include -#include #include "c2pa.hpp" -#include "test_signer.hpp" -using namespace std; -namespace fs = std::filesystem; using namespace c2pa; -const std::string manifest_json = R"{ - "claim_generator": "c2pa_c_test/0.1", +const std::string manifest_json = R"({ "claim_generator_info": [ { - "name": "c2pa-c example", + "name": "c2pa-cpp test", "version": "0.1" } ], "assertions": [ { - "label": "cawg.training-mining", + "label": "c2pa.training-mining", "data": { "entries": { - "cawg.ai_generative_training": { "use": "notAllowed" }, - "cawg.ai_inference": { "use": "notAllowed" }, - "cawg.ai_training": { "use": "notAllowed" }, - "cawg.data_mining": { "use": "notAllowed" } + "c2pa.ai_generative_training": { "use": "notAllowed" }, + "c2pa.ai_inference": { "use": "notAllowed" }, + "c2pa.ai_training": { "use": "notAllowed" }, + "c2pa.data_mining": { "use": "notAllowed" } } } } ] - }; +})"; -auto builder = Builder(manifest_json); -``` \ No newline at end of file +c2pa::Context context; +auto builder = c2pa::Builder(context, manifest_json); + +Signer signer = c2pa::Signer("Es256", certs, private_key, "http://timestamp.digicert.com"); +auto manifest_data = builder.sign("source_asset.jpg", "output_asset.jpg", signer); +``` diff --git a/docs/tasks/cpp/_cpp-get-resources.md b/docs/tasks/cpp/_cpp-get-resources.md index 60bb1591..44f1343d 100644 --- a/docs/tasks/cpp/_cpp-get-resources.md +++ b/docs/tasks/cpp/_cpp-get-resources.md @@ -4,13 +4,7 @@ This is how to get resources from a manifest using C++. #include #include #include -#include -#include -#include -#include -#include #include "c2pa.hpp" -#include "test_signer.hpp" #include // this example uses nlohmann json for parsing the manifest @@ -19,30 +13,15 @@ using namespace std; namespace fs = std::filesystem; using namespace c2pa; -string read_text_file(const fs::path &path) -{ - ifstream file(path); - if (!file.is_open()) - { - throw runtime_error("Could not open file " + string(path)); - } - string contents((istreambuf_iterator(file)), istreambuf_iterator()); - file.close(); - return contents.data(); -} - int main() { - fs::path manifest_path = current_dir / "../tests/fixtures/training.json"; - //fs::path certs_path = current_dir / "../tests/fixtures/es256_certs.pem"; - //fs::path image_path = current_dir / "../tests/fixtures/A.jpg"; fs::path output_path = current_dir / "../target/example/training.jpg"; fs::path thumbnail_path = current_dir / "../target/example/thumbnail.jpg"; try { - // read the new manifest and display the JSON - auto reader = Reader(output_path); + c2pa::Context context; + auto reader = Reader(context, output_path); auto manifest_store_json = reader.json(); cout << "The new manifest is " << manifest_store_json << endl; @@ -54,27 +33,18 @@ int main() string active_manifest = manifest_store["active_manifest"]; json &manifest = manifest_store["manifests"][active_manifest]; - string identifer = manifest["thumbnail"]["identifier"]; + string identifier = manifest["thumbnail"]["identifier"]; - reader.get_resource(identifer, thumbnail_path); + std::ofstream ofs(thumbnail_path, std::ios::binary); + reader.get_resource(identifier, ofs); - cout << "thumbnail written to" << thumbnail_path << endl; + cout << "thumbnail written to " << thumbnail_path << endl; } } - catch (c2pa::Exception const &e) + catch (c2pa::C2paException const &e) { cout << "C2PA Error: " << e.what() << endl; } - - catch (runtime_error const &e) - { - cout << "setup error" << e.what() << endl; - } - - catch (json::parse_error const &e) - { - cout << "parse error " << e.what() << endl; - } } -``` \ No newline at end of file +``` diff --git a/docs/tasks/cpp/_cpp-intents.md b/docs/tasks/cpp/_cpp-intents.md new file mode 100644 index 00000000..707ed88b --- /dev/null +++ b/docs/tasks/cpp/_cpp-intents.md @@ -0,0 +1,99 @@ +_Intents_ tell the `Builder` what kind of manifest you are creating. They enable validation, add required default actions, and help prevent invalid operations. + +### Setting the intent + +Set the intent through `Context` settings or by calling `set_intent` on the `Builder`. Using `Context` keeps intent configuration alongside other builder settings: + +```cpp +#include "c2pa.hpp" + +c2pa::Context context(R"({ + "version": 1, + "builder": { + "intent": {"Create": "digitalCapture"}, + "claim_generator_info": {"name": "My App", "version": "1.0"} + } +})"); + +c2pa::Builder builder(context, R"({})"); +builder.sign(source_path, output_path, signer); +``` + +Alternatively, call `set_intent` directly on the `Builder`: + +```cpp +c2pa::Context context; +c2pa::Builder builder(context, R"({})"); +builder.set_intent(Create, DigitalCapture); +builder.sign(source_path, output_path, signer); +``` + +### Intent types + +| Intent | Operation | Parent ingredient | Auto-generated action | +|--------|-----------|-------------------|-----------------------| +| `Create` | Brand-new content | Must NOT have one | `c2pa.created` | +| `Edit` | Modifying existing content | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | +| `Update` | Metadata-only changes | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | + +### Create intent + +Use the `Create` intent for new digital creations without a parent ingredient. A `C2paDigitalSourceType` is required; common values include `Empty`, `DigitalCapture`, `TrainedAlgorithmicMedia`, and `DigitalCreation`. + +```cpp +c2pa::Context context(R"({ + "version": 1, + "builder": {"intent": {"Create": "trainedAlgorithmicMedia"}} +})"); + +c2pa::Builder builder(context, R"({})"); +builder.sign(source_path, output_path, signer); +``` + +### Edit intent + +Use the `Edit` intent for editing an existing asset. If no parent ingredient has been added, the `Builder` automatically creates one from the source stream passed to `sign()`: + +```cpp +c2pa::Context context(R"({ + "version": 1, + "builder": {"intent": "edit"} +})"); + +c2pa::Builder builder(context, R"({})"); +builder.sign("original.jpg", "edited.jpg", signer); +``` + +To manually provide the parent ingredient: + +```cpp +c2pa::Context context(R"({ + "version": 1, + "builder": {"intent": "edit"} +})"); + +c2pa::Builder builder(context, R"({})"); + +std::ifstream original("original.jpg", std::ios::binary); +builder.add_ingredient( + R"({"title": "Original Photo", "relationship": "parentOf"})", + "image/jpeg", + original +); + +builder.sign("canvas.jpg", "edited.jpg", signer); +``` + +### Update intent + +Use the `Update` intent for non-editorial, metadata-only changes. It allows exactly one ingredient (the parent) and does not allow changes to the parent's hashed content: + +```cpp +c2pa::Context context(R"({ + "version": 1, + "builder": {"intent": "update"} +})"); + +c2pa::Builder builder(context, R"({})"); +builder.sign("signed_asset.jpg", "updated_asset.jpg", signer); +``` diff --git a/docs/tasks/cpp/_cpp-read.md b/docs/tasks/cpp/_cpp-read.md index e48e63f2..ef7a2202 100644 --- a/docs/tasks/cpp/_cpp-read.md +++ b/docs/tasks/cpp/_cpp-read.md @@ -1,26 +1,28 @@ -Use the `read_file` function to read C2PA data from the specified file. This function examines the specified asset file for C2PA data and returns a JSON report if it finds any; it throws exceptions on errors. If there are validation errors, the report includes a `validation_status` field. +Use the `Reader` constructor to read C2PA data from a file or stream. Pass a `Context` as the first argument to configure SDK behavior; for the default configuration, use `c2pa::Context context;`. + +### Reading from a file ```cpp -#include -#include -#include -#include -#include -#include -#include -#include #include "c2pa.hpp" -#include "test_signer.hpp" - -using namespace std; -namespace fs = std::filesystem; -using namespace c2pa; +#include -auto json_store = C2pa::read_file("work/media_file.jpg", "output/data_dir") +c2pa::Context context; +auto reader = c2pa::Reader(context, "work/media_file.jpg"); +std::cout << reader.json() << std::endl; ``` -Where: +### Reading from a stream + +```cpp +#include "c2pa.hpp" +#include +#include + +c2pa::Context context; +std::ifstream ifs("work/media_file.jpg", std::ios::binary); +auto reader = c2pa::Reader(context, "image/jpeg", ifs); +std::cout << reader.json() << std::endl; +``` -- `work/media_file.jpg` is the asset file to read. -- `output/data_dir` is the optional path to data output directory; If provided, the function extracts any binary resources, such as thumbnails, icons, and C2PA data into that directory. These files are referenced by the identifier fields in the manifest store report. +If there are validation errors, the report includes a `validation_status` field. diff --git a/docs/tasks/cpp/_cpp-settings.md b/docs/tasks/cpp/_cpp-settings.md index e59d0194..83ebed0e 100644 --- a/docs/tasks/cpp/_cpp-settings.md +++ b/docs/tasks/cpp/_cpp-settings.md @@ -1,56 +1,107 @@ +- [Creating a Context](#creating-a-context) +- [Using Context with Reader](#using-context-with-reader) +- [Using Context with Builder](#using-context-with-builder) + +:::info +For more details on using `Context` and `Settings` in Rust, see [C++ library - Configuring SDK settings](../../docs/c2pa-cpp/docs/context-settings) +::: + +### Creating a Context + +The simplest way to create a `Context` is with SDK default settings: + ```cpp #include "c2pa.hpp" -// set settings to not generate thumbnails -c2pa::load_settings("{\"builder\": { \"thumbnail\":{\"enabled\": false}}}", "json"); + +c2pa::Context context; // Uses SDK defaults + +// Use with Reader or Builder +c2pa::Reader reader(context, "image.jpg"); +c2pa::Builder builder(context, manifest_json); ``` -Load from a file: +#### Creating from inline JSON + +To specify a simple configuration that doesn't need to be shared across the codebase, you can use inline JSON like this: ```cpp -#include -#include -#include -#include "c2pa.hpp" +c2pa::Context context(R"({ + "version": 1, + "verify": {"verify_after_sign": true}, + "builder": {"claim_generator_info": {"name": "My App"}} +})"); +``` + +#### Creating from a Settings object + +To specify a configuration that needs runtime logic or incremental construction, use a Settings object like this: + +```cpp +c2pa::Settings settings; +settings.set("builder.thumbnail.enabled", "false"); +settings.set("verify.verify_after_sign", "true"); +settings.update(R"({"builder": {"claim_generator_info": {"name": "My App"}}})"); + +c2pa::Context context(settings); +``` + +#### Creating using ContextBuilder + +To load a configuration from files or combine multiple configuration sources, use `ContextBuilder`. Don't use if you have a single configuration source, since direct construction is simpler. + +```cpp +c2pa::Settings base_settings; +base_settings.set("builder.thumbnail.enabled", "true"); -int main(int argc, char** argv) { - if (argc < 2) { - std::cerr << "Usage: " << argv[0] << " /path/to/settings.json\n"; - return 1; - } - - const std::string settings_path = argv[1]; - - std::ifstream in(settings_path); - if (!in) { - std::cerr << "Failed to open settings file: " << settings_path << "\n"; - return 1; - } - - std::ostringstream buffer; - buffer << in.rdbuf(); - const std::string json = buffer.str(); - - try { - c2pa::load_settings(json, "json"); // format is "json" - std::cout << "Loaded settings successfully. c2pa version: " - << c2pa::version() << "\n"; - } catch (const c2pa::C2paException& e) { - std::cerr << "Failed to load settings: " << e.what() << "\n"; - return 1; - } - return 0; -} +auto context = c2pa::Context::ContextBuilder() + .with_settings(base_settings) + .with_json(R"({"verify": {"verify_after_sign": true}})") + .with_json_settings_file("config/overrides.json") + .create_context(); ``` -Example `settings.json`: +### Using Context with Reader -```json -{ - "builder": { - "thumbnail": { "enabled": false }, - "actions": { - "auto_placed_action": { "enabled": true } - } +`Reader` uses `Context` to control validation, trust configuration, network access, and performance. +Since `Context` is used only at construction, `Context` doesn't need to outlive the `Reader`. + +#### Reading an asset from a file + +```cpp +c2pa::Context context(R"({ + "version": 1, + "verify": { + "remote_manifest_fetch": false, + "ocsp_fetch": false } -} -``` \ No newline at end of file +})"); + +c2pa::Reader reader(context, "image.jpg"); +std::cout << reader.json() << std::endl; +``` + +#### Reading an asset from a stream + +```cpp +std::ifstream stream("image.jpg", std::ios::binary); +c2pa::Reader reader(context, "image/jpeg", stream); +std::cout << reader.json() << std::endl; +``` + +### Using Context with Builder + +`Builder` uses `Context` to control manifest creation, signing, thumbnails, and more. +`Context` doesn't need to outlive the `Builder`, since its used only at construction. + +Once you've created a `Context`, you use it with `Builder` like this: + +```cpp +... +c2pa::Builder builder(context, manifest_json); + +// Pass signer explicitly at signing time +c2pa::Signer signer("es256", certs, private_key); +builder.sign(source_path, output_path, signer); +``` + + diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index 217b67b2..9d113561 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -6,9 +6,7 @@ title: Getting resources from a manifest import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import JSGetResources from './js/_js-get-resources.md'; import PythonGetResources from './python/_python-get-resources.md'; -import NodeGetResources from './node/_node-get-resources.md'; import CppGetResources from './cpp/_cpp-get-resources.md'; import RustGetResources from './rust/_rust-get-resources.md'; @@ -16,34 +14,22 @@ Manifest data can include binary resources such as thumbnail and icon images whi - + - + - + - + - + - + - + - + - - - - - - - - - - - - - + diff --git a/docs/tasks/index.md b/docs/tasks/index.md index 623f3976..ada66715 100644 --- a/docs/tasks/index.md +++ b/docs/tasks/index.md @@ -4,7 +4,9 @@ title: Working with the SDK At a high level, using the CAI open-source SDK is the same in every language, but how you accomplish each task is specific to the language you're using. -- [Configuring SDK settings](./settings.mdx) -- [Reading manifest data](./read.mdx) -- [Getting resources from a manifest](./get-resources.mdx) -- [Adding a manifest to an asset and signing it](./build.mdx) +- [Configuring SDK settings with Context](./settings.mdx) +- [Reading and verifying manifest data](./read.mdx) +- [Getting resources from manifest data](./get-resources.mdx) +- [Using builder intents](./intents.mdx) +- [Using working stores and archives](./archives.mdx) +- [Adding manifest data to an asset and signing it](./build.mdx) diff --git a/docs/tasks/intents.mdx b/docs/tasks/intents.mdx new file mode 100644 index 00000000..5b298150 --- /dev/null +++ b/docs/tasks/intents.mdx @@ -0,0 +1,34 @@ +--- +id: intents +title: Using builder intents +hide_table_of_contents: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import Rustintents from './rust/_rust-intents.md'; +import Cppintents from './cpp/_cpp-intents.md'; +import Pythonintents from './python/_python-intents.md'; + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/tasks/python/_python-archives.md b/docs/tasks/python/_python-archives.md new file mode 100644 index 00000000..ce6a6474 --- /dev/null +++ b/docs/tasks/python/_python-archives.md @@ -0,0 +1,162 @@ +_Working stores_ and _archives_ provide a standard way to save and restore the state of a `Builder`. + +A **working store** is the editable C2PA manifest state (claims, ingredients, assertions) that has not yet been bound to a final asset. An **archive** is a working store serialized to a file or stream (typically a `.c2pa` file) using the standard JUMBF `application/c2pa` format. + +### Saving a working store to an archive + +Use `to_archive()` to save a `Builder` to a stream: + +```py +import io +import json +from c2pa import Context, Builder + +ctx = Context.from_dict({ + "builder": { + "claim_generator_info": {"name": "My App", "version": "0.1.0"} + } +}) + +builder = Builder(manifest_json, context=ctx) + +with open("thumbnail.jpg", "rb") as thumb: + builder.add_resource("thumbnail", thumb) + +ingredient_json = json.dumps({ + "title": "Original asset", + "relationship": "parentOf" +}) +with open("source.jpg", "rb") as ingredient: + builder.add_ingredient(ingredient_json, "image/jpeg", ingredient) + +# Save working store to archive +archive = io.BytesIO() +builder.to_archive(archive) + +# Write to a file +with open("manifest.c2pa", "wb") as f: + archive.seek(0) + f.write(archive.read()) +``` + +### Restoring a working store from an archive + +Use `with_archive()` to load an archive into a `Builder` while preserving its `Context`: + +```py +from c2pa import Context, Builder + +ctx = Context.from_dict({ + "builder": { + "thumbnail": {"enabled": False}, + "claim_generator_info": {"name": "My App", "version": "0.1.0"} + } +}) + +# Create builder with context, then load archive into it +with open("manifest.c2pa", "rb") as archive: + builder = Builder({}, context=ctx) + builder.with_archive(archive) + +# The builder has the archived manifest definition +# but keeps the context settings +with open("asset.jpg", "rb") as src, open("signed.jpg", "w+b") as dst: + builder.sign(signer, "image/jpeg", src, dst) +``` + +### Two-phase workflow + +Prepare a manifest in one step, sign it later: + +**Phase 1: Prepare** + +```py +import io +import json +from c2pa import Context, Builder + +ctx = Context.from_dict({ + "builder": { + "claim_generator_info": {"name": "My App", "version": "0.1.0"} + } +}) + +manifest_json = json.dumps({ + "title": "Artwork draft", + "assertions": [] +}) + +builder = Builder(manifest_json, context=ctx) + +with open("thumb.jpg", "rb") as thumb: + builder.add_resource("thumbnail", thumb) +with open("sketch.png", "rb") as sketch: + builder.add_ingredient( + json.dumps({"title": "Sketch"}), "image/png", sketch + ) + +with open("artwork_manifest.c2pa", "wb") as f: + builder.to_archive(f) +``` + +**Phase 2: Sign** + +```py +from c2pa import Context, Builder + +ctx = Context.from_dict({ + "builder": {"thumbnail": {"enabled": False}} +}) + +with open("artwork_manifest.c2pa", "rb") as archive: + builder = Builder({}, context=ctx) + builder.with_archive(archive) + +with open("artwork.jpg", "rb") as src, open("signed_artwork.jpg", "w+b") as dst: + builder.sign(signer, "image/jpeg", src, dst) +``` + +### Linking an ingredient archive to an action + +Use a `label` in the `add_ingredient()` call on the signing builder, and reference it in `ingredientIds`: + +```py +import json +from c2pa import Context, Builder + +ctx = Context.from_dict({ + "builder": { + "claim_generator_info": {"name": "My App", "version": "0.1.0"} + } +}) + +manifest_json = json.dumps({ + "assertions": [{ + "label": "c2pa.actions.v2", + "data": { + "actions": [{ + "action": "c2pa.placed", + "parameters": { + "ingredientIds": ["my-ingredient"] + } + }] + } + }] +}) + +builder = Builder(manifest_json, context=ctx) + +with open("ingredient.c2pa", "rb") as archive: + builder.add_ingredient( + json.dumps({ + "title": "photo.jpg", + "relationship": "componentOf", + "label": "my-ingredient" + }), + "application/c2pa", + archive + ) + +with open("source.jpg", "rb") as src, open("signed.jpg", "w+b") as dst: + builder.sign(signer, "image/jpeg", src, dst) +``` diff --git a/docs/tasks/python/_python-build.md b/docs/tasks/python/_python-build.md index b90cfb11..5fad011c 100644 --- a/docs/tasks/python/_python-build.md +++ b/docs/tasks/python/_python-build.md @@ -4,68 +4,43 @@ This is an example of how to assign a manifest to an asset and sign the claim us Use a `Builder` object to add a manifest to an asset. ```python -# Import the C2PA Python package. -from c2pa import * - -# Import standard general-purpose packages. -import os -import io -import logging import json -import base64 - -try: - # Define a function to sign the claim bytes. - # In this case we are using a pre-defined sign_ps256 method, passing in our private cert. - # Normally this cert would be kept safe in some other location. - def private_sign(data: bytes) -> bytes: - return sign_ps256(data, "tests/fixtures/ps256.pem") - - # Read our public certs into memory. - certs = open(data_dir + "ps256.pub", "rb").read() - - # Create a signer from the private signer, certs and a time stamp service URL. - signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") - - # Create a builder add a thumbnail resource and an ingredient file. - builder = Builder(manifest_json) +from c2pa import Builder, Context, Signer, C2paSignerInfo, C2paSigningAlg - # Add the resource from a stream. - a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb") - builder.add_resource("image/jpeg", a_thumbnail_jpg_stream) +manifest_json = json.dumps({ + "claim_generator": "python_test/0.1", + "assertions": [] +}) - # Add the resource from a file. - # The URI provided here, "thumbnail", must match an identifier in the manifest definition. - builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg") - - # Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail. - ingredient_json = { - "title": "A.jpg", - "relationship": "parentOf", # "parentOf", "componentOf" or "inputTo" - "thumbnail": { - "identifier": "thumbnail", - "format": "image/jpeg" - } - } - - # Add the ingredient from a stream. - a_jpg_stream = open("tests/fixtures/A.jpg", "rb") - builder.add_ingredient("image/jpeg", a_jpg_stream) - - # At this point archive or unarchive Builder to continue later. - # This example uses a bytearray for the archive stream. - # All ingredients and resources are saved in the archive. - archive = io.BytesIO(bytearray()) - builder.to_archive(archive) - archive.seek() - builder = builder.from_archive(archive) - - # Sign the builder with a stream and output it to a stream. - # This returns the binary manifest data that could be uploaded to cloud storage. - input_stream = open("tests/fixtures/A.jpg", "rb") - output_stream = open("target/out.jpg", "wb") - c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream) +try: + with open("tests/fixtures/ps256.pub", "rb") as cert_file, \ + open("tests/fixtures/ps256.pem", "rb") as key_file: + cert_data = cert_file.read() + key_data = key_file.read() + + signer_info = C2paSignerInfo( + alg=C2paSigningAlg.PS256, + sign_cert=cert_data, + private_key=key_data, + ta_url=b"http://timestamp.digicert.com" + ) + + with Context() as ctx: + with Signer.from_info(signer_info) as signer: + with Builder(manifest_json, ctx) as builder: + # Add an ingredient from a stream. + ingredient_json = json.dumps({ + "title": "A.jpg", + "relationship": "parentOf" + }) + with open("tests/fixtures/A.jpg", "rb") as ingredient_file: + builder.add_ingredient(ingredient_json, "image/jpeg", ingredient_file) + + # Sign and write to the output file. + with open("tests/fixtures/A.jpg", "rb") as source, \ + open("target/out.jpg", "w+b") as dest: + builder.sign(signer, "image/jpeg", source, dest) except Exception as err: - print(err) -``` \ No newline at end of file + print(err) +``` diff --git a/docs/tasks/python/_python-get-resources.md b/docs/tasks/python/_python-get-resources.md index 833d8660..52914089 100644 --- a/docs/tasks/python/_python-get-resources.md +++ b/docs/tasks/python/_python-get-resources.md @@ -1,32 +1,25 @@ The example below shows how to get resources from manifest data using the Python library. -Retrieve binary resources such as thumbnails from the manifest data, use the `resource_to_stream` or `resource_to_file` methods using the associated `identifier` field values and a `uri`. - -_NOTE: Need to add example of using `reader.resource_to_stream()`._ +To retrieve binary resources such as thumbnails from the manifest data, use the `resource_to_stream` method with the associated `identifier` field value as the URI. ```py -# Import the C2PA Python package. -from c2pa import * - -# Import standard general-purpose packages. -import os -import io -import logging import json +from c2pa import Context, Reader try: - # Create a reader from a file path. - reader = c2pa.Reader.from_file("path/to/media_file.jpg") - - # Get the active manifest. - manifest = reader.get_active_manifest() - if manifest != None: - - # get the uri to the manifest's thumbnail and write it to a file. - uri = manifest["thumbnail"]["identifier"] - reader.resource_to_file(uri, "thumbnail_v2.jpg") + with Context() as ctx: + with Reader("path/to/media_file.jpg", context=ctx) as reader: + manifest_store = json.loads(reader.json()) + active_manifest_label = manifest_store.get("active_manifest") + if active_manifest_label: + manifest = manifest_store["manifests"][active_manifest_label] + + # Get the URI to the manifest's thumbnail and write it to a file. + uri = manifest["thumbnail"]["identifier"] + with open("thumbnail_v2.jpg", "w+b") as thumb_file: + reader.resource_to_stream(uri, thumb_file) except Exception as err: print(err) -``` \ No newline at end of file +``` diff --git a/docs/tasks/python/_python-intents.md b/docs/tasks/python/_python-intents.md new file mode 100644 index 00000000..7ecdbca3 --- /dev/null +++ b/docs/tasks/python/_python-intents.md @@ -0,0 +1,93 @@ +_Intents_ tell the `Builder` what kind of manifest you are creating. They enable validation, add required default actions, and help prevent invalid operations. + +### Setting the intent + +Set the intent through `Context` settings. Using `Context` keeps intent configuration alongside other builder settings: + +```py +from c2pa import Context, Builder + +ctx = Context.from_dict({ + "builder": { + "intent": {"Create": "digitalCapture"}, + "claim_generator_info": {"name": "My App", "version": "1.0"} + } +}) + +builder = Builder(manifest_json, context=ctx) + +with open("source.jpg", "rb") as src, open("signed.jpg", "w+b") as dst: + builder.sign(signer, "image/jpeg", src, dst) +``` + +### Intent types + +| Intent | Operation | Parent ingredient | Auto-generated action | +|--------|-----------|-------------------|-----------------------| +| `Create` | Brand-new content | Must NOT have one | `c2pa.created` | +| `Edit` | Modifying existing content | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | +| `Update` | Metadata-only changes | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | + +### Create intent + +Use the `Create` intent for new digital creations without a parent ingredient. A digital source type is required; common values include `"digitalCapture"`, `"trainedAlgorithmicMedia"`, and `"digitalCreation"`. + +```py +ctx = Context.from_dict({ + "builder": {"intent": {"Create": "trainedAlgorithmicMedia"}} +}) + +builder = Builder(manifest_json, context=ctx) +with open("source.jpg", "rb") as src, open("signed.jpg", "w+b") as dst: + builder.sign(signer, "image/jpeg", src, dst) +``` + +### Edit intent + +Use the `Edit` intent for editing an existing asset. If no parent ingredient has been added, the `Builder` automatically creates one from the source stream passed to `sign()`: + +```py +ctx = Context.from_dict({ + "builder": {"intent": "edit"} +}) + +builder = Builder(manifest_json, context=ctx) +with open("original.jpg", "rb") as src, open("edited.jpg", "w+b") as dst: + builder.sign(signer, "image/jpeg", src, dst) +``` + +To manually provide the parent ingredient: + +```py +import json + +ctx = Context.from_dict({ + "builder": {"intent": "edit"} +}) + +builder = Builder(manifest_json, context=ctx) + +ingredient_json = json.dumps({ + "title": "Original Photo", + "relationship": "parentOf" +}) +with open("original.jpg", "rb") as ingredient: + builder.add_ingredient(ingredient_json, "image/jpeg", ingredient) + +with open("canvas.jpg", "rb") as src, open("edited.jpg", "w+b") as dst: + builder.sign(signer, "image/jpeg", src, dst) +``` + +### Update intent + +Use the `Update` intent for non-editorial, metadata-only changes. It allows exactly one ingredient (the parent) and does not allow changes to the parent's hashed content: + +```py +ctx = Context.from_dict({ + "builder": {"intent": "update"} +}) + +builder = Builder(manifest_json, context=ctx) +with open("signed_asset.jpg", "rb") as src, open("updated.jpg", "w+b") as dst: + builder.sign(signer, "image/jpeg", src, dst) +``` diff --git a/docs/tasks/python/_python-read.md b/docs/tasks/python/_python-read.md index 604c7831..31d76203 100644 --- a/docs/tasks/python/_python-read.md +++ b/docs/tasks/python/_python-read.md @@ -1,48 +1,42 @@ -Use the `c2pa.Reader` object to read manifest data from a file or stream and perform validation on the manifest store. +Use the `c2pa.Reader` object to read manifest data from a file or stream and perform validation on the manifest store. This example shows how to read a C2PA manifest embedded in a media file, and validate that it is trusted according to the official trust anchor certificate list. The output is printed as prettified JSON. ```py import sys -import c2pa +import json import urllib.request +from c2pa import Context, Reader, Settings TRUST_ANCHORS_URL = "https://contentcredentials.org/trust/anchors.pem" def load_trust_anchors(): try: with urllib.request.urlopen(TRUST_ANCHORS_URL) as response: - anchors = response.read().decode('utf-8') - settings = { - "verify": { - "verify_cert_anchors": True - }, - "trust": { - "trust_anchors": anchors - } - } - c2pa.load_settings(settings) + return response.read().decode('utf-8') except Exception as e: print(f"Warning: Could not load trust anchors from {TRUST_ANCHORS_URL}: {e}") + return None def read_c2pa_data(media_path: str): print(f"Reading {media_path}") try: - with c2pa.Reader(media_path) as reader: - print(reader.detailed_json()) + anchors = load_trust_anchors() + config = {"verify": {"verify_cert_anchors": True}} + if anchors: + config["trust"] = {"trust_anchors": anchors} + + ctx = Context.from_dict(config) + with Reader(media_path, context=ctx) as reader: + print(reader.json()) except Exception as e: print(f"Error reading C2PA data from {media_path}: {e}") sys.exit(1) if __name__ == '__main__': - if len(sys.argv) < 2: - media_path = "tests/fixtures/cloud.jpg" - else: - media_path = sys.argv[1] - - load_trust_anchors() + media_path = sys.argv[1] if len(sys.argv) >= 2 else "tests/fixtures/cloud.jpg" read_c2pa_data(media_path) -``` \ No newline at end of file +``` diff --git a/docs/tasks/python/_python-settings.md b/docs/tasks/python/_python-settings.md index 28af3472..33a1cb9b 100644 --- a/docs/tasks/python/_python-settings.md +++ b/docs/tasks/python/_python-settings.md @@ -1,14 +1,103 @@ +- [Creating a Context](#creating-a-context) +- [Using Context with Reader](#using-context-with-reader) +- [Using Context with Builder](#using-context-with-builder) + +:::info +For more details on using `Context` and `Settings` in Rust, see [Python library - Configuring SDK settings](../../docs/c2pa-python/docs/context-settings) +::: + + +### Creating a Context + +The simplest way to create a `Context` is with default settings: + ```py +from c2pa import Context + +ctx = Context() # Uses SDK defaults +``` + +This is appropriate for quick prototyping or when the SDK defaults are acceptable. + +#### Creating from inline JSON + +```py +from c2pa import Context + try: - settings = { + ctx = Context.from_dict({ "verify": { "verify_cert_anchors": True }, "trust": { "trust_anchors": "some url" } - } - c2pa.load_settings(settings) + }) except Exception as e: print(f"Exception loading settings: {e}") -``` \ No newline at end of file +``` + +#### Creating from a JSON file + +```py +import json +from c2pa import Context, Settings + +with open("config/settings.json", "r") as f: + settings = Settings.from_json(f.read()) + +ctx = Context(settings) +``` + +### Using Context with Reader + +`Reader` uses `Context` to control how it validates manifests and handles remote resources: + +- **Verification behavior**: Whether to verify after reading, check trust, and so on. +- **Trust configuration**: Which certificates to trust when validating signatures. +- **Network access**: Whether to fetch remote manifests or OCSP responses. + +> [!IMPORTANT] +> `Context` is used only at construction time. `Reader` copies the configuration it needs internally, so the `Context` does not need to outlive the `Reader`. A single `Context` can be reused for multiple `Reader` instances. + +```py +ctx = Context.from_dict({"verify": {"remote_manifest_fetch": False}}) +reader = Reader("image.jpg", context=ctx) +print(reader.json()) +``` + +Reading an asset from a stream: + +```py +with open("image.jpg", "rb") as stream: + reader = Reader("image/jpeg", stream, context=ctx) + print(reader.json()) +``` + +### Using Context with Builder + +`Builder` uses `Context` to control how it creates and signs C2PA manifests. The `Context` affects: + +- **Claim generator information**: Application name, version, and metadata embedded in the manifest. +- **Thumbnail generation**: Whether to create thumbnails, and their size, quality, and format. +- **Action tracking**: Auto-generation of actions like `c2pa.created`, `c2pa.opened`, `c2pa.placed`. +- **Intent**: The purpose of the claim (create, edit, or update). +- **Verification after signing**: Whether to validate the manifest immediately after signing. +- **Signer configuration** (optional): Credentials stored in the context for reuse. + +> [!IMPORTANT] +> `Context` is used only when constructing the `Builder`. The `Builder` copies the configuration it needs internally, so the `Context` does not need to outlive the `Builder`. A single `Context` can be reused for multiple `Builder` instances. + +```py +ctx = Context.from_dict({ + "builder": { + "claim_generator_info": {"name": "An app", "version": "0.1.0"}, + "intent": {"Create": "digitalCapture"} + } +}) + +builder = Builder(manifest_json, context=ctx) + +with open("source.jpg", "rb") as src, open("output.jpg", "w+b") as dst: + builder.sign(signer, "image/jpeg", src, dst) +``` \ No newline at end of file diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index 0401b1aa..fe80cc31 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -1,48 +1,34 @@ --- id: read -title: Reading manifest data +title: Reading and verifying manifest data hide_table_of_contents: true --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import JSRead from './js/_js-read.md'; import PythonRead from './python/_python-read.md'; -import NodeRead from './node/_node-read.md'; import CppRead from './cpp/_cpp-read.md'; import RustRead from './rust/_rust-read.md'; - + - + - + - + - + - + - + - + - - - - - - - - - - - - - + diff --git a/docs/tasks/rust/_rust-archives.md b/docs/tasks/rust/_rust-archives.md new file mode 100644 index 00000000..22f05cf1 --- /dev/null +++ b/docs/tasks/rust/_rust-archives.md @@ -0,0 +1,129 @@ +_Working stores_ and _archives_ provide a standard way to save and restore the state of a `Builder`. + +A **working store** is the editable C2PA manifest state (claims, ingredients, assertions) that has not yet been bound to a final asset. An **archive** is a working store serialized to a file or stream (typically a `.c2pa` file) using the standard JUMBF `application/c2pa` format. + +### Saving a working store to an archive + +Use `to_archive()` to save a `Builder` to a stream: + +```rust +use c2pa::{Context, Builder, Result}; +use std::io::Cursor; + +fn main() -> Result<()> { + let context = Context::new() + .with_settings(include_str!("config.json"))?; + + let mut builder = Builder::from_context(context) + .with_definition(r#"{"title": "My Image"}"#)?; + + // Save the working store to an archive + let mut archive = Cursor::new(Vec::new()); + builder.to_archive(&mut archive)?; + std::fs::write("work.c2pa", archive.get_ref())?; + + Ok(()) +} +``` + +### Restoring a working store from an archive + +Use `from_archive` to restore with default `Context`, or `with_archive` to restore into a `Builder` with a custom `Context`: + +```rust +use c2pa::{Context, Builder, Result}; +use std::io::Cursor; + +fn main() -> Result<()> { + // Restore with default context + let builder = Builder::from_archive( + Cursor::new(std::fs::read("work.c2pa")?) + )?; + + // Or restore with a custom shared context (preserves settings) + let context = Context::new() + .with_settings(include_str!("config.json"))?; + + let builder = Builder::from_context(context) + .with_archive(Cursor::new(std::fs::read("work.c2pa")?))?; + + Ok(()) +} +``` + +### Two-phase workflow + +Prepare a manifest in one step, sign it later: + +```rust +use c2pa::{Context, Builder, Result}; +use serde_json::json; +use std::io::Cursor; + +fn prepare() -> Result<()> { + let context = Context::new() + .with_settings(include_str!("config.json"))?; + + let mut builder = Builder::from_context(context) + .with_definition(json!({"title": "Artwork draft"}))?; + + builder.add_ingredient_from_stream( + json!({"title": "Sketch", "relationship": "parentOf"}).to_string(), + "image/png", + &mut std::fs::File::open("sketch.png")?, + )?; + + let mut archive = Cursor::new(Vec::new()); + builder.to_archive(&mut archive)?; + std::fs::write("artwork.c2pa", archive.get_ref())?; + Ok(()) +} + +fn sign() -> Result<()> { + let context = Context::new() + .with_settings(include_str!("config.json"))?; + + let mut builder = Builder::from_context(context) + .with_archive(Cursor::new(std::fs::read("artwork.c2pa")?))?; + + let mut source = std::fs::File::open("artwork.jpg")?; + let mut dest = std::fs::File::create("signed_artwork.jpg")?; + builder.save_to_stream("image/jpeg", &mut source, &mut dest)?; + Ok(()) +} +``` + +### Capturing an ingredient as an archive + +Save validation results by capturing an ingredient into an archive, then reuse it later: + +```rust +use c2pa::{Context, Builder, Result}; +use serde_json::json; +use std::io::Cursor; + +fn main() -> Result<()> { + let context = Context::new() + .with_settings(include_str!("config.json"))?; + + let mut builder = Builder::from_context(context) + .with_definition(json!({"title": "New Image"}))?; + + builder.add_ingredient_from_stream( + json!({ + "title": "Archived Ingredient", + "relationship": "componentOf", + "label": "ingredient_1" + }).to_string(), + "application/c2pa", + &mut Cursor::new(std::fs::read("ingredient.c2pa")?), + )?; + + builder.add_action(json!({ + "action": "c2pa.placed", + "parameters": { "ingredientIds": ["ingredient_1"] } + }))?; + + Ok(()) +} +``` diff --git a/docs/tasks/rust/_rust-build.md b/docs/tasks/rust/_rust-build.md index 6faadd69..ee995163 100644 --- a/docs/tasks/rust/_rust-build.md +++ b/docs/tasks/rust/_rust-build.md @@ -1,57 +1,42 @@ This is an example of how to assign a manifest to an asset and sign the claim using Rust. -This example is from [`c2pa-rs/sdk/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L88C5-L134C1): +Configure a `Context` with signer settings and use `Builder::from_context` to create and sign a manifest: ```rust -use std::io::{Cursor, Seek}; - -use anyhow::Result; -use c2pa::{ - crypto::raw_signature::SigningAlg, settings::Settings, validation_results::ValidationState, - Builder, CallbackSigner, Reader, -}; +use c2pa::{Context, Builder, Result}; use serde_json::json; +use std::io::Cursor; -let json = manifest_def(title, format); -let mut builder = Builder::from_json(&json)?; - -builder.add_ingredient_from_stream( - json!({ - "title": parent_name, - "relationship": "parentOf" - }) - .to_string(), - format, - &mut source, -)?; - -let thumb_uri = builder - .definition - .thumbnail - .as_ref() - .map(|t| t.identifier.clone()); - -// add a manifest thumbnail ( just reuse the image for now ) -if let Some(uri) = thumb_uri { - if !uri.starts_with("self#jumbf") { - source.rewind()?; - builder.add_resource(&uri, &mut source)?; - } -} +fn main() -> Result<()> { + let context = Context::new() + .with_settings(include_str!("config.json"))?; + + let mut builder = Builder::from_context(context) + .with_definition(json!({"title": "My Image"}))?; -// write the manifest builder to a zipped stream -let mut zipped = Cursor::new(Vec::new()); -builder.to_archive(&mut zipped)?; + let mut source = std::fs::File::open("source.jpg")?; + let mut dest = Cursor::new(Vec::new()); + builder.save_to_stream("image/jpeg", &mut source, &mut dest)?; -// unzip the manifest builder from the zipped stream -zipped.rewind()?; + Ok(()) +} +``` -let ed_signer = - |_context: *const (), data: &[u8]| CallbackSigner::ed25519_sign(data, PRIVATE_KEY); -let signer = CallbackSigner::new(ed_signer, SigningAlg::Ed25519, CERTS); +The `config.json` file specifies the signer and builder configuration. For example: -let mut builder = Builder::from_archive(&mut zipped)?; -// sign the ManifestStoreBuilder and write it to the output stream -let mut dest = Cursor::new(Vec::new()); -builder.sign(&signer, format, &mut source, &mut dest)?; +```json +{ + "signer": { + "local": { + "alg": "ps256", + "sign_cert": "path/to/cert.pem", + "private_key": "path/to/key.pem", + "tsa_url": "http://timestamp.digicert.com" + } + }, + "builder": { + "claim_generator_info": {"name": "My App", "version": "1.0"}, + "intent": {"Create": "digitalCapture"} + } +} ``` diff --git a/docs/tasks/rust/_rust-get-resources.md b/docs/tasks/rust/_rust-get-resources.md index ade19f45..8b7df62b 100644 --- a/docs/tasks/rust/_rust-get-resources.md +++ b/docs/tasks/rust/_rust-get-resources.md @@ -1,45 +1,28 @@ The example below shows how to get resources from manifest data using the Rust library. -_NOTE: Need to clarify if/how these two code examples work together._ - -This is from [`resource_to_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.resource_to_stream) API doc: - -```rust -use c2pa::Reader; -#[cfg(feature = "file_io")] -{ - let stream = std::io::Cursor::new(Vec::new()); - let reader = Reader::from_file("path/to/file.jpg").unwrap(); - let manifest = reader.active_manifest().unwrap(); - let uri = &manifest.thumbnail_ref().unwrap().identifier; - let bytes_written = reader.resource_to_stream(uri, stream).unwrap(); -} -``` - -This is from [`c2pa-rs/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L138): +Use [`Reader::from_context`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_context) to read a manifest and then use [`resource_to_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.resource_to_stream) to extract binary resources such as thumbnails: ```rust -use std::io::{Cursor, Seek}; - -use anyhow::Result; -use c2pa::{ - crypto::raw_signature::SigningAlg, settings::Settings, validation_results::ValidationState, - Builder, CallbackSigner, Reader, -}; -use serde_json::json; - -let reader = Reader::from_stream(format, &mut dest)?; - -// extract a thumbnail image from the ManifestStore -let mut thumbnail = Cursor::new(Vec::new()); -if let Some(manifest) = reader.active_manifest() { - if let Some(thumbnail_ref) = manifest.thumbnail_ref() { - reader.resource_to_stream(&thumbnail_ref.identifier, &mut thumbnail)?; - println!( - "wrote thumbnail {} of size {}", - thumbnail_ref.format, - thumbnail.get_ref().len() - ); +use c2pa::{Context, Reader, Result}; +use std::io::Cursor; + +fn main() -> Result<()> { + let context = Context::new(); + let stream = std::fs::File::open("path/to/file.jpg")?; + let reader = Reader::from_context(context) + .with_stream("image/jpeg", stream)?; + + let mut thumbnail = Cursor::new(Vec::new()); + if let Some(manifest) = reader.active_manifest() { + if let Some(thumbnail_ref) = manifest.thumbnail_ref() { + reader.resource_to_stream(&thumbnail_ref.identifier, &mut thumbnail)?; + println!( + "wrote thumbnail {} of size {}", + thumbnail_ref.format, + thumbnail.get_ref().len() + ); + } } + Ok(()) } -``` \ No newline at end of file +``` diff --git a/docs/tasks/rust/_rust-intents.md b/docs/tasks/rust/_rust-intents.md new file mode 100644 index 00000000..6c33d3a9 --- /dev/null +++ b/docs/tasks/rust/_rust-intents.md @@ -0,0 +1,74 @@ +_Intents_ tell the `Builder` what kind of manifest you are creating. They enable validation, add required default actions, and help prevent invalid operations. + +### Setting the intent + +Set the intent through `Context` settings or by calling `set_intent` on the `Builder`. Using `Context` keeps intent configuration alongside other builder settings: + +```rust +use c2pa::{Context, Builder, Result}; + +fn main() -> Result<()> { + let context = Context::new() + .with_settings(r#"{ + "builder": { + "intent": {"Create": "digitalCapture"}, + "claim_generator_info": {"name": "My App", "version": "1.0"} + } + }"#)?; + + let mut builder = Builder::from_context(context) + .with_definition(r#"{"title": "New Image"}"#)?; + + let mut source = std::fs::File::open("source.jpg")?; + let mut dest = std::fs::File::create("signed.jpg")?; + builder.save_to_stream("image/jpeg", &mut source, &mut dest)?; + Ok(()) +} +``` + +Alternatively, call `set_intent` directly on the `Builder`: + +```rust +use c2pa::{Builder, BuilderIntent, DigitalSourceType}; + +builder.set_intent(BuilderIntent::Create(DigitalSourceType::DigitalCapture)); +``` + +### Intent types + +| Intent | Operation | Parent ingredient | Auto-generated action | +|--------|-----------|-------------------|-----------------------| +| `Create` | Brand-new content | Must NOT have one | `c2pa.created` | +| `Edit` | Modifying existing content | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | +| `Update` | Metadata-only changes | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | + +### Create intent + +Use `BuilderIntent::Create(DigitalSourceType)` for new digital creations without a parent ingredient. A `DigitalSourceType` is required; common values include `Empty`, `DigitalCapture`, `TrainedAlgorithmicMedia`, and `DigitalCreation`. + +```rust +builder.set_intent(BuilderIntent::Create(DigitalSourceType::TrainedAlgorithmicMedia)); +``` + +### Edit intent + +Use `BuilderIntent::Edit` for editing an existing asset. If no parent ingredient has been added, the `Builder` automatically derives one from the source stream: + +```rust +use serde_json::json; + +builder.set_intent(BuilderIntent::Edit); +builder.add_ingredient_from_stream( + json!({"title": "Original", "relationship": "parentOf"}).to_string(), + "image/jpeg", + &mut source_stream, +)?; +``` + +### Update intent + +Use `BuilderIntent::Update` for non-editorial, metadata-only changes. It allows exactly one ingredient (the parent) and does not allow changes to the parent's hashed content: + +```rust +builder.set_intent(BuilderIntent::Update); +``` diff --git a/docs/tasks/rust/_rust-read.md b/docs/tasks/rust/_rust-read.md index 9521fd07..65335d65 100644 --- a/docs/tasks/rust/_rust-read.md +++ b/docs/tasks/rust/_rust-read.md @@ -3,51 +3,36 @@ Use the [`Reader`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html) struct t ### Reading from a file -Use [`from_file`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file) to read manifest data from a file: +Open a file and use [`from_context`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_context) with [`with_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.with_stream) to read manifest data: ```rust -use std::{ - io::{Cursor, Write}, - process::{Command, Stdio}, -}; - -use anyhow::Result; -use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader}; -use c2pa_crypto::raw_signature::SigningAlg; -use serde_json::json; - -const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg"); -const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub"); -const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem"); - -use c2pa::Reader; -let reader = Reader::from_file("path/to/file.jpg").unwrap(); +use c2pa::{Context, Reader, Result}; +use std::fs::File; + +fn main() -> Result<()> { + let context = Context::new(); + let stream = File::open("path/to/file.jpg")?; + let reader = Reader::from_context(context) + .with_stream("image/jpeg", stream)?; + println!("{}", reader.json()); + Ok(()) +} ``` -There is also an asynchronous version of this method, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file_async). - ### Reading from a stream -Use [`from_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream) to read manifest data from a stream: - ```rust -use std::{ - io::{Cursor, Write}, - process::{Command, Stdio}, -}; - -use anyhow::Result; -use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader}; -use c2pa_crypto::raw_signature::SigningAlg; -use serde_json::json; - -const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg"); -const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub"); -const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem"); - -let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg")); -let reader = Reader::from_stream("image/jpeg", stream).unwrap(); -println!("{}", reader.json()); +use c2pa::{Context, Reader, Result}; +use std::io::Cursor; + +fn main() -> Result<()> { + let context = Context::new(); + let image_data = include_bytes!("path/to/file.jpg"); + let reader = Reader::from_context(context) + .with_stream("image/jpeg", Cursor::new(image_data))?; + println!("{}", reader.json()); + Ok(()) +} ``` -There is also an asynchronous version of this method, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream_async). +There is also an asynchronous version, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream_async). diff --git a/docs/tasks/rust/_rust-settings.md b/docs/tasks/rust/_rust-settings.md index 8effb800..5b435628 100644 --- a/docs/tasks/rust/_rust-settings.md +++ b/docs/tasks/rust/_rust-settings.md @@ -1,13 +1,132 @@ +- [Creating a Context](#creating-a-context) +- [Using Context with Reader](#using-context-with-reader) +- [Using Context with Builder](#using-context-with-builder) + +:::info +For more details on using `Context` and `Settings` in Rust, see [Rust library - Configuring SDK settings](../../docs/rust-sdk/docs/context-settings) +::: + + +### Creating a Context + +The simplest way to create a `Context` is with default settings: + +```rust +use c2pa::Context; + +let context = Context::new(); +``` + +#### Loading settings from a file + +Load settings from a file using the `with_settings()` method, which automatically detects the format (JSON or TOML): + ```rust use c2pa::{Context, Builder, Result}; fn main() -> Result<()> { - // Create a Context with settings from a file + // From a file let context = Context::new() .with_settings(include_str!("settings.json"))?; - + + // Create builder using context settings let builder = Builder::from_context(context); - // ... use builder + Ok(()) +} +``` + +#### Loading settings inline + +You can also provide settings inline in either JSON or TOML format. For example, using JSON: + +```rust +use c2pa::{Context, Result}; + +fn main() -> Result<()> { + // Inline JSON format + let context = Context::new() + .with_settings(r#" + {"verify": + {"verify_after_sign": true}} + "#)?; + + Ok(()) +} +``` + +#### Loading settings programmatically + +```rust +use c2pa::{Context, Settings, Result}; + +fn main() -> Result<()> { + let mut settings = Settings::default(); + settings.verify.verify_after_sign = true; + + let context = Context::new().with_settings(settings)?; + Ok(()) +} +``` + +### Using Context with Reader + +`Reader` uses `Context` to control manifest validation and remote resource fetching: + +```rust +use c2pa::{Context, Reader, Result}; +use std::fs::File; + +fn main() -> Result<()> { + // Configure context + let context = Context::new() + .with_settings(r#"{"verify": {"remote_manifest_fetch": false}}"#)?; + + // Create reader with context + let stream = File::open("path/to/image.jpg")?; + let reader = Reader::from_context(context) + .with_stream("image/jpeg", stream)?; + + println!("{}", reader.json()); + Ok(()) +} +``` + +### Using Context with Builder + +`Builder` uses `Context` to configure signing operations. The `Context` automatically creates a signer from settings when needed: + +```rust +use c2pa::{Context, Builder, Result}; +use std::io::Cursor; +use serde_json::json; + +fn main() -> Result<()> { + // Configure context with signer and builder settings + let context = Context::new() + .with_settings(json!({ + "signer": { + "local": { + "alg": "ps256", + "sign_cert": "path/to/cert.pem", + "private_key": "path/to/key.pem", + "tsa_url": "http://timestamp.digicert.com" + } + }, + "builder": { + "claim_generator_info": {"name": "My App"}, + "intent": {"Create": "digitalCapture"} + } + }))?; + + // Create builder with context and inline JSON definition + let mut builder = Builder::from_context(context) + .with_definition(json!({"title": "My Image"}))?; + + // Save with automatic signer from context + let mut source = std::fs::File::open("source.jpg")?; + let mut dest = Cursor::new(Vec::new()); + builder.save_to_stream("image/jpeg", &mut source, &mut dest)?; + Ok(()) } ``` \ No newline at end of file diff --git a/docs/tasks/settings.mdx b/docs/tasks/settings.mdx index e0f62fbf..acd23431 100644 --- a/docs/tasks/settings.mdx +++ b/docs/tasks/settings.mdx @@ -1,49 +1,95 @@ --- id: settings -title: Configuring settings +title: Configuring SDK settings with Context hide_table_of_contents: true --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import JSRead from './js/_js-settings.md'; import PythonRead from './python/_python-settings.md'; -import NodeSettings from './node/_node-settings.md'; import CppRead from './cpp/_cpp-settings.md'; import RustRead from './rust/_rust-settings.md'; - +Regardless of which language you're working in, you use the `Context` and `Settings` classes to control SDK behavior including verification, trust anchors, thumbnails, signing, and more. + +## Overview of Context + +`Context` encapsulates SDK configuration that controls how `Reader`, `Builder`, and other components operate, including: + +- **Settings**: Trust configuration, builder behavior, thumbnails, and more. +- **Signer configuration**: Optional signing credentials that can be stored for reuse. + +Using `Context` provides explicit, isolated configuration without thread-local state. It enables you to run different configurations simultaneously (for example for development with test certificates or production with strict validation), simplifies testing, and improves code clarity. + +`Context`: + +- Can be moved but not copied. After moving, `is_valid()` returns `false` on the source. +- Is used at construction: `Reader` and `Builder` copy configuration from the `Context` at construction time. The `Context` doesn't need to outlive them. +- Is reusable: Use the same `Context` to create multiple `Reader` and `Builder` instances. + +## Overview of Settings - -Settings supports: `trust`, `cawgTrust`, `verify`, and `builder`. +You can specify a declarative configuration for the SDK using `Settings`, that you can load from a JSON file or set programmatically. - +### Settings object structure - +> [!TIP] +> For the complete reference to the Settings object, see [SDK object reference - Settings](https://opensource.contentauthenticity.org/docs/manifest/json-ref/settings-schema). - +Settings JSON has this top-level structure: - +```json +{ + "version": 1, + "trust": { ... }, + "cawg_trust": { ... }, + "core": { ... }, + "verify": { ... }, + "builder": { ... }, + "signer": { ... }, + "cawg_x509_signer": { ... } +} +``` + +| Property | Description | +| --------------------------------------------------------------------------- | ------------------------------------------------------------ | +| `version` | Settings format version (must be 1) | +| [`trust`](../manifest/json-ref/settings-schema/#trust) | Certificate trust configuration for C2PA validation | +| [`cawg_trust`](../manifest/json-ref/settings-schema/#trust) | Certificate trust configuration for CAWG identity assertions | +| [`core`](../manifest/json-ref/settings-schema/#core) | Core SDK behavior and performance tuning | +| [`verify`](../manifest/json-ref/settings-schema/#verify) | Validation and verification behavior | +| [`builder`](../manifest/json-ref/settings-schema/#builder) | Manifest creation and embedding behavior | +| [`signer`](../manifest/json-ref/settings-schema/#signer-settings) | C2PA signer configuration | +| [`cawg_x509_signer`](../manifest/json-ref/settings-schema/#signer-settings) | CAWG identity assertion signer configuration | + +The `version` property must be `1`. All other properties are optional. + +> [!IMPORTANT] +> If you don't specify a property, the SDK uses the default value. If you specify `null`, the property is explicitly set to null (not the default). This distinction matters when overriding default behavior. + +For Boolean values, use JSON `true` and `false`, not the strings `"true"` and `"false"`. + +## Creating and using Context + + - + - + - + - + - + - + - + - + - - - + diff --git a/sidebars.js b/sidebars.js index ac22c27e..2c9f4fa2 100644 --- a/sidebars.js +++ b/sidebars.js @@ -115,6 +115,16 @@ const sidebars = { id: 'tasks/get-resources', label: 'Getting manifest resources', }, + { + type: 'doc', + id: 'tasks/intents', + label: 'Using builder intents', + }, + { + type: 'doc', + id: 'tasks/archives', + label: 'Using working stores and archives', + }, { type: 'doc', id: 'tasks/build', From ffdb8f8488747cb26cb01f27e70cc3c8b4bb1e5c Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Thu, 2 Apr 2026 13:03:37 -0700 Subject: [PATCH 51/58] Clean up intents --- docs/tasks/cpp/_cpp-intents.md | 10 ---------- docs/tasks/intents.mdx | 13 ++++++++++++- docs/tasks/python/_python-intents.md | 18 ++++++++++-------- docs/tasks/rust/_rust-intents.md | 10 ---------- 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/docs/tasks/cpp/_cpp-intents.md b/docs/tasks/cpp/_cpp-intents.md index 707ed88b..9304f9ef 100644 --- a/docs/tasks/cpp/_cpp-intents.md +++ b/docs/tasks/cpp/_cpp-intents.md @@ -1,5 +1,3 @@ -_Intents_ tell the `Builder` what kind of manifest you are creating. They enable validation, add required default actions, and help prevent invalid operations. - ### Setting the intent Set the intent through `Context` settings or by calling `set_intent` on the `Builder`. Using `Context` keeps intent configuration alongside other builder settings: @@ -28,14 +26,6 @@ builder.set_intent(Create, DigitalCapture); builder.sign(source_path, output_path, signer); ``` -### Intent types - -| Intent | Operation | Parent ingredient | Auto-generated action | -|--------|-----------|-------------------|-----------------------| -| `Create` | Brand-new content | Must NOT have one | `c2pa.created` | -| `Edit` | Modifying existing content | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | -| `Update` | Metadata-only changes | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | - ### Create intent Use the `Create` intent for new digital creations without a parent ingredient. A `C2paDigitalSourceType` is required; common values include `Empty`, `DigitalCapture`, `TrainedAlgorithmicMedia`, and `DigitalCreation`. diff --git a/docs/tasks/intents.mdx b/docs/tasks/intents.mdx index 5b298150..752a1714 100644 --- a/docs/tasks/intents.mdx +++ b/docs/tasks/intents.mdx @@ -6,11 +6,22 @@ hide_table_of_contents: true import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; - import Rustintents from './rust/_rust-intents.md'; import Cppintents from './cpp/_cpp-intents.md'; import Pythonintents from './python/_python-intents.md'; +_Intents_ tell the `Builder` what kind of manifest you are creating. They enable validation, add required default actions, and help prevent invalid operations. + +### Intent types + +There are three types of intents, shown here: + +| Intent | Use when... | Parent ingredient | Auto-generated action | +| -------- | ---------------------------- | ---------------------------------------- | -------------------------------- | +| `Create` | Creating brand-new content | Must NOT have one | `c2pa.created` | +| `Edit` | Modifying existing content | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | +| `Update` | Making metadata-only changes | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | + diff --git a/docs/tasks/python/_python-intents.md b/docs/tasks/python/_python-intents.md index 7ecdbca3..4bac4350 100644 --- a/docs/tasks/python/_python-intents.md +++ b/docs/tasks/python/_python-intents.md @@ -1,5 +1,3 @@ -_Intents_ tell the `Builder` what kind of manifest you are creating. They enable validation, add required default actions, and help prevent invalid operations. - ### Setting the intent Set the intent through `Context` settings. Using `Context` keeps intent configuration alongside other builder settings: @@ -20,13 +18,17 @@ with open("source.jpg", "rb") as src, open("signed.jpg", "w+b") as dst: builder.sign(signer, "image/jpeg", src, dst) ``` -### Intent types +Alternatively, you can call `set_intent` directly on a `Builder` instance for one-off operations or when the intent is determined at runtime. For example: -| Intent | Operation | Parent ingredient | Auto-generated action | -|--------|-----------|-------------------|-----------------------| -| `Create` | Brand-new content | Must NOT have one | `c2pa.created` | -| `Edit` | Modifying existing content | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | -| `Update` | Metadata-only changes | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | +```py +with Builder({}) as builder: + builder.set_intent( + C2paBuilderIntent.CREATE, + C2paDigitalSourceType.TRAINED_ALGORITHMIC_MEDIA, + ) + with open("source.jpg", "rb") as source, open("output.jpg", "wb") as dest: + builder.sign(signer, "image/jpeg", source, dest) +``` ### Create intent diff --git a/docs/tasks/rust/_rust-intents.md b/docs/tasks/rust/_rust-intents.md index 6c33d3a9..b2bd9405 100644 --- a/docs/tasks/rust/_rust-intents.md +++ b/docs/tasks/rust/_rust-intents.md @@ -1,5 +1,3 @@ -_Intents_ tell the `Builder` what kind of manifest you are creating. They enable validation, add required default actions, and help prevent invalid operations. - ### Setting the intent Set the intent through `Context` settings or by calling `set_intent` on the `Builder`. Using `Context` keeps intent configuration alongside other builder settings: @@ -34,14 +32,6 @@ use c2pa::{Builder, BuilderIntent, DigitalSourceType}; builder.set_intent(BuilderIntent::Create(DigitalSourceType::DigitalCapture)); ``` -### Intent types - -| Intent | Operation | Parent ingredient | Auto-generated action | -|--------|-----------|-------------------|-----------------------| -| `Create` | Brand-new content | Must NOT have one | `c2pa.created` | -| `Edit` | Modifying existing content | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | -| `Update` | Metadata-only changes | Auto-created from source if not provided | `c2pa.opened` (linked to parent) | - ### Create intent Use `BuilderIntent::Create(DigitalSourceType)` for new digital creations without a parent ingredient. A `DigitalSourceType` is required; common values include `Empty`, `DigitalCapture`, `TrainedAlgorithmicMedia`, and `DigitalCreation`. From 2c0835b4ea19dba6fdaa29d5cc194fb81df0a816 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 6 Apr 2026 16:35:53 -0700 Subject: [PATCH 52/58] Update working stores and archives --- docs/tasks/archives.mdx | 11 +- docs/tasks/cpp/_cpp-archives.md | 110 --------- docs/tasks/cpp/_cpp-archives.mdx | 204 ++++++++++++++++ docs/tasks/python/_python-archives.md | 162 ------------- docs/tasks/python/_python-archives.mdx | 221 ++++++++++++++++++ .../{_rust-archives.md => _rust-archives.mdx} | 66 ++++-- 6 files changed, 474 insertions(+), 300 deletions(-) delete mode 100644 docs/tasks/cpp/_cpp-archives.md create mode 100644 docs/tasks/cpp/_cpp-archives.mdx delete mode 100644 docs/tasks/python/_python-archives.md create mode 100644 docs/tasks/python/_python-archives.mdx rename docs/tasks/rust/{_rust-archives.md => _rust-archives.mdx} (67%) diff --git a/docs/tasks/archives.mdx b/docs/tasks/archives.mdx index e6cdfd9e..ba345e16 100644 --- a/docs/tasks/archives.mdx +++ b/docs/tasks/archives.mdx @@ -7,9 +7,14 @@ hide_table_of_contents: true import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import Rustarchives from './rust/_rust-archives.md'; -import Cpparchives from './cpp/_cpp-archives.md'; -import Pythonarchives from './python/_python-archives.md'; +import Rustarchives from './rust/_rust-archives.mdx'; +import Cpparchives from './cpp/_cpp-archives.mdx'; +import Pythonarchives from './python/_python-archives.mdx'; + +_Working stores_ and _archives_ provide a standard way to save and restore the state of a `Builder`: + +- A **working store** is the editable manifest state (including claims, ingredients, assertions) that has not yet been bound to a final asset. +- An **archive** is a working store serialized to a file or stream (typically a `.c2pa` file) using the standard JUMBF `application/c2pa` format. diff --git a/docs/tasks/cpp/_cpp-archives.md b/docs/tasks/cpp/_cpp-archives.md deleted file mode 100644 index 729393e6..00000000 --- a/docs/tasks/cpp/_cpp-archives.md +++ /dev/null @@ -1,110 +0,0 @@ -_Working stores_ and _archives_ provide a standard way to save and restore the state of a `Builder`. - -A **working store** is the editable C2PA manifest state (claims, ingredients, assertions) that has not yet been bound to a final asset. An **archive** is a working store serialized to a file or stream (typically a `.c2pa` file) using the standard JUMBF `application/c2pa` format. - -### Saving a working store to an archive - -Use `to_archive()` to save a `Builder` to a file or stream: - -```cpp -#include "c2pa.hpp" -#include - -c2pa::Context context; -auto builder = c2pa::Builder(context, manifest_json); -builder.add_resource("thumbnail", "thumbnail.jpg"); -builder.add_ingredient(ingredient_json, "source.jpg"); - -// Save working store to archive file -builder.to_archive("manifest.c2pa"); - -// Or save to a stream -std::ofstream archive_stream("manifest.c2pa", std::ios::binary); -builder.to_archive(archive_stream); -archive_stream.close(); -``` - -### Restoring a working store from an archive - -Use `from_archive` to create a new `Builder` from an archive, or `with_archive` to load an archive into an existing `Builder` while preserving its `Context`: - -```cpp -#include "c2pa.hpp" -#include - -// Restore from file (default context) -auto builder = c2pa::Builder::from_archive("manifest.c2pa"); - -// Or restore with a custom context (preserves settings) -c2pa::Context context(R"({ - "builder": { - "thumbnail": {"enabled": false} - } -})"); -auto builder = c2pa::Builder(context); -std::ifstream archive_stream("manifest.c2pa", std::ios::binary); -builder.with_archive(archive_stream); -archive_stream.close(); - -// Sign with the restored working store -builder.sign("asset.jpg", "signed.jpg", signer); -``` - -### Two-phase workflow - -Prepare a manifest in one step, sign it later: - -**Phase 1: Prepare** - -```cpp -c2pa::Context context; -auto builder = c2pa::Builder(context, manifest_json); -builder.add_resource("thumbnail", "thumb.jpg"); -builder.add_ingredient( - R"({"title": "Sketch", "relationship": "parentOf"})", - "sketch.png" -); - -builder.to_archive("artwork_manifest.c2pa"); -``` - -**Phase 2: Sign** - -```cpp -auto builder = c2pa::Builder::from_archive("artwork_manifest.c2pa"); - -auto signer = c2pa::Signer("Es256", certs, private_key, tsa_url); -builder.sign("artwork.jpg", "signed_artwork.jpg", signer); -``` - -### Linking an ingredient archive to an action - -Use a `label` in the `add_ingredient` call on the signing builder, and reference it in `ingredientIds`: - -```cpp -c2pa::Context context; - -const std::string manifest_def = R"({ - "claim_generator_info": [{"name": "My App", "version": "0.1.0"}], - "assertions": [{ - "label": "c2pa.actions.v2", - "data": { - "actions": [{ - "action": "c2pa.placed", - "parameters": { - "ingredientIds": ["my-ingredient"] - } - }] - } - }] -})"; - -auto builder = c2pa::Builder(context, manifest_def); - -builder.add_ingredient( - R"({"title": "photo.jpg", "relationship": "componentOf", "label": "my-ingredient"})", - "ingredient.c2pa" -); - -builder.sign("source.jpg", "signed.jpg", signer); -``` diff --git a/docs/tasks/cpp/_cpp-archives.mdx b/docs/tasks/cpp/_cpp-archives.mdx new file mode 100644 index 00000000..e56d5431 --- /dev/null +++ b/docs/tasks/cpp/_cpp-archives.mdx @@ -0,0 +1,204 @@ +import TOCInline from '@theme/TOCInline'; + + + +### Saving a working store to an archive + +Use `to_archive()` to save a `Builder` to a file or stream: + +```cpp +#include "c2pa.hpp" +#include + +c2pa::Context context; +auto builder = c2pa::Builder(context, manifest_json); +builder.add_resource("thumbnail", "thumbnail.jpg"); +builder.add_ingredient(ingredient_json, "source.jpg"); + +// Save working store to archive file +builder.to_archive("manifest.c2pa"); + +// Or save to a stream +std::ofstream archive_stream("manifest.c2pa", std::ios::binary); +builder.to_archive(archive_stream); +archive_stream.close(); +``` + +### Restoring a working store from an archive + +Use `from_archive` to create a new `Builder` from an archive, or `with_archive` to load an archive into an existing `Builder` while preserving its `Context`: + +```cpp +#include "c2pa.hpp" +#include + +// Restore from file (default context) +auto builder = c2pa::Builder::from_archive("manifest.c2pa"); + +// Or restore with a custom context (preserves settings) +c2pa::Context context(R"({ + "builder": { + "thumbnail": {"enabled": false} + } +})"); +auto builder = c2pa::Builder(context); +std::ifstream archive_stream("manifest.c2pa", std::ios::binary); +builder.with_archive(archive_stream); +archive_stream.close(); + +// Sign with the restored working store +builder.sign("asset.jpg", "signed.jpg", signer); +``` + +### Two-phase workflow + +Prepare a manifest in one step, and sign it later: + +**Phase 1: Prepare manifest** + +```cpp +void prepare_manifest() { + const std::string manifest_json = R"({ + "title": "Artwork draft", + "assertions": [ ... ] + })"; + + c2pa::Context context; + auto builder = c2pa::Builder(context, manifest_json); + builder.add_resource("thumbnail", "thumb.jpg"); + builder.add_ingredient("{\"title\": \"Sketch\"}", "sketch.png"); + + // Save working store as archive (C2PA JUMBF format) + builder.to_archive("artwork_manifest.c2pa"); + + std::cout << "Working store saved to artwork_manifest.c2pa" << std::endl; +} +``` + +**Phase 2: Sign the asset** + +```cpp +void sign_asset() { + // Restore the working store + auto builder = c2pa::Builder::from_archive("artwork_manifest.c2pa"); + + // Create signer using HSM (not shown) + auto signer = create_hsm_signer(); + + // Sign + builder.sign("artwork.jpg", "signed_artwork.jpg", signer); + + std::cout << "Asset signed with manifest store" << std::endl; +} +``` + +### Linking an ingredient archive to an action + +To link an ingredient archive to an action via `ingredientIds`, you must use a `label` set in the `add_ingredient` call on the signing builder. Labels baked into the archive ingredient are not carried through, and `instance_id` does not work as a linking key for ingredient archives regardless of where it is set. + +```cpp +c2pa::Context context; + +// Step 1: Create the ingredient archive. +auto manifest_str = read_file("training.json"); +auto archive_builder = c2pa::Builder(context, manifest_str); +archive_builder.add_ingredient( + R"({"title": "photo.jpg", "relationship": "componentOf"})", + "photo.jpg"); +archive_builder.to_archive("ingredient.c2pa"); + +// Step 2: Build a manifest with an action that references the ingredient. +auto manifest_json = R"({ + "claim_generator_info": [{"name": "an-application", "version": "0.1.0"}], + "assertions": [{ + "label": "c2pa.actions.v2", + "data": { + "actions": [{ + "action": "c2pa.placed", + "parameters": { + "ingredientIds": ["my-ingredient"] + } + }] + } + }] +})"; + +auto builder = c2pa::Builder(context, manifest_json); + +// Step 3: Add the ingredient archive with a label matching the ingredientIds value. +// The label MUST be set here, on the signing builder's add_ingredient call. +builder.add_ingredient( + R"({"title": "photo.jpg", "relationship": "componentOf", "label": "my-ingredient"})", + "ingredient.c2pa"); + +builder.sign("source.jpg", "signed.jpg", signer); +``` + +When linking multiple ingredient archives, give each a distinct label and reference it in the appropriate action's `ingredientIds` array. + +If each ingredient has its own action (e.g., one `c2pa.opened` for the parent and one `c2pa.placed` for a composited element), set up two actions with separate `ingredientIds`: + +```cpp +auto manifest_json = R"({ + "claim_generator_info": [{"name": "an-application", "version": "0.1.0"}], + "assertions": [{ + "label": "c2pa.actions.v2", + "data": { + "actions": [ + { + "action": "c2pa.opened", + "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation", + "parameters": { "ingredientIds": ["parent-photo"] } + }, + { + "action": "c2pa.placed", + "parameters": { "ingredientIds": ["overlay-graphic"] } + } + ] + } + }] +})"; + +auto builder = c2pa::Builder(context, manifest_json); + +builder.add_ingredient( + R"({"title": "photo.jpg", "relationship": "parentOf", "label": "parent-photo"})", + "photo_archive.c2pa"); +builder.add_ingredient( + R"({"title": "overlay.png", "relationship": "componentOf", "label": "overlay-graphic"})", + "overlay_archive.c2pa"); + +builder.sign("source.jpg", "signed.jpg", signer); +``` + +A single `c2pa.placed` action can also reference several `componentOf` ingredients composited together. List all labels in the `ingredientIds` array: + +```cpp +auto manifest_json = R"({ + "claim_generator_info": [{"name": "an-application", "version": "0.1.0"}], + "assertions": [{ + "label": "c2pa.actions.v2", + "data": { + "actions": [{ + "action": "c2pa.placed", + "parameters": { + "ingredientIds": ["base-layer", "overlay-layer"] + } + }] + } + }] +})"; + +auto builder = c2pa::Builder(context, manifest_json); + +builder.add_ingredient( + R"({"title": "base.jpg", "relationship": "componentOf", "label": "base-layer"})", + "base_ingredient.c2pa"); +builder.add_ingredient( + R"({"title": "overlay.jpg", "relationship": "componentOf", "label": "overlay-layer"})", + "overlay_ingredient.c2pa"); + +builder.sign("source.jpg", "signed.jpg", signer); +``` + +After signing, the action's `parameters.ingredients` array contains one resolved URL per ingredient. diff --git a/docs/tasks/python/_python-archives.md b/docs/tasks/python/_python-archives.md deleted file mode 100644 index ce6a6474..00000000 --- a/docs/tasks/python/_python-archives.md +++ /dev/null @@ -1,162 +0,0 @@ -_Working stores_ and _archives_ provide a standard way to save and restore the state of a `Builder`. - -A **working store** is the editable C2PA manifest state (claims, ingredients, assertions) that has not yet been bound to a final asset. An **archive** is a working store serialized to a file or stream (typically a `.c2pa` file) using the standard JUMBF `application/c2pa` format. - -### Saving a working store to an archive - -Use `to_archive()` to save a `Builder` to a stream: - -```py -import io -import json -from c2pa import Context, Builder - -ctx = Context.from_dict({ - "builder": { - "claim_generator_info": {"name": "My App", "version": "0.1.0"} - } -}) - -builder = Builder(manifest_json, context=ctx) - -with open("thumbnail.jpg", "rb") as thumb: - builder.add_resource("thumbnail", thumb) - -ingredient_json = json.dumps({ - "title": "Original asset", - "relationship": "parentOf" -}) -with open("source.jpg", "rb") as ingredient: - builder.add_ingredient(ingredient_json, "image/jpeg", ingredient) - -# Save working store to archive -archive = io.BytesIO() -builder.to_archive(archive) - -# Write to a file -with open("manifest.c2pa", "wb") as f: - archive.seek(0) - f.write(archive.read()) -``` - -### Restoring a working store from an archive - -Use `with_archive()` to load an archive into a `Builder` while preserving its `Context`: - -```py -from c2pa import Context, Builder - -ctx = Context.from_dict({ - "builder": { - "thumbnail": {"enabled": False}, - "claim_generator_info": {"name": "My App", "version": "0.1.0"} - } -}) - -# Create builder with context, then load archive into it -with open("manifest.c2pa", "rb") as archive: - builder = Builder({}, context=ctx) - builder.with_archive(archive) - -# The builder has the archived manifest definition -# but keeps the context settings -with open("asset.jpg", "rb") as src, open("signed.jpg", "w+b") as dst: - builder.sign(signer, "image/jpeg", src, dst) -``` - -### Two-phase workflow - -Prepare a manifest in one step, sign it later: - -**Phase 1: Prepare** - -```py -import io -import json -from c2pa import Context, Builder - -ctx = Context.from_dict({ - "builder": { - "claim_generator_info": {"name": "My App", "version": "0.1.0"} - } -}) - -manifest_json = json.dumps({ - "title": "Artwork draft", - "assertions": [] -}) - -builder = Builder(manifest_json, context=ctx) - -with open("thumb.jpg", "rb") as thumb: - builder.add_resource("thumbnail", thumb) -with open("sketch.png", "rb") as sketch: - builder.add_ingredient( - json.dumps({"title": "Sketch"}), "image/png", sketch - ) - -with open("artwork_manifest.c2pa", "wb") as f: - builder.to_archive(f) -``` - -**Phase 2: Sign** - -```py -from c2pa import Context, Builder - -ctx = Context.from_dict({ - "builder": {"thumbnail": {"enabled": False}} -}) - -with open("artwork_manifest.c2pa", "rb") as archive: - builder = Builder({}, context=ctx) - builder.with_archive(archive) - -with open("artwork.jpg", "rb") as src, open("signed_artwork.jpg", "w+b") as dst: - builder.sign(signer, "image/jpeg", src, dst) -``` - -### Linking an ingredient archive to an action - -Use a `label` in the `add_ingredient()` call on the signing builder, and reference it in `ingredientIds`: - -```py -import json -from c2pa import Context, Builder - -ctx = Context.from_dict({ - "builder": { - "claim_generator_info": {"name": "My App", "version": "0.1.0"} - } -}) - -manifest_json = json.dumps({ - "assertions": [{ - "label": "c2pa.actions.v2", - "data": { - "actions": [{ - "action": "c2pa.placed", - "parameters": { - "ingredientIds": ["my-ingredient"] - } - }] - } - }] -}) - -builder = Builder(manifest_json, context=ctx) - -with open("ingredient.c2pa", "rb") as archive: - builder.add_ingredient( - json.dumps({ - "title": "photo.jpg", - "relationship": "componentOf", - "label": "my-ingredient" - }), - "application/c2pa", - archive - ) - -with open("source.jpg", "rb") as src, open("signed.jpg", "w+b") as dst: - builder.sign(signer, "image/jpeg", src, dst) -``` diff --git a/docs/tasks/python/_python-archives.mdx b/docs/tasks/python/_python-archives.mdx new file mode 100644 index 00000000..aba6bdec --- /dev/null +++ b/docs/tasks/python/_python-archives.mdx @@ -0,0 +1,221 @@ +import TOCInline from '@theme/TOCInline'; + + + +### Saving a working store to an archive + +Use `to_archive()` to save a `Builder` to a stream: + +```py +import io +import json +from c2pa import Context, Builder + +ctx = Context.from_dict({ + "builder": { + "claim_generator_info": {"name": "My App", "version": "0.1.0"} + } +}) + +builder = Builder(manifest_json, context=ctx) + +with open("thumbnail.jpg", "rb") as thumb: + builder.add_resource("thumbnail", thumb) + +ingredient_json = json.dumps({ + "title": "Original asset", + "relationship": "parentOf" +}) +with open("source.jpg", "rb") as ingredient: + builder.add_ingredient(ingredient_json, "image/jpeg", ingredient) + +# Save working store to archive +archive = io.BytesIO() +builder.to_archive(archive) + +# Write to a file +with open("manifest.c2pa", "wb") as f: + archive.seek(0) + f.write(archive.read()) +``` + +### Restoring a working store from an archive + +Use `with_archive()` to load an archive into a `Builder` while preserving its `Context`: + +```py +from c2pa import Context, Builder + +ctx = Context.from_dict({ + "builder": { + "thumbnail": {"enabled": False}, + "claim_generator_info": {"name": "My App", "version": "0.1.0"} + } +}) + +# Create builder with context, then load archive into it +with open("manifest.c2pa", "rb") as archive: + builder = Builder({}, context=ctx) + builder.with_archive(archive) + +# The builder has the archived manifest definition +# but keeps the context settings +with open("asset.jpg", "rb") as src, open("signed.jpg", "w+b") as dst: + builder.sign(signer, "image/jpeg", src, dst) +``` + +### Two-phase workflow + +Prepare a manifest in one step, sign it later: + +**Phase 1: Prepare** + +```py +import io +import json +from c2pa import Context, Builder + +ctx = Context.from_dict({ + "builder": { + "claim_generator_info": {"name": "My App", "version": "0.1.0"} + } +}) + +manifest_json = json.dumps({ + "title": "Artwork draft", + "assertions": [] +}) + +builder = Builder(manifest_json, context=ctx) + +with open("thumb.jpg", "rb") as thumb: + builder.add_resource("thumbnail", thumb) +with open("sketch.png", "rb") as sketch: + builder.add_ingredient( + json.dumps({"title": "Sketch"}), "image/png", sketch + ) + +with open("artwork_manifest.c2pa", "wb") as f: + builder.to_archive(f) +``` + +**Phase 2: Sign** + +```py +from c2pa import Context, Builder + +ctx = Context.from_dict({ + "builder": {"thumbnail": {"enabled": False}} +}) + +with open("artwork_manifest.c2pa", "rb") as archive: + builder = Builder({}, context=ctx) + builder.with_archive(archive) + +with open("artwork.jpg", "rb") as src, open("signed_artwork.jpg", "w+b") as dst: + builder.sign(signer, "image/jpeg", src, dst) +``` + +### Linking an ingredient archive to an action + +To link an ingredient archive to an action via `ingredientIds`, you must use a `label` set in the `add_ingredient()` call on the signing builder. Labels baked into the archive ingredient are not carried through, and `instance_id` does not work as a linking key for ingredient archives regardless of where it is set. + +**Step 1: Create the ingredient archive** + +```py +import io, json + +archive_builder = Builder.from_json({ + "claim_generator_info": [{"name": "an-application", "version": "0.1.0"}], + "assertions": [], +}) +with open("photo.jpg", "rb") as f: + archive_builder.add_ingredient( + {"title": "photo.jpg", "relationship": "componentOf"}, + "image/jpeg", + f, + ) +archive = io.BytesIO() +archive_builder.to_archive(archive) +archive.seek(0) +``` + +**Step 2: Build a manifest with an action that references the ingredient** + +```py +manifest_json = { + "claim_generator_info": [{"name": "an-application", "version": "0.1.0"}], + "assertions": [ + { + "label": "c2pa.actions.v2", + "data": { + "actions": [ + { + "action": "c2pa.placed", + "parameters": { + "ingredientIds": ["my-ingredient"] + }, + } + ] + }, + } + ], +} + +ctx = Context.from_dict({"signer": signer}) +builder = Builder(manifest_json, context=ctx) +``` + +**Step 3: Add the ingredient archive** + +The label must match the ingredientIds value and MUST be set here, on the signing builder's `add_ingredient()` call. + +```py +builder.add_ingredient( + {"title": "photo.jpg", "relationship": "componentOf", "label": "my-ingredient"}, + "application/c2pa", + archive, +) + +with open("source.jpg", "rb") as src, open("signed.jpg", "w+b") as dst: + builder.sign("image/jpeg", src, dst) +``` + +When linking multiple ingredient archives, give each a distinct label and reference it in the appropriate action's `ingredientIds` array. + +If each ingredient has its own action (e.g., one `c2pa.opened` for the parent and one `c2pa.placed` for a composited element), set up two actions with separate `ingredientIds`: + +```py +manifest_json = { + "claim_generator_info": [{"name": "an-application", "version": "0.1.0"}], + "assertions": [{ + "label": "c2pa.actions.v2", + "data": { + "actions": [ + { + "action": "c2pa.opened", + "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation", + "parameters": {"ingredientIds": ["parent-photo"]}, + }, + { + "action": "c2pa.placed", + "parameters": {"ingredientIds": ["overlay-graphic"]}, + }, + ] + }, + }], +} + +builder = Builder(manifest_json, context=ctx) + +builder.add_ingredient( + {"title": "photo.jpg", "relationship": "parentOf", "label": "parent-photo"}, + "application/c2pa", + photo_archive, +) +builder.add_ingredient( + {"title": "overlay.png", "relationship": "componentOf", "label": "overlay-graphic"}, + "application/c2pa", + overlay_archive, +) +``` diff --git a/docs/tasks/rust/_rust-archives.md b/docs/tasks/rust/_rust-archives.mdx similarity index 67% rename from docs/tasks/rust/_rust-archives.md rename to docs/tasks/rust/_rust-archives.mdx index 22f05cf1..336495c0 100644 --- a/docs/tasks/rust/_rust-archives.md +++ b/docs/tasks/rust/_rust-archives.mdx @@ -1,6 +1,6 @@ -_Working stores_ and _archives_ provide a standard way to save and restore the state of a `Builder`. +import TOCInline from '@theme/TOCInline'; -A **working store** is the editable C2PA manifest state (claims, ingredients, assertions) that has not yet been bound to a final asset. An **archive** is a working store serialized to a file or stream (typically a `.c2pa` file) using the standard JUMBF `application/c2pa` format. + ### Saving a working store to an archive @@ -95,35 +95,51 @@ fn sign() -> Result<()> { ### Capturing an ingredient as an archive -Save validation results by capturing an ingredient into an archive, then reuse it later: +Save validation results by capturing an ingredient into an archive, then reuse it later. + +Capture and sign an archive (no embedded asset): ```rust -use c2pa::{Context, Builder, Result}; -use serde_json::json; -use std::io::Cursor; +let signer = context.signer()?; +let ingredient_c2pa = builder.sign( + signer, + "application/c2pa", + &mut io::empty(), + &mut io::empty(), +)?; +``` -fn main() -> Result<()> { - let context = Context::new() - .with_settings(include_str!("config.json"))?; +This returns the raw C2PA manifest store as `Vec`. - let mut builder = Builder::from_context(context) - .with_definition(json!({"title": "New Image"}))?; +Later, you can add that archived ingredient to a new manifest as follows: - builder.add_ingredient_from_stream( +```rust +let mut builder = Builder::from_shared_context(&context) + .with_definition( json!({ - "title": "Archived Ingredient", - "relationship": "componentOf", - "label": "ingredient_1" - }).to_string(), - "application/c2pa", - &mut Cursor::new(std::fs::read("ingredient.c2pa")?), + "title": "New Title", + "relationship": "componentOf" + }) )?; - builder.add_action(json!({ - "action": "c2pa.placed", - "parameters": { "ingredientIds": ["ingredient_1"] } - }))?; - - Ok(()) -} +builder.add_ingredient_from_stream( + json!({ + "title": "Archived Ingredient", + "relationship": "componentOf", + "label": "ingredient_1" + }) + .to_string(), + "application/c2pa", + &mut Cursor::new(ingredient_c2pa), +)?; +builder.add_action(json!({ + "action": "c2pa.placed", + "parameters": { "ingredientIds": ["ingredient_1"] } +}))?; ``` + +Calling [`add_ingredient_from_stream()`](https://docs.rs/c2pa/latest/c2pa/struct.Builder.html#method.add_ingredient_from_stream) with format `"application/c2pa"`: + +1. Reads the archive. +2. Extracts the first ingredient from the active manifest. +3. Merges with provided JSON properties, but your overrides take precedence. From 0d4c761f8a68a822cfe3be0895d9223ea87f6018 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 13 Apr 2026 10:53:07 -0700 Subject: [PATCH 53/58] Final cleanup and corrections to build and read tasks --- docs/tasks/cpp/_cpp-build.md | 5 +++-- docs/tasks/cpp/_cpp-read.md | 8 +++++--- docs/tasks/python/_python-read.md | 2 +- docs/tasks/rust/_rust-read.md | 6 +++++- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/tasks/cpp/_cpp-build.md b/docs/tasks/cpp/_cpp-build.md index 6f267696..3044c8fe 100644 --- a/docs/tasks/cpp/_cpp-build.md +++ b/docs/tasks/cpp/_cpp-build.md @@ -2,6 +2,7 @@ This is an example of how to assign a manifest to an asset and sign the claim us ```cpp #include +#include #include #include "c2pa.hpp" @@ -29,9 +30,9 @@ const std::string manifest_json = R"({ ] })"; -c2pa::Context context; +auto context = std::make_shared(); auto builder = c2pa::Builder(context, manifest_json); -Signer signer = c2pa::Signer("Es256", certs, private_key, "http://timestamp.digicert.com"); +Signer signer = c2pa::Signer("es256", certs, private_key, "http://timestamp.digicert.com"); auto manifest_data = builder.sign("source_asset.jpg", "output_asset.jpg", signer); ``` diff --git a/docs/tasks/cpp/_cpp-read.md b/docs/tasks/cpp/_cpp-read.md index ef7a2202..e12f7531 100644 --- a/docs/tasks/cpp/_cpp-read.md +++ b/docs/tasks/cpp/_cpp-read.md @@ -1,13 +1,14 @@ -Use the `Reader` constructor to read C2PA data from a file or stream. Pass a `Context` as the first argument to configure SDK behavior; for the default configuration, use `c2pa::Context context;`. +Use the `Reader` constructor to read C2PA data from a file or stream. Pass a shared pointer to a `Context` as the first argument to configure SDK behavior; for the default configuration, use `std::make_shared()`. ### Reading from a file ```cpp #include "c2pa.hpp" #include +#include -c2pa::Context context; +auto context = std::make_shared(); auto reader = c2pa::Reader(context, "work/media_file.jpg"); std::cout << reader.json() << std::endl; ``` @@ -18,8 +19,9 @@ std::cout << reader.json() << std::endl; #include "c2pa.hpp" #include #include +#include -c2pa::Context context; +auto context = std::make_shared(); std::ifstream ifs("work/media_file.jpg", std::ios::binary); auto reader = c2pa::Reader(context, "image/jpeg", ifs); std::cout << reader.json() << std::endl; diff --git a/docs/tasks/python/_python-read.md b/docs/tasks/python/_python-read.md index 31d76203..cde94907 100644 --- a/docs/tasks/python/_python-read.md +++ b/docs/tasks/python/_python-read.md @@ -7,7 +7,7 @@ This example shows how to read a C2PA manifest embedded in a media file, and val import sys import json import urllib.request -from c2pa import Context, Reader, Settings +from c2pa import Context, Reader TRUST_ANCHORS_URL = "https://contentcredentials.org/trust/anchors.pem" diff --git a/docs/tasks/rust/_rust-read.md b/docs/tasks/rust/_rust-read.md index 65335d65..6f649671 100644 --- a/docs/tasks/rust/_rust-read.md +++ b/docs/tasks/rust/_rust-read.md @@ -19,6 +19,10 @@ fn main() -> Result<()> { } ``` +:::tip +The `with_stream()` format parameter accepts either a MIME type or a file extension as an argument. +::: + ### Reading from a stream ```rust @@ -35,4 +39,4 @@ fn main() -> Result<()> { } ``` -There is also an asynchronous version, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream_async). +There is also a deprecated asynchronous version, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream_async). From f4ce7c19a98c083d2b4b4c80c1eacdf4bc95fb63 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 13 Apr 2026 10:54:57 -0700 Subject: [PATCH 54/58] Fix unpublished node/read doc --- docs/tasks/node/_node-read.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tasks/node/_node-read.md b/docs/tasks/node/_node-read.md index d6bceb76..3c2e2485 100644 --- a/docs/tasks/node/_node-read.md +++ b/docs/tasks/node/_node-read.md @@ -1,5 +1,5 @@ -Use the `c2pa.read()` function to read a manifest; for example: +Use the `Reader` class to read C2PA manifest data from a file or buffer. For example: ```ts // read-manifest.ts From fe1b96df98d29b0a39a533cf09cc3442c0c822ce Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 13 Apr 2026 13:50:48 -0700 Subject: [PATCH 55/58] Apply suggestions from code review From @ok-nick Co-authored-by: nick --- docs/tasks/rust/_rust-build.md | 17 ++++++++++++++++- docs/tasks/rust/_rust-get-resources.md | 13 +++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/docs/tasks/rust/_rust-build.md b/docs/tasks/rust/_rust-build.md index ee995163..3f15ac65 100644 --- a/docs/tasks/rust/_rust-build.md +++ b/docs/tasks/rust/_rust-build.md @@ -8,8 +8,23 @@ use serde_json::json; use std::io::Cursor; fn main() -> Result<()> { + let settings = json!({ + "signer": { + "local": { + "alg": "ps256", + "sign_cert": "path/to/cert.pem", + "private_key": "path/to/key.pem", + "tsa_url": "http://timestamp.digicert.com" + } + }, + "builder": { + "claim_generator_info": { "name": "My App", "version": "1.0" }, + "intent": { "Create": "digitalCapture" } + } + }); + let context = Context::new() - .with_settings(include_str!("config.json"))?; + .with_settings(settings)?; let mut builder = Builder::from_context(context) .with_definition(json!({"title": "My Image"}))?; diff --git a/docs/tasks/rust/_rust-get-resources.md b/docs/tasks/rust/_rust-get-resources.md index 8b7df62b..affd4e17 100644 --- a/docs/tasks/rust/_rust-get-resources.md +++ b/docs/tasks/rust/_rust-get-resources.md @@ -4,23 +4,20 @@ Use [`Reader::from_context`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html ```rust use c2pa::{Context, Reader, Result}; -use std::io::Cursor; +use std::{fs::File, io::Cursor}; fn main() -> Result<()> { let context = Context::new(); - let stream = std::fs::File::open("path/to/file.jpg")?; + let stream = File::open("path/to/file.jpg")?; let reader = Reader::from_context(context) .with_stream("image/jpeg", stream)?; let mut thumbnail = Cursor::new(Vec::new()); if let Some(manifest) = reader.active_manifest() { if let Some(thumbnail_ref) = manifest.thumbnail_ref() { - reader.resource_to_stream(&thumbnail_ref.identifier, &mut thumbnail)?; - println!( - "wrote thumbnail {} of size {}", - thumbnail_ref.format, - thumbnail.get_ref().len() - ); + let output_path = format!("thumbnail.{}", thumbnail_ref.format); + let mut output_file = File::create(output_path)?; + reader.resource_to_stream(&thumbnail_ref.identifier, &mut output_file)?; } } Ok(()) From a27409b214ecbc479ad719c17c3e786464e881e2 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 13 Apr 2026 13:52:21 -0700 Subject: [PATCH 56/58] Review comments from Nick --- docs/tasks/rust/_rust-read.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tasks/rust/_rust-read.md b/docs/tasks/rust/_rust-read.md index 6f649671..047b58a0 100644 --- a/docs/tasks/rust/_rust-read.md +++ b/docs/tasks/rust/_rust-read.md @@ -25,18 +25,18 @@ The `with_stream()` format parameter accepts either a MIME type or a file extens ### Reading from a stream +If you have image bytes from a non-file source such as an HTTP response or a database, wrap them in a `Cursor` to provide the seekable stream that `with_stream` requires: + ```rust use c2pa::{Context, Reader, Result}; use std::io::Cursor; -fn main() -> Result<()> { +fn read_manifest_from_bytes(image_bytes: Vec) -> Result<()> { let context = Context::new(); - let image_data = include_bytes!("path/to/file.jpg"); let reader = Reader::from_context(context) - .with_stream("image/jpeg", Cursor::new(image_data))?; + .with_stream("image/jpeg", Cursor::new(image_bytes))?; println!("{}", reader.json()); Ok(()) } ``` -There is also a deprecated asynchronous version, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream_async). From 8694bd9fd129c7fc55fdc21393f4977ff64738fe Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 13 Apr 2026 14:15:29 -0700 Subject: [PATCH 57/58] Apply suggestions from code review Co-authored-by: nick --- docs/tasks/rust/_rust-build.md | 19 ------------------- docs/tasks/rust/_rust-get-resources.md | 11 +++++++---- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/docs/tasks/rust/_rust-build.md b/docs/tasks/rust/_rust-build.md index 3f15ac65..c3bf7d01 100644 --- a/docs/tasks/rust/_rust-build.md +++ b/docs/tasks/rust/_rust-build.md @@ -36,22 +36,3 @@ fn main() -> Result<()> { Ok(()) } ``` - -The `config.json` file specifies the signer and builder configuration. For example: - -```json -{ - "signer": { - "local": { - "alg": "ps256", - "sign_cert": "path/to/cert.pem", - "private_key": "path/to/key.pem", - "tsa_url": "http://timestamp.digicert.com" - } - }, - "builder": { - "claim_generator_info": {"name": "My App", "version": "1.0"}, - "intent": {"Create": "digitalCapture"} - } -} -``` diff --git a/docs/tasks/rust/_rust-get-resources.md b/docs/tasks/rust/_rust-get-resources.md index affd4e17..9fc3be41 100644 --- a/docs/tasks/rust/_rust-get-resources.md +++ b/docs/tasks/rust/_rust-get-resources.md @@ -12,12 +12,15 @@ fn main() -> Result<()> { let reader = Reader::from_context(context) .with_stream("image/jpeg", stream)?; - let mut thumbnail = Cursor::new(Vec::new()); if let Some(manifest) = reader.active_manifest() { if let Some(thumbnail_ref) = manifest.thumbnail_ref() { - let output_path = format!("thumbnail.{}", thumbnail_ref.format); - let mut output_file = File::create(output_path)?; - reader.resource_to_stream(&thumbnail_ref.identifier, &mut output_file)?; + let mut thumbnail = Cursor::new(Vec::new()); + reader.resource_to_stream(&thumbnail_ref.identifier, &mut thumbnail)?; + println!( + "wrote thumbnail {} of size {}", + thumbnail_ref.format, + thumbnail.get_ref().len() + ); } } Ok(()) From a6be28bfb3155265a4cda75a9ccb60db748abdf6 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Mon, 13 Apr 2026 14:15:54 -0700 Subject: [PATCH 58/58] Remove one example per @ok-nick --- docs/tasks/rust/_rust-read.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/docs/tasks/rust/_rust-read.md b/docs/tasks/rust/_rust-read.md index 047b58a0..65699ddb 100644 --- a/docs/tasks/rust/_rust-read.md +++ b/docs/tasks/rust/_rust-read.md @@ -1,8 +1,6 @@ Use the [`Reader`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html) struct to read manifest data from a file or stream. -### Reading from a file - Open a file and use [`from_context`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_context) with [`with_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.with_stream) to read manifest data: ```rust @@ -23,20 +21,5 @@ fn main() -> Result<()> { The `with_stream()` format parameter accepts either a MIME type or a file extension as an argument. ::: -### Reading from a stream - -If you have image bytes from a non-file source such as an HTTP response or a database, wrap them in a `Cursor` to provide the seekable stream that `with_stream` requires: - -```rust -use c2pa::{Context, Reader, Result}; -use std::io::Cursor; -fn read_manifest_from_bytes(image_bytes: Vec) -> Result<()> { - let context = Context::new(); - let reader = Reader::from_context(context) - .with_stream("image/jpeg", Cursor::new(image_bytes))?; - println!("{}", reader.json()); - Ok(()) -} -```