Skip to content

Commit f8894bb

Browse files
committed
Added data_dir to verify and add_manifest
removed sidecar and remote_url options Updated Readme Updated c2pa-rs version
1 parent 0a4e0be commit f8894bb

File tree

8 files changed

+150
-81
lines changed

8 files changed

+150
-81
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ debug/
44
target/
55

66
venv/
7+
.venv/
78

89
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
910
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html

Cargo.toml

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

@@ -10,7 +10,7 @@ name = "c2pa_python"
1010
crate-type = ["cdylib"]
1111

1212
[dependencies]
13-
c2pa = {version="0.25.0", features = ["file_io", "add_thumbnails", "fetch_remote_manifests"]}
13+
c2pa = {version="0.26.0", features = ["file_io", "add_thumbnails", "fetch_remote_manifests"]}
1414
serde = { version = "1.0", features = ["derive"] }
1515
serde_derive = "1.0"
1616
serde_json = "1.0"

README.md

Lines changed: 92 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,102 @@
1-
# C2PA Python Package
1+
# C2PA Python
22

3-
This Package is contributed as part of the [Content Authenticity Initiative](https://contentauthenticity.org) and [released it to open source](https://contentauthenticity.org/blog/cai-releases-suite-of-open-source-tools-to-advance-digital-content-provenance) in Sept, 2023.
3+
Python bindings for the C2PA Content Authenticity Initiative (CAI) library
44

5-
## Key features
5+
This library allows you to read and validate c2pa data in supported media files
6+
And to add signed manifests to supported media files.
67

7-
The SDK enables Python applications to:
8-
* Create and sign C2PA manifests.
9-
* Embed manifests in certain file formats.
10-
* Parse and validate manifests found in certain file formats.
8+
## Installation
119

12-
## State of the project
10+
```pip install c2pa-python```
1311

14-
This is a beta release (version 0.x.x) of the project. The minor version number (0.x.0) is incremented when there are breaking API changes, which may happen frequently.
12+
## Usage
1513

16-
### Contributions and feedback
14+
### Import
1715

18-
We welcome contributions to this project. For information on contributing, providing feedback, and about ongoing work, see [Contributing](https://github.com/contentauth/c2pa-js/blob/main/CONTRIBUTING.md).
16+
```import c2pa-python as c2pa```
17+
18+
### Reading and Validating C2PA data in a file
19+
20+
Read any C2PA data from a given file
21+
22+
```json_store = c2pa.verify_from_file_json("path/to/media_file.jpg", data_dir)```
23+
24+
This will examine any supported media file for c2pa data and generate
25+
a JSON report of any data it finds. The report will include a validation_status field if any validation errors were found.
26+
27+
A media file may contain many manifests in a manifest store. The most recent manifest can be accessed by looking up the active_manifest field value in the manifests map.
28+
29+
If the optional data_dir is provided, any binary resources, such as thumbnails, icons and c2pa_data will be extracted into that directory.
30+
These files will be referenced by the identifier fields in the manifest store report.
31+
32+
33+
### Adding a Signed Manifest to a media file
34+
The source is the media file which should receive new c2pa data.
35+
The destination will have a copy of the source with the data added.
36+
The manifest Json is a a JSON formatted string containing the data you want to add.
37+
(see: [Generating SignerInfo](#generating-signerinfo) for how to construct SignerInfo)
38+
The optional data_dir allows you to load resource files referenced from manifest_json identifiers.
39+
When building your manifest, any files referenced by identifier fields will be loaded relative to this path.
40+
This allows you to load thumbnails, icons and manifest data for ingredients
41+
42+
```result = c2pa.add_manifest_to_file_json("path/to/source.jpg", "path/to/dest.jpg", manifest_json, sign_info, data_dir)```
43+
44+
### Generating SignerInfo
45+
46+
Set up the signer info from pem and key files.
47+
48+
```certs = open("path/to/public_certs.pem","rb").read()```
49+
```prv_key = open("path/to/private_key.pem","rb").read()```
50+
51+
Then create a new SignerInfo instance using those keys.
52+
You must specify the signing algorithm used and may optionally add a time stamp authority URL.
53+
54+
```sign_info = c2pa.SignerInfo(certs, priv_key, "es256", "http://timestamp.digicert.com") ```
55+
56+
57+
### Creating a Manifest Json Definition File
58+
59+
The manifest json string defines the c2pa to add to the file.
60+
61+
```
62+
manifest_json = json.dumps({
63+
"claim_generator": "python_test/0.1",
64+
"assertions": [
65+
{
66+
"label": "c2pa.training-mining",
67+
"data": {
68+
"entries": {
69+
"c2pa.ai_generative_training": { "use": "notAllowed" },
70+
"c2pa.ai_inference": { "use": "notAllowed" },
71+
"c2pa.ai_training": { "use": "notAllowed" },
72+
"c2pa.data_mining": { "use": "notAllowed" }
73+
}
74+
}
75+
}
76+
]
77+
})
78+
```
79+
## Development
80+
81+
It is best to set up a virtual environment for development and testing
82+
https://virtualenv.pypa.io/en/latest/installation.html
83+
84+
We use maturin for packaging Rust in Python. It can can be installed with pip
85+
86+
```pip install maturin```
87+
88+
You will also need to install uniffi bindgen and pytest for testing
1989

20-
## Requirements
90+
``pip install uniffi_bindgen``
2191

22-
The SDK requires **Python version ???** or newer.
92+
``pip install -U pytest``
2393

24-
### Supported platforms
94+
``pip install <path to.whl> --force-reinstall``
2595

26-
The SDK has been tested on the following operating systems:
96+
### Testing
97+
98+
```pytest```
2799

28-
* Windows (Intel only)
29-
* MacOS (Intel and Apple silicon)
30-
* Ubuntu Linux (64-bit Intel and ARM v8)
31100

32101
## Supported file formats
33102

@@ -49,29 +118,14 @@ The SDK has been tested on the following operating systems:
49118
| `wav` | `audio/x-wav` |
50119
| `webp` | `image/webp` |
51120

52-
## Distribution
53-
54-
This package can be installed with :
55-
56-
```pip install c2pa-python```
57-
58-
## Building
59-
60-
This uses maturin for packaging Rust in Python. It can can be installed with pip
61-
62-
```pip install maturin```
63-
64-
You will also need to install uniffi bindgen and pytest for testing
65-
66-
``pip install uniffi_bindgen``
67-
68-
``pip install -U pytest``
69-
70-
``pip install <path to.whl> --force-reinstall``
71-
72121
## License
73122

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

76125
Note that some components and dependent crates are licensed under different terms; please check the license terms for each crate and component for details.
77126

127+
### Contributions and feedback
128+
129+
We welcome contributions to this project. For information on contributing, providing feedback, and about ongoing work, see [Contributing](https://github.com/contentauth/c2pa-js/blob/main/CONTRIBUTING.md).
130+
131+

pyproject.toml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,25 @@ build-backend = "maturin"
55
[project]
66
name = "c2pa-python"
77
requires-python = ">=3.7"
8+
description = "Python bindings for the C2PA Content Authenticity Initiative (CAI) library"
9+
readme = { file = "README.md", content-type = "text/markdown" }
10+
license = {file = "LICENSE-MIT"}
11+
keywords = ["C2PA", "CAI", "content credentials", "metadata", "provenance"]
812
classifiers = [
13+
"Development Status :: 3 - Alpha",
914
"Programming Language :: Rust",
1015
"Programming Language :: Python :: Implementation :: CPython",
1116
"Programming Language :: Python :: Implementation :: PyPy",
1217
]
18+
authors = [
19+
{name = "Gavin Peacock", email = "[email protected]"}
20+
]
21+
maintainers = [
22+
{name = "Gavin Peacock", email = "[email protected]"}
23+
]
24+
urls = {homepage = "contentauthenticity.org", repository = "github.com/contentauth/c2pa-python"}
1325

14-
26+
[project.optional-dependencies]
27+
test = [
28+
"pytest < 5.0.0"
29+
]

src/c2pa.udl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ namespace c2pa {
33
string version();
44
string sdk_version();
55
[Throws=Error]
6-
string verify_from_file_json([ByRef] string path);
6+
string verify_from_file_json([ByRef] string path, string? data_dir);
77
[Throws=Error]
88
string ingredient_from_file_json([ByRef] string path, [ByRef] string data_dir);
99
[Throws=Error]
10-
sequence<u8> add_manifest_to_file_json([ByRef] string source, [ByRef] string dest, [ByRef] string manifest, SignerInfo signer_info, boolean side_car, string? remote_url);
10+
sequence<u8> add_manifest_to_file_json([ByRef] string source, [ByRef] string dest, [ByRef] string manifest, SignerInfo signer_info, string? data_dir);
1111
};
1212

1313
[Error]

src/json_api.rs

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,27 @@
1010
// specific language governing permissions and limitations under
1111
// each license.
1212

13-
use std::path::PathBuf;
14-
1513
use c2pa::{Ingredient, Manifest, ManifestStore};
1614

1715
use crate::{Error, Result, SignerInfo};
1816

1917
/// Returns ManifestStore JSON string from a file path.
2018
///
19+
/// If data_dir is provided, any thumbnail or c2pa data will be written to that folder.
2120
/// Any Validation errors will be reported in the validation_status field.
2221
///
23-
pub fn verify_from_file_json(path: &str) -> Result<String> {
24-
Ok(ManifestStore::from_file(path)
25-
.map_err(Error::Sdk)?
26-
.to_string())
22+
pub fn verify_from_file_json(path: &str, data_dir: Option<String>) -> Result<String> {
23+
Ok(match data_dir {
24+
Some(dir) => ManifestStore::from_file_with_resources(path, &dir),
25+
None => ManifestStore::from_file(path),
26+
}
27+
.map_err(Error::Sdk)?
28+
.to_string())
2729
}
2830

2931
/// Returns an Ingredient JSON string from a file path.
3032
///
31-
/// Thumbnail and c2pa data written to data_dir if provided
33+
/// Any thumbnail or c2pa data will be written to data_dir if provided
3234
pub fn ingredient_from_file_json(path: &str, data_dir: &str) -> Result<String> {
3335
Ok(Ingredient::from_file_with_folder(path, data_dir)
3436
.map_err(Error::Sdk)?
@@ -46,31 +48,13 @@ pub fn add_manifest_to_file_json(
4648
dest: &str,
4749
manifest_info: &str,
4850
signer_info: SignerInfo,
49-
side_car: bool,
50-
remote_url: Option<String>,
51+
data_dir: Option<String>,
5152
) -> Result<Vec<u8>> {
5253
let mut manifest = Manifest::from_json(manifest_info).map_err(Error::Sdk)?;
5354

54-
// read any manifest referenced files from the source path
55-
// or current folder if no path available
56-
if let Some(path) = PathBuf::from(source).parent() {
55+
// if data_dir is provided, set the base path for the manifest
56+
if let Some(path) = data_dir {
5757
manifest.with_base_path(path).map_err(Error::Sdk)?;
58-
} else if let Ok(path) = std::env::current_dir() {
59-
manifest.with_base_path(&path).map_err(Error::Sdk)?;
60-
}
61-
62-
// if side_car then don't embed the manifest
63-
if side_car {
64-
manifest.set_sidecar_manifest();
65-
}
66-
67-
// add the remote url if provided
68-
if let Some(url) = remote_url {
69-
if side_car {
70-
manifest.set_remote_manifest(url);
71-
} else {
72-
manifest.set_embedded_manifest_with_remote_ref(url);
73-
}
7458
}
7559

7660
// If the source file has a manifest store, and no parent is specified, treat the source's manifest store as the parent.
@@ -88,6 +72,7 @@ pub fn add_manifest_to_file_json(
8872
#[cfg(test)]
8973
mod tests {
9074
use super::*;
75+
use std::{fs::remove_dir_all, path::PathBuf};
9176

9277
/// returns a path to a file in the fixtures folder
9378
pub fn test_path(path: &str) -> String {
@@ -96,13 +81,27 @@ mod tests {
9681
}
9782

9883
#[test]
99-
fn test_verify_from_file() {
84+
fn test_verify_from_file_no_base() {
10085
let path = test_path("tests/fixtures/C.jpg");
101-
let result = verify_from_file_json(&path);
86+
let result = verify_from_file_json(&path, None);
10287
assert!(result.is_ok());
10388
let json_report = result.unwrap();
10489
println!("{}", json_report);
10590
assert!(json_report.contains("C.jpg"));
10691
//assert!(!json_report.contains("validation_status"));
10792
}
93+
94+
#[test]
95+
fn test_verify_from_file_with_base() {
96+
let path = test_path("tests/fixtures/C.jpg");
97+
let data_dir = "target/data_dir";
98+
remove_dir_all(data_dir).unwrap();
99+
let result = verify_from_file_json(&path, Some(data_dir.to_owned()));
100+
assert!(result.is_ok());
101+
let json_report = result.unwrap();
102+
println!("{}", json_report);
103+
assert!(json_report.contains("C.jpg"));
104+
assert!(PathBuf::from(data_dir).exists());
105+
assert!(json_report.contains("thumbnail"));
106+
}
108107
}

tests/test_c2pa.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@
1515
import pytest
1616

1717
def test_version():
18-
assert c2pa.version() == "0.1.0"
18+
assert c2pa.version() == "0.1.1"
1919

2020
def test_sdk_version():
21-
assert c2pa.sdk_version() == "0.25.2"
21+
assert c2pa.sdk_version() == "0.26.0"
2222

2323

24-
#def test_verify_from_file():
25-
# json_store = c2pa.verify_from_file_json("tests/fixtures/A.jpg")
26-
# assert not "validation_status" in json_store
24+
def test_verify_from_file():
25+
json_store = c2pa.verify_from_file_json("tests/fixtures/C.jpg", None)
26+
assert not "validation_status" in json_store
2727

2828
def test_verify_from_file_no_store():
2929
with pytest.raises(c2pa.Error.Sdk) as err:
30-
json_store = c2pa.verify_from_file_json("tests/fixtures/A.jpg")
30+
json_store = c2pa.verify_from_file_json("tests/fixtures/A.jpg", None)
3131
assert str(err.value) == "no JUMBF data found"
3232

tests/training.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def getitem(d, key):
5757
test_key = open(keyFile,"rb").read()
5858
sign_info = c2pa.SignerInfo(test_pem, test_key, "es256", "http://timestamp.digicert.com")
5959

60-
result = c2pa.add_manifest_to_file_json(testFile, testOutputFile, manifest_json, sign_info, False, None)
60+
result = c2pa.add_manifest_to_file_json(testFile, testOutputFile, manifest_json, sign_info, None)
6161

6262
except Exception as err:
6363
sys.exit(err)
@@ -69,7 +69,7 @@ def getitem(d, key):
6969

7070
allowed = True # opt out model, assume training is ok if the assertion doesn't exist
7171
try:
72-
json_store = c2pa.verify_from_file_json(testOutputFile)
72+
json_store = c2pa.verify_from_file_json(testOutputFile, None)
7373
manifest_store = json.loads(json_store)
7474

7575
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]

0 commit comments

Comments
 (0)