diff --git a/.gitignore b/.gitignore index 0500d8a1..6fabebf6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,9 +8,8 @@ .docusaurus .cache-loader /docs/js-sdk/api -/docs/js-sdk/examples/quickstart/*.ts -/docs/js-sdk/examples/view-manifest/*.ts -/docs/js-sdk/examples/view-manifest/*.html +/docs/js-sdk/examples/* +/docs/js-sdk/examples/* /docs/c2patool/*.md /docs/c2patool/docs/*.md /docs/c2pa-node/*.md @@ -35,4 +34,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.dccache +.dccache \ No newline at end of file diff --git a/docs/manifest/manifest-tasks.mdx b/docs/manifest/manifest-tasks.mdx deleted file mode 100644 index d24fb6a9..00000000 --- a/docs/manifest/manifest-tasks.mdx +++ /dev/null @@ -1,97 +0,0 @@ ---- -id: tasks -title: Common tasks working with manifest data ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -## Reading a manifest - - - - - This is how to read a manifest using JavaScript. - - - - This is how to read a manifest using Python. - - - - This is how to read a manifest using C++. Do we want also want C? - - - - This is how to read a manifest using Node.js. - - - -## 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..2d4d0e3c --- /dev/null +++ b/docs/tasks/build.mdx @@ -0,0 +1,50 @@ +--- +id: build +title: Attaching and signing a manifest +hide_table_of_contents: true +--- + +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'; + + + + + +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 new file mode 100644 index 00000000..cb55f1f8 --- /dev/null +++ b/docs/tasks/get-resources.mdx @@ -0,0 +1,52 @@ +--- +id: get-resources +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 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'; + +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 new file mode 100644 index 00000000..04f27f6e --- /dev/null +++ b/docs/tasks/includes/_cpp-build.md @@ -0,0 +1,29 @@ +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"{ + "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..c0c6853a --- /dev/null +++ b/docs/tasks/includes/_cpp-setup.md @@ -0,0 +1,21 @@ +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/includes/_js-get-resources.md b/docs/tasks/includes/_js-get-resources.md new file mode 100644 index 00000000..4dd02de4 --- /dev/null +++ b/docs/tasks/includes/_js-get-resources.md @@ -0,0 +1,67 @@ +:::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 +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..7ddf4d98 --- /dev/null +++ b/docs/tasks/includes/_js-read.md @@ -0,0 +1,24 @@ +:::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 + 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..56885a08 --- /dev/null +++ b/docs/tasks/includes/_js-setup.md @@ -0,0 +1,52 @@ +:::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/includes/_node-build.md b/docs/tasks/includes/_node-build.md new file mode 100644 index 00000000..72d1e2e5 --- /dev/null +++ b/docs/tasks/includes/_node-build.md @@ -0,0 +1,170 @@ +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', + }, + }, + ], +}); +``` + +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 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) . + +```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..422062ba --- /dev/null +++ b/docs/tasks/includes/_node-get-resources.md @@ -0,0 +1,5 @@ +The example below shows how to get resources from manifest data using the Node.js library. + +```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..763985f6 --- /dev/null +++ b/docs/tasks/includes/_node-setup.md @@ -0,0 +1,16 @@ +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/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/includes/_python-build.md b/docs/tasks/includes/_python-build.md new file mode 100644 index 00000000..19338a0b --- /dev/null +++ b/docs/tasks/includes/_python-build.md @@ -0,0 +1,61 @@ + +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 +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 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) + +except Exception as err: + print(err) +``` \ 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..f2200c4c --- /dev/null +++ b/docs/tasks/includes/_python-get-resources.md @@ -0,0 +1,23 @@ + +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()`._ + +```py +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..4020ef6e --- /dev/null +++ b/docs/tasks/includes/_python-read.md @@ -0,0 +1,22 @@ + +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()) + +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..01388bfa --- /dev/null +++ b/docs/tasks/includes/_python-setup.md @@ -0,0 +1,21 @@ +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/includes/_rust-build.md b/docs/tasks/includes/_rust-build.md new file mode 100644 index 00000000..4a569420 --- /dev/null +++ b/docs/tasks/includes/_rust-build.md @@ -0,0 +1,48 @@ +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): + +```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..52347b94 --- /dev/null +++ b/docs/tasks/includes/_rust-get-resources.md @@ -0,0 +1,33 @@ +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; +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..d44ea756 --- /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..017826cb --- /dev/null +++ b/docs/tasks/includes/_rust-setup.md @@ -0,0 +1,21 @@ +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/index.md b/docs/tasks/index.md new file mode 100644 index 00000000..bedb3c62 --- /dev/null +++ b/docs/tasks/index.md @@ -0,0 +1,12 @@ +--- +id: working-manifests +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/docs/tasks/read.mdx b/docs/tasks/read.mdx new file mode 100644 index 00000000..9c20c7bf --- /dev/null +++ b/docs/tasks/read.mdx @@ -0,0 +1,51 @@ +--- +id: read +title: Reading manifest data +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 NodeWIP from './includes/_node-wip.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 new file mode 100644 index 00000000..b272ae94 --- /dev/null +++ b/docs/tasks/setup.mdx @@ -0,0 +1,50 @@ +--- +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'; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 diff --git a/sidebars.js b/sidebars.js index 168ed59b..943562e7 100644 --- a/sidebars.js +++ b/sidebars.js @@ -61,6 +61,31 @@ const sidebars = { ], }, + { + type: 'category', + label: 'Working with manifests', + link: { type: 'doc', id: 'tasks/working-manifests' }, + collapsed: true, + items: [ + { + type: 'doc', + id: 'tasks/setup', + }, + { + type: 'doc', + id: 'tasks/read', + }, + { + type: 'doc', + id: 'tasks/get-resources', + }, + { + type: 'doc', + id: 'tasks/build', + }, + ], + }, + { type: 'category', label: 'C2PA Tool',