Skip to content

Commit 87a0ee4

Browse files
authored
feat: add_action API on Builder (#181)
* fix: Initial test * fix: add the tests with settings * fix: Settings test * fix: Add_action API * fix: COmments * fix: Review comments * fix: From dict API * fix: dict APIs * fix: dict support * fix: Fromat 1 * fix: Format 2 * fix: COmments * fix: Format
1 parent 73697b7 commit 87a0ee4

File tree

3 files changed

+713
-12
lines changed

3 files changed

+713
-12
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "c2pa-python"
7-
version = "0.26.0"
7+
version = "0.27.0"
88
requires-python = ">=3.10"
99
description = "Python bindings for the C2PA Content Authenticity Initiative (CAI) library"
1010
readme = { file = "README.md", content-type = "text/markdown" }

src/c2pa/c2pa.py

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
'c2pa_builder_set_remote_url',
5050
'c2pa_builder_add_resource',
5151
'c2pa_builder_add_ingredient_from_stream',
52+
'c2pa_builder_add_action',
5253
'c2pa_builder_to_archive',
5354
'c2pa_builder_sign',
5455
'c2pa_manifest_bytes_free',
@@ -403,6 +404,9 @@ def _setup_function(func, argtypes, restype=None):
403404
ctypes.c_char_p,
404405
ctypes.POINTER(C2paStream)],
405406
ctypes.c_int)
407+
_setup_function(_lib.c2pa_builder_add_action,
408+
[ctypes.POINTER(C2paBuilder), ctypes.c_char_p],
409+
ctypes.c_int)
406410
_setup_function(_lib.c2pa_builder_to_archive,
407411
[ctypes.POINTER(C2paBuilder), ctypes.POINTER(C2paStream)],
408412
ctypes.c_int)
@@ -667,20 +671,44 @@ def version() -> str:
667671
return _convert_to_py_string(result)
668672

669673

674+
@overload
670675
def load_settings(settings: str, format: str = "json") -> None:
671-
"""Load C2PA settings from a string.
676+
...
677+
678+
679+
@overload
680+
def load_settings(settings: dict) -> None:
681+
...
682+
683+
684+
def load_settings(settings: Union[str, dict], format: str = "json") -> None:
685+
"""Load C2PA settings from a string or dict.
672686
673687
Args:
674-
settings: The settings string to load
675-
format: The format of the settings string (default: "json")
688+
settings: The settings string or dict to load
689+
format: The format of the settings string (default: "json").
690+
Ignored when settings is a dict.
676691
677692
Raises:
678693
C2paError: If there was an error loading the settings
679694
"""
680-
result = _lib.c2pa_load_settings(
681-
settings.encode('utf-8'),
682-
format.encode('utf-8')
683-
)
695+
# Convert to JSON string as necessary
696+
try:
697+
if isinstance(settings, dict):
698+
settings_str = json.dumps(settings)
699+
format = "json"
700+
else:
701+
settings_str = settings
702+
except (TypeError, ValueError) as e:
703+
raise C2paError(f"Failed to serialize settings to JSON: {e}")
704+
705+
try:
706+
settings_bytes = settings_str.encode('utf-8')
707+
format_bytes = format.encode('utf-8')
708+
except (AttributeError, UnicodeEncodeError) as e:
709+
raise C2paError(f"Failed to encode settings to UTF-8: {e}")
710+
711+
result = _lib.c2pa_load_settings(settings_bytes, format_bytes)
684712
if result != 0:
685713
error = _parse_operation_result_for_error(_lib.c2pa_error())
686714
if error:
@@ -2198,6 +2226,7 @@ class Builder:
21982226
'url_error': "Error setting remote URL: {}",
21992227
'resource_error': "Error adding resource: {}",
22002228
'ingredient_error': "Error adding ingredient: {}",
2229+
'action_error': "Error adding action: {}",
22012230
'archive_error': "Error writing archive: {}",
22022231
'sign_error': "Error during signing: {}",
22032232
'encoding_error': "Invalid UTF-8 characters in manifest: {}",
@@ -2495,13 +2524,16 @@ def add_resource(self, uri: str, stream: Any):
24952524
)
24962525
)
24972526

2498-
def add_ingredient(self, ingredient_json: str, format: str, source: Any):
2527+
def add_ingredient(
2528+
self, ingredient_json: Union[str, dict], format: str, source: Any
2529+
):
24992530
"""Add an ingredient to the builder (facade method).
25002531
The added ingredient's source should be a stream-like object
25012532
(for instance, a file opened as stream).
25022533
25032534
Args:
25042535
ingredient_json: The JSON ingredient definition
2536+
(either a JSON string or a dictionary)
25052537
format: The MIME type or extension of the ingredient
25062538
source: The stream containing the ingredient data
25072539
(any Python stream-like object)
@@ -2521,14 +2553,15 @@ def add_ingredient(self, ingredient_json: str, format: str, source: Any):
25212553

