Skip to content

Commit a4f47ef

Browse files
author
Dave Kozma
authored
Merge pull request #15 from contentauth/v2
New API model with Builder and Reader
2 parents 2304265 + a94a7d0 commit a4f47ef

22 files changed

+1679
-238
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@ __pycache__/
2121
.pytest_cache/
2222
dist
2323

24+
c2pa/c2pa/
25+
2426
# Mac OS X files
2527
.DS_Store

.vscode/settings.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"rust-analyzer.linkedProjects": [
3+
"./Cargo.toml"
4+
],
5+
"rust-analyzer.showUnlinkedFileNotification": false
6+
}

Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "c2pa-python"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
edition = "2021"
55
authors = ["Gavin Peacock <[email protected]"]
66

@@ -9,9 +9,11 @@ authors = ["Gavin Peacock <[email protected]"]
99
name = "c2pa"
1010
crate-type = ["cdylib"]
1111

12+
1213
[dependencies]
13-
c2pa-c = { git = "https://github.com/contentauth/c2pa-c.git", branch = "main" }
14-
serde = { version = "1.0", features = ["derive"] }
14+
c2pa = {version = "0.32.0", features = ["unstable_api", "openssl"]}
15+
pem = "3.0.3"
16+
serde = { version = "1.0.197", features = ["derive"] }
1517
serde_derive = "1.0"
1618
serde_json = "1.0"
1719
thiserror = "1.0.49"

README.md

Lines changed: 108 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ Python bindings for the C2PA 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-
**WARNING**: This is an early prerelease version of this library. There may be bugs and unimplemented features, and the API is subject to change.
7+
**NOTE**: This is a completely different API from 0.4.0. Check [Release notes](#release-notes) for changes.
8+
9+
**WARNING**: This is an prerelease version of this library. There may be bugs and unimplemented features, and the API is subject to change.
810

911
## Installation
1012

@@ -28,63 +30,119 @@ pip install --upgrade --force-reinstall c2pa-python
2830

2931
### Import
3032

31-
Import the C2PA module as follows:
33+
Import the API as follows:
3234

3335
```py
34-
import c2pa
36+
from c2pa import *
3537
```
3638

3739
### Read and validate C2PA data in a file
3840

39-
Use the `read_file` function to read C2PA data from the specified file:
40-
41-
```py
42-
json_store = c2pa.read_file("path/to/media_file.jpg", "path/to/data_dir")
43-
```
44-
45-
This function examines the specified media file for C2PA data and generates a JSON 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).
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).
4643

4744
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.
4845

49-
If the optional `data_dir` is 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.
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`.
5047

5148
NOTE: For a comprehensive reference to the JSON manifest structure, see the [Manifest store reference](https://opensource.contentauthenticity.org/docs/manifest/manifest-ref).
49+
```py
50+
try:
51+
reader = c2pa.Reader("path/to/media_file.jpg")
5252

53-
### Add a signed manifest to a media file
53+
# Print the JSON for a manifest.
54+
print("manifest store:", reader.json())
5455

55-
Use the `sign_file` function to add a signed manifest to a media file.
56+
# Get the active manifest.
57+
manifest = reader.get_active_manifest()
58+
if manifest != None:
5659

57-
```py
58-
result = c2pa.sign_file("path/to/source.jpg",
59-
"path/to/dest.jpg",
60-
manifest_json,
61-
sign_info,
62-
data_dir)
63-
```
60+
# get the uri to the manifest's thumbnail and write it to a file
61+
uri = manifest["thumbnail"]["identifier"]
62+
reader.resource_to_file(uri, "thumbnail_v2.jpg")
6463

