Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
05967de
fix: Add test to verify PDF support
tmathern Aug 18, 2025
c12c2e8
fix: Update in progress
tmathern Aug 18, 2025
2b61679
fix: Version bump
tmathern Aug 18, 2025
e72e4c4
fix: Files handling in sign_file
tmathern Aug 18, 2025
b63f79a
fix: Update sign_file
tmathern Aug 18, 2025
98dee9d
fix: Update signing
tmathern Aug 18, 2025
27f5070
fix: Last failure is in CAG data
tmathern Aug 18, 2025
ff11d1c
fix: Threaded tests: update manifests
tmathern Aug 18, 2025
4c660e5
fix: Refactor
tmathern Aug 18, 2025
eed499c
fix: Test API redirect
tmathern Aug 18, 2025
e5d6d5b
fix: Refactor
tmathern Aug 18, 2025
e414eba
fix: Move tests
tmathern Aug 18, 2025
0c931e8
fix: Typings
tmathern Aug 18, 2025
8a73d68
fix: Initial fixes
tmathern Aug 23, 2025
357a1f3
fix: Increase test coverage, fix accordingly
tmathern Aug 23, 2025
2b4efed
fix: Some more changes
tmathern Aug 23, 2025
4f05cdf
fix: Increase test coverage, fix accordingly
tmathern Aug 23, 2025
a5ddbfa
fix: Increase test coverage, fix accordingly 2
tmathern Aug 23, 2025
fca9a1c
fix: Refactor and docs
tmathern Aug 23, 2025
e3ffd95
fix: Increase test coverage, fix accordingly 2
tmathern Aug 23, 2025
6b98d53
fix: FIles
tmathern Aug 23, 2025
a0ca592
fix: WIP
tmathern Aug 23, 2025
385f4f6
fix: Merge commit
tmathern Aug 23, 2025
6eaab37
fix: Udpate libs loading
tmathern Aug 23, 2025
7547678
fix: Make sure all test fixtures are here
tmathern Aug 23, 2025
d6e3c52
fix: Add a test for video signing
tmathern Aug 23, 2025
aad3c3e
fix: Reactivate cawg test
tmathern Aug 25, 2025
9d53fc7
fix: Write buffer to file example in test
tmathern Aug 25, 2025
c990cfd
fix: Skip cawg test for now
tmathern Aug 25, 2025
55f0c06
fix: Retrigger pipeline
tmathern Aug 25, 2025
7f6d4ac
fix: Fix version issues for Linux
tmathern Aug 25, 2025
1e9b92e
fix: Fix version issues for Linux for realx
tmathern Aug 25, 2025
7b88bb1
fix: Format
tmathern Aug 25, 2025
5c00a60
fix: Be more surgical on deprecation check
tmathern Aug 25, 2025
ead739f
fix: Claim versions
tmathern Aug 25, 2025
a700bfa
fix: Assertion structure
tmathern Aug 25, 2025
e8bb869
fix: Assertion structure
tmathern Aug 25, 2025
e61a061
fix: Debug in progress
tmathern Aug 25, 2025
910560f
fix: One char stream fix
tmathern Aug 25, 2025
a862342
fix: Unignore some code lines
tmathern Aug 25, 2025
cdf5ac3
fix: Format
tmathern Aug 25, 2025
6cda020
fix: Add one more test
tmathern Aug 25, 2025
89988e5
fix: Add one more test 2
tmathern Aug 25, 2025
aa99c86
fix: Update examples
tmathern Aug 25, 2025
06d90b4
fix: Docs
tmathern Aug 25, 2025
68fd16a
fix: Docs for streams
tmathern Aug 25, 2025
3d072c2
fix: Docs for streams
tmathern Aug 25, 2025
c200797
fix: Clean up test manifests
tmathern Aug 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build-wheel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ jobs:
C2PA_LIBS_PLATFORM=\"${{ format('{0}', inputs.architecture == 'aarch64' && 'aarch64-unknown-linux-gnu' || 'x86_64-unknown-linux-gnu') }}\" /opt/python/cp310-cp310/bin/python scripts/download_artifacts.py $C2PA_VERSION &&
for PYBIN in /opt/python/cp3{10,11}-*/bin; do
\${PYBIN}/pip install --upgrade pip wheel &&
\${PYBIN}/pip install toml &&
\${PYBIN}/pip install toml==0.10.2 &&
\${PYBIN}/pip install setuptools==68.0.0 &&
CFLAGS=\"-I/opt/python/cp310-cp310/include/python3.10\" LDFLAGS=\"-L/opt/python/cp310-cp310/lib\" \${PYBIN}/python setup.py bdist_wheel --plat-name $PLATFORM_TAG
done &&
rm -f /io/dist/*-linux_*.whl
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,5 @@ target/
*.dll
*.so
src/c2pa/libs/
!tests/fixtures/*.pem
!tests/fixtures/*.key
26 changes: 15 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ clean-c2pa-env: clean
install-deps:
python3 -m pip install -r requirements.txt
python3 -m pip install -r requirements-dev.txt
pip install -e .

# Installs the package in development mode
build-python:
pip install -e .
python3 -m pip install -e .

# Performs a complete rebuild of the development environment
rebuild: clean-c2pa-env install-deps download-native-artifacts build-python
@echo "Development rebuild done"

run-examples:
python3 ./examples/sign.py
python3 ./examples/sign_info.py
python3 ./examples/training.py
rm -rf output/

Expand All @@ -42,23 +42,23 @@ test:

# Runs benchmarks in the venv
benchmark:
python -m pytest tests/benchmark.py -v
python3 -m pytest tests/benchmark.py -v

# Tests building and installing a local wheel package
# Downloads required artifacts, builds the wheel, installs it, and verifies the installation
test-local-wheel-build:
# Clean any existing builds
rm -rf build/ dist/
# Download artifacts and place them where they should go
python scripts/download_artifacts.py $(C2PA_VERSION)
python3 scripts/download_artifacts.py $(C2PA_VERSION)
# Install Python
python3 -m pip install -r requirements.txt
python3 -m pip install -r requirements-dev.txt
python -m build --wheel
python3 -m build --wheel
# Install local build in venv
pip install $$(ls dist/*.whl)
# Verify installation in local venv
python -c "import c2pa; print('C2PA package installed at:', c2pa.__file__)"
python3 -c "import c2pa; print('C2PA package installed at:', c2pa.__file__)"
# Verify wheel structure
twine check dist/*

Expand All @@ -68,33 +68,37 @@ test-local-sdist-build:
# Clean any existing builds
rm -rf build/ dist/
# Download artifacts and place them where they should go
python scripts/download_artifacts.py $(C2PA_VERSION)
python3 scripts/download_artifacts.py $(C2PA_VERSION)
# Install Python
python3 -m pip install -r requirements.txt
python3 -m pip install -r requirements-dev.txt
# Build sdist package
python setup.py sdist
python3 setup.py sdist
# Install local build in venv
pip install $$(ls dist/*.tar.gz)
# Verify installation in local venv
python -c "import c2pa; print('C2PA package installed at:', c2pa.__file__)"
python3 -c "import c2pa; print('C2PA package installed at:', c2pa.__file__)"
# Verify sdist structure
twine check dist/*

# Verifies the wheel build process and checks the built package and its metadata
verify-wheel-build:
rm -rf build/ dist/ src/*.egg-info/
python -m build
python3 -m build
twine check dist/*

# Manually publishes the package to PyPI after creating a release
publish: release
python3 -m pip install twine
python3 -m twine upload dist/*

# Code analysis
check-format:
flake8 src/c2pa/c2pa.py

# Formats Python source code using autopep8 with aggressive settings
format:
autopep8 --aggressive --aggressive --in-place src/c2pa/**/*.py
autopep8 --aggressive --aggressive --in-place src/c2pa/*.py

# Downloads the required native artifacts for the specified version
download-native-artifacts:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# C2PA Python library

The [c2pa-python](https://github.com/contentauth/c2pa-python) repository provides a Python library that can:

- Read and validate C2PA manifest data from media files in supported formats.
- Create and sign manifest data, and attach it to media files in supported formats.

Expand Down
2 changes: 1 addition & 1 deletion c2pa-native-version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
c2pa-v0.58.0
c2pa-v0.59.1
15 changes: 7 additions & 8 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The [`examples/sign.py`](https://github.com/contentauth/c2pa-python/blob/main/ex
The `examples/sign.py` script shows how to sign an asset with a C2PA manifest and verify it using a callback signer. Callback signers let you define signing logic, for example where to load keys from.

The `examples/sign_info.py` script shows how to sign an asset with a C2PA manifest and verify it using a "default" signer created with the needed signer information.

These statements create a `builder` object with the specified manifest JSON (omitted in the snippet below), call `builder.sign()` to sign and attach the manifest to the source file, `tests/fixtures/C.jpg`, and save the signed asset to the output file, `output/C_signed.jpg`:

```py
Expand Down Expand Up @@ -80,14 +81,6 @@ Run the "do not train" assertion example:
python examples/training.py
```

### Run the signing and verification example

In this example, `SignerInfo` creates a `Signer` object that signs the manifest.

```bash
python examples/sign_info.py
```

### Run the callback signing and verification example

In this example, a callback signer is the signer:
Expand All @@ -96,4 +89,10 @@ In this example, a callback signer is the signer:
python examples/sign.py
```

### Run the signing and verification example

In this example, `SignerInfo` creates a `Signer` object that signs the manifest.

```bash
python examples/sign_info.py
```
16 changes: 8 additions & 8 deletions examples/sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
print(version)


# Load certificates and private key (here from the test fixtures)
# Load certificates and private key (here from the test fixtures).
# This is OK for development, but in production you should use a
# secure way to load the certificates and private key.
certs = open(fixtures_dir + "es256_certs.pem", "rb").read()
Expand Down Expand Up @@ -63,15 +63,17 @@ def callback_signer_es256(data: bytes) -> bytes:
tsa_url="http://timestamp.digicert.com"
)

# Create a manifest definition as a dictionary
# This manifest follows the V2 manifest format
# Create a manifest definition as a dictionary.
# This manifest follows the V2 manifest format.
manifest_definition = {
"claim_generator": "python_example",
"claim_generator_info": [{
"name": "python_example",
"version": "0.0.1",
}],
"claim_version": 2,
# Claims version 2 is the default, so the version
# number can be omitted.
# "claim_version": 2,
"format": "image/jpeg",
"title": "Python Example Image",
"ingredients": [],
Expand All @@ -82,10 +84,7 @@ def callback_signer_es256(data: bytes) -> bytes:
"actions": [
{
"action": "c2pa.created",
"parameters": {
# could hold additional information about this step
# eg. model used, etc.
}
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation"
}
]
}
Expand All @@ -99,6 +98,7 @@ def callback_signer_es256(data: bytes) -> bytes:
# Sign the image with the signer created above,
# which will use the callback signer
print("\nSigning the image file...")

builder.sign_file(
source_path=fixtures_dir + "A.jpg",
dest_path=output_dir + "A_signed.jpg",
Expand Down
12 changes: 8 additions & 4 deletions examples/sign_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,16 @@

# Create a manifest definition as a dictionary
# This examples signs using a V1 manifest
# Note that this is a v1 spec manifest (legacy)
manifest_definition = {
"claim_generator": "python_example",
"claim_generator_info": [{
"name": "python_example",
"version": "0.0.1",
}],
# This manifest uses v1 claims,
# so the version 1 must be explicitly set.
"claim_version": 1,
"format": "image/jpeg",
"title": "Python Example Image",
"ingredients": [],
Expand All @@ -77,9 +81,7 @@
"actions": [
{
"action": "c2pa.created",
"parameters": {
# could hold additional information about this step
}
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation"
}
]
}
Expand All @@ -93,7 +95,9 @@
# Sign the image
print("\nSigning the image...")
with open(fixtures_dir + "C.jpg", "rb") as source:
with open(output_dir + "C_signed.jpg", "wb") as dest:
# File needs to be opened in write+read mode to be signed
# and verified properly.
with open(output_dir + "C_signed.jpg", "w+b") as dest:
result = builder.sign(signer, "image/jpeg", source, dest)

# Read the signed image to verify
Expand Down
61 changes: 42 additions & 19 deletions examples/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import json
import os
import sys

# Example of using python crypto to sign data using openssl with Ps256
from cryptography.hazmat.primitives import hashes, serialization
Expand Down Expand Up @@ -54,19 +53,32 @@ def getitem(d, key):
"format": "image/jpeg",
"identifier": "thumbnail"
},
"assertions": [{
"label": "cawg.training-mining",
"data": {
"entries": {
"cawg.ai_inference": {
"use": "notAllowed"
},
"cawg.ai_generative_training": {
"use": "notAllowed"
"assertions": [
{
"label": "c2pa.actions",
"data": {
"actions": [
{
"action": "c2pa.created",
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation"
}
]
}
},
{
"label": "cawg.training-mining",
"data": {
"entries": {
"cawg.ai_inference": {
"use": "notAllowed"
},
"cawg.ai_generative_training": {
"use": "notAllowed"
}
}
}
}
}]
]
}

ingredient_json = {
Expand Down Expand Up @@ -109,37 +121,48 @@ def getitem(d, key):

# Sign the file using the stream-based sign method
with open(testFile, "rb") as source_file:
with open(testOutputFile, "wb") as dest_file:
with open(testOutputFile, "w+b") as dest_file:
result = builder.sign(signer, "image/jpeg", source_file, dest_file)

except Exception as err:
sys.exit(err)
# As an alternative, you can also use file paths directly during signing:
# builder.sign_file(testFile, testOutputFile, signer)

print("V2: successfully added do not train manifest to file " + testOutputFile)
# Clean up native resources (using a with statement works too!)
signer.close()
builder.close()

except Exception as err:
print("Exception during signing: ", err)

print("\nSuccessfully added do not train manifest to file " + testOutputFile)

# now verify the asset and check the manifest for a do not train assertion...

allowed = True # opt out model, assume training is ok if the assertion doesn't exist
try:
# Create reader using the current API
# Create reader using the Reader API
reader = c2pa.Reader(testOutputFile)

# Retrieve the manifest store
manifest_store = json.loads(reader.json())

# Look at data in the active manifest
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]

for assertion in manifest["assertions"]:
if assertion["label"] == "cawg.training-mining":
if getitem(assertion, ("data","entries","cawg.ai_generative_training","use")) == "notAllowed":
allowed = False

# get the ingredient thumbnail and save it to a file using resource_to_stream
# Get the ingredient thumbnail and save it to a file using resource_to_stream
uri = getitem(manifest,("ingredients", 0, "thumbnail", "identifier"))
with open(output_dir + "thumbnail_v2.jpg", "wb") as thumbnail_output:
reader.resource_to_stream(uri, thumbnail_output)

# Clean up native resources (using a with statement works too!)
reader.close()

except Exception as err:
sys.exit(err)
print("Exception during assertions reading: ", err)

if allowed:
print("Training is allowed")
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "c2pa-python"
version = "0.12.1"
version = "0.13.0"
requires-python = ">=3.10"
description = "Python bindings for the C2PA Content Authenticity Initiative (CAI) library"
readme = { file = "README.md", content-type = "text/markdown" }
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ requests>=2.0.0

# Code formatting
autopep8==2.0.4 # For automatic code formatting
flake8==7.3.0

# Test dependencies (for callback signers)
cryptography==45.0.4
Loading
Loading