Skip to content

Commit 5df715a

Browse files
committed
Move project contributions and usage info to separate files in docs dir
1 parent 92d6e58 commit 5df715a

File tree

3 files changed

+327
-322
lines changed

3 files changed

+327
-322
lines changed

README.md

Lines changed: 5 additions & 322 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ It enables you to read and validate C2PA manifest data from and add signed manif
99

1010
<div style={{display: 'none'}}>
1111

12-
For information on what's in the current release, see the [Release notes](docs/release-notes.md).
12+
Additional documentation:
13+
- [Using the Python library](docs/usage.md)
14+
- [Release notes](docs/release-notes.md)
15+
- [Contributing to the project](docs/project-contributions.md)
1316

1417
</div>
1518

@@ -21,7 +24,7 @@ Install from PyPI by entering this command:
2124
pip install -U c2pa-python
2225
```
2326

24-
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.
27+
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 [Project contributions - Development](docs/project-contributions.md#development) for information on how to build from source.
2528

2629
### Updating
2730

@@ -45,326 +48,6 @@ pip install --upgrade --force-reinstall c2pa-python
4548

4649
The Python library [supports the same media file formats](https://github.com/contentauth/c2pa-rs/blob/main/docs/supported-formats.md) as the Rust library.
4750

48-
## Usage
49-
50-
This package works with media files in the [supported formats](https://github.com/contentauth/c2pa-rs/blob/main/docs/supported-formats.md).
51-
52-
### Import
53-
54-
Import the API as follows:
55-
56-
```py
57-
from c2pa import *
58-
```
59-
60-
### Define manifest JSON
61-
62-
The Python library works with both file-based and stream-based operations.
63-
In both cases, the manifest JSON string defines the C2PA manifest to add to an asset; for example:
64-
65-
```py
66-
manifest_json = json.dumps({
67-
"claim_generator": "python_test/0.1",
68-
"assertions": [
69-
{
70-
"label": "c2pa.training-mining",
71-
"data": {
72-
"entries": {
73-
"c2pa.ai_generative_training": { "use": "notAllowed" },
74-
"c2pa.ai_inference": { "use": "notAllowed" },
75-
"c2pa.ai_training": { "use": "notAllowed" },
76-
"c2pa.data_mining": { "use": "notAllowed" }
77-
}
78-
}
79-
}
80-
]
81-
})
82-
```
83-
84-
### Signing function
85-
86-
The `sign_ps256` function is [defined in the library](https://github.com/contentauth/c2pa-python/blob/main/c2pa/c2pa_api/c2pa_api.py#L209) is used in both file-based and stream-based methods and is reproduced here to show how signing is performed.
87-
88-
```py
89-
# Example of using Python crypto to sign data using openssl with Ps256
90-
from cryptography.hazmat.primitives import hashes, serialization
91-
from cryptography.hazmat.primitives.asymmetric import padding
92-
93-
def sign_ps256(data: bytes, key_path: str) -> bytes:
94-
with open(key_path, "rb") as key_file:
95-
private_key = serialization.load_pem_private_key(
96-
key_file.read(),
97-
password=None,
98-
)
99-
signature = private_key.sign(
100-
data,
101-
padding.PSS(
102-
mgf=padding.MGF1(hashes.SHA256()),
103-
salt_length=padding.PSS.MAX_LENGTH
104-
),
105-
hashes.SHA256()
106-
)
107-
return signature
108-
```
109-
110-
### File-based operation
111-
112-
**Read and validate C2PA data from an asset file**
113-
114-
Use the `Reader` to read C2PA data from the specified asset file.
115-
116-
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.
117-
118-
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`.
119-
120-
NOTE: For a comprehensive reference to the JSON manifest structure, see the [Manifest store reference](https://opensource.contentauthenticity.org/docs/manifest/manifest-ref).
121-
122-
```py
123-
try:
124-
# Create a reader from a file path
125-
reader = c2pa.Reader.from_file("path/to/media_file.jpg")
126-
127-
# Print the JSON for a manifest.
128-
print("manifest store:", reader.json())
129-
130-
# Get the active manifest.
131-
manifest = reader.get_active_manifest()
132-
if manifest != None:
133-
134-
# get the uri to the manifest's thumbnail and write it to a file
135-
uri = manifest["thumbnail"]["identifier"]
136-
reader.resource_to_file(uri, "thumbnail_v2.jpg")
137-
138-
except Exception as err:
139-
print(err)
140-
```
141-
142-
**Add a signed manifest to an asset file**
143-
144-
**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).
145-
146-
Use a `Builder` to add a manifest to an asset:
147-
148-
```py
149-
try:
150-
# Define a function to sign the claim bytes
151-
# In this case we are using a pre-defined sign_ps256 method, passing in our private cert
152-
# Normally this cert would be kept safe in some other location
153-
def private_sign(data: bytes) -> bytes:
154-
return sign_ps256(data, "tests/fixtures/ps256.pem")
155-
156-
# read our public certs into memory
157-
certs = open(data_dir + "ps256.pub", "rb").read()
158-
159-
# Create a signer from the private signer, certs and a time stamp service url
160-
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
161-
162-
# Create a builder add a thumbnail resource and an ingredient file.
163-
builder = Builder(manifest_json)
164-
165-
# The uri provided here "thumbnail" must match an identifier in the manifest definition.
166-
builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg")
167-
168-
# Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail
169-
ingredient_json = {
170-
"title": "A.jpg",
171-
"relationship": "parentOf", # "parentOf", "componentOf" or "inputTo"
172-
"thumbnail": {
173-
"identifier": "thumbnail",
174-
"format": "image/jpeg"
175-
}
176-
}
177-
178-
# Add the ingredient to the builder loading information from a source file.
179-
builder.add_ingredient_file(ingredient_json, "tests/fixtures/A.jpg")
180-
181-
# At this point we could archive or unarchive our Builder to continue later.
182-
# In this example we use a bytearray for the archive stream.
183-
# all ingredients and resources will be saved in the archive
184-
archive = io.BytesIO(bytearray())
185-
builder.to_archive(archive)
186-
archive.seek()
187-
builder = builder.from_archive(archive)
188-
189-
# Sign and add our manifest to a source file, writing it to an output file.
190-
# This returns the binary manifest data that could be uploaded to cloud storage.
191-
c2pa_data = builder.sign_file(signer, "tests/fixtures/A.jpg", "target/out.jpg")
192-
193-
except Exception as err:
194-
print(err)
195-
```
196-
197-
### Stream-based operation
198-
199-
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.
200-
201-
**Read and validate C2PA data from a stream**
202-
203-
```py
204-
try:
205-
# It's also possible to create a reader from a format and stream
206-
# Note that these two readers are functionally equivalent
207-
stream = open("path/to/media_file.jpg", "rb")
208-
reader = c2pa.Reader("image/jpeg", stream)
209-
210-
# Print the JSON for a manifest.
211-
print("manifest store:", reader.json())
212-
213-
# Get the active manifest.
214-
manifest = reader.get_active_manifest()
215-
if manifest != None:
216-
217-
# get the uri to the manifest's thumbnail and write it to a file
218-
uri = manifest["thumbnail"]["identifier"]
219-
reader.resource_to_file(uri, "thumbnail_v2.jpg")
220-
221-
except Exception as err:
222-
print(err)
223-
```
224-
225-
**Add a signed manifest to a stream**
226-
227-
**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).
228-
229-
Use a `Builder` to add a manifest to an asset:
230-
231-
```py
232-
try:
233-
# Define a function to sign the claim bytes
234-
# In this case we are using a pre-defined sign_ps256 method, passing in our private cert
235-
# Normally this cert would be kept safe in some other location
236-
def private_sign(data: bytes) -> bytes:
237-
return sign_ps256(data, "tests/fixtures/ps256.pem")
238-
239-
# read our public certs into memory
240-
certs = open(data_dir + "ps256.pub", "rb").read()
241-
242-
# Create a signer from the private signer, certs and a time stamp service url
243-
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
244-
245-
# Create a builder add a thumbnail resource and an ingredient file.
246-
builder = Builder(manifest_json)
247-
248-
# Add the resource from a stream
249-
a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb")
250-
builder.add_resource("image/jpeg", a_thumbnail_jpg_stream)
251-
252-
# Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail
253-
ingredient_json = {
254-
"title": "A.jpg",
255-
"relationship": "parentOf", # "parentOf", "componentOf" or "inputTo"
256-
"thumbnail": {
257-
"identifier": "thumbnail",
258-
"format": "image/jpeg"
259-
}
260-
}
261-
262-
# Add the ingredient from a stream
263-
a_jpg_stream = open("tests/fixtures/A.jpg", "rb")
264-
builder.add_ingredient("image/jpeg", a_jpg_stream)
265-
266-
# At this point we could archive or unarchive our Builder to continue later.
267-
# In this example we use a bytearray for the archive stream.
268-
# all ingredients and resources will be saved in the archive
269-
archive = io.BytesIO(bytearray())
270-
builder.to_archive(archive)
271-
archive.seek()
272-
builder = builder.from_archive(archive)
273-
274-
# Sign the builder with a stream and output it to a stream
275-
# This returns the binary manifest data that could be uploaded to cloud storage.
276-
input_stream = open("tests/fixtures/A.jpg", "rb")
277-
output_stream = open("target/out.jpg", "wb")
278-
c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream)
279-
280-
except Exception as err:
281-
print(err)
282-
```
283-
284-
## Development
285-
286-
It is best to [set up a virtual environment](https://virtualenv.pypa.io/en/latest/installation.html) for development and testing.
287-
288-
To build from source on Linux, install `curl` and `rustup` then set up Python.
289-
290-
First update `apt` then (if needed) install `curl`:
291-
292-
```bash
293-
apt update
294-
apt install curl
295-
```
296-
297-
Install Rust:
298-
299-
```bash
300-
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
301-
source "$HOME/.cargo/env"
302-
```
303-
304-
Install Python, `pip`, and `venv`:
305-
306-
```bash
307-
apt install python3
308-
apt install pip
309-
apt install python3.11-venv
310-
python3 -m venv .venv
311-
```
312-
313-
Build the wheel for your platform (from the root of the repository):
314-
315-
```bash
316-
source .venv/bin/activate
317-
pip install -r requirements.txt
318-
python3 -m pip install build
319-
pip install -U pytest
320-
321-
python3 -m build --wheel
322-
```
323-
324-
Note: To peek at the Python code (uniffi generated and non-generated), run `maturin develop` and look in the c2pa folder.
325-
326-
### ManyLinux build
327-
328-
Build using [manylinux](https://github.com/pypa/manylinux) by using a Docker image as follows:
329-
330-
```bash
331-
docker run -it quay.io/pypa/manylinux_2_28_aarch64 bash
332-
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
333-
source "$HOME/.cargo/env"
334-
export PATH=/opt/python/cp312-cp312/bin:$PATH
335-
pip install maturin
336-
pip install venv
337-
pip install build
338-
pip install -U pytest
339-
340-
cd home
341-
git clone https://github.com/contentauth/c2pa-python.git
342-
cd c2pa-python
343-
python3 -m build --wheel
344-
auditwheel repair target/wheels/c2pa_python-0.4.0-py3-none-linux_aarch64.whl
345-
```
346-
347-
### Testing
348-
349-
We use [PyTest](https://docs.pytest.org/) for testing.
350-
351-
Run tests by following these steps:
352-
353-
1. Activate the virtual environment: `source .venv/bin/activate`
354-
2. (optional) Install dependencies: `pip install -r requirements.txt`
355-
3. Setup the virtual environment with local changes: `maturin develop`
356-
4. Run the tests: `pytest`
357-
5. Deactivate the virtual environment: `deactivate`
358-
359-
For example:
360-
361-
```bash
362-
source .venv/bin/activate
363-
maturin develop
364-
python3 tests/training.py
365-
deactivate
366-
```
367-
36851
## License
36952

37053
This package is distributed under the terms of both the [MIT license](https://github.com/contentauth/c2pa-python/blob/main/LICENSE-MIT) and the [Apache License (Version 2.0)](https://github.com/contentauth/c2pa-python/blob/main/LICENSE-APACHE).

0 commit comments

Comments
 (0)