Skip to content

Commit d0a6d36

Browse files
committed
fix: New bindings
1 parent bcdb721 commit d0a6d36

File tree

2 files changed

+134
-3
lines changed

2 files changed

+134
-3
lines changed

src/c2pa/c2pa.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,10 @@
6464
'c2pa_free_string_array',
6565
'c2pa_reader_supported_mime_types',
6666
'c2pa_builder_supported_mime_types',
67+
'c2pa_reader_is_embedded',
68+
'c2pa_reader_remote_url',
6769
]
6870

69-
# TODO Bindings:
70-
# c2pa_reader_is_embedded
71-
# c2pa_reader_remote_url
7271

7372

7473
def _validate_library_exports(lib):
@@ -369,6 +368,16 @@ def _setup_function(func, argtypes, restype=None):
369368
[ctypes.POINTER(ctypes.c_size_t)],
370369
ctypes.POINTER(ctypes.c_char_p)
371370
)
371+
_setup_function(
372+
_lib.c2pa_reader_is_embedded,
373+
[ctypes.POINTER(C2paReader)],
374+
ctypes.c_bool
375+
)
376+
_setup_function(
377+
_lib.c2pa_reader_remote_url,
378+
[ctypes.POINTER(C2paReader)],
379+
ctypes.c_void_p
380+
)
372381

373382
# Set up Builder function prototypes
374383
_setup_function(
@@ -1677,6 +1686,52 @@ def resource_to_stream(self, uri: str, stream: Any) -> int:
16771686

16781687
return result
16791688

1689+
def is_embedded(self) -> bool:
1690+
"""Check if the reader was created from an embedded manifest.
1691+
1692+
This method determines whether the C2PA manifest is embedded directly
1693+
in the asset file or stored remotely.
1694+
1695+
Returns:
1696+
True if the reader was created from an embedded manifest,
1697+
False if it was created from a remote manifest
1698+
1699+
Raises:
1700+
C2paError: If there was an error checking the embedded status
1701+
"""
1702+
self._ensure_valid_state()
1703+
1704+
result = _lib.c2pa_reader_is_embedded(self._reader)
1705+
1706+
# c_bool should return a Python bool directly
1707+
return bool(result)
1708+
1709+
def get_remote_url(self) -> Optional[str]:
1710+
"""Get the remote URL of the manifest if it was obtained remotely.
1711+
1712+
This method returns the URL from which the C2PA manifest was fetched
1713+
if the reader was created from a remote manifest. If the manifest
1714+
is embedded in the asset, this will return None.
1715+
1716+
Returns:
1717+
The remote URL as a string if the manifest was obtained remotely,
1718+
None if the manifest is embedded or no remote URL is available
1719+
1720+
Raises:
1721+
C2paError: If there was an error getting the remote URL
1722+
"""
1723+
self._ensure_valid_state()
1724+
1725+
result = _lib.c2pa_reader_remote_url(self._reader)
1726+
1727+
if result is None:
1728+
# No remote URL set (manifest is embedded)
1729+
return None
1730+
1731+
# Convert the C string to Python string
1732+
url_str = _convert_to_py_string(result)
1733+
return url_str
1734+
16801735

16811736
class Signer:
16821737
"""High-level wrapper for C2PA Signer operations."""

tests/test_unit_tests.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,82 @@ def test_reader_state_with_invalid_native_pointer(self):
367367
# assert active_manifest is not None, "Active manifest should not be null"
368368
# assert len(active_manifest) > 0, "Active manifest should not be empty"
369369

370+
def test_reader_is_embedded(self):
371+
"""Test the is_embedded method returns correct values for embedded and remote manifests."""
372+
373+
with open(self.testPath, "rb") as file:
374+
reader = Reader("image/jpeg", file)
375+
self.assertTrue(reader.is_embedded(),)
376+
reader.close()
377+
378+
# Test remote manifest
379+
with open(os.path.join(self.data_dir, "es256_certs.pem"), "rb") as cert_file:
380+
certs = cert_file.read()
381+
with open(os.path.join(self.data_dir, "es256_private.key"), "rb") as key_file:
382+
key = key_file.read()
383+
384+
# Create signer info and signer
385+
signer_info = C2paSignerInfo(
386+
alg=b"es256",
387+
sign_cert=certs,
388+
private_key=key,
389+
ta_url=b"http://timestamp.digicert.com"
390+
)
391+
signer = Signer.from_info(signer_info)
392+
393+
# Define a simple manifest
394+
manifest_definition = {
395+
"claim_generator": "python_test",
396+
"claim_generator_info": [{
397+
"name": "python_test",
398+
"version": "0.0.1",
399+
}],
400+
"claim_version": 1,
401+
"format": "image/jpeg",
402+
"title": "Python Test Image",
403+
"ingredients": [],
404+
"assertions": [
405+
{
406+
"label": "c2pa.actions",
407+
"data": {
408+
"actions": [
409+
{
410+
"action": "c2pa.opened"
411+
}
412+
]
413+
}
414+
}
415+
]
416+
}
417+
418+
# Test remote manifest
419+
with open(self.testPath, "rb") as file:
420+
builder = Builder(manifest_definition)
421+
builder.set_no_embed()
422+
with io.BytesIO() as output_buffer:
423+
manifest_data = builder.sign(
424+
signer, "image/jpeg", file, output_buffer)
425+
output_buffer.seek(0)
426+
read_buffer = io.BytesIO(output_buffer.getvalue())
427+
428+
with Reader("image/jpeg", read_buffer, manifest_data) as reader:
429+
self.assertFalse(reader.is_embedded(), "Remote manifest should return False")
430+
431+
def test_reader_get_remote_url(self):
432+
"""Test the get_remote_url method returns correct values for embedded and remote manifests."""
433+
434+
# Test embedded manifest (should return None)
435+
with open(self.testPath, "rb") as file:
436+
reader = Reader("image/jpeg", file)
437+
self.assertIsNone(reader.get_remote_url(), "Embedded manifest should return None for remote URL")
438+
reader.close()
439+
440+
# Test remote manifest using cloud.jpg fixture which has a remote URL
441+
cloud_fixture_path = os.path.join(self.data_dir, "files-for-reading-tests", "cloud.jpg")
442+
with Reader("image/jpeg", cloud_fixture_path) as reader:
443+
remote_url = reader.get_remote_url()
444+
self.assertFalse(reader.is_embedded(), "This should be a remote manifest")
445+
370446

371447
class TestBuilderWithSigner(unittest.TestCase):
372448
def setUp(self):

0 commit comments

Comments
 (0)