65-
The parameters (in order) are:
66-
- The source (original) media file.
67-
- The destination file that will contain a copy of the source file with the manifest data added.
68-
- `manifest_json`, a JSON-formatted string containing the manifest data you want to add; see [Creating a manifest JSON definition file](#creating-a-manifest-json-definition-file) below.
69-
- `sign_info`, a `SignerInfo` object instance; see [Generating SignerInfo](#generating-signerinfo) below.
70-
- `data_dir` optionally specifies a directory path from which to load resource files referenced in the manifest JSON identifier fields; for example, thumbnails, icons, and manifest data for ingredients.
64+
except Exception as err:
65+
print(err)
66+
```
7167

72-
### Create a SignerInfo instance
68+
### Add a signed manifest to a media file
7369

74-
A `SignerInfo` object contains information about a signature. To create an instance of `SignerInfo`, first set up the signer information from the public and private key `.pem` files as follows:
70+
Use a `Builder` to add a manifest to an asset.
7571

7672
```py
77-
certs = open("path/to/public_certs.pem","rb").read()
78-
prv_key = open("path/to/private_key.pem","rb").read()
79-
```
73+
try:
74+
# Define a function to sign the claim bytes
75+
# In this case we are using a pre-defined sign_ps256 method, passing in our private cert
76+
# Normally this cert would be kept safe in some other location
77+
def private_sign(data: bytes) -> bytes:
78+
return sign_ps256(data, "tests/fixtures/ps256.pem")
79+
80+
# read our public certs into memory
81+
certs = open(data_dir + "ps256.pub", "rb").read()
82+
83+
# Create a signer from the private signer, certs and a time stamp service url
84+
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
85+
86+
# Define a manifest with thumbnail and an assertion.
87+
manifest_json = {
88+
"claim_generator_info": [{
89+
"name": "python_test",
90+
"version": "0.1"
91+
}],
92+
"title": "Do Not Train Example",
93+
"thumbnail": {
94+
"format": "image/jpeg",
95+
"identifier": "thumbnail"
96+
},
97+
"assertions": [
98+
{
99+
"label": "c2pa.training-mining",
100+
"data": {
101+
"entries": {
102+
"c2pa.ai_generative_training": { "use": "notAllowed" },
103+
"c2pa.ai_inference": { "use": "notAllowed" },
104+
"c2pa.ai_training": { "use": "notAllowed" },
105+
"c2pa.data_mining": { "use": "notAllowed" }
106+
}
107+
}
108+
}
109+
]
110+
}
111+
112+
# Create a builder add a thumbnail resource and an ingredient file.
113+
builder = Builder(manifest_json)
114+
115+
# The uri provided here "thumbnail" must match an identifier in the manifest definition.
116+
builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg")
117+
118+
# Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail
119+
ingredient_json = {
120+
"title": "A.jpg",
121+
"relationship": "parentOf", # "parentOf", "componentOf" or "inputTo"
122+
"thumbnail": {
123+
"identifier": "thumbnail",
124+
"format": "image/jpeg"
125+
}
126+
}
80127

81-
Then create a new `SignerInfo` instance using the keys as follows, specifying the signing algorithm used and optionally a time stamp authority URL:
128+
# Add the ingredient to the builder loading information from a source file.
129+
builder.add_ingredient_file(ingredient_json, "tests/fixtures/A.jpg")
82130

83-
```py
84-
sign_info = c2pa.SignerInfo("es256", certs, priv_key, "http://timestamp.digicert.com")
85-
```
131+
# At this point we could archive or unarchive our Builder to continue later.
132+
# In this example we use a bytearray for the archive stream.
133+
# all ingredients and resources will be saved in the archive
134+
archive = io.BytesIO(bytearray())
135+
builder.to_archive(archive)
136+
archive.seek()
137+
builder = builder.from_archive(archive)
86138

87-
For the list of supported signing algorithms, see [Creating and using an X.509 certificate](https://opensource.contentauthenticity.org/docs/c2patool/x_509).
139+
# Sign and add our manifest to a source file, writing it to an output file.
140+
# This returns the binary manifest data that could be uploaded to cloud storage.
141+
c2pa_data = builder.sign_file(signer, "tests/fixtures/A.jpg", "target/out.jpg")
142+
143+
except Exception as err:
144+
print(err)
145+
```
88146

89147
### Creating a manifest JSON definition file
90148

@@ -214,6 +272,20 @@ deactivate
214272

215273
## Release notes
216274

275+
### Version 0.5.0
276+
277+
- This release rewrites the API to be stream based using a Builder and Reader model.
278+
- The functions now support throwing c2pa.Error values, caught with try/except.
279+
- Instead of `c2pa.read_file` you now call `c2pa_api.Reader.from_file` and `reader.json`.
280+
- Read thumbnails and other resources use `reader.resource_to_stream` or `reader.resource.to_file`.
281+
- Instead of `c2pa.sign_file` use `c2pa_api.Builder.from_json` and `builder.sign` or `builder.sign_file`.
282+
- Add thumbnails or other resources with `builder.add_resource` or `builder.add_resource_file`.
283+
- Add Ingredients with `builder.add_ingredient` or `builder.add_ingredient_file`.
284+
- You can archive a `Builder` using `builder.to_archive` and reconstruct it with `builder.from_archive`.
285+
- Signers can be constructed with `c2pa_api.create_signer`.
286+
- The signer now requires a signing function to keep private keys private.
287+
- Example signing functions are provided in c2pa_api.py
288+
217289
### Version 0.4.0
218290

219291
This release:

c2pa/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2024 Adobe. All rights reserved.
2+
# This file is licensed to you under the Apache License,
3+
# Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
4+
# or the MIT license (http://opensource.org/licenses/MIT),
5+
# at your option.
6+
7+
# Unless required by applicable law or agreed to in writing,
8+
# this software is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
10+
# implied. See the LICENSE-MIT and LICENSE-APACHE files for the
11+
# specific language governing permissions and limitations under
12+
# each license.
13+
14+
from .c2pa_api import Reader, Builder, create_signer, sign_ps256
15+
from .c2pa import Error, SigningAlg, sdk_version, version
16+
17+
__all__ = ['Reader', 'Builder', 'create_signer', 'sign_ps256', 'Error', 'SigningAlg', 'sdk_version', 'version']

c2pa/c2pa_api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .c2pa_api import *

0 commit comments

Comments
 (0)