25222554
def add_ingredient_from_stream(
25232555
self,
2524-
ingredient_json: str,
2556+
ingredient_json: Union[str, dict],
25252557
format: str,
25262558
source: Any):
25272559
"""Add an ingredient from a stream to the builder.
25282560
Explicitly named API requiring a stream as input parameter.
25292561
25302562
Args:
25312563
ingredient_json: The JSON ingredient definition
2564+
(either a JSON string or a dictionary)
25322565
format: The MIME type or extension of the ingredient
25332566
source: The stream containing the ingredient data
25342567
(any Python stream-like object)
@@ -2540,6 +2573,9 @@ def add_ingredient_from_stream(
25402573
"""
25412574
self._ensure_valid_state()
25422575

2576+
if isinstance(ingredient_json, dict):
2577+
ingredient_json = json.dumps(ingredient_json)
2578+
25432579
try:
25442580
ingredient_str = ingredient_json.encode('utf-8')
25452581
format_str = format.encode('utf-8')
@@ -2570,7 +2606,7 @@ def add_ingredient_from_stream(
25702606

25712607
def add_ingredient_from_file_path(
25722608
self,
2573-
ingredient_json: str,
2609+
ingredient_json: Union[str, dict],
25742610
format: str,
25752611
filepath: Union[str, Path]):
25762612
"""Add an ingredient from a file path to the builder (deprecated).
@@ -2582,6 +2618,7 @@ def add_ingredient_from_file_path(
25822618
25832619
Args:
25842620
ingredient_json: The JSON ingredient definition
2621+
(either a JSON string or a dictionary)
25852622
format: The MIME type or extension of the ingredient
25862623
filepath: The path to the file containing the ingredient data
25872624
(can be a string or Path object)
@@ -2613,6 +2650,42 @@ def add_ingredient_from_file_path(
26132650
except Exception as e:
26142651
raise C2paError.Other(f"Could not add ingredient: {e}") from e
26152652

2653+
def add_action(self, action_json: Union[str, dict]) -> None:
2654+
"""Add an action to the builder, that will be placed
2655+
in the actions assertion array in the generated manifest.
2656+
2657+
Args:
2658+
action_json: The JSON action definition
2659+
(either a JSON string or a dictionary)
2660+
2661+
Raises:
2662+
C2paError: If there was an error adding the action
2663+
C2paError.Encoding: If the action JSON contains invalid UTF-8 chars
2664+
"""
2665+
self._ensure_valid_state()
2666+
2667+
if isinstance(action_json, dict):
2668+
action_json = json.dumps(action_json)
2669+
2670+
try:
2671+
action_str = action_json.encode('utf-8')
2672+
except UnicodeError as e:
2673+
raise C2paError.Encoding(
2674+
Builder._ERROR_MESSAGES['encoding_error'].format(str(e))
2675+
)
2676+
2677+
result = _lib.c2pa_builder_add_action(self._builder, action_str)
2678+
2679+
if result != 0:
2680+
error = _parse_operation_result_for_error(_lib.c2pa_error())
2681+
if error:
2682+
raise C2paError(error)
2683+
raise C2paError(
2684+
Builder._ERROR_MESSAGES['action_error'].format(
2685+
"Unknown error"
2686+
)
2687+
)
2688+
26162689
def to_archive(self, stream: Any) -> None:
26172690
"""Write an archive of the builder to a stream.
26182691

0 commit comments

Comments
 (0)