c2pa-python 0.32.12__tar.gz → 0.32.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {c2pa_python-0.32.12/src/c2pa_python.egg-info → c2pa_python-0.32.13}/PKG-INFO +43 -1
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/README.md +42 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/pyproject.toml +1 -1
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/src/c2pa/c2pa.py +97 -87
- {c2pa_python-0.32.12 → c2pa_python-0.32.13/src/c2pa_python.egg-info}/PKG-INFO +43 -1
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/tests/test_unit_tests.py +16 -1
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/LICENSE-APACHE +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/LICENSE-MIT +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/MANIFEST.in +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/requirements.txt +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/scripts/download_artifacts.py +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/setup.cfg +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/setup.py +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/src/c2pa/__init__.py +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/src/c2pa/build.py +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/src/c2pa/lib.py +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/src/c2pa_python.egg-info/SOURCES.txt +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/src/c2pa_python.egg-info/dependency_links.txt +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/src/c2pa_python.egg-info/entry_points.txt +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/src/c2pa_python.egg-info/requires.txt +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/src/c2pa_python.egg-info/top_level.txt +0 -0
- {c2pa_python-0.32.12 → c2pa_python-0.32.13}/tests/test_unit_tests_threaded.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: c2pa-python
|
|
3
|
-
Version: 0.32.
|
|
3
|
+
Version: 0.32.13
|
|
4
4
|
Summary: Python bindings for the C2PA Content Authenticity Initiative (CAI) library
|
|
5
5
|
Author-email: Gavin Peacock <gvnpeacock@adobe.com>, Tania Mathern <mathern@adobe.com>
|
|
6
6
|
Maintainer-email: Gavin Peacock <gpeacock@adobe.com>
|
|
@@ -69,6 +69,48 @@ To use the module in Python code, import the module like this:
|
|
|
69
69
|
import c2pa
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
## Building from local c2pa-rs sources
|
|
73
|
+
|
|
74
|
+
### Using a virtual environment with local builds
|
|
75
|
+
|
|
76
|
+
The `make` targets honor an active virtualenv. Create a virtual environment `./.venv` and activate it before running them so commands use the project interpreter rather than the global Python interpreter:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
make create-venv && source .venv/bin/activate
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Build steps
|
|
83
|
+
|
|
84
|
+
By default the build downloads a prebuilt native library from a [c2pa-rs](https://github.com/contentauth/c2pa-rs) release. To test the Python bindings against a local, unreleased c2pa-rs checkout, you can instead build the native library from source.
|
|
85
|
+
|
|
86
|
+
Prerequisites:
|
|
87
|
+
|
|
88
|
+
- A local clone of [c2pa-rs](https://github.com/contentauth/c2pa-rs).
|
|
89
|
+
- The [Rust toolchain](https://rust-lang.org/tools/install/) (`cargo` on your `PATH`).
|
|
90
|
+
|
|
91
|
+
Point `C2PA_RS_PATH` at your c2pa-rs checkout and run the `build-from-source` target:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
export C2PA_RS_PATH=/path/to/c2pa-rs
|
|
95
|
+
make build-from-source C2PA_RS_PATH=$C2PA_RS_PATH
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This does a clean build of the `c2pa-c-ffi` crate (with the `file_io` feature, which the Python wrapper requires), stages the resulting library under both `artifacts/` and `src/c2pa/libs/`, and installs the package in editable mode, replacing any prebuilt artifacts from `make download-native-artifacts`. The release profile is used by default; to build the debug profile instead, pass `EXTRA_BUILD_ARGS="--debug"`:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
make build-from-source C2PA_RS_PATH=$C2PA_RS_PATH EXTRA_BUILD_ARGS="--debug"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Note on targets for macOS
|
|
105
|
+
|
|
106
|
+
On macOS this produces a universal (arm64+x86_64) library by default, which requires both Rust targets:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
rustup target add aarch64-apple-darwin x86_64-apple-darwin
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
To build a single-architecture library instead, set `C2PA_LIBS_PLATFORM` to a specific platform (for example `aarch64-apple-darwin`).
|
|
113
|
+
|
|
72
114
|
## Examples
|
|
73
115
|
|
|
74
116
|
See the [`examples` directory](https://github.com/contentauth/c2pa-python/tree/main/examples) for some helpful examples:
|
|
@@ -46,6 +46,48 @@ To use the module in Python code, import the module like this:
|
|
|
46
46
|
import c2pa
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
## Building from local c2pa-rs sources
|
|
50
|
+
|
|
51
|
+
### Using a virtual environment with local builds
|
|
52
|
+
|
|
53
|
+
The `make` targets honor an active virtualenv. Create a virtual environment `./.venv` and activate it before running them so commands use the project interpreter rather than the global Python interpreter:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
make create-venv && source .venv/bin/activate
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Build steps
|
|
60
|
+
|
|
61
|
+
By default the build downloads a prebuilt native library from a [c2pa-rs](https://github.com/contentauth/c2pa-rs) release. To test the Python bindings against a local, unreleased c2pa-rs checkout, you can instead build the native library from source.
|
|
62
|
+
|
|
63
|
+
Prerequisites:
|
|
64
|
+
|
|
65
|
+
- A local clone of [c2pa-rs](https://github.com/contentauth/c2pa-rs).
|
|
66
|
+
- The [Rust toolchain](https://rust-lang.org/tools/install/) (`cargo` on your `PATH`).
|
|
67
|
+
|
|
68
|
+
Point `C2PA_RS_PATH` at your c2pa-rs checkout and run the `build-from-source` target:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
export C2PA_RS_PATH=/path/to/c2pa-rs
|
|
72
|
+
make build-from-source C2PA_RS_PATH=$C2PA_RS_PATH
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This does a clean build of the `c2pa-c-ffi` crate (with the `file_io` feature, which the Python wrapper requires), stages the resulting library under both `artifacts/` and `src/c2pa/libs/`, and installs the package in editable mode, replacing any prebuilt artifacts from `make download-native-artifacts`. The release profile is used by default; to build the debug profile instead, pass `EXTRA_BUILD_ARGS="--debug"`:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
make build-from-source C2PA_RS_PATH=$C2PA_RS_PATH EXTRA_BUILD_ARGS="--debug"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Note on targets for macOS
|
|
82
|
+
|
|
83
|
+
On macOS this produces a universal (arm64+x86_64) library by default, which requires both Rust targets:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
rustup target add aarch64-apple-darwin x86_64-apple-darwin
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
To build a single-architecture library instead, set `C2PA_LIBS_PLATFORM` to a specific platform (for example `aarch64-apple-darwin`).
|
|
90
|
+
|
|
49
91
|
## Examples
|
|
50
92
|
|
|
51
93
|
See the [`examples` directory](https://github.com/contentauth/c2pa-python/tree/main/examples) for some helpful examples:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "c2pa-python"
|
|
7
|
-
version = "0.32.
|
|
7
|
+
version = "0.32.13"
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
description = "Python bindings for the C2PA Content Authenticity Initiative (CAI) library"
|
|
10
10
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
@@ -253,7 +253,8 @@ class ManagedResource:
|
|
|
253
253
|
if self._lifecycle_state != LifecycleState.ACTIVE:
|
|
254
254
|
raise C2paError(f"{name} is not properly initialized")
|
|
255
255
|
if not self._handle:
|
|
256
|
-
raise C2paError(
|
|
256
|
+
raise C2paError(
|
|
257
|
+
f"{name} has an invalid internal state (active but no handle)")
|
|
257
258
|
_clear_error_state()
|
|
258
259
|
|
|
259
260
|
def _release(self):
|
|
@@ -1011,7 +1012,11 @@ def _parse_operation_result_for_error(
|
|
|
1011
1012
|
return None
|
|
1012
1013
|
|
|
1013
1014
|
|
|
1014
|
-
def _check_ffi_operation_result(
|
|
1015
|
+
def _check_ffi_operation_result(
|
|
1016
|
+
result,
|
|
1017
|
+
fallback_msg,
|
|
1018
|
+
*,
|
|
1019
|
+
check=lambda r: not r):
|
|
1015
1020
|
"""Check an FFI native call result and raise C2paError if it indicates failure.
|
|
1016
1021
|
|
|
1017
1022
|
Args:
|
|
@@ -1036,7 +1041,8 @@ def _check_ffi_operation_result(result, fallback_msg, *, check=lambda r: not r):
|
|
|
1036
1041
|
return result
|
|
1037
1042
|
|
|
1038
1043
|
|
|
1039
|
-
def _to_utf8_bytes(data: Union[str, dict],
|
|
1044
|
+
def _to_utf8_bytes(data: Union[str, dict],
|
|
1045
|
+
error_context: str = "input") -> bytes:
|
|
1040
1046
|
"""Convert a string or dict to UTF-8 bytes.
|
|
1041
1047
|
|
|
1042
1048
|
If data is a dict, it is serialized to JSON first.
|
|
@@ -1059,8 +1065,8 @@ def _to_utf8_bytes(data: Union[str, dict], error_context: str = "input") -> byte
|
|
|
1059
1065
|
raise C2paError.Json(f"Failed to serialize {error_context}: {e}")
|
|
1060
1066
|
if not isinstance(data, str):
|
|
1061
1067
|
raise C2paError.Encoding(
|
|
1062
|
-
f"Expected str or dict for {error_context},
|
|
1063
|
-
|
|
1068
|
+
f"Expected str or dict for {error_context}, "
|
|
1069
|
+
f"got {type(data).__name__}")
|
|
1064
1070
|
try:
|
|
1065
1071
|
return data.encode('utf-8')
|
|
1066
1072
|
except UnicodeError as e:
|
|
@@ -1146,7 +1152,10 @@ def load_settings(settings: Union[str, dict], format: str = "json") -> None:
|
|
|
1146
1152
|
raise C2paError(f"Failed to encode settings to UTF-8: {e}")
|
|
1147
1153
|
|
|
1148
1154
|
result = _lib.c2pa_load_settings(settings_bytes, format_bytes)
|
|
1149
|
-
_check_ffi_operation_result(
|
|
1155
|
+
_check_ffi_operation_result(
|
|
1156
|
+
result,
|
|
1157
|
+
"Error loading settings",
|
|
1158
|
+
check=lambda r: r != 0)
|
|
1150
1159
|
|
|
1151
1160
|
return result
|
|
1152
1161
|
|
|
@@ -1219,7 +1228,8 @@ def read_ingredient_file(
|
|
|
1219
1228
|
result = _lib.c2pa_read_ingredient_file(
|
|
1220
1229
|
container._path_str, container._data_dir_str)
|
|
1221
1230
|
|
|
1222
|
-
_check_ffi_operation_result(
|
|
1231
|
+
_check_ffi_operation_result(
|
|
1232
|
+
result, "Error reading ingredient file {}".format(path))
|
|
1223
1233
|
|
|
1224
1234
|
return _convert_to_py_string(result)
|
|
1225
1235
|
|
|
@@ -1258,7 +1268,8 @@ def read_file(path: Union[str, Path],
|
|
|
1258
1268
|
container._data_dir_str = str(data_dir).encode('utf-8')
|
|
1259
1269
|
|
|
1260
1270
|
result = _lib.c2pa_read_file(container._path_str, container._data_dir_str)
|
|
1261
|
-
_check_ffi_operation_result(
|
|
1271
|
+
_check_ffi_operation_result(
|
|
1272
|
+
result, "Error during read of manifest from file {}".format(path))
|
|
1262
1273
|
|
|
1263
1274
|
return _convert_to_py_string(result)
|
|
1264
1275
|
|
|
@@ -1419,7 +1430,6 @@ class Settings(ManagedResource):
|
|
|
1419
1430
|
apply settings to Reader/Builder operations.
|
|
1420
1431
|
"""
|
|
1421
1432
|
|
|
1422
|
-
|
|
1423
1433
|
def __init__(self):
|
|
1424
1434
|
"""Create new Settings with default values."""
|
|
1425
1435
|
super().__init__()
|
|
@@ -1516,7 +1526,6 @@ class Settings(ManagedResource):
|
|
|
1516
1526
|
return self._handle
|
|
1517
1527
|
|
|
1518
1528
|
|
|
1519
|
-
|
|
1520
1529
|
class ContextBuilder:
|
|
1521
1530
|
"""Fluent builder for Context.
|
|
1522
1531
|
|
|
@@ -1574,7 +1583,6 @@ class Context(ManagedResource, ContextProvider):
|
|
|
1574
1583
|
used directly again after that.
|
|
1575
1584
|
"""
|
|
1576
1585
|
|
|
1577
|
-
|
|
1578
1586
|
def __init__(
|
|
1579
1587
|
self,
|
|
1580
1588
|
settings: Optional['Settings'] = None,
|
|
@@ -1718,7 +1726,6 @@ class Context(ManagedResource, ContextProvider):
|
|
|
1718
1726
|
return self._handle
|
|
1719
1727
|
|
|
1720
1728
|
|
|
1721
|
-
|
|
1722
1729
|
class Stream:
|
|
1723
1730
|
# Class-level somewhat atomic counter for generating
|
|
1724
1731
|
# unique stream IDs (useful for tracing streams usage in debug)
|
|
@@ -2132,7 +2139,6 @@ class Reader(ManagedResource):
|
|
|
2132
2139
|
Where `output` is either an in-memory stream or an opened file.
|
|
2133
2140
|
"""
|
|
2134
2141
|
|
|
2135
|
-
|
|
2136
2142
|
# Supported mimetypes cache
|
|
2137
2143
|
_supported_mime_types_cache = None
|
|
2138
2144
|
|
|
@@ -2163,8 +2169,7 @@ class Reader(ManagedResource):
|
|
|
2163
2169
|
C2paError: If there was an error retrieving the MIME types
|
|
2164
2170
|
"""
|
|
2165
2171
|
result, cls._supported_mime_types_cache = _get_supported_mime_types(
|
|
2166
|
-
_lib.c2pa_reader_supported_mime_types, cls._supported_mime_types_cache
|
|
2167
|
-
)
|
|
2172
|
+
_lib.c2pa_reader_supported_mime_types, cls._supported_mime_types_cache)
|
|
2168
2173
|
return result
|
|
2169
2174
|
|
|
2170
2175
|
@classmethod
|
|
@@ -2347,8 +2352,9 @@ class Reader(ManagedResource):
|
|
|
2347
2352
|
else:
|
|
2348
2353
|
if not isinstance(manifest_data, bytes):
|
|
2349
2354
|
raise TypeError(Reader._ERROR_MESSAGES['manifest_error'])
|
|
2350
|
-
manifest_array = (
|
|
2351
|
-
|
|
2355
|
+
manifest_array = (
|
|
2356
|
+
ctypes.c_ubyte *
|
|
2357
|
+
len(manifest_data)).from_buffer_copy(manifest_data)
|
|
2352
2358
|
self._handle = (
|
|
2353
2359
|
_lib.c2pa_reader_from_manifest_data_and_stream(
|
|
2354
2360
|
format_bytes,
|
|
@@ -2358,9 +2364,9 @@ class Reader(ManagedResource):
|
|
|
2358
2364
|
)
|
|
2359
2365
|
)
|
|
2360
2366
|
|
|
2361
|
-
_check_ffi_operation_result(
|
|
2362
|
-
|
|
2363
|
-
|
|
2367
|
+
_check_ffi_operation_result(
|
|
2368
|
+
self._handle,
|
|
2369
|
+
Reader._ERROR_MESSAGES['reader_error'].format("Unknown error"))
|
|
2364
2370
|
|
|
2365
2371
|
def _init_from_file(self, path, format_bytes,
|
|
2366
2372
|
manifest_data=None):
|
|
@@ -2422,10 +2428,10 @@ class Reader(ManagedResource):
|
|
|
2422
2428
|
)
|
|
2423
2429
|
try:
|
|
2424
2430
|
_check_ffi_operation_result(reader_ptr,
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2431
|
+
Reader._ERROR_MESSAGES[
|
|
2432
|
+
'reader_error'
|
|
2433
|
+
].format("Unknown error")
|
|
2434
|
+
)
|
|
2429
2435
|
except Exception:
|
|
2430
2436
|
if reader_ptr:
|
|
2431
2437
|
ManagedResource._free_native_ptr(reader_ptr)
|
|
@@ -2436,8 +2442,9 @@ class Reader(ManagedResource):
|
|
|
2436
2442
|
raise TypeError(
|
|
2437
2443
|
Reader._ERROR_MESSAGES[
|
|
2438
2444
|
'manifest_error'])
|
|
2439
|
-
manifest_array = (
|
|
2440
|
-
|
|
2445
|
+
manifest_array = (
|
|
2446
|
+
ctypes.c_ubyte *
|
|
2447
|
+
len(manifest_data)).from_buffer_copy(manifest_data)
|
|
2441
2448
|
# Consume current reader,
|
|
2442
2449
|
# with manifest data and stream (C FFI pattern),
|
|
2443
2450
|
# to create a new one (switch out)
|
|
@@ -2462,10 +2469,10 @@ class Reader(ManagedResource):
|
|
|
2462
2469
|
self._handle = new_ptr
|
|
2463
2470
|
|
|
2464
2471
|
_check_ffi_operation_result(new_ptr,
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2472
|
+
Reader._ERROR_MESSAGES[
|
|
2473
|
+
'reader_error'
|
|
2474
|
+
].format("Unknown error")
|
|
2475
|
+
)
|
|
2469
2476
|
|
|
2470
2477
|
self._lifecycle_state = LifecycleState.ACTIVE
|
|
2471
2478
|
except Exception:
|
|
@@ -2556,9 +2563,9 @@ class Reader(ManagedResource):
|
|
|
2556
2563
|
if not new_ptr:
|
|
2557
2564
|
self._mark_consumed()
|
|
2558
2565
|
_check_ffi_operation_result(new_ptr,
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2566
|
+
Reader._ERROR_MESSAGES[
|
|
2567
|
+
'fragment_error'
|
|
2568
|
+
].format("Unknown error"))
|
|
2562
2569
|
self._handle = new_ptr
|
|
2563
2570
|
|
|
2564
2571
|
# Invalidate caches: processing a new BMFF fragment updates the native
|
|
@@ -2594,7 +2601,7 @@ class Reader(ManagedResource):
|
|
|
2594
2601
|
|
|
2595
2602
|
result = _lib.c2pa_reader_json(self._handle)
|
|
2596
2603
|
_check_ffi_operation_result(result,
|
|
2597
|
-
|
|
2604
|
+
"Error during manifest parsing in Reader")
|
|
2598
2605
|
|
|
2599
2606
|
# Cache the result and return it
|
|
2600
2607
|
self._manifest_json_str_cache = _convert_to_py_string(result)
|
|
@@ -2619,8 +2626,8 @@ class Reader(ManagedResource):
|
|
|
2619
2626
|
self._ensure_valid_state()
|
|
2620
2627
|
|
|
2621
2628
|
result = _lib.c2pa_reader_detailed_json(self._handle)
|
|
2622
|
-
_check_ffi_operation_result(
|
|
2623
|
-
"Error during detailed manifest parsing in Reader")
|
|
2629
|
+
_check_ffi_operation_result(
|
|
2630
|
+
result, "Error during detailed manifest parsing in Reader")
|
|
2624
2631
|
|
|
2625
2632
|
return _convert_to_py_string(result)
|
|
2626
2633
|
|
|
@@ -2745,7 +2752,8 @@ class Reader(ManagedResource):
|
|
|
2745
2752
|
result = _lib.c2pa_reader_resource_to_stream(
|
|
2746
2753
|
self._handle, uri_str, stream_obj._stream)
|
|
2747
2754
|
|
|
2748
|
-
_check_ffi_operation_result(
|
|
2755
|
+
_check_ffi_operation_result(
|
|
2756
|
+
result,
|
|
2749
2757
|
"Error during resource {} to stream conversion".format(uri),
|
|
2750
2758
|
check=lambda r: r < 0)
|
|
2751
2759
|
|
|
@@ -2794,7 +2802,6 @@ class Reader(ManagedResource):
|
|
|
2794
2802
|
class Signer(ManagedResource):
|
|
2795
2803
|
"""High-level wrapper for C2PA Signer operations."""
|
|
2796
2804
|
|
|
2797
|
-
|
|
2798
2805
|
# Class-level error messages to avoid multiple creation
|
|
2799
2806
|
_ERROR_MESSAGES = {
|
|
2800
2807
|
'closed_error': "Signer is closed",
|
|
@@ -2827,8 +2834,8 @@ class Signer(ManagedResource):
|
|
|
2827
2834
|
|
|
2828
2835
|
signer_ptr = _lib.c2pa_signer_from_info(ctypes.byref(signer_info))
|
|
2829
2836
|
|
|
2830
|
-
_check_ffi_operation_result(
|
|
2831
|
-
"Failed to create signer from configured signer_info")
|
|
2837
|
+
_check_ffi_operation_result(
|
|
2838
|
+
signer_ptr, "Failed to create signer from configured signer_info")
|
|
2832
2839
|
|
|
2833
2840
|
return cls(signer_ptr)
|
|
2834
2841
|
|
|
@@ -2959,7 +2966,7 @@ class Signer(ManagedResource):
|
|
|
2959
2966
|
)
|
|
2960
2967
|
|
|
2961
2968
|
_check_ffi_operation_result(signer_ptr,
|
|
2962
|
-
|
|
2969
|
+
"Failed to create signer")
|
|
2963
2970
|
|
|
2964
2971
|
# Create and return the signer instance with the callback
|
|
2965
2972
|
signer_instance = cls(signer_ptr)
|
|
@@ -3009,8 +3016,10 @@ class Signer(ManagedResource):
|
|
|
3009
3016
|
|
|
3010
3017
|
result = _lib.c2pa_signer_reserve_size(self._handle)
|
|
3011
3018
|
|
|
3012
|
-
_check_ffi_operation_result(
|
|
3013
|
-
|
|
3019
|
+
_check_ffi_operation_result(
|
|
3020
|
+
result,
|
|
3021
|
+
"Failed to get reserve size",
|
|
3022
|
+
check=lambda r: r < 0)
|
|
3014
3023
|
|
|
3015
3024
|
return result
|
|
3016
3025
|
|
|
@@ -3018,7 +3027,6 @@ class Signer(ManagedResource):
|
|
|
3018
3027
|
class Builder(ManagedResource):
|
|
3019
3028
|
"""High-level wrapper for C2PA Builder operations."""
|
|
3020
3029
|
|
|
3021
|
-
|
|
3022
3030
|
# Supported mimetypes cache
|
|
3023
3031
|
_supported_mime_types_cache = None
|
|
3024
3032
|
|
|
@@ -3051,8 +3059,7 @@ class Builder(ManagedResource):
|
|
|
3051
3059
|
C2paError: If there was an error retrieving the MIME types
|
|
3052
3060
|
"""
|
|
3053
3061
|
result, cls._supported_mime_types_cache = _get_supported_mime_types(
|
|
3054
|
-
_lib.c2pa_builder_supported_mime_types, cls._supported_mime_types_cache
|
|
3055
|
-
)
|
|
3062
|
+
_lib.c2pa_builder_supported_mime_types, cls._supported_mime_types_cache)
|
|
3056
3063
|
return result
|
|
3057
3064
|
|
|
3058
3065
|
@classmethod
|
|
@@ -3125,8 +3132,8 @@ class Builder(ManagedResource):
|
|
|
3125
3132
|
)
|
|
3126
3133
|
|
|
3127
3134
|
_check_ffi_operation_result(builder._handle,
|
|
3128
|
-
|
|
3129
|
-
|
|
3135
|
+
"Failed to create builder from archive"
|
|
3136
|
+
)
|
|
3130
3137
|
|
|
3131
3138
|
builder._lifecycle_state = LifecycleState.ACTIVE
|
|
3132
3139
|
return builder
|
|
@@ -3178,11 +3185,9 @@ class Builder(ManagedResource):
|
|
|
3178
3185
|
else:
|
|
3179
3186
|
self._handle = _lib.c2pa_builder_from_json(json_str)
|
|
3180
3187
|
|
|
3181
|
-
_check_ffi_operation_result(
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
)
|
|
3185
|
-
)
|
|
3188
|
+
_check_ffi_operation_result(
|
|
3189
|
+
self._handle,
|
|
3190
|
+
Builder._ERROR_MESSAGES['builder_error'].format("Unknown error"))
|
|
3186
3191
|
|
|
3187
3192
|
self._lifecycle_state = LifecycleState.ACTIVE
|
|
3188
3193
|
|
|
@@ -3200,10 +3205,10 @@ class Builder(ManagedResource):
|
|
|
3200
3205
|
)
|
|
3201
3206
|
try:
|
|
3202
3207
|
_check_ffi_operation_result(builder_ptr,
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3208
|
+
Builder._ERROR_MESSAGES[
|
|
3209
|
+
'builder_error'
|
|
3210
|
+
].format("Unknown error")
|
|
3211
|
+
)
|
|
3207
3212
|
except Exception:
|
|
3208
3213
|
if builder_ptr:
|
|
3209
3214
|
ManagedResource._free_native_ptr(builder_ptr)
|
|
@@ -3215,10 +3220,10 @@ class Builder(ManagedResource):
|
|
|
3215
3220
|
self._handle = new_ptr
|
|
3216
3221
|
|
|
3217
3222
|
_check_ffi_operation_result(new_ptr,
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3223
|
+
Builder._ERROR_MESSAGES[
|
|
3224
|
+
'builder_error'
|
|
3225
|
+
].format("Unknown error")
|
|
3226
|
+
)
|
|
3222
3227
|
|
|
3223
3228
|
def set_no_embed(self):
|
|
3224
3229
|
"""Set the no-embed flag.
|
|
@@ -3247,7 +3252,8 @@ class Builder(ManagedResource):
|
|
|
3247
3252
|
url_bytes = _to_utf8_bytes(remote_url, "remote URL")
|
|
3248
3253
|
result = _lib.c2pa_builder_set_remote_url(self._handle, url_bytes)
|
|
3249
3254
|
|
|
3250
|
-
_check_ffi_operation_result(
|
|
3255
|
+
_check_ffi_operation_result(
|
|
3256
|
+
result,
|
|
3251
3257
|
Builder._ERROR_MESSAGES['url_error'].format("Unknown error"),
|
|
3252
3258
|
check=lambda r: r != 0)
|
|
3253
3259
|
|
|
@@ -3285,7 +3291,8 @@ class Builder(ManagedResource):
|
|
|
3285
3291
|
ctypes.c_uint(digital_source_type),
|
|
3286
3292
|
)
|
|
3287
3293
|
|
|
3288
|
-
_check_ffi_operation_result(
|
|
3294
|
+
_check_ffi_operation_result(
|
|
3295
|
+
result,
|
|
3289
3296
|
"Error setting intent for Builder: Unknown error",
|
|
3290
3297
|
check=lambda r: r != 0)
|
|
3291
3298
|
|
|
@@ -3307,10 +3314,9 @@ class Builder(ManagedResource):
|
|
|
3307
3314
|
result = _lib.c2pa_builder_add_resource(
|
|
3308
3315
|
self._handle, uri_bytes, stream_obj._stream)
|
|
3309
3316
|
|
|
3310
|
-
_check_ffi_operation_result(
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
),
|
|
3317
|
+
_check_ffi_operation_result(
|
|
3318
|
+
result,
|
|
3319
|
+
Builder._ERROR_MESSAGES['resource_error'].format("Unknown error"),
|
|
3314
3320
|
check=lambda r: r != 0)
|
|
3315
3321
|
|
|
3316
3322
|
def add_ingredient(
|
|
@@ -3375,10 +3381,9 @@ class Builder(ManagedResource):
|
|
|
3375
3381
|
)
|
|
3376
3382
|
)
|
|
3377
3383
|
|
|
3378
|
-
_check_ffi_operation_result(
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
),
|
|
3384
|
+
_check_ffi_operation_result(
|
|
3385
|
+
result,
|
|
3386
|
+
Builder._ERROR_MESSAGES['ingredient_error'].format("Unknown error"),
|
|
3382
3387
|
check=lambda r: r != 0)
|
|
3383
3388
|
|
|
3384
3389
|
def add_ingredient_from_file_path(
|
|
@@ -3444,10 +3449,9 @@ class Builder(ManagedResource):
|
|
|
3444
3449
|
action_str = _to_utf8_bytes(action_json, "action JSON")
|
|
3445
3450
|
result = _lib.c2pa_builder_add_action(self._handle, action_str)
|
|
3446
3451
|
|
|
3447
|
-
_check_ffi_operation_result(
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
),
|
|
3452
|
+
_check_ffi_operation_result(
|
|
3453
|
+
result,
|
|
3454
|
+
Builder._ERROR_MESSAGES['action_error'].format("Unknown error"),
|
|
3451
3455
|
check=lambda r: r != 0)
|
|
3452
3456
|
|
|
3453
3457
|
def to_archive(self, stream: Any) -> None:
|
|
@@ -3466,10 +3470,9 @@ class Builder(ManagedResource):
|
|
|
3466
3470
|
result = _lib.c2pa_builder_to_archive(
|
|
3467
3471
|
self._handle, stream_obj._stream)
|
|
3468
3472
|
|
|
3469
|
-
_check_ffi_operation_result(
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
),
|
|
3473
|
+
_check_ffi_operation_result(
|
|
3474
|
+
result,
|
|
3475
|
+
Builder._ERROR_MESSAGES["archive_error"].format("Unknown error"),
|
|
3473
3476
|
check=lambda r: r != 0)
|
|
3474
3477
|
|
|
3475
3478
|
def with_archive(self, stream: Any) -> 'Builder':
|
|
@@ -3491,7 +3494,8 @@ class Builder(ManagedResource):
|
|
|
3491
3494
|
|
|
3492
3495
|
with Stream(stream) as stream_obj:
|
|
3493
3496
|
try:
|
|
3494
|
-
new_ptr = _lib.c2pa_builder_with_archive(
|
|
3497
|
+
new_ptr = _lib.c2pa_builder_with_archive(
|
|
3498
|
+
self._handle, stream_obj._stream)
|
|
3495
3499
|
except Exception as e:
|
|
3496
3500
|
self._mark_consumed()
|
|
3497
3501
|
raise C2paError(
|
|
@@ -3499,7 +3503,8 @@ class Builder(ManagedResource):
|
|
|
3499
3503
|
)
|
|
3500
3504
|
# Old handle consumed by FFI
|
|
3501
3505
|
self._handle = new_ptr
|
|
3502
|
-
_check_ffi_operation_result(
|
|
3506
|
+
_check_ffi_operation_result(
|
|
3507
|
+
new_ptr, "Failed to load archive into builder")
|
|
3503
3508
|
|
|
3504
3509
|
return self
|
|
3505
3510
|
|
|
@@ -3566,8 +3571,10 @@ class Builder(ManagedResource):
|
|
|
3566
3571
|
self.close()
|
|
3567
3572
|
raise C2paError(f"Error during signing: {e}")
|
|
3568
3573
|
|
|
3569
|
-
_check_ffi_operation_result(
|
|
3570
|
-
|
|
3574
|
+
_check_ffi_operation_result(
|
|
3575
|
+
result,
|
|
3576
|
+
"Error during signing",
|
|
3577
|
+
check=lambda r: r < 0)
|
|
3571
3578
|
|
|
3572
3579
|
# Capture the manifest bytes if available
|
|
3573
3580
|
manifest_bytes = b""
|
|
@@ -3623,7 +3630,8 @@ class Builder(ManagedResource):
|
|
|
3623
3630
|
signer=signer,
|
|
3624
3631
|
)
|
|
3625
3632
|
elif self._has_context_signer:
|
|
3626
|
-
manifest_bytes = self._sign_internal(
|
|
3633
|
+
manifest_bytes = self._sign_internal(
|
|
3634
|
+
format, source_stream, dest_stream)
|
|
3627
3635
|
else:
|
|
3628
3636
|
raise C2paError(
|
|
3629
3637
|
"No signer provided. Either pass a"
|
|
@@ -3792,8 +3800,10 @@ def format_embeddable(format: str, manifest_bytes: bytes) -> tuple[int, bytes]:
|
|
|
3792
3800
|
ctypes.byref(result_bytes_ptr)
|
|
3793
3801
|
)
|
|
3794
3802
|
|
|
3795
|
-
_check_ffi_operation_result(
|
|
3796
|
-
|
|
3803
|
+
_check_ffi_operation_result(
|
|
3804
|
+
result,
|
|
3805
|
+
"Failed to format embeddable manifest",
|
|
3806
|
+
check=lambda r: r < 0)
|
|
3797
3807
|
|
|
3798
3808
|
size = result
|
|
3799
3809
|
try:
|
|
@@ -3924,7 +3934,7 @@ def ed25519_sign(data: bytes, private_key: str) -> bytes:
|
|
|
3924
3934
|
)
|
|
3925
3935
|
|
|
3926
3936
|
_check_ffi_operation_result(signature_ptr,
|
|
3927
|
-
|
|
3937
|
+
"Failed to sign data with Ed25519")
|
|
3928
3938
|
|
|
3929
3939
|
try:
|
|
3930
3940
|
# Ed25519 signatures are always 64 bytes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: c2pa-python
|
|
3
|
-
Version: 0.32.
|
|
3
|
+
Version: 0.32.13
|
|
4
4
|
Summary: Python bindings for the C2PA Content Authenticity Initiative (CAI) library
|
|
5
5
|
Author-email: Gavin Peacock <gvnpeacock@adobe.com>, Tania Mathern <mathern@adobe.com>
|
|
6
6
|
Maintainer-email: Gavin Peacock <gpeacock@adobe.com>
|
|
@@ -69,6 +69,48 @@ To use the module in Python code, import the module like this:
|
|
|
69
69
|
import c2pa
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
## Building from local c2pa-rs sources
|
|
73
|
+
|
|
74
|
+
### Using a virtual environment with local builds
|
|
75
|
+
|
|
76
|
+
The `make` targets honor an active virtualenv. Create a virtual environment `./.venv` and activate it before running them so commands use the project interpreter rather than the global Python interpreter:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
make create-venv && source .venv/bin/activate
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Build steps
|
|
83
|
+
|
|
84
|
+
By default the build downloads a prebuilt native library from a [c2pa-rs](https://github.com/contentauth/c2pa-rs) release. To test the Python bindings against a local, unreleased c2pa-rs checkout, you can instead build the native library from source.
|
|
85
|
+
|
|
86
|
+
Prerequisites:
|
|
87
|
+
|
|
88
|
+
- A local clone of [c2pa-rs](https://github.com/contentauth/c2pa-rs).
|
|
89
|
+
- The [Rust toolchain](https://rust-lang.org/tools/install/) (`cargo` on your `PATH`).
|
|
90
|
+
|
|
91
|
+
Point `C2PA_RS_PATH` at your c2pa-rs checkout and run the `build-from-source` target:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
export C2PA_RS_PATH=/path/to/c2pa-rs
|
|
95
|
+
make build-from-source C2PA_RS_PATH=$C2PA_RS_PATH
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This does a clean build of the `c2pa-c-ffi` crate (with the `file_io` feature, which the Python wrapper requires), stages the resulting library under both `artifacts/` and `src/c2pa/libs/`, and installs the package in editable mode, replacing any prebuilt artifacts from `make download-native-artifacts`. The release profile is used by default; to build the debug profile instead, pass `EXTRA_BUILD_ARGS="--debug"`:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
make build-from-source C2PA_RS_PATH=$C2PA_RS_PATH EXTRA_BUILD_ARGS="--debug"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Note on targets for macOS
|
|
105
|
+
|
|
106
|
+
On macOS this produces a universal (arm64+x86_64) library by default, which requires both Rust targets:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
rustup target add aarch64-apple-darwin x86_64-apple-darwin
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
To build a single-architecture library instead, set `C2PA_LIBS_PLATFORM` to a specific platform (for example `aarch64-apple-darwin`).
|
|
113
|
+
|
|
72
114
|
## Examples
|
|
73
115
|
|
|
74
116
|
See the [`examples` directory](https://github.com/contentauth/c2pa-python/tree/main/examples) for some helpful examples:
|
|
@@ -67,10 +67,25 @@ def load_test_settings_json():
|
|
|
67
67
|
return settings_data
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
def parse_native_version():
|
|
71
|
+
"""
|
|
72
|
+
Parse the expected native SDK version from c2pa-native-version.txt.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
str: The semantic version string (e.g. "0.85.2").
|
|
76
|
+
"""
|
|
77
|
+
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
78
|
+
version_path = os.path.join(repo_root, 'c2pa-native-version.txt')
|
|
79
|
+
with open(version_path, 'r') as f:
|
|
80
|
+
raw = f.read().strip()
|
|
81
|
+
# Strip the "c2pa-v" prefix to get the bare semantic version.
|
|
82
|
+
return raw.split('v', 1)[1] if 'v' in raw else raw
|
|
83
|
+
|
|
84
|
+
|
|
70
85
|
class TestC2paSdk(unittest.TestCase):
|
|
71
86
|
def test_sdk_version(self):
|
|
72
87
|
# This test verifies the native libraries used match the expected version.
|
|
73
|
-
self.assertIn(
|
|
88
|
+
self.assertIn(parse_native_version(), sdk_version())
|
|
74
89
|
|
|
75
90
|
|
|
76
91
|
class TestReader(unittest.TestCase):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|