c2pa-python 0.37.0__tar.gz → 0.37.2__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.37.0/src/c2pa_python.egg-info → c2pa_python-0.37.2}/PKG-INFO +1 -1
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/pyproject.toml +1 -1
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/src/c2pa/c2pa.py +196 -87
- {c2pa_python-0.37.0 → c2pa_python-0.37.2/src/c2pa_python.egg-info}/PKG-INFO +1 -1
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/tests/test_unit_tests.py +754 -23
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/LICENSE-APACHE +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/LICENSE-MIT +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/MANIFEST.in +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/README.md +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/requirements.txt +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/scripts/download_artifacts.py +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/setup.cfg +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/setup.py +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/src/c2pa/__init__.py +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/src/c2pa/build.py +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/src/c2pa/lib.py +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/src/c2pa_python.egg-info/SOURCES.txt +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/src/c2pa_python.egg-info/dependency_links.txt +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/src/c2pa_python.egg-info/entry_points.txt +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/src/c2pa_python.egg-info/requires.txt +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/src/c2pa_python.egg-info/top_level.txt +0 -0
- {c2pa_python-0.37.0 → c2pa_python-0.37.2}/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.37.
|
|
3
|
+
Version: 0.37.2
|
|
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>, Tania Mathern <mathern@adobe.com>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "c2pa-python"
|
|
7
|
-
version = "0.37.
|
|
7
|
+
version = "0.37.2"
|
|
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" }
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# specific language governing permissions and limitations under
|
|
12
12
|
# each license.
|
|
13
13
|
|
|
14
|
-
# Version: 0.37.
|
|
14
|
+
# Version: 0.37.2
|
|
15
15
|
|
|
16
16
|
import ctypes
|
|
17
17
|
import enum
|
|
@@ -1196,16 +1196,21 @@ def load_settings(settings: Union[str, dict], format: str = "json") -> None:
|
|
|
1196
1196
|
|
|
1197
1197
|
|
|
1198
1198
|
def _get_mime_type_from_path(path: Union[str, Path]) -> str:
|
|
1199
|
-
"""Attempt to guess the MIME type from a file path
|
|
1199
|
+
"""Attempt to guess the MIME type from a file path's extension.
|
|
1200
|
+
When the extension is missing or unrecognized, this returns an empty
|
|
1201
|
+
string so the caller hands it to the lib for auto-detection.
|
|
1202
|
+
A recognized-but-wrong extension still returns a mimetype:
|
|
1203
|
+
the native layer will attempt to correct it from the bytes when
|
|
1204
|
+
reading (the real type still needs to be supported by the lib),
|
|
1205
|
+
and if it can't, an error will happen then.
|
|
1200
1206
|
|
|
1201
1207
|
Args:
|
|
1202
1208
|
path: File path as string or Path object
|
|
1203
1209
|
|
|
1204
1210
|
Returns:
|
|
1205
|
-
MIME type string
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
C2paError.NotSupported: If MIME type cannot be determined
|
|
1211
|
+
MIME type string, or an empty string
|
|
1212
|
+
(when it cannot be determined from the extension).
|
|
1213
|
+
An empty string here means the native lib should attempt auto-detect.
|
|
1209
1214
|
"""
|
|
1210
1215
|
path_obj = Path(path)
|
|
1211
1216
|
file_extension = path_obj.suffix.lower() if path_obj.suffix else ""
|
|
@@ -1215,11 +1220,9 @@ def _get_mime_type_from_path(path: Union[str, Path]) -> str:
|
|
|
1215
1220
|
# so we bypass it and set the correct type
|
|
1216
1221
|
return "image/dng"
|
|
1217
1222
|
else:
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
f"Could not determine MIME type for file: {path}")
|
|
1222
|
-
return mime_type
|
|
1223
|
+
# Fall back to an empty string for extensionless or unknown files.
|
|
1224
|
+
# Empty string flags this as a guess-type attempt.
|
|
1225
|
+
return mimetypes.guess_type(str(path))[0] or ""
|
|
1223
1226
|
|
|
1224
1227
|
|
|
1225
1228
|
class ContextProvider(ABC):
|
|
@@ -1567,6 +1570,9 @@ class Stream:
|
|
|
1567
1570
|
# Maximum value for a 32-bit signed integer (2^31 - 1)
|
|
1568
1571
|
_MAX_STREAM_ID = 2**31 - 1
|
|
1569
1572
|
|
|
1573
|
+
# Methods an object must expose to be wrapped as Stream.
|
|
1574
|
+
_REQUIRED_STREAM_METHODS = ("read", "write", "seek", "tell", "flush")
|
|
1575
|
+
|
|
1570
1576
|
# Class-level error messages to avoid multiple creation
|
|
1571
1577
|
_ERROR_MESSAGES = {
|
|
1572
1578
|
'stream_error': "Error cleaning up stream: {}",
|
|
@@ -1613,15 +1619,14 @@ class Stream:
|
|
|
1613
1619
|
self._stream_id = f"{id(self)}-{stream_counter}"
|
|
1614
1620
|
|
|
1615
1621
|
# Rest of the existing initialization code...
|
|
1616
|
-
required_methods = ['read', 'write', 'seek', 'tell', 'flush']
|
|
1617
1622
|
missing_methods = [
|
|
1618
|
-
method for method in
|
|
1623
|
+
method for method in Stream._REQUIRED_STREAM_METHODS if not hasattr(
|
|
1619
1624
|
file_like_stream, method)]
|
|
1620
1625
|
if missing_methods:
|
|
1621
1626
|
raise TypeError(
|
|
1622
1627
|
"Object must be a stream-like object with methods: {}. "
|
|
1623
1628
|
"Missing: {}".format(
|
|
1624
|
-
", ".join(
|
|
1629
|
+
", ".join(Stream._REQUIRED_STREAM_METHODS),
|
|
1625
1630
|
", ".join(missing_methods),
|
|
1626
1631
|
)
|
|
1627
1632
|
)
|
|
@@ -1949,7 +1954,8 @@ def _get_supported_mime_types(ffi_func, cache):
|
|
|
1949
1954
|
try:
|
|
1950
1955
|
if arr[i] is None:
|
|
1951
1956
|
continue
|
|
1952
|
-
mime_type = arr[i].decode(
|
|
1957
|
+
mime_type = arr[i].decode(
|
|
1958
|
+
"utf-8", errors='replace').strip().lower()
|
|
1953
1959
|
if mime_type:
|
|
1954
1960
|
result.append(mime_type)
|
|
1955
1961
|
except Exception:
|
|
@@ -1968,33 +1974,75 @@ def _get_supported_mime_types(ffi_func, cache):
|
|
|
1968
1974
|
return [], cache
|
|
1969
1975
|
|
|
1970
1976
|
|
|
1971
|
-
def
|
|
1972
|
-
format_str:
|
|
1973
|
-
|
|
1974
|
-
|
|
1977
|
+
def _encode_format(
|
|
1978
|
+
format_str: Optional[str],
|
|
1979
|
+
class_name: str,
|
|
1980
|
+
allow_autodetect: bool = True,
|
|
1981
|
+
) -> Optional[bytes]:
|
|
1982
|
+
"""Normalize a MIME type/format string and encode it to UTF-8 bytes.
|
|
1983
|
+
|
|
1984
|
+
The binding does not validate the format itself: the native library is the
|
|
1985
|
+
authority on what is supported and detects the asset type from the bytes,
|
|
1986
|
+
so the format is passed straight through after trimming whitespace.
|
|
1987
|
+
|
|
1988
|
+
None, an empty string, or whitespace all mean the same thing: no format was
|
|
1989
|
+
given. When allow_autodetect is True (default), that requests detection and
|
|
1990
|
+
the native library guesses the type from the bytes. When allow_autodetect
|
|
1991
|
+
is False, a missing format is rejected instead.
|
|
1975
1992
|
|
|
1976
1993
|
Args:
|
|
1977
|
-
format_str: The MIME type or format
|
|
1978
|
-
|
|
1979
|
-
|
|
1994
|
+
format_str: The MIME type or format, or None. Pass None, an empty
|
|
1995
|
+
string, or whitespace to request detection from the asset's bytes
|
|
1996
|
+
(only when ``allow_autodetect`` is True).
|
|
1997
|
+
class_name: Name of the calling class (for error messages).
|
|
1998
|
+
allow_autodetect: When True (default), a missing format requests
|
|
1999
|
+
detection. When False, a missing format raises
|
|
2000
|
+
C2paError.NotSupported.
|
|
1980
2001
|
|
|
1981
2002
|
Returns:
|
|
1982
|
-
UTF-8
|
|
2003
|
+
The lowercased UTF-8 format bytes, or None to request auto-detection
|
|
2004
|
+
(no format given). Use _format_ffi_arg to turn the result into the
|
|
2005
|
+
empty-bytes flag the native library expects.
|
|
1983
2006
|
|
|
1984
2007
|
Raises:
|
|
1985
|
-
C2paError.NotSupported: If the format is
|
|
1986
|
-
|
|
2008
|
+
C2paError.NotSupported: If the format is missing and
|
|
2009
|
+
``allow_autodetect`` is False.
|
|
2010
|
+
C2paError.Encoding: If the format contains invalid UTF-8 characters.
|
|
1987
2011
|
"""
|
|
1988
|
-
|
|
2012
|
+
key = (format_str or "").strip().lower()
|
|
2013
|
+
if not key:
|
|
2014
|
+
if allow_autodetect:
|
|
2015
|
+
return None
|
|
1989
2016
|
raise C2paError.NotSupported(
|
|
1990
|
-
f"{class_name}
|
|
2017
|
+
f"{class_name} requires an explicit format (MIME type)"
|
|
2018
|
+
)
|
|
1991
2019
|
try:
|
|
1992
|
-
return
|
|
2020
|
+
return key.encode('utf-8')
|
|
1993
2021
|
except UnicodeError as e:
|
|
1994
2022
|
raise C2paError.Encoding(
|
|
1995
2023
|
f"Invalid UTF-8 characters in input: {e}")
|
|
1996
2024
|
|
|
1997
2025
|
|
|
2026
|
+
def _format_ffi_arg(fmt: Optional[bytes]) -> bytes:
|
|
2027
|
+
"""Convert an encoded format to the native FFI argument.
|
|
2028
|
+
|
|
2029
|
+
The native library treats empty bytes as the "detect the type from the
|
|
2030
|
+
bytes" flag, so a missing format (None) maps to b"" rather than a NULL
|
|
2031
|
+
``c_char_p``.
|
|
2032
|
+
"""
|
|
2033
|
+
return fmt if fmt is not None else b""
|
|
2034
|
+
|
|
2035
|
+
|
|
2036
|
+
def _is_read_stream(obj) -> bool:
|
|
2037
|
+
"""Return True if obj is a stream-like object this SDK can use.
|
|
2038
|
+
Note: only method presence to identify streams are checked,
|
|
2039
|
+
not that they work (a broken stream can fail later).
|
|
2040
|
+
"""
|
|
2041
|
+
if obj is None or isinstance(obj, (str, Path)):
|
|
2042
|
+
return False
|
|
2043
|
+
return all(hasattr(obj, method) for method in Stream._REQUIRED_STREAM_METHODS)
|
|
2044
|
+
|
|
2045
|
+
|
|
1998
2046
|
class Reader(ManagedResource):
|
|
1999
2047
|
"""High-level wrapper for C2PA Reader operations.
|
|
2000
2048
|
|
|
@@ -2043,6 +2091,10 @@ class Reader(ManagedResource):
|
|
|
2043
2091
|
def _is_mime_type_supported(cls, mime_type: str) -> bool:
|
|
2044
2092
|
"""Check if a MIME type is supported.
|
|
2045
2093
|
|
|
2094
|
+
Not used internally: the native library is the authority on format
|
|
2095
|
+
support and detects the type from the bytes. Kept for callers that
|
|
2096
|
+
want a pre-flight check before handing an asset to a Reader.
|
|
2097
|
+
|
|
2046
2098
|
Args:
|
|
2047
2099
|
mime_type: The MIME type to check
|
|
2048
2100
|
|
|
@@ -2057,7 +2109,16 @@ class Reader(ManagedResource):
|
|
|
2057
2109
|
@overload
|
|
2058
2110
|
def try_create(
|
|
2059
2111
|
cls,
|
|
2060
|
-
|
|
2112
|
+
stream: Any,
|
|
2113
|
+
manifest_data: Optional[Any] = None,
|
|
2114
|
+
context: Optional['ContextProvider'] = None,
|
|
2115
|
+
) -> Optional["Reader"]: ...
|
|
2116
|
+
|
|
2117
|
+
@classmethod
|
|
2118
|
+
@overload
|
|
2119
|
+
def try_create(
|
|
2120
|
+
cls,
|
|
2121
|
+
format_or_path: Union[str, Path, None] = None,
|
|
2061
2122
|
stream: Optional[Any] = None,
|
|
2062
2123
|
manifest_data: Optional[Any] = None,
|
|
2063
2124
|
) -> Optional["Reader"]: ...
|
|
@@ -2066,7 +2127,7 @@ class Reader(ManagedResource):
|
|
|
2066
2127
|
@overload
|
|
2067
2128
|
def try_create(
|
|
2068
2129
|
cls,
|
|
2069
|
-
format_or_path: Union[str, Path],
|
|
2130
|
+
format_or_path: Union[str, Path, None],
|
|
2070
2131
|
stream: Optional[Any],
|
|
2071
2132
|
manifest_data: Optional[Any],
|
|
2072
2133
|
context: 'ContextProvider',
|
|
@@ -2075,7 +2136,7 @@ class Reader(ManagedResource):
|
|
|
2075
2136
|
@classmethod
|
|
2076
2137
|
def try_create(
|
|
2077
2138
|
cls,
|
|
2078
|
-
format_or_path: Union[str, Path],
|
|
2139
|
+
format_or_path: Union[str, Path, None] = None,
|
|
2079
2140
|
stream: Optional[Any] = None,
|
|
2080
2141
|
manifest_data: Optional[Any] = None,
|
|
2081
2142
|
context: Optional['ContextProvider'] = None,
|
|
@@ -2089,8 +2150,13 @@ class Reader(ManagedResource):
|
|
|
2089
2150
|
want to check if an asset contains C2PA data without handling
|
|
2090
2151
|
exceptions for the expected case of no manifest.
|
|
2091
2152
|
|
|
2153
|
+
Pass a stream as the only argument (``try_create(stream)``) to read it
|
|
2154
|
+
with an auto-detected format.
|
|
2155
|
+
|
|
2092
2156
|
Args:
|
|
2093
|
-
format_or_path: The format or path to read from
|
|
2157
|
+
format_or_path: The format or path to read from, or a stream to
|
|
2158
|
+
read with an auto-detected format. None or an empty string
|
|
2159
|
+
requests auto-detection (best effort).
|
|
2094
2160
|
stream: Optional stream to read from (Python stream-like object)
|
|
2095
2161
|
manifest_data: Optional manifest data in bytes
|
|
2096
2162
|
context: Optional ContextProvider for settings
|
|
@@ -2113,7 +2179,15 @@ class Reader(ManagedResource):
|
|
|
2113
2179
|
@overload
|
|
2114
2180
|
def __init__(
|
|
2115
2181
|
self,
|
|
2116
|
-
|
|
2182
|
+
stream: Any,
|
|
2183
|
+
manifest_data: Optional[Any] = None,
|
|
2184
|
+
context: Optional['ContextProvider'] = None,
|
|
2185
|
+
) -> None: ...
|
|
2186
|
+
|
|
2187
|
+
@overload
|
|
2188
|
+
def __init__(
|
|
2189
|
+
self,
|
|
2190
|
+
format_or_path: Union[str, Path, None] = None,
|
|
2117
2191
|
stream: Optional[Any] = None,
|
|
2118
2192
|
manifest_data: Optional[Any] = None,
|
|
2119
2193
|
) -> None: ...
|
|
@@ -2121,7 +2195,7 @@ class Reader(ManagedResource):
|
|
|
2121
2195
|
@overload
|
|
2122
2196
|
def __init__(
|
|
2123
2197
|
self,
|
|
2124
|
-
format_or_path: Union[str, Path],
|
|
2198
|
+
format_or_path: Union[str, Path, None],
|
|
2125
2199
|
stream: Optional[Any],
|
|
2126
2200
|
manifest_data: Optional[Any],
|
|
2127
2201
|
context: 'ContextProvider',
|
|
@@ -2129,21 +2203,40 @@ class Reader(ManagedResource):
|
|
|
2129
2203
|
|
|
2130
2204
|
def __init__(
|
|
2131
2205
|
self,
|
|
2132
|
-
format_or_path: Union[str, Path],
|
|
2206
|
+
format_or_path: Union[str, Path, None] = None,
|
|
2133
2207
|
stream: Optional[Any] = None,
|
|
2134
2208
|
manifest_data: Optional[Any] = None,
|
|
2135
2209
|
context: Optional['ContextProvider'] = None,
|
|
2136
2210
|
):
|
|
2137
2211
|
"""Create a new Reader.
|
|
2138
2212
|
|
|
2213
|
+
The format is optional. Passing a known format gives the native
|
|
2214
|
+
library more to work with, so prefer it. Pass None, an empty string,
|
|
2215
|
+
or an extensionless file path to let the library detect the type from
|
|
2216
|
+
the asset bytes.
|
|
2217
|
+
|
|
2218
|
+
The library reconciles the format against the container it detects in
|
|
2219
|
+
the bytes: when the given format disagrees with the detected container,
|
|
2220
|
+
the library ignores the format and uses its own best guess (so reading
|
|
2221
|
+
does not fail on a wrong file extension); when no container is
|
|
2222
|
+
detected, the format is used as a hint and the asset is treated as a
|
|
2223
|
+
sidecar. If the bytes are not a recognized asset and no format resolves
|
|
2224
|
+
the type, a C2paError is raised.
|
|
2225
|
+
|
|
2226
|
+
Pass a stream as the only argument (``Reader(stream)``) to read it with
|
|
2227
|
+
an auto-detected format.
|
|
2228
|
+
|
|
2139
2229
|
Args:
|
|
2140
|
-
format_or_path: The format or path to read from
|
|
2230
|
+
format_or_path: The format (MIME type) or path to read from, or a
|
|
2231
|
+
stream to read with an auto-detected format. None or an empty
|
|
2232
|
+
string requests detection from the bytes.
|
|
2141
2233
|
stream: Optional stream to read from (Python stream-like object)
|
|
2142
2234
|
manifest_data: Optional manifest data in bytes
|
|
2143
2235
|
context: Optional context implementing ContextProvider with settings
|
|
2144
2236
|
|
|
2145
2237
|
Raises:
|
|
2146
|
-
C2paError: If there was an error creating the reader
|
|
2238
|
+
C2paError: If there was an error creating the reader, including
|
|
2239
|
+
when the format cannot be detected from an unrecognized asset
|
|
2147
2240
|
C2paError.Encoding: If any of the string inputs
|
|
2148
2241
|
contain invalid UTF-8 characters
|
|
2149
2242
|
"""
|
|
@@ -2165,6 +2258,15 @@ class Reader(ManagedResource):
|
|
|
2165
2258
|
|
|
2166
2259
|
self._context = context
|
|
2167
2260
|
|
|
2261
|
+
# Only stream, no format: Reader(fh) must auto-detect on the stream.
|
|
2262
|
+
if stream is None and _is_read_stream(format_or_path):
|
|
2263
|
+
stream = format_or_path
|
|
2264
|
+
format_or_path = None
|
|
2265
|
+
# A context supplies settings, not the asset, so a path or stream is
|
|
2266
|
+
# still required to read from.
|
|
2267
|
+
if format_or_path is None and stream is None:
|
|
2268
|
+
raise C2paError("Reader requires a path or a stream")
|
|
2269
|
+
|
|
2168
2270
|
if context is not None:
|
|
2169
2271
|
self._init_from_context(
|
|
2170
2272
|
context, format_or_path, stream,
|
|
@@ -2172,50 +2274,53 @@ class Reader(ManagedResource):
|
|
|
2172
2274
|
)
|
|
2173
2275
|
return
|
|
2174
2276
|
|
|
2175
|
-
|
|
2277
|
+
format_bytes = self._resolve_format_bytes(format_or_path, stream)
|
|
2176
2278
|
|
|
2177
2279
|
if stream is None:
|
|
2178
|
-
# Create a stream from the file path in format_or_path
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
if not mime_type:
|
|
2183
|
-
raise C2paError.NotSupported(
|
|
2184
|
-
f"Could not determine MIME type for file: {path}")
|
|
2185
|
-
|
|
2186
|
-
format_bytes = _validate_and_encode_format(
|
|
2187
|
-
mime_type, supported, "Reader")
|
|
2188
|
-
self._init_from_file(path, format_bytes)
|
|
2280
|
+
# Create a stream from the file path in format_or_path.
|
|
2281
|
+
# A None format lets the native lib guess from the bytes.
|
|
2282
|
+
self._init_from_file(str(format_or_path), format_bytes)
|
|
2189
2283
|
|
|
2190
2284
|
elif isinstance(stream, str):
|
|
2191
2285
|
# stream is a file path, format_or_path is the format
|
|
2192
|
-
format_bytes = _validate_and_encode_format(
|
|
2193
|
-
str(format_or_path), supported, "Reader")
|
|
2194
2286
|
self._init_from_file(
|
|
2195
2287
|
stream, format_bytes, manifest_data)
|
|
2196
2288
|
|
|
2197
2289
|
else:
|
|
2198
2290
|
# format_or_path is a format string, stream is a stream object
|
|
2199
|
-
format_bytes = _validate_and_encode_format(
|
|
2200
|
-
str(format_or_path), supported, "Reader")
|
|
2201
|
-
|
|
2202
2291
|
with Stream(stream) as stream_obj:
|
|
2203
2292
|
self._create_reader(
|
|
2204
2293
|
format_bytes, stream_obj, manifest_data)
|
|
2205
2294
|
self._lifecycle_state = LifecycleState.ACTIVE
|
|
2206
2295
|
|
|
2296
|
+
@staticmethod
|
|
2297
|
+
def _resolve_format_bytes(format_or_path, stream) -> Optional[bytes]:
|
|
2298
|
+
"""Resolve the encoded format for a (format_or_path, stream) pair.
|
|
2299
|
+
|
|
2300
|
+
When only a path is given (``stream is None``), the format is derived
|
|
2301
|
+
from the file extension. Otherwise ``format_or_path`` is the format
|
|
2302
|
+
string (or None).
|
|
2303
|
+
Returns None to request auto-detection.
|
|
2304
|
+
"""
|
|
2305
|
+
if stream is None:
|
|
2306
|
+
return _encode_format(
|
|
2307
|
+
_get_mime_type_from_path(str(format_or_path)), "Reader")
|
|
2308
|
+
return _encode_format(
|
|
2309
|
+
None if format_or_path is None else str(format_or_path), "Reader")
|
|
2310
|
+
|
|
2207
2311
|
def _create_reader(self, format_bytes, stream_obj,
|
|
2208
2312
|
manifest_data=None):
|
|
2209
2313
|
"""Create a Reader from a Stream.
|
|
2210
2314
|
|
|
2211
2315
|
Args:
|
|
2212
|
-
format_bytes:
|
|
2316
|
+
format_bytes: Encoded format/MIME type, or None for auto-detection
|
|
2213
2317
|
stream_obj: A Stream instance
|
|
2214
2318
|
manifest_data: Optional manifest bytes
|
|
2215
2319
|
"""
|
|
2320
|
+
format_arg = _format_ffi_arg(format_bytes)
|
|
2216
2321
|
if manifest_data is None:
|
|
2217
2322
|
self._handle = _lib.c2pa_reader_from_stream(
|
|
2218
|
-
|
|
2323
|
+
format_arg, stream_obj._stream)
|
|
2219
2324
|
else:
|
|
2220
2325
|
if not isinstance(manifest_data, bytes):
|
|
2221
2326
|
raise TypeError(Reader._ERROR_MESSAGES['manifest_error'])
|
|
@@ -2224,7 +2329,7 @@ class Reader(ManagedResource):
|
|
|
2224
2329
|
len(manifest_data)).from_buffer_copy(manifest_data)
|
|
2225
2330
|
self._handle = (
|
|
2226
2331
|
_lib.c2pa_reader_from_manifest_data_and_stream(
|
|
2227
|
-
|
|
2332
|
+
format_arg,
|
|
2228
2333
|
stream_obj._stream,
|
|
2229
2334
|
manifest_array,
|
|
2230
2335
|
len(manifest_data),
|
|
@@ -2269,26 +2374,16 @@ class Reader(ManagedResource):
|
|
|
2269
2374
|
raise TypeError(Reader._ERROR_MESSAGES['manifest_error'])
|
|
2270
2375
|
|
|
2271
2376
|
# Determine format and open stream
|
|
2272
|
-
|
|
2273
|
-
|
|
2377
|
+
format_bytes = self._resolve_format_bytes(format_or_path, stream)
|
|
2378
|
+
format_arg = _format_ffi_arg(format_bytes)
|
|
2274
2379
|
if stream is None:
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
if not mime_type:
|
|
2278
|
-
raise C2paError.NotSupported(
|
|
2279
|
-
f"Could not determine MIME type for file: {path}")
|
|
2280
|
-
format_bytes = _validate_and_encode_format(
|
|
2281
|
-
mime_type, supported, "Reader")
|
|
2282
|
-
self._backing_file = open(path, 'rb')
|
|
2380
|
+
# A None format lets the native lib guess from the bytes.
|
|
2381
|
+
self._backing_file = open(str(format_or_path), 'rb')
|
|
2283
2382
|
self._own_stream = Stream(self._backing_file)
|
|
2284
2383
|
elif isinstance(stream, str):
|
|
2285
|
-
format_bytes = _validate_and_encode_format(
|
|
2286
|
-
str(format_or_path), supported, "Reader")
|
|
2287
2384
|
self._backing_file = open(stream, 'rb')
|
|
2288
2385
|
self._own_stream = Stream(self._backing_file)
|
|
2289
2386
|
else:
|
|
2290
|
-
format_bytes = _validate_and_encode_format(
|
|
2291
|
-
str(format_or_path), supported, "Reader")
|
|
2292
2387
|
self._own_stream = Stream(stream)
|
|
2293
2388
|
|
|
2294
2389
|
try:
|
|
@@ -2317,7 +2412,7 @@ class Reader(ManagedResource):
|
|
|
2317
2412
|
new_ptr = (
|
|
2318
2413
|
_lib.c2pa_reader_with_manifest_data_and_stream(
|
|
2319
2414
|
reader_ptr,
|
|
2320
|
-
|
|
2415
|
+
format_arg,
|
|
2321
2416
|
self._own_stream._stream,
|
|
2322
2417
|
manifest_array,
|
|
2323
2418
|
len(manifest_data),
|
|
@@ -2326,7 +2421,7 @@ class Reader(ManagedResource):
|
|
|
2326
2421
|
else:
|
|
2327
2422
|
# Consume reader with stream
|
|
2328
2423
|
new_ptr = _lib.c2pa_reader_with_stream(
|
|
2329
|
-
reader_ptr,
|
|
2424
|
+
reader_ptr, format_arg,
|
|
2330
2425
|
self._own_stream._stream,
|
|
2331
2426
|
)
|
|
2332
2427
|
|
|
@@ -2394,7 +2489,7 @@ class Reader(ManagedResource):
|
|
|
2394
2489
|
|
|
2395
2490
|
return self._manifest_data_cache
|
|
2396
2491
|
|
|
2397
|
-
def with_fragment(self, format: str, stream,
|
|
2492
|
+
def with_fragment(self, format: Optional[str], stream,
|
|
2398
2493
|
fragment_stream) -> "Reader":
|
|
2399
2494
|
"""Process a BMFF fragment stream with this reader.
|
|
2400
2495
|
|
|
@@ -2402,7 +2497,9 @@ class Reader(ManagedResource):
|
|
|
2402
2497
|
content is split into init segments and fragment files.
|
|
2403
2498
|
|
|
2404
2499
|
Args:
|
|
2405
|
-
format: MIME type of the media (e.g., "video/mp4")
|
|
2500
|
+
format: MIME type of the media (e.g., "video/mp4"). None or an
|
|
2501
|
+
empty string requests detection from the bytes; the native
|
|
2502
|
+
library reconciles the format against the detected container.
|
|
2406
2503
|
stream: Stream-like object with the main/init segment data
|
|
2407
2504
|
fragment_stream: Stream-like object with the fragment data
|
|
2408
2505
|
|
|
@@ -2414,15 +2511,12 @@ class Reader(ManagedResource):
|
|
|
2414
2511
|
"""
|
|
2415
2512
|
self._ensure_valid_state()
|
|
2416
2513
|
|
|
2417
|
-
|
|
2418
|
-
format_bytes = _validate_and_encode_format(
|
|
2419
|
-
format, supported, "Reader"
|
|
2420
|
-
)
|
|
2514
|
+
format_arg = _format_ffi_arg(_encode_format(format, "Reader"))
|
|
2421
2515
|
|
|
2422
2516
|
with Stream(stream) as main_obj, Stream(fragment_stream) as frag_obj:
|
|
2423
2517
|
new_ptr = _lib.c2pa_reader_with_fragment(
|
|
2424
2518
|
self._handle,
|
|
2425
|
-
|
|
2519
|
+
format_arg,
|
|
2426
2520
|
main_obj._stream,
|
|
2427
2521
|
frag_obj._stream,
|
|
2428
2522
|
)
|
|
@@ -3338,7 +3432,8 @@ class Builder(ManagedResource):
|
|
|
3338
3432
|
result = _lib.c2pa_builder_write_ingredient_archive(
|
|
3339
3433
|
self._handle, ingredient_id_str, stream_obj._stream)
|
|
3340
3434
|
|
|
3341
|
-
_check_ffi_operation_result(
|
|
3435
|
+
_check_ffi_operation_result(
|
|
3436
|
+
result,
|
|
3342
3437
|
Builder._ERROR_MESSAGES["archive_error"].format(
|
|
3343
3438
|
"Unknown error"
|
|
3344
3439
|
),
|
|
@@ -3361,7 +3456,8 @@ class Builder(ManagedResource):
|
|
|
3361
3456
|
result = _lib.c2pa_builder_add_ingredient_from_archive(
|
|
3362
3457
|
self._handle, stream_obj._stream)
|
|
3363
3458
|
|
|
3364
|
-
_check_ffi_operation_result(
|
|
3459
|
+
_check_ffi_operation_result(
|
|
3460
|
+
result,
|
|
3365
3461
|
Builder._ERROR_MESSAGES["archive_read_error"].format(
|
|
3366
3462
|
"Unknown error"
|
|
3367
3463
|
),
|
|
@@ -3414,7 +3510,8 @@ class Builder(ManagedResource):
|
|
|
3414
3510
|
are single-sign use. The Builder is closed after signing.
|
|
3415
3511
|
|
|
3416
3512
|
Args:
|
|
3417
|
-
format: The MIME type or extension of the content
|
|
3513
|
+
format: The MIME type or extension of the content. Required for
|
|
3514
|
+
signing: a missing format raises C2paError.NotSupported.
|
|
3418
3515
|
source_stream: The source stream
|
|
3419
3516
|
dest_stream: The destination stream,
|
|
3420
3517
|
opened in w+b (write+read binary) mode.
|
|
@@ -3433,15 +3530,16 @@ class Builder(ManagedResource):
|
|
|
3433
3530
|
if not hasattr(signer, '_handle') or not signer._handle:
|
|
3434
3531
|
raise C2paError("Invalid or closed signer")
|
|
3435
3532
|
|
|
3436
|
-
|
|
3437
|
-
|
|
3533
|
+
# allow_autodetect=False, so this never returns None (raises instead).
|
|
3534
|
+
format_arg = _format_ffi_arg(
|
|
3535
|
+
_encode_format(format, "Builder", allow_autodetect=False))
|
|
3438
3536
|
manifest_bytes_ptr = ctypes.POINTER(ctypes.c_ubyte)()
|
|
3439
3537
|
|
|
3440
3538
|
try:
|
|
3441
3539
|
if signer is not None:
|
|
3442
3540
|
result = _lib.c2pa_builder_sign(
|
|
3443
3541
|
self._handle,
|
|
3444
|
-
|
|
3542
|
+
format_arg,
|
|
3445
3543
|
source_stream._stream,
|
|
3446
3544
|
dest_stream._stream,
|
|
3447
3545
|
signer._handle,
|
|
@@ -3450,7 +3548,7 @@ class Builder(ManagedResource):
|
|
|
3450
3548
|
else:
|
|
3451
3549
|
result = _lib.c2pa_builder_sign_context(
|
|
3452
3550
|
self._handle,
|
|
3453
|
-
|
|
3551
|
+
format_arg,
|
|
3454
3552
|
source_stream._stream,
|
|
3455
3553
|
dest_stream._stream,
|
|
3456
3554
|
ctypes.byref(manifest_bytes_ptr),
|
|
@@ -3647,9 +3745,17 @@ class Builder(ManagedResource):
|
|
|
3647
3745
|
Manifest bytes
|
|
3648
3746
|
|
|
3649
3747
|
Raises:
|
|
3748
|
+
C2paError.NotSupported: If the format cannot be determined from
|
|
3749
|
+
the source path (extensionless or unrecognized extension),
|
|
3750
|
+
since signing requires an explicit, resolvable format.
|
|
3650
3751
|
C2paError: If there was an error during signing
|
|
3651
3752
|
"""
|
|
3652
3753
|
mime_type = _get_mime_type_from_path(source_path)
|
|
3754
|
+
if not mime_type:
|
|
3755
|
+
raise C2paError.NotSupported(
|
|
3756
|
+
"Could not determine the format (MIME type) from "
|
|
3757
|
+
f"'{source_path}'. Sign a stream with an explicit format "
|
|
3758
|
+
"instead, or use a recognized file extension.")
|
|
3653
3759
|
|
|
3654
3760
|
try:
|
|
3655
3761
|
with (
|
|
@@ -3660,6 +3766,9 @@ class Builder(ManagedResource):
|
|
|
3660
3766
|
return self.sign(signer, mime_type, source_file, dest_file)
|
|
3661
3767
|
# else:
|
|
3662
3768
|
return self.sign(mime_type, source_file, dest_file)
|
|
3769
|
+
except C2paError:
|
|
3770
|
+
# Preserve C2paError and its subtypes
|
|
3771
|
+
raise
|
|
3663
3772
|
except Exception as e:
|
|
3664
3773
|
raise C2paError(f"Error signing file: {str(e)}") from e
|
|
3665
3774
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: c2pa-python
|
|
3
|
-
Version: 0.37.
|
|
3
|
+
Version: 0.37.2
|
|
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>, Tania Mathern <mathern@adobe.com>
|