Skip to content

Commit 2622fa9

Browse files
authored
docs: Update examples (#118)
* fix: More docs, and readme for examples * fix: Wording * fix: Examples * fix: Examples * fix: Examples * fix: Add a note * fix: More editorial changes * fix: Update corrupted file (#120) * fix: Add a test * fix: Add a debug test for CAWG * fix: Fix corrupted file * fix: Cleaning up * fix: Fix non-threaded tests * fix: unrandomize test * fix: Improve test sensitivity * fix: Improve comment * fix: No-op change
1 parent d9cafc4 commit 2622fa9

File tree

6 files changed

+235
-190
lines changed

6 files changed

+235
-190
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Install the c2pa-python package from PyPI by running:
2323
pip install c2pa-python
2424
```
2525

26-
To use the module in Python code, import it like this:
26+
To use the module in Python code, import the module like this:
2727

2828
```python
2929
import c2pa

docs/usage.md

Lines changed: 111 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22

33
This package works with media files in the [supported formats](https://github.com/contentauth/c2pa-rs/blob/main/docs/supported-formats.md).
44

5+
For complete working examples, see the [examples folder](https://github.com/contentauth/c2pa-python/tree/main/examples) in the repository.
6+
57
## Import
68

7-
Import the API as follows:
9+
Import the objects needed from the API:
810

911
```py
10-
from c2pa import *
12+
from c2pa import Builder, Reader, Signer, C2paSigningAlg, C2paSignerInfo
1113
```
1214

15+
You can use both `Builder` and `Reader` classes with context managers by using a `with` statement.
16+
Doing this is recommended to ensure proper resource and memory cleanup.
17+
1318
## Define manifest JSON
1419

1520
The Python library works with both file-based and stream-based operations.
16-
In both cases, the manifest JSON string defines the C2PA manifest to add to an asset; for example:
21+
In both cases, the manifest JSON string defines the C2PA manifest to add to an asset. For example:
1722

1823
```py
1924
manifest_json = json.dumps({
@@ -34,58 +39,33 @@ manifest_json = json.dumps({
3439
})
3540
```
3641

37-
## Signing function
38-
39-
The `sign_ps256` function is [defined in the library](https://github.com/contentauth/c2pa-python/blob/main/c2pa/c2pa_api/c2pa_api.py#L244) and used in both file-based and stream-based methods. It's reproduced here to show how signing is performed.
40-
41-
```py
42-
# Example of using Python crypto to sign data using openssl with Ps256
43-
from cryptography.hazmat.primitives import hashes, serialization
44-
from cryptography.hazmat.primitives.asymmetric import padding
45-
46-
def sign_ps256(data: bytes, key: bytes) -> bytes:
47-
private_key = serialization.load_pem_private_key(
48-
key,
49-
password=None,
50-
)
51-
signature = private_key.sign(
52-
data,
53-
padding.PSS(
54-
mgf=padding.MGF1(hashes.SHA256()),
55-
salt_length=padding.PSS.MAX_LENGTH
56-
),
57-
hashes.SHA256()
58-
)
59-
return signature
60-
```
61-
6242
## File-based operation
6343

6444
### Read and validate C2PA data
6545

66-
Use the `Reader` to read C2PA data from the specified asset file.
46+
Use the `Reader` to read C2PA data from the specified asset file.
6747

6848
This examines the specified media file for C2PA data and generates a report of any data it finds. If there are validation errors, the report includes a `validation_status` field.
6949

70-
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. The manifests may contain binary resources such as thumbnails which can be retrieved with `resource_to_stream` or `resource_to_file` using the associated `identifier` field values and a `uri`.
50+
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. The manifests may contain binary resources such as thumbnails which can be retrieved with `resource_to_stream` using the associated `identifier` field values and a `uri`.
7151

7252
NOTE: For a comprehensive reference to the JSON manifest structure, see the [Manifest store reference](https://opensource.contentauthenticity.org/docs/manifest/manifest-ref).
7353

7454
```py
7555
try:
76-
# Create a reader from a file path
77-
reader = c2pa.Reader.from_file("path/to/media_file.jpg")
78-
79-
# Print the JSON for a manifest.
80-
print("Manifest store:", reader.json())
81-
82-
# Get the active manifest.
83-
manifest = reader.get_active_manifest()
84-
if manifest != None:
85-
86-
# get the uri to the manifest's thumbnail and write it to a file
87-
uri = manifest["thumbnail"]["identifier"]
88-
reader.resource_to_file(uri, "thumbnail_v2.jpg")
56+
# Create a reader from a file path
57+
with Reader("path/to/media_file.jpg") as reader:
58+
# Print manifest store as JSON
59+
print("Manifest store:", reader.json())
60+
61+
# Get the active manifest.
62+
manifest = json.loads(reader.json())
63+
active_manifest = manifest["manifests"][manifest["active_manifest"]]
64+
if active_manifest:
65+
# get the uri to the manifest's thumbnail and write it to a file
66+
uri = active_manifest["thumbnail"]["identifier"]
67+
with open("thumbnail_v2.jpg", "wb") as f:
68+
reader.resource_to_stream(uri, f)
8969

9070
except Exception as err:
9171
print(err)
@@ -98,76 +78,68 @@ except Exception as err:
9878
Use a `Builder` to add a manifest to an asset:
9979

10080
```py
101-
def test_v2_sign(self):
102-
# Define source folder for any assets being read.
103-
data_dir = "tests/fixtures/"
104-
try:
105-
key = open(data_dir + "ps256.pem", "rb").read()
106-
def sign(data: bytes) -> bytes:
107-
return sign_ps256(data, key)
108-
109-
certs = open(data_dir + "ps256.pub", "rb").read()
110-
# Create a local signer from a certificate pem file.
111-
signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
112-
113-
builder = Builder(manifest_def)
114-
115-
builder.add_ingredient_file(ingredient_def, data_dir + "A.jpg")
116-
117-
builder.add_resource_file("A.jpg", data_dir + "A.jpg")
118-
119-
builder.to_archive(open("target/archive.zip", "wb"))
120-
121-
builder = Builder.from_archive(open("target/archive.zip", "rb"))
122-
123-
with tempfile.TemporaryDirectory() as output_dir:
124-
output_path = output_dir + "out.jpg"
125-
if os.path.exists(output_path):
126-
os.remove(output_path)
127-
c2pa_data = builder.sign_file(signer, data_dir + "A.jpg", output_dir + "out.jpg")
128-
assert len(c2pa_data) > 0
129-
130-
reader = Reader.from_file(output_dir + "out.jpg")
131-
print(reader.json())
132-
manifest_store = json.loads(reader.json())
133-
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]
134-
assert "python_test" in manifest["claim_generator"]
135-
# Check custom title and format.
136-
assert manifest["title"]== "My Title"
137-
assert manifest,["format"] == "image/jpeg"
138-
# There should be no validation status errors.
139-
assert manifest.get("validation_status") == None
140-
assert manifest["ingredients"][0]["relationship"] == "parentOf"
141-
assert manifest["ingredients"][0]["title"] == "A.jpg"
142-
143-
except Exception as e:
144-
print("Failed to sign manifest store: " + str(e))
145-
exit(1)
81+
try:
82+
# Create a signer from certificate and key files
83+
with open("path/to/cert.pem", "rb") as cert_file, open("path/to/key.pem", "rb") as key_file:
84+
cert_data = cert_file.read()
85+
key_data = key_file.read()
86+
87+
# Create signer info using cert and key info
88+
signer_info = C2paSignerInfo(
89+
alg=C2paSigningAlg.PS256,
90+
cert=cert_data,
91+
key=key_data,
92+
timestamp_url="http://timestamp.digicert.com"
93+
)
94+
95+
# Create signer using the defined SignerInfo
96+
signer = Signer.from_info(signer_info)
97+
98+
# Create builder with manifest and add ingredients
99+
with Builder(manifest_json) as builder:
100+
# Add any ingredients if needed
101+
with open("path/to/ingredient.jpg", "rb") as ingredient_file:
102+
ingredient_json = json.dumps({"title": "Ingredient Image"})
103+
builder.add_ingredient(ingredient_json, "image/jpeg", ingredient_file)
104+
105+
# Sign the file
106+
with open("path/to/source.jpg", "rb") as source_file, open("path/to/output.jpg", "wb") as dest_file:
107+
manifest_bytes = builder.sign(signer, "image/jpeg", source_file, dest_file)
108+
109+
# Verify the signed file
110+
with Reader("path/to/output.jpg") as reader:
111+
manifest_store = json.loads(reader.json())
112+
active_manifest = manifest_store["manifests"][manifest_store["active_manifest"]]
113+
print("Signed manifest:", active_manifest)
114+
115+
except Exception as e:
116+
print("Failed to sign manifest store: " + str(e))
146117
```
147118

148119
## Stream-based operation
149120

150-
Instead of working with files, you can read, validate, and add a signed manifest to streamed data. This example code does the same thing as the file-based example.
121+
Instead of working with files, you can read, validate, and add a signed manifest to streamed data. This example is similar to what the file-based example does.
151122

152-
### Read and validate C2PA data
123+
### Read and validate C2PA data using streams
153124

154125
```py
155126
try:
156-
# It's also possible to create a reader from a format and stream
157-
# Note that these two readers are functionally equivalent
158-
stream = open("path/to/media_file.jpg", "rb")
159-
reader = c2pa.Reader("image/jpeg", stream)
160-
161-
# Print the JSON for a manifest.
162-
print("manifest store:", reader.json())
163-
164-
# Get the active manifest.
165-
manifest = reader.get_active_manifest()
166-
if manifest != None:
167-
168-
# get the uri to the manifest's thumbnail and write it to a file
169-
uri = manifest["thumbnail"]["identifier"]
170-
reader.resource_to_file(uri, "thumbnail_v2.jpg")
127+
# Create a reader from a format and stream
128+
with open("path/to/media_file.jpg", "rb") as stream:
129+
# First parameter can be mimetype or extension of the file
130+
# But in any case we need something to identify the file type
131+
with Reader("image/jpeg", stream) as reader:
132+
# Print manifest store as JSON, as extracted by the Reader
133+
print("manifest store:", reader.json())
134+
135+
# Get the active manifest.
136+
manifest = json.loads(reader.json())
137+
active_manifest = manifest["manifests"][manifest["active_manifest"]]
138+
if active_manifest:
139+
# get the uri to the manifest's thumbnail and write it to a file
140+
uri = active_manifest["thumbnail"]["identifier"]
141+
with open("thumbnail_v2.jpg", "wb") as f:
142+
reader.resource_to_stream(uri, f)
171143

172144
except Exception as err:
173145
print(err)
@@ -180,39 +152,41 @@ except Exception as err:
180152
Use a `Builder` to add a manifest to an asset:
181153

182154
```py
183-
from c2pa import Builder, Error, Reader, SigningAlg, create_signer, sdk_version, sign_ps256, version
184-
...
185-
data_dir = "tests/fixtures/"
186155
try:
187-
key = open(data_dir + "ps256.pem", "rb").read()
188-
def sign(data: bytes) -> bytes:
189-
return sign_ps256(data, key)
190-
191-
certs = open(data_dir + "ps256.pub", "rb").read()
192-
# Create a local signer from a certificate pem file
193-
signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
194-
195-
builder = Builder(manifest_def)
196-
197-
builder.add_ingredient_file(ingredient_def, data_dir + "A.jpg")
198-
builder.add_resource_file("A.jpg", data_dir + "A.jpg")
199-
builder.to_archive(open("target/archive.zip", "wb"))
200-
201-
builder = Builder.from_archive(open("target/archive.zip", "rb"))
202-
203-
with tempfile.TemporaryDirectory() as output_dir:
204-
output_path = output_dir + "out.jpg"
205-
if os.path.exists(output_path):
206-
os.remove(output_path)
207-
c2pa_data = builder.sign_file(signer, data_dir + "A.jpg", output_dir + "out.jpg")
208-
assert len(c2pa_data) > 0
209-
210-
reader = Reader.from_file(output_dir + "out.jpg")
211-
print(reader.json())
212-
manifest_store = json.loads(reader.json())
213-
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]
156+
# Create a signer from certificate and key files
157+
with open("path/to/cert.pem", "rb") as cert_file, open("path/to/key.pem", "rb") as key_file:
158+
cert_data = cert_file.read()
159+
key_data = key_file.read()
160+
161+
# Create signer info
162+
signer_info = C2paSignerInfo(
163+
alg=C2paSigningAlg.PS256,
164+
cert=cert_data,
165+
key=key_data,
166+
timestamp_url="http://timestamp.digicert.com"
167+
)
168+
169+
# Create signer using the defined SignerInfo
170+
signer = Signer.from_info(signer_info)
171+
172+
# Create builder with manifest and add ingredients
173+
with Builder(manifest_json) as builder:
174+
# Add any ingredients if needed
175+
with open("path/to/ingredient.jpg", "rb") as ingredient_file:
176+
ingredient_json = json.dumps({"title": "Ingredient Image"})
177+
builder.add_ingredient(ingredient_json, "image/jpeg", ingredient_file)
178+
179+
# Sign using streams
180+
with open("path/to/source.jpg", "rb") as source_file, open("path/to/output.jpg", "wb") as dest_file:
181+
manifest_bytes = builder.sign(signer, "image/jpeg", source_file, dest_file)
182+
183+
# Verify the signed file
184+
with open("path/to/output.jpg", "rb") as stream:
185+
with Reader("image/jpeg", stream) as reader:
186+
manifest_store = json.loads(reader.json())
187+
active_manifest = manifest_store["manifests"][manifest_store["active_manifest"]]
188+
print("Signed manifest:", active_manifest)
214189

215190
except Exception as e:
216191
print("Failed to sign manifest store: " + str(e))
217-
exit(1)
218-
```
192+
```

examples/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Usage examples
2+
3+
## Examples
4+
5+
### Adding a "Do Not Train" Assertion
6+
7+
The `examples/training.py` script demonstrates how to add a "Do Not Train" assertion to an asset and verify it.
8+
9+
### Signing and Verifying Assets
10+
11+
The `examples/sign.py` script shows how to sign an asset with a C2PA manifest and verify it.
12+
13+
## Running the Examples
14+
15+
To run the examples, make sure you have the c2pa-python package installed and you're in the root directory of the project. We recommend working using virtual environments (venv).
16+
17+
Then you can run the examples with the following commands:
18+
19+
```bash
20+
# Run the "Do Not Train" assertion example
21+
python examples/training.py
22+
23+
# Run the signing and verification example
24+
python examples/sign.py
25+
```
26+
27+
The examples will use test files from the `tests/fixtures` directory and output the results to the temporary `output` directory. Read manifest store data will be shown in the console you run the examples from.
51.3 KB
Loading

tests/test_unit_tests.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,30 @@ def test_read_all_files_using_extension(self):
212212
except Exception as e:
213213
self.fail(f"Failed to read metadata from {filename}: {str(e)}")
214214

215+
def test_read_cawg_data_file(self):
216+
"""Test reading C2PA metadata from C_with_CAWG_data.jpg file."""
217+
file_path = os.path.join(self.data_dir, "files-for-reading-tests", "C_with_CAWG_data.jpg")
218+
219+
with open(file_path, "rb") as file:
220+
reader = Reader("image/jpeg", file)
221+
json_data = reader.json()
222+
self.assertIsInstance(json_data, str)
223+
224+
# Parse the JSON and verify specific fields
225+
manifest_data = json.loads(json_data)
226+
227+
# Verify basic manifest structure
228+
self.assertIn("manifests", manifest_data)
229+
self.assertIn("active_manifest", manifest_data)
230+
231+
# Get the active manifest
232+
active_manifest_id = manifest_data["active_manifest"]
233+
active_manifest = manifest_data["manifests"][active_manifest_id]
234+
235+
# Verify manifest is not null or empty
236+
assert active_manifest is not None, "Active manifest should not be null"
237+
assert len(active_manifest) > 0, "Active manifest should not be empty"
238+
215239

216240
class TestBuilder(unittest.TestCase):
217241
def setUp(self):

0 commit comments

Comments
 (0)