Skip to content

Commit 90f2e6e

Browse files
authored
Merge branch 'main' into gpeacock/manifest_and_stream
2 parents 8b072eb + 297e9d2 commit 90f2e6e

File tree

1 file changed

+169
-72
lines changed

1 file changed

+169
-72
lines changed

README.md

Lines changed: 169 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# C2PA Python
22

3-
Python bindings for the C2PA Content Authenticity Initiative (CAI) library.
3+
This repository implements Python bindings for the Content Authenticity Initiative (CAI) library.
44

55
This library enables you to read and validate C2PA data in supported media files and add signed manifests to supported media files.
66

7-
**NOTE**: This is a completely different API from 0.4.0. Check [Release notes](#release-notes) for changes.
7+
**NOTE**: Starting with version 0.5.0, this package has a completely different API from version 0.4.0. See [Release notes](#release-notes) for more information.
88

99
**WARNING**: This is an prerelease version of this library. There may be bugs and unimplemented features, and the API is subject to change.
1010

@@ -18,6 +18,16 @@ pip install -U c2pa-python
1818

1919
This is a platform wheel built with Rust that works on Windows, macOS, and most Linux distributions (using [manylinux](https://github.com/pypa/manylinux)). If you need to run on another platform, see [Development](#development) for information on how to build from source.
2020

21+
### Updating
22+
23+
Determine what version you've got by entering this command:
24+
25+
```
26+
pip list | grep c2pa-python
27+
```
28+
29+
If the version shown is lower than the most recent version, then update by [reinstalling](#installation).
30+
2131
### Reinstalling
2232

2333
If you tried unsuccessfully to install this package before the [0.40 release](https://github.com/contentauth/c2pa-python/releases/tag/v0.4), then use this command to reinstall:
@@ -36,24 +46,72 @@ Import the API as follows:
3646
from c2pa import *
3747
```
3848

39-
### Read and validate C2PA data in a file or stream
49+
### Define manifest JSON
4050

41-
Use the `Reader` to read C2PA data from the specified file.
42-
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. For a summary of supported media types, see [Supported file formats](#supported-file-formats).
51+
The Python library works with both file-based and stream-based operations.
52+
In both cases, the manifest JSON string defines the C2PA manifest to add to an asset; for example:
4353

44-
A media 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.
54+
```py
55+
manifest_json = json.dumps({
56+
"claim_generator": "python_test/0.1",
57+
"assertions": [
58+
{
59+
"label": "c2pa.training-mining",
60+
"data": {
61+
"entries": {
62+
"c2pa.ai_generative_training": { "use": "notAllowed" },
63+
"c2pa.ai_inference": { "use": "notAllowed" },
64+
"c2pa.ai_training": { "use": "notAllowed" },
65+
"c2pa.data_mining": { "use": "notAllowed" }
66+
}
67+
}
68+
}
69+
]
70+
})
71+
```
72+
73+
### Signing function
74+
75+
The `sign_ps256` function is [defined in the library](https://github.com/contentauth/c2pa-python/blob/main/c2pa/c2pa_api/c2pa_api.py#L209) and is reproduced here to show how signing is performed.
76+
77+
```py
78+
# Example of using Python crypto to sign data using openssl with Ps256
79+
from cryptography.hazmat.primitives import hashes, serialization
80+
from cryptography.hazmat.primitives.asymmetric import padding
81+
82+
def sign_ps256(data: bytes, key_path: str) -> bytes:
83+
with open(key_path, "rb") as key_file:
84+
private_key = serialization.load_pem_private_key(
85+
key_file.read(),
86+
password=None,
87+
)
88+
signature = private_key.sign(
89+
data,
90+
padding.PSS(
91+
mgf=padding.MGF1(hashes.SHA256()),
92+
salt_length=padding.PSS.MAX_LENGTH
93+
),
94+
hashes.SHA256()
95+
)
96+
return signature
97+
```
98+
99+
### File-based operation
100+
101+
**Read and validate C2PA data from an asset file**
102+
103+
Use the `Reader` to read C2PA data from the specified asset file (see [supported file formats](#supported-file-formats)).
104+
105+
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.
45106

46-
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`.
107+
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`.
47108

48109
NOTE: For a comprehensive reference to the JSON manifest structure, see the [Manifest store reference](https://opensource.contentauthenticity.org/docs/manifest/manifest-ref).
110+
49111
```py
50112
try:
51113
# Create a reader from a file path
52114
reader = c2pa.Reader.from_file("path/to/media_file.jpg")
53-
# It's also possible to create a reader from a format and stream
54-
# Note that these two readers are functionally equivalent
55-
stream = open("path/to/media_file.jpg", "rb")
56-
reader = c2pa.Reader("image/jpeg", stream)
57115

58116
# Print the JSON for a manifest.
59117
print("manifest store:", reader.json())
@@ -70,9 +128,9 @@ except Exception as err:
70128
print(err)
71129
```
72130

73-
### Add a signed manifest to a media file or stream
131+
**Add a signed manifest to an asset file**
74132

75-
**WARNING**: This example accesses the private key and security certficate directly from the local file system. This is fine during development, but doing so in production may be insecure. Instead use a Key Management Service (KMS) or a hardware security module (HSM) to access the certificate and key; for example as show in the [C2PA Python Example](https://github.com/contentauth/c2pa-python-example).
133+
**WARNING**: This example accesses the private key and security certificate directly from the local file system. This is fine during development, but doing so in production may be insecure. Instead use a Key Management Service (KMS) or a hardware security module (HSM) to access the certificate and key; for example as show in the [C2PA Python Example](https://github.com/contentauth/c2pa-python-example).
76134

77135
Use a `Builder` to add a manifest to an asset:
78136

@@ -90,42 +148,12 @@ try:
90148
# Create a signer from the private signer, certs and a time stamp service url
91149
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
92150

93-
# Define a manifest with thumbnail and an assertion.
94-
manifest_json = {
95-
"claim_generator_info": [{
96-
"name": "python_test",
97-
"version": "0.1"
98-
}],
99-
"title": "Do Not Train Example",
100-
"thumbnail": {
101-
"format": "image/jpeg",
102-
"identifier": "thumbnail"
103-
},
104-
"assertions": [
105-
{
106-
"label": "c2pa.training-mining",
107-
"data": {
108-
"entries": {
109-
"c2pa.ai_generative_training": { "use": "notAllowed" },
110-
"c2pa.ai_inference": { "use": "notAllowed" },
111-
"c2pa.ai_training": { "use": "notAllowed" },
112-
"c2pa.data_mining": { "use": "notAllowed" }
113-
}
114-
}
115-
}
116-
]
117-
}
118-
119151
# Create a builder add a thumbnail resource and an ingredient file.
120152
builder = Builder(manifest_json)
121153

122154
# The uri provided here "thumbnail" must match an identifier in the manifest definition.
123155
builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg")
124156

125-
# Or add the resource from a stream
126-
a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb")
127-
builder.add_resource("image/jpeg", a_thumbnail_jpg_stream)
128-
129157
# Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail
130158
ingredient_json = {
131159
"title": "A.jpg",
@@ -139,10 +167,6 @@ try:
139167
# Add the ingredient to the builder loading information from a source file.
140168
builder.add_ingredient_file(ingredient_json, "tests/fixtures/A.jpg")
141169

142-
# Or add the ingredient from a stream
143-
a_jpg_stream = open("tests/fixtures/A.jpg", "rb")
144-
builder.add_ingredient("image/jpeg", a_jpg_stream)
145-
146170
# At this point we could archive or unarchive our Builder to continue later.
147171
# In this example we use a bytearray for the archive stream.
148172
# all ingredients and resources will be saved in the archive
@@ -155,37 +179,96 @@ try:
155179
# This returns the binary manifest data that could be uploaded to cloud storage.
156180
c2pa_data = builder.sign_file(signer, "tests/fixtures/A.jpg", "target/out.jpg")
157181

158-
# Or sign the builder with a stream and output it to a stream
159-
input_stream = open("tests/fixtures/A.jpg", "rb")
160-
output_stream = open("target/out.jpg", "wb")
161-
c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream)
182+
except Exception as err:
183+
print(err)
184+
```
185+
186+
### Stream-based operation
187+
188+
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.
189+
190+
**Read and validate C2PA data from a stream**
191+
192+
```py
193+
try:
194+
# It's also possible to create a reader from a format and stream
195+
# Note that these two readers are functionally equivalent
196+
stream = open("path/to/media_file.jpg", "rb")
197+
reader = c2pa.Reader("image/jpeg", stream)
198+
199+
# Print the JSON for a manifest.
200+
print("manifest store:", reader.json())
201+
202+
# Get the active manifest.
203+
manifest = reader.get_active_manifest()
204+
if manifest != None:
205+
206+
# get the uri to the manifest's thumbnail and write it to a file
207+
uri = manifest["thumbnail"]["identifier"]
208+
reader.resource_to_file(uri, "thumbnail_v2.jpg")
162209

163210
except Exception as err:
164211
print(err)
165-
```
212+
```
213+
214+
**Add a signed manifest to a stream**
166215

167-
### Creating a manifest JSON definition file
216+
**WARNING**: This example accesses the private key and security certificate directly from the local file system. This is fine during development, but doing so in production may be insecure. Instead use a Key Management Service (KMS) or a hardware security module (HSM) to access the certificate and key; for example as show in the [C2PA Python Example](https://github.com/contentauth/c2pa-python-example).
168217

169-
The manifest JSON string defines the C2PA manifest to add to the file.
218+
Use a `Builder` to add a manifest to an asset:
170219

171220
```py
172-
manifest_json = json.dumps({
173-
"claim_generator": "python_test/0.1",
174-
"assertions": [
175-
{
176-
"label": "c2pa.training-mining",
177-
"data": {
178-
"entries": {
179-
"c2pa.ai_generative_training": { "use": "notAllowed" },
180-
"c2pa.ai_inference": { "use": "notAllowed" },
181-
"c2pa.ai_training": { "use": "notAllowed" },
182-
"c2pa.data_mining": { "use": "notAllowed" }
183-
}
184-
}
221+
try:
222+
# Define a function to sign the claim bytes
223+
# In this case we are using a pre-defined sign_ps256 method, passing in our private cert
224+
# Normally this cert would be kept safe in some other location
225+
def private_sign(data: bytes) -> bytes:
226+
return sign_ps256(data, "tests/fixtures/ps256.pem")
227+
228+
# read our public certs into memory
229+
certs = open(data_dir + "ps256.pub", "rb").read()
230+
231+
# Create a signer from the private signer, certs and a time stamp service url
232+
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
233+
234+
# Create a builder add a thumbnail resource and an ingredient file.
235+
builder = Builder(manifest_json)
236+
237+
# Add the resource from a stream
238+
a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb")
239+
builder.add_resource("image/jpeg", a_thumbnail_jpg_stream)
240+
241+
# Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail
242+
ingredient_json = {
243+
"title": "A.jpg",
244+
"relationship": "parentOf", # "parentOf", "componentOf" or "inputTo"
245+
"thumbnail": {
246+
"identifier": "thumbnail",
247+
"format": "image/jpeg"
185248
}
186-
]
187-
})
188-
```
249+
}
250+
251+
# Add the ingredient from a stream
252+
a_jpg_stream = open("tests/fixtures/A.jpg", "rb")
253+
builder.add_ingredient("image/jpeg", a_jpg_stream)
254+
255+
# At this point we could archive or unarchive our Builder to continue later.
256+
# In this example we use a bytearray for the archive stream.
257+
# all ingredients and resources will be saved in the archive
258+
archive = io.BytesIO(bytearray())
259+
builder.to_archive(archive)
260+
archive.seek()
261+
builder = builder.from_archive(archive)
262+
263+
# Sign the builder with a stream and output it to a stream
264+
# This returns the binary manifest data that could be uploaded to cloud storage.
265+
input_stream = open("tests/fixtures/A.jpg", "rb")
266+
output_stream = open("target/out.jpg", "wb")
267+
c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream)
268+
269+
except Exception as err:
270+
print(err)
271+
```
189272

190273
## Supported file formats
191274

@@ -195,10 +278,12 @@ manifest_json = json.dumps({
195278
| `avif` | `image/avif` |
196279
| `c2pa` | `application/x-c2pa-manifest-store` |
197280
| `dng` | `image/x-adobe-dng` |
281+
| `gif` | `image/gif` |
198282
| `heic` | `image/heic` |
199283
| `heif` | `image/heif` |
200284
| `jpg`, `jpeg` | `image/jpeg` |
201285
| `m4a` | `audio/mp4` |
286+
| `mp3` | `audio/mpeg` |
202287
| `mp4` | `video/mp4`, `application/mp4` |
203288
| `mov` | `video/quicktime` |
204289
| `png` | `image/png` |
@@ -292,10 +377,22 @@ deactivate
292377

293378
## Release notes
294379

380+
### Version 0.5.2
381+
382+
New features:
383+
384+
- Allow EC signatures in DER format from signers and verify signature format during validation.
385+
- Fix bug in signing audio and video files in ISO Base Media File Format (BMFF).
386+
- Add the ability to verify PDF files (but not to sign them).
387+
- Increase speed of `sign_file` by 2x or more, when using file paths (uses native Rust file I/O).
388+
- Fixes for RIFF and GIF formats.
389+
295390
### Version 0.5.0
296391

297-
- This release rewrites the API to be stream based using a Builder and Reader model.
298-
- The functions now support throwing c2pa.Error values, caught with try/except.
392+
New features in this release:
393+
394+
- Rewrites the API to be stream-based using a Builder / Reader model.
395+
- The functions now support throwing `c2pa.Error` values, caught with `try`/`except`.
299396
- Instead of `c2pa.read_file` you now call `c2pa_api.Reader.from_file` and `reader.json`.
300397
- Read thumbnails and other resources use `reader.resource_to_stream` or `reader.resource.to_file`.
301398
- Instead of `c2pa.sign_file` use `c2pa_api.Builder.from_json` and `builder.sign` or `builder.sign_file`.

0 commit comments

Comments
 (0)