c2pa-python 0.26.0__tar.gz → 0.27.0__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.26.0/src/c2pa_python.egg-info → c2pa_python-0.27.0}/PKG-INFO +1 -1
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/pyproject.toml +1 -1
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/src/c2pa/c2pa.py +83 -10
- {c2pa_python-0.26.0 → c2pa_python-0.27.0/src/c2pa_python.egg-info}/PKG-INFO +1 -1
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/tests/test_unit_tests.py +629 -1
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/LICENSE-APACHE +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/LICENSE-MIT +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/MANIFEST.in +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/README.md +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/requirements.txt +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/scripts/download_artifacts.py +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/setup.cfg +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/setup.py +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/src/c2pa/__init__.py +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/src/c2pa/build.py +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/src/c2pa/lib.py +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/src/c2pa_python.egg-info/SOURCES.txt +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/src/c2pa_python.egg-info/dependency_links.txt +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/src/c2pa_python.egg-info/entry_points.txt +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/src/c2pa_python.egg-info/requires.txt +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/src/c2pa_python.egg-info/top_level.txt +0 -0
- {c2pa_python-0.26.0 → c2pa_python-0.27.0}/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.
|
|
3
|
+
Version: 0.27.0
|
|
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>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "c2pa-python"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.27.0"
|
|
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" }
|
|
@@ -49,6 +49,7 @@ _REQUIRED_FUNCTIONS = [
|
|
|
49
49
|
'c2pa_builder_set_remote_url',
|
|
50
50
|
'c2pa_builder_add_resource',
|
|
51
51
|
'c2pa_builder_add_ingredient_from_stream',
|
|
52
|
+
'c2pa_builder_add_action',
|
|
52
53
|
'c2pa_builder_to_archive',
|
|
53
54
|
'c2pa_builder_sign',
|
|
54
55
|
'c2pa_manifest_bytes_free',
|
|
@@ -403,6 +404,9 @@ _setup_function(_lib.c2pa_builder_add_ingredient_from_stream,
|
|
|
403
404
|
ctypes.c_char_p,
|
|
404
405
|
ctypes.POINTER(C2paStream)],
|
|
405
406
|
ctypes.c_int)
|
|
407
|
+
_setup_function(_lib.c2pa_builder_add_action,
|
|
408
|
+
[ctypes.POINTER(C2paBuilder), ctypes.c_char_p],
|
|
409
|
+
ctypes.c_int)
|
|
406
410
|
_setup_function(_lib.c2pa_builder_to_archive,
|
|
407
411
|
[ctypes.POINTER(C2paBuilder), ctypes.POINTER(C2paStream)],
|
|
408
412
|
ctypes.c_int)
|
|
@@ -667,20 +671,44 @@ def version() -> str:
|
|
|
667
671
|
return _convert_to_py_string(result)
|
|
668
672
|
|
|
669
673
|
|
|
674
|
+
@overload
|
|
670
675
|
def load_settings(settings: str, format: str = "json") -> None:
|
|
671
|
-
|
|
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.
|
|
672
686
|
|
|
673
687
|
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.
|
|
676
691
|
|
|
677
692
|
Raises:
|
|
678
693
|
C2paError: If there was an error loading the settings
|
|
679
694
|
"""
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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)
|
|
684
712
|
if result != 0:
|
|
685
713
|
error = _parse_operation_result_for_error(_lib.c2pa_error())
|
|
686
714
|
if error:
|
|
@@ -2198,6 +2226,7 @@ class Builder:
|
|
|
2198
2226
|
'url_error': "Error setting remote URL: {}",
|
|
2199
2227
|
'resource_error': "Error adding resource: {}",
|
|
2200
2228
|
'ingredient_error': "Error adding ingredient: {}",
|
|
2229
|
+
'action_error': "Error adding action: {}",
|
|
2201
2230
|
'archive_error': "Error writing archive: {}",
|
|
2202
2231
|
'sign_error': "Error during signing: {}",
|
|
2203
2232
|
'encoding_error': "Invalid UTF-8 characters in manifest: {}",
|
|
@@ -2495,13 +2524,16 @@ class Builder:
|
|
|
2495
2524
|
)
|
|
2496
2525
|
)
|
|
2497
2526
|
|
|
2498
|
-
def add_ingredient(
|
|
2527
|
+
def add_ingredient(
|
|
2528
|
+
self, ingredient_json: Union[str, dict], format: str, source: Any
|
|
2529
|
+
):
|
|
2499
2530
|
"""Add an ingredient to the builder (facade method).
|
|
2500
2531
|
The added ingredient's source should be a stream-like object
|
|
2501
2532
|
(for instance, a file opened as stream).
|
|
2502
2533
|
|
|
2503
2534
|
Args:
|
|
2504
2535
|
ingredient_json: The JSON ingredient definition
|
|
2536
|
+
(either a JSON string or a dictionary)
|
|
2505
2537
|
format: The MIME type or extension of the ingredient
|
|
2506
2538
|
source: The stream containing the ingredient data
|
|
2507
2539
|
(any Python stream-like object)
|
|
@@ -2521,7 +2553,7 @@ class Builder:
|
|
|
2521
2553
|
|
|
2522
2554
|
def add_ingredient_from_stream(
|
|
2523
2555
|
self,
|
|
2524
|
-
ingredient_json: str,
|
|
2556
|
+
ingredient_json: Union[str, dict],
|
|
2525
2557
|
format: str,
|
|
2526
2558
|
source: Any):
|
|
2527
2559
|
"""Add an ingredient from a stream to the builder.
|
|
@@ -2529,6 +2561,7 @@ class Builder:
|
|
|
2529
2561
|
|
|
2530
2562
|
Args:
|
|
2531
2563
|
ingredient_json: The JSON ingredient definition
|
|
2564
|
+
(either a JSON string or a dictionary)
|
|
2532
2565
|
format: The MIME type or extension of the ingredient
|
|
2533
2566
|
source: The stream containing the ingredient data
|
|
2534
2567
|
(any Python stream-like object)
|
|
@@ -2540,6 +2573,9 @@ class Builder:
|
|
|
2540
2573
|
"""
|
|
2541
2574
|
self._ensure_valid_state()
|
|
2542
2575
|
|
|
2576
|
+
if isinstance(ingredient_json, dict):
|
|
2577
|
+
ingredient_json = json.dumps(ingredient_json)
|
|
2578
|
+
|
|
2543
2579
|
try:
|
|
2544
2580
|
ingredient_str = ingredient_json.encode('utf-8')
|
|
2545
2581
|
format_str = format.encode('utf-8')
|
|
@@ -2570,7 +2606,7 @@ class Builder:
|
|
|
2570
2606
|
|
|
2571
2607
|
def add_ingredient_from_file_path(
|
|
2572
2608
|
self,
|
|
2573
|
-
ingredient_json: str,
|
|
2609
|
+
ingredient_json: Union[str, dict],
|
|
2574
2610
|
format: str,
|
|
2575
2611
|
filepath: Union[str, Path]):
|
|
2576
2612
|
"""Add an ingredient from a file path to the builder (deprecated).
|
|
@@ -2582,6 +2618,7 @@ class Builder:
|
|
|
2582
2618
|
|
|
2583
2619
|
Args:
|
|
2584
2620
|
ingredient_json: The JSON ingredient definition
|
|
2621
|
+
(either a JSON string or a dictionary)
|
|
2585
2622
|
format: The MIME type or extension of the ingredient
|
|
2586
2623
|
filepath: The path to the file containing the ingredient data
|
|
2587
2624
|
(can be a string or Path object)
|
|
@@ -2613,6 +2650,42 @@ class Builder:
|
|
|
2613
2650
|
except Exception as e:
|
|
2614
2651
|
raise C2paError.Other(f"Could not add ingredient: {e}") from e
|
|
2615
2652
|
|
|
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
|
+
|
|
2616
2689
|
def to_archive(self, stream: Any) -> None:
|
|
2617
2690
|
"""Write an archive of the builder to a stream.
|
|
2618
2691
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: c2pa-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.27.0
|
|
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>
|
|
@@ -733,6 +733,7 @@ class TestBuilderWithSigner(unittest.TestCase):
|
|
|
733
733
|
"actions": [
|
|
734
734
|
{
|
|
735
735
|
"action": "c2pa.opened"
|
|
736
|
+
# Should have more parameters here, but omitted in tests
|
|
736
737
|
}
|
|
737
738
|
]
|
|
738
739
|
}
|
|
@@ -742,7 +743,6 @@ class TestBuilderWithSigner(unittest.TestCase):
|
|
|
742
743
|
|
|
743
744
|
# Define a V2 manifest as a dictionary
|
|
744
745
|
self.manifestDefinitionV2 = {
|
|
745
|
-
"claim_generator": "python_test",
|
|
746
746
|
"claim_generator_info": [{
|
|
747
747
|
"name": "python_test",
|
|
748
748
|
"version": "0.0.1",
|
|
@@ -1289,6 +1289,17 @@ class TestBuilderWithSigner(unittest.TestCase):
|
|
|
1289
1289
|
|
|
1290
1290
|
builder.close()
|
|
1291
1291
|
|
|
1292
|
+
def test_builder_add_ingredient_dict(self):
|
|
1293
|
+
builder = Builder.from_json(self.manifestDefinition)
|
|
1294
|
+
assert builder._builder is not None
|
|
1295
|
+
|
|
1296
|
+
# Test adding ingredient with a dictionary instead of JSON string
|
|
1297
|
+
ingredient_dict = {"test": "ingredient"}
|
|
1298
|
+
with open(self.testPath, 'rb') as f:
|
|
1299
|
+
builder.add_ingredient(ingredient_dict, "image/jpeg", f)
|
|
1300
|
+
|
|
1301
|
+
builder.close()
|
|
1302
|
+
|
|
1292
1303
|
def test_builder_add_multiple_ingredients(self):
|
|
1293
1304
|
builder = Builder.from_json(self.manifestDefinition)
|
|
1294
1305
|
assert builder._builder is not None
|
|
@@ -1309,6 +1320,26 @@ class TestBuilderWithSigner(unittest.TestCase):
|
|
|
1309
1320
|
|
|
1310
1321
|
builder.close()
|
|
1311
1322
|
|
|
1323
|
+
def test_builder_add_multiple_ingredients_2(self):
|
|
1324
|
+
builder = Builder.from_json(self.manifestDefinition)
|
|
1325
|
+
assert builder._builder is not None
|
|
1326
|
+
|
|
1327
|
+
# Test builder operations
|
|
1328
|
+
builder.set_no_embed()
|
|
1329
|
+
builder.set_remote_url("http://test.url")
|
|
1330
|
+
|
|
1331
|
+
# Test adding ingredient with a dictionary
|
|
1332
|
+
ingredient_dict = {"test": "ingredient"}
|
|
1333
|
+
with open(self.testPath, 'rb') as f:
|
|
1334
|
+
builder.add_ingredient(ingredient_dict, "image/jpeg", f)
|
|
1335
|
+
|
|
1336
|
+
# Test adding another ingredient with a JSON string
|
|
1337
|
+
ingredient_json = '{"test": "ingredient2"}'
|
|
1338
|
+
with open(self.testPath2, 'rb') as f:
|
|
1339
|
+
builder.add_ingredient(ingredient_json, "image/png", f)
|
|
1340
|
+
|
|
1341
|
+
builder.close()
|
|
1342
|
+
|
|
1312
1343
|
def test_builder_add_multiple_ingredients_and_resources(self):
|
|
1313
1344
|
builder = Builder.from_json(self.manifestDefinition)
|
|
1314
1345
|
assert builder._builder is not None
|
|
@@ -1451,6 +1482,53 @@ class TestBuilderWithSigner(unittest.TestCase):
|
|
|
1451
1482
|
# Settings are thread-local, so we reset to the default "true" here
|
|
1452
1483
|
load_settings('{"builder": { "thumbnail": {"enabled": true}}}')
|
|
1453
1484
|
|
|
1485
|
+
def test_builder_sign_with_settingdict_no_thumbnail_and_ingredient(self):
|
|
1486
|
+
builder = Builder.from_json(self.manifestDefinition)
|
|
1487
|
+
assert builder._builder is not None
|
|
1488
|
+
|
|
1489
|
+
# The following removes the manifest's thumbnail - using dict instead of string
|
|
1490
|
+
load_settings({"builder": {"thumbnail": {"enabled": False}}})
|
|
1491
|
+
|
|
1492
|
+
# Test adding ingredient
|
|
1493
|
+
ingredient_json = '{ "title": "Test Ingredient" }'
|
|
1494
|
+
with open(self.testPath3, 'rb') as f:
|
|
1495
|
+
builder.add_ingredient(ingredient_json, "image/jpeg", f)
|
|
1496
|
+
|
|
1497
|
+
with open(self.testPath2, "rb") as file:
|
|
1498
|
+
output = io.BytesIO(bytearray())
|
|
1499
|
+
builder.sign(self.signer, "image/jpeg", file, output)
|
|
1500
|
+
output.seek(0)
|
|
1501
|
+
reader = Reader("image/jpeg", output)
|
|
1502
|
+
json_data = reader.json()
|
|
1503
|
+
manifest_data = json.loads(json_data)
|
|
1504
|
+
|
|
1505
|
+
# Verify active manifest exists
|
|
1506
|
+
self.assertIn("active_manifest", manifest_data)
|
|
1507
|
+
active_manifest_id = manifest_data["active_manifest"]
|
|
1508
|
+
|
|
1509
|
+
# Verify active manifest object exists
|
|
1510
|
+
self.assertIn("manifests", manifest_data)
|
|
1511
|
+
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
1512
|
+
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
1513
|
+
|
|
1514
|
+
# There should be no thumbnail anymore here
|
|
1515
|
+
self.assertNotIn("thumbnail", active_manifest)
|
|
1516
|
+
|
|
1517
|
+
# Verify ingredients array exists in active manifest
|
|
1518
|
+
self.assertIn("ingredients", active_manifest)
|
|
1519
|
+
self.assertIsInstance(active_manifest["ingredients"], list)
|
|
1520
|
+
self.assertTrue(len(active_manifest["ingredients"]) > 0)
|
|
1521
|
+
|
|
1522
|
+
# Verify the first ingredient's title matches what we set
|
|
1523
|
+
first_ingredient = active_manifest["ingredients"][0]
|
|
1524
|
+
self.assertEqual(first_ingredient["title"], "Test Ingredient")
|
|
1525
|
+
self.assertNotIn("thumbnail", first_ingredient)
|
|
1526
|
+
|
|
1527
|
+
builder.close()
|
|
1528
|
+
|
|
1529
|
+
# Settings are thread-local, so we reset to the default "true" here - using dict instead of string
|
|
1530
|
+
load_settings({"builder": {"thumbnail": {"enabled": True}}})
|
|
1531
|
+
|
|
1454
1532
|
def test_builder_sign_with_duplicate_ingredient(self):
|
|
1455
1533
|
builder = Builder.from_json(self.manifestDefinition)
|
|
1456
1534
|
assert builder._builder is not None
|
|
@@ -1537,6 +1615,46 @@ class TestBuilderWithSigner(unittest.TestCase):
|
|
|
1537
1615
|
|
|
1538
1616
|
builder.close()
|
|
1539
1617
|
|
|
1618
|
+
def test_builder_sign_with_ingredient_dict_from_stream(self):
|
|
1619
|
+
builder = Builder.from_json(self.manifestDefinition)
|
|
1620
|
+
assert builder._builder is not None
|
|
1621
|
+
|
|
1622
|
+
# Test adding ingredient using stream with a dictionary
|
|
1623
|
+
ingredient_dict = {"title": "Test Ingredient Stream"}
|
|
1624
|
+
with open(self.testPath3, 'rb') as f:
|
|
1625
|
+
builder.add_ingredient_from_stream(
|
|
1626
|
+
ingredient_dict, "image/jpeg", f)
|
|
1627
|
+
|
|
1628
|
+
with open(self.testPath2, "rb") as file:
|
|
1629
|
+
output = io.BytesIO(bytearray())
|
|
1630
|
+
builder.sign(self.signer, "image/jpeg", file, output)
|
|
1631
|
+
output.seek(0)
|
|
1632
|
+
reader = Reader("image/jpeg", output)
|
|
1633
|
+
json_data = reader.json()
|
|
1634
|
+
manifest_data = json.loads(json_data)
|
|
1635
|
+
|
|
1636
|
+
# Verify active manifest exists
|
|
1637
|
+
self.assertIn("active_manifest", manifest_data)
|
|
1638
|
+
active_manifest_id = manifest_data["active_manifest"]
|
|
1639
|
+
|
|
1640
|
+
# Verify active manifest object exists
|
|
1641
|
+
self.assertIn("manifests", manifest_data)
|
|
1642
|
+
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
1643
|
+
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
1644
|
+
|
|
1645
|
+
# Verify ingredients array exists in active manifest
|
|
1646
|
+
self.assertIn("ingredients", active_manifest)
|
|
1647
|
+
self.assertIsInstance(active_manifest["ingredients"], list)
|
|
1648
|
+
self.assertTrue(len(active_manifest["ingredients"]) > 0)
|
|
1649
|
+
|
|
1650
|
+
# Verify the first ingredient's title matches what we set
|
|
1651
|
+
first_ingredient = active_manifest["ingredients"][0]
|
|
1652
|
+
self.assertEqual(
|
|
1653
|
+
first_ingredient["title"],
|
|
1654
|
+
"Test Ingredient Stream")
|
|
1655
|
+
|
|
1656
|
+
builder.close()
|
|
1657
|
+
|
|
1540
1658
|
def test_builder_sign_with_multiple_ingredient(self):
|
|
1541
1659
|
builder = Builder.from_json(self.manifestDefinition)
|
|
1542
1660
|
assert builder._builder is not None
|
|
@@ -2323,6 +2441,476 @@ class TestBuilderWithSigner(unittest.TestCase):
|
|
|
2323
2441
|
with self.assertRaises(Error):
|
|
2324
2442
|
builder.set_no_embed()
|
|
2325
2443
|
|
|
2444
|
+
def test_builder_add_action_to_manifest_no_auto_add(self):
|
|
2445
|
+
# For testing, remove auto-added actions
|
|
2446
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":false},"auto_opened_action":{"enabled":false},"auto_created_action":{"enabled":false}}}}')
|
|
2447
|
+
|
|
2448
|
+
initial_manifest_definition = {
|
|
2449
|
+
"claim_generator_info": [{
|
|
2450
|
+
"name": "python_test",
|
|
2451
|
+
"version": "0.0.1",
|
|
2452
|
+
}],
|
|
2453
|
+
# claim version 2 is the default
|
|
2454
|
+
# "claim_version": 2,
|
|
2455
|
+
"format": "image/jpeg",
|
|
2456
|
+
"title": "Python Test Image V2",
|
|
2457
|
+
"assertions": [
|
|
2458
|
+
{
|
|
2459
|
+
"label": "c2pa.actions",
|
|
2460
|
+
"data": {
|
|
2461
|
+
"actions": [
|
|
2462
|
+
{
|
|
2463
|
+
"action": "c2pa.created",
|
|
2464
|
+
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation"
|
|
2465
|
+
}
|
|
2466
|
+
]
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
]
|
|
2470
|
+
}
|
|
2471
|
+
builder = Builder.from_json(initial_manifest_definition)
|
|
2472
|
+
|
|
2473
|
+
action_json = '{"action": "c2pa.color_adjustments", "parameters": {"name": "brightnesscontrast"}}'
|
|
2474
|
+
builder.add_action(action_json)
|
|
2475
|
+
|
|
2476
|
+
with open(self.testPath2, "rb") as file:
|
|
2477
|
+
output = io.BytesIO(bytearray())
|
|
2478
|
+
builder.sign(self.signer, "image/jpeg", file, output)
|
|
2479
|
+
output.seek(0)
|
|
2480
|
+
reader = Reader("image/jpeg", output)
|
|
2481
|
+
json_data = reader.json()
|
|
2482
|
+
manifest_data = json.loads(json_data)
|
|
2483
|
+
|
|
2484
|
+
# Verify active manifest exists
|
|
2485
|
+
self.assertIn("active_manifest", manifest_data)
|
|
2486
|
+
active_manifest_id = manifest_data["active_manifest"]
|
|
2487
|
+
|
|
2488
|
+
# Verify active manifest object exists
|
|
2489
|
+
self.assertIn("manifests", manifest_data)
|
|
2490
|
+
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
2491
|
+
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
2492
|
+
|
|
2493
|
+
# Verify assertions object exists in active manifest
|
|
2494
|
+
self.assertIn("assertions", active_manifest)
|
|
2495
|
+
assertions = active_manifest["assertions"]
|
|
2496
|
+
|
|
2497
|
+
# Find the c2pa.actions.v2 assertion to check what we added
|
|
2498
|
+
actions_assertion = None
|
|
2499
|
+
for assertion in assertions:
|
|
2500
|
+
if assertion.get("label") == "c2pa.actions.v2":
|
|
2501
|
+
actions_assertion = assertion
|
|
2502
|
+
break
|
|
2503
|
+
|
|
2504
|
+
self.assertIsNotNone(actions_assertion)
|
|
2505
|
+
self.assertIn("data", actions_assertion)
|
|
2506
|
+
assertion_data = actions_assertion["data"]
|
|
2507
|
+
# Verify the manifest now contains actions
|
|
2508
|
+
self.assertIn("actions", assertion_data)
|
|
2509
|
+
actions = assertion_data["actions"]
|
|
2510
|
+
# Verify "c2pa.color_adjustments" action exists anywhere in the actions array
|
|
2511
|
+
created_action_found = False
|
|
2512
|
+
for action in actions:
|
|
2513
|
+
if action.get("action") == "c2pa.color_adjustments":
|
|
2514
|
+
created_action_found = True
|
|
2515
|
+
break
|
|
2516
|
+
|
|
2517
|
+
self.assertTrue(created_action_found)
|
|
2518
|
+
|
|
2519
|
+
builder.close()
|
|
2520
|
+
|
|
2521
|
+
# Reset settings
|
|
2522
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":true},"auto_opened_action":{"enabled":true},"auto_created_action":{"enabled":true}}}}')
|
|
2523
|
+
|
|
2524
|
+
def test_builder_add_action_to_manifest_from_dict_no_auto_add(self):
|
|
2525
|
+
# For testing, remove auto-added actions
|
|
2526
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":false},"auto_opened_action":{"enabled":false},"auto_created_action":{"enabled":false}}}}')
|
|
2527
|
+
|
|
2528
|
+
initial_manifest_definition = {
|
|
2529
|
+
"claim_generator_info": [{
|
|
2530
|
+
"name": "python_test",
|
|
2531
|
+
"version": "0.0.1",
|
|
2532
|
+
}],
|
|
2533
|
+
# claim version 2 is the default
|
|
2534
|
+
# "claim_version": 2,
|
|
2535
|
+
"format": "image/jpeg",
|
|
2536
|
+
"title": "Python Test Image V2",
|
|
2537
|
+
"assertions": [
|
|
2538
|
+
{
|
|
2539
|
+
"label": "c2pa.actions",
|
|
2540
|
+
"data": {
|
|
2541
|
+
"actions": [
|
|
2542
|
+
{
|
|
2543
|
+
"action": "c2pa.created",
|
|
2544
|
+
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation"
|
|
2545
|
+
}
|
|
2546
|
+
]
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
]
|
|
2550
|
+
}
|
|
2551
|
+
builder = Builder.from_json(initial_manifest_definition)
|
|
2552
|
+
|
|
2553
|
+
# Using a dictionary instead of a JSON string
|
|
2554
|
+
action_dict = {"action": "c2pa.color_adjustments", "parameters": {"name": "brightnesscontrast"}}
|
|
2555
|
+
builder.add_action(action_dict)
|
|
2556
|
+
|
|
2557
|
+
with open(self.testPath2, "rb") as file:
|
|
2558
|
+
output = io.BytesIO(bytearray())
|
|
2559
|
+
builder.sign(self.signer, "image/jpeg", file, output)
|
|
2560
|
+
output.seek(0)
|
|
2561
|
+
reader = Reader("image/jpeg", output)
|
|
2562
|
+
json_data = reader.json()
|
|
2563
|
+
manifest_data = json.loads(json_data)
|
|
2564
|
+
|
|
2565
|
+
# Verify active manifest exists
|
|
2566
|
+
self.assertIn("active_manifest", manifest_data)
|
|
2567
|
+
active_manifest_id = manifest_data["active_manifest"]
|
|
2568
|
+
|
|
2569
|
+
# Verify active manifest object exists
|
|
2570
|
+
self.assertIn("manifests", manifest_data)
|
|
2571
|
+
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
2572
|
+
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
2573
|
+
|
|
2574
|
+
# Verify assertions object exists in active manifest
|
|
2575
|
+
self.assertIn("assertions", active_manifest)
|
|
2576
|
+
assertions = active_manifest["assertions"]
|
|
2577
|
+
|
|
2578
|
+
# Find the c2pa.actions.v2 assertion to check what we added
|
|
2579
|
+
actions_assertion = None
|
|
2580
|
+
for assertion in assertions:
|
|
2581
|
+
if assertion.get("label") == "c2pa.actions.v2":
|
|
2582
|
+
actions_assertion = assertion
|
|
2583
|
+
break
|
|
2584
|
+
|
|
2585
|
+
self.assertIsNotNone(actions_assertion)
|
|
2586
|
+
self.assertIn("data", actions_assertion)
|
|
2587
|
+
assertion_data = actions_assertion["data"]
|
|
2588
|
+
# Verify the manifest now contains actions
|
|
2589
|
+
self.assertIn("actions", assertion_data)
|
|
2590
|
+
actions = assertion_data["actions"]
|
|
2591
|
+
# Verify "c2pa.color_adjustments" action exists anywhere in the actions array
|
|
2592
|
+
created_action_found = False
|
|
2593
|
+
for action in actions:
|
|
2594
|
+
if action.get("action") == "c2pa.color_adjustments":
|
|
2595
|
+
created_action_found = True
|
|
2596
|
+
break
|
|
2597
|
+
|
|
2598
|
+
self.assertTrue(created_action_found)
|
|
2599
|
+
|
|
2600
|
+
builder.close()
|
|
2601
|
+
|
|
2602
|
+
# Reset settings
|
|
2603
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":true},"auto_opened_action":{"enabled":true},"auto_created_action":{"enabled":true}}}}')
|
|
2604
|
+
|
|
2605
|
+
def test_builder_add_action_to_manifest_with_auto_add(self):
|
|
2606
|
+
# For testing, force settings
|
|
2607
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":true},"auto_opened_action":{"enabled":true},"auto_created_action":{"enabled":true}}}}')
|
|
2608
|
+
|
|
2609
|
+
initial_manifest_definition = {
|
|
2610
|
+
"claim_generator_info": [{
|
|
2611
|
+
"name": "python_test",
|
|
2612
|
+
"version": "0.0.1",
|
|
2613
|
+
}],
|
|
2614
|
+
# claim version 2 is the default
|
|
2615
|
+
# "claim_version": 2,
|
|
2616
|
+
"format": "image/jpeg",
|
|
2617
|
+
"title": "Python Test Image V2",
|
|
2618
|
+
"ingredients": [],
|
|
2619
|
+
"assertions": [
|
|
2620
|
+
{
|
|
2621
|
+
"label": "c2pa.actions",
|
|
2622
|
+
"data": {
|
|
2623
|
+
"actions": [
|
|
2624
|
+
{
|
|
2625
|
+
"action": "c2pa.created",
|
|
2626
|
+
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation"
|
|
2627
|
+
}
|
|
2628
|
+
]
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
]
|
|
2632
|
+
}
|
|
2633
|
+
builder = Builder.from_json(initial_manifest_definition)
|
|
2634
|
+
|
|
2635
|
+
action_json = '{"action": "c2pa.color_adjustments", "parameters": {"name": "brightnesscontrast"}}'
|
|
2636
|
+
builder.add_action(action_json)
|
|
2637
|
+
|
|
2638
|
+
with open(self.testPath2, "rb") as file:
|
|
2639
|
+
output = io.BytesIO(bytearray())
|
|
2640
|
+
builder.sign(self.signer, "image/jpeg", file, output)
|
|
2641
|
+
output.seek(0)
|
|
2642
|
+
reader = Reader("image/jpeg", output)
|
|
2643
|
+
json_data = reader.json()
|
|
2644
|
+
manifest_data = json.loads(json_data)
|
|
2645
|
+
|
|
2646
|
+
# Verify active manifest exists
|
|
2647
|
+
self.assertIn("active_manifest", manifest_data)
|
|
2648
|
+
active_manifest_id = manifest_data["active_manifest"]
|
|
2649
|
+
|
|
2650
|
+
# Verify active manifest object exists
|
|
2651
|
+
self.assertIn("manifests", manifest_data)
|
|
2652
|
+
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
2653
|
+
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
2654
|
+
|
|
2655
|
+
# Verify assertions object exists in active manifest
|
|
2656
|
+
self.assertIn("assertions", active_manifest)
|
|
2657
|
+
assertions = active_manifest["assertions"]
|
|
2658
|
+
|
|
2659
|
+
# Find the c2pa.actions.v2 assertion to check what we added
|
|
2660
|
+
actions_assertion = None
|
|
2661
|
+
for assertion in assertions:
|
|
2662
|
+
if assertion.get("label") == "c2pa.actions.v2":
|
|
2663
|
+
actions_assertion = assertion
|
|
2664
|
+
break
|
|
2665
|
+
|
|
2666
|
+
self.assertIsNotNone(actions_assertion)
|
|
2667
|
+
self.assertIn("data", actions_assertion)
|
|
2668
|
+
assertion_data = actions_assertion["data"]
|
|
2669
|
+
# Verify the manifest now contains actions
|
|
2670
|
+
self.assertIn("actions", assertion_data)
|
|
2671
|
+
actions = assertion_data["actions"]
|
|
2672
|
+
# Verify "c2pa.color_adjustments" action exists anywhere in the actions array
|
|
2673
|
+
created_action_found = False
|
|
2674
|
+
for action in actions:
|
|
2675
|
+
if action.get("action") == "c2pa.color_adjustments":
|
|
2676
|
+
created_action_found = True
|
|
2677
|
+
break
|
|
2678
|
+
|
|
2679
|
+
self.assertTrue(created_action_found)
|
|
2680
|
+
|
|
2681
|
+
# Verify "c2pa.created" action exists only once in the actions array
|
|
2682
|
+
created_count = 0
|
|
2683
|
+
for action in actions:
|
|
2684
|
+
if action.get("action") == "c2pa.created":
|
|
2685
|
+
created_count += 1
|
|
2686
|
+
|
|
2687
|
+
self.assertEqual(created_count, 1, "c2pa.created action should appear exactly once")
|
|
2688
|
+
|
|
2689
|
+
builder.close()
|
|
2690
|
+
|
|
2691
|
+
# Reset settings to default
|
|
2692
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":true},"auto_opened_action":{"enabled":true},"auto_created_action":{"enabled":true}}}}')
|
|
2693
|
+
|
|
2694
|
+
def test_builder_minimal_manifest_add_actions_and_sign_no_auto_add(self):
|
|
2695
|
+
# For testing, remove auto-added actions
|
|
2696
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":false},"auto_opened_action":{"enabled":false},"auto_created_action":{"enabled":false}}}}')
|
|
2697
|
+
|
|
2698
|
+
initial_manifest_definition = {
|
|
2699
|
+
"claim_generator": "python_test",
|
|
2700
|
+
"claim_generator_info": [{
|
|
2701
|
+
"name": "python_test",
|
|
2702
|
+
"version": "0.0.1",
|
|
2703
|
+
}],
|
|
2704
|
+
"format": "image/jpeg",
|
|
2705
|
+
"title": "Python Test Image V2",
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
builder = Builder.from_json(initial_manifest_definition)
|
|
2709
|
+
builder.add_action('{ "action": "c2pa.created", "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation"}')
|
|
2710
|
+
|
|
2711
|
+
with open(self.testPath2, "rb") as file:
|
|
2712
|
+
output = io.BytesIO(bytearray())
|
|
2713
|
+
builder.sign(self.signer, "image/jpeg", file, output)
|
|
2714
|
+
output.seek(0)
|
|
2715
|
+
reader = Reader("image/jpeg", output)
|
|
2716
|
+
json_data = reader.json()
|
|
2717
|
+
manifest_data = json.loads(json_data)
|
|
2718
|
+
|
|
2719
|
+
# Verify active manifest exists
|
|
2720
|
+
self.assertIn("active_manifest", manifest_data)
|
|
2721
|
+
active_manifest_id = manifest_data["active_manifest"]
|
|
2722
|
+
|
|
2723
|
+
# Verify active manifest object exists
|
|
2724
|
+
self.assertIn("manifests", manifest_data)
|
|
2725
|
+
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
2726
|
+
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
2727
|
+
|
|
2728
|
+
# Verify assertions object exists in active manifest
|
|
2729
|
+
self.assertIn("assertions", active_manifest)
|
|
2730
|
+
assertions = active_manifest["assertions"]
|
|
2731
|
+
|
|
2732
|
+
# Find the c2pa.actions.v2 assertion to look for what we added
|
|
2733
|
+
actions_assertion = None
|
|
2734
|
+
for assertion in assertions:
|
|
2735
|
+
if assertion.get("label") == "c2pa.actions.v2":
|
|
2736
|
+
actions_assertion = assertion
|
|
2737
|
+
break
|
|
2738
|
+
|
|
2739
|
+
self.assertIsNotNone(actions_assertion)
|
|
2740
|
+
self.assertIn("data", actions_assertion)
|
|
2741
|
+
assertion_data = actions_assertion["data"]
|
|
2742
|
+
# Verify the manifest now contains actions
|
|
2743
|
+
self.assertIn("actions", assertion_data)
|
|
2744
|
+
actions = assertion_data["actions"]
|
|
2745
|
+
# Verify "c2pa.created" action exists anywhere in the actions array
|
|
2746
|
+
created_action_found = False
|
|
2747
|
+
for action in actions:
|
|
2748
|
+
if action.get("action") == "c2pa.created":
|
|
2749
|
+
created_action_found = True
|
|
2750
|
+
break
|
|
2751
|
+
|
|
2752
|
+
self.assertTrue(created_action_found)
|
|
2753
|
+
|
|
2754
|
+
builder.close()
|
|
2755
|
+
|
|
2756
|
+
# Reset settings
|
|
2757
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":true},"auto_opened_action":{"enabled":true},"auto_created_action":{"enabled":true}}}}')
|
|
2758
|
+
|
|
2759
|
+
def test_builder_minimal_manifest_add_actions_and_sign_with_auto_add(self):
|
|
2760
|
+
# For testing, remove auto-added actions
|
|
2761
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":true},"auto_opened_action":{"enabled":true},"auto_created_action":{"enabled":true}}}}')
|
|
2762
|
+
|
|
2763
|
+
initial_manifest_definition = {
|
|
2764
|
+
"claim_generator_info": [{
|
|
2765
|
+
"name": "python_test",
|
|
2766
|
+
"version": "0.0.1",
|
|
2767
|
+
}],
|
|
2768
|
+
"format": "image/jpeg",
|
|
2769
|
+
"title": "Python Test Image V2",
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
builder = Builder.from_json(initial_manifest_definition)
|
|
2773
|
+
action_json = '{"action": "c2pa.color_adjustments", "parameters": {"name": "brightnesscontrast"}}'
|
|
2774
|
+
builder.add_action(action_json)
|
|
2775
|
+
|
|
2776
|
+
with open(self.testPath2, "rb") as file:
|
|
2777
|
+
output = io.BytesIO(bytearray())
|
|
2778
|
+
builder.sign(self.signer, "image/jpeg", file, output)
|
|
2779
|
+
output.seek(0)
|
|
2780
|
+
reader = Reader("image/jpeg", output)
|
|
2781
|
+
json_data = reader.json()
|
|
2782
|
+
manifest_data = json.loads(json_data)
|
|
2783
|
+
|
|
2784
|
+
# Verify active manifest exists
|
|
2785
|
+
self.assertIn("active_manifest", manifest_data)
|
|
2786
|
+
active_manifest_id = manifest_data["active_manifest"]
|
|
2787
|
+
|
|
2788
|
+
# Verify active manifest object exists
|
|
2789
|
+
self.assertIn("manifests", manifest_data)
|
|
2790
|
+
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
2791
|
+
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
2792
|
+
|
|
2793
|
+
# Verify assertions object exists in active manifest
|
|
2794
|
+
self.assertIn("assertions", active_manifest)
|
|
2795
|
+
assertions = active_manifest["assertions"]
|
|
2796
|
+
|
|
2797
|
+
# Find the c2pa.actions.v2 assertion to look for what we added
|
|
2798
|
+
actions_assertion = None
|
|
2799
|
+
for assertion in assertions:
|
|
2800
|
+
if assertion.get("label") == "c2pa.actions.v2":
|
|
2801
|
+
actions_assertion = assertion
|
|
2802
|
+
break
|
|
2803
|
+
|
|
2804
|
+
self.assertIsNotNone(actions_assertion)
|
|
2805
|
+
self.assertIn("data", actions_assertion)
|
|
2806
|
+
assertion_data = actions_assertion["data"]
|
|
2807
|
+
# Verify the manifest now contains actions
|
|
2808
|
+
self.assertIn("actions", assertion_data)
|
|
2809
|
+
actions = assertion_data["actions"]
|
|
2810
|
+
# Verify "c2pa.created" action exists anywhere in the actions array
|
|
2811
|
+
created_action_found = False
|
|
2812
|
+
for action in actions:
|
|
2813
|
+
if action.get("action") == "c2pa.created":
|
|
2814
|
+
created_action_found = True
|
|
2815
|
+
break
|
|
2816
|
+
|
|
2817
|
+
self.assertTrue(created_action_found)
|
|
2818
|
+
|
|
2819
|
+
# Verify "c2pa.color_adjustments" action also exists in the same actions array
|
|
2820
|
+
color_adjustments_found = False
|
|
2821
|
+
for action in actions:
|
|
2822
|
+
if action.get("action") == "c2pa.color_adjustments":
|
|
2823
|
+
color_adjustments_found = True
|
|
2824
|
+
break
|
|
2825
|
+
|
|
2826
|
+
self.assertTrue(color_adjustments_found)
|
|
2827
|
+
|
|
2828
|
+
builder.close()
|
|
2829
|
+
|
|
2830
|
+
# Reset settings
|
|
2831
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":true},"auto_opened_action":{"enabled":true},"auto_created_action":{"enabled":true}}}}')
|
|
2832
|
+
|
|
2833
|
+
def test_builder_sign_dicts_no_auto_add(self):
|
|
2834
|
+
# For testing, remove auto-added actions
|
|
2835
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":false},"auto_opened_action":{"enabled":false},"auto_created_action":{"enabled":false}}}}')
|
|
2836
|
+
|
|
2837
|
+
initial_manifest_definition = {
|
|
2838
|
+
"claim_generator_info": [{
|
|
2839
|
+
"name": "python_test",
|
|
2840
|
+
"version": "0.0.1",
|
|
2841
|
+
}],
|
|
2842
|
+
# claim version 2 is the default
|
|
2843
|
+
# "claim_version": 2,
|
|
2844
|
+
"format": "image/jpeg",
|
|
2845
|
+
"title": "Python Test Image V2",
|
|
2846
|
+
"assertions": [
|
|
2847
|
+
{
|
|
2848
|
+
"label": "c2pa.actions",
|
|
2849
|
+
"data": {
|
|
2850
|
+
"actions": [
|
|
2851
|
+
{
|
|
2852
|
+
"action": "c2pa.created",
|
|
2853
|
+
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation"
|
|
2854
|
+
}
|
|
2855
|
+
]
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
]
|
|
2859
|
+
}
|
|
2860
|
+
builder = Builder.from_json(initial_manifest_definition)
|
|
2861
|
+
|
|
2862
|
+
# Using a dictionary instead of a JSON string
|
|
2863
|
+
action_dict = {"action": "c2pa.color_adjustments", "parameters": {"name": "brightnesscontrast"}}
|
|
2864
|
+
builder.add_action(action_dict)
|
|
2865
|
+
|
|
2866
|
+
with open(self.testPath2, "rb") as file:
|
|
2867
|
+
output = io.BytesIO(bytearray())
|
|
2868
|
+
builder.sign(self.signer, "image/jpeg", file, output)
|
|
2869
|
+
output.seek(0)
|
|
2870
|
+
reader = Reader("image/jpeg", output)
|
|
2871
|
+
json_data = reader.json()
|
|
2872
|
+
manifest_data = json.loads(json_data)
|
|
2873
|
+
|
|
2874
|
+
# Verify active manifest exists
|
|
2875
|
+
self.assertIn("active_manifest", manifest_data)
|
|
2876
|
+
active_manifest_id = manifest_data["active_manifest"]
|
|
2877
|
+
|
|
2878
|
+
# Verify active manifest object exists
|
|
2879
|
+
self.assertIn("manifests", manifest_data)
|
|
2880
|
+
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
2881
|
+
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
2882
|
+
|
|
2883
|
+
# Verify assertions object exists in active manifest
|
|
2884
|
+
self.assertIn("assertions", active_manifest)
|
|
2885
|
+
assertions = active_manifest["assertions"]
|
|
2886
|
+
|
|
2887
|
+
# Find the c2pa.actions.v2 assertion to check what we added
|
|
2888
|
+
actions_assertion = None
|
|
2889
|
+
for assertion in assertions:
|
|
2890
|
+
if assertion.get("label") == "c2pa.actions.v2":
|
|
2891
|
+
actions_assertion = assertion
|
|
2892
|
+
break
|
|
2893
|
+
|
|
2894
|
+
self.assertIsNotNone(actions_assertion)
|
|
2895
|
+
self.assertIn("data", actions_assertion)
|
|
2896
|
+
assertion_data = actions_assertion["data"]
|
|
2897
|
+
# Verify the manifest now contains actions
|
|
2898
|
+
self.assertIn("actions", assertion_data)
|
|
2899
|
+
actions = assertion_data["actions"]
|
|
2900
|
+
# Verify "c2pa.color_adjustments" action exists anywhere in the actions array
|
|
2901
|
+
created_action_found = False
|
|
2902
|
+
for action in actions:
|
|
2903
|
+
if action.get("action") == "c2pa.color_adjustments":
|
|
2904
|
+
created_action_found = True
|
|
2905
|
+
break
|
|
2906
|
+
|
|
2907
|
+
self.assertTrue(created_action_found)
|
|
2908
|
+
|
|
2909
|
+
builder.close()
|
|
2910
|
+
|
|
2911
|
+
# Reset settings
|
|
2912
|
+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":true},"auto_opened_action":{"enabled":true},"auto_created_action":{"enabled":true}}}}')
|
|
2913
|
+
|
|
2326
2914
|
|
|
2327
2915
|
class TestStream(unittest.TestCase):
|
|
2328
2916
|
def setUp(self):
|
|
@@ -2786,6 +3374,46 @@ class TestLegacyAPI(unittest.TestCase):
|
|
|
2786
3374
|
|
|
2787
3375
|
builder.close()
|
|
2788
3376
|
|
|
3377
|
+
def test_builder_sign_with_ingredient_dict_from_file(self):
|
|
3378
|
+
"""Test Builder class operations with an ingredient added from file path using a dictionary."""
|
|
3379
|
+
|
|
3380
|
+
builder = Builder.from_json(self.manifestDefinition)
|
|
3381
|
+
|
|
3382
|
+
# Test adding ingredient from file path with a dictionary
|
|
3383
|
+
ingredient_dict = {"title": "Test Ingredient From File"}
|
|
3384
|
+
# Suppress the specific deprecation warning for this test, as this is a legacy method
|
|
3385
|
+
with warnings.catch_warnings():
|
|
3386
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
|
3387
|
+
builder.add_ingredient_from_file_path(ingredient_dict, "image/jpeg", self.testPath3)
|
|
3388
|
+
|
|
3389
|
+
with open(self.testPath2, "rb") as file:
|
|
3390
|
+
output = io.BytesIO(bytearray())
|
|
3391
|
+
builder.sign(self.signer, "image/jpeg", file, output)
|
|
3392
|
+
output.seek(0)
|
|
3393
|
+
reader = Reader("image/jpeg", output)
|
|
3394
|
+
json_data = reader.json()
|
|
3395
|
+
manifest_data = json.loads(json_data)
|
|
3396
|
+
|
|
3397
|
+
# Verify active manifest exists
|
|
3398
|
+
self.assertIn("active_manifest", manifest_data)
|
|
3399
|
+
active_manifest_id = manifest_data["active_manifest"]
|
|
3400
|
+
|
|
3401
|
+
# Verify active manifest object exists
|
|
3402
|
+
self.assertIn("manifests", manifest_data)
|
|
3403
|
+
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
3404
|
+
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
3405
|
+
|
|
3406
|
+
# Verify ingredients array exists in active manifest
|
|
3407
|
+
self.assertIn("ingredients", active_manifest)
|
|
3408
|
+
self.assertIsInstance(active_manifest["ingredients"], list)
|
|
3409
|
+
self.assertTrue(len(active_manifest["ingredients"]) > 0)
|
|
3410
|
+
|
|
3411
|
+
# Verify the first ingredient's title matches what we set
|
|
3412
|
+
first_ingredient = active_manifest["ingredients"][0]
|
|
3413
|
+
self.assertEqual(first_ingredient["title"], "Test Ingredient From File")
|
|
3414
|
+
|
|
3415
|
+
builder.close()
|
|
3416
|
+
|
|
2789
3417
|
def test_builder_add_ingredient_from_file_path(self):
|
|
2790
3418
|
"""Test Builder class add_ingredient_from_file_path method."""
|
|
2791
3419
|
|
|
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
|
|
File without changes
|