@@ -1024,12 +1024,9 @@ def read_callback(ctx, data, length):
1024
1024
1025
1025
# Ensure we don't write beyond the allocated memory
1026
1026
actual_length = min (len (buffer ), length )
1027
- # Create a view of the buffer to avoid copying
1028
- buffer_view = (
1029
- ctypes .c_ubyte *
1030
- actual_length ).from_buffer_copy (buffer )
1031
- # Direct memory copy for better performance
1032
- ctypes .memmove (data , buffer_view , actual_length )
1027
+ # Direct memory copy
1028
+ ctypes .memmove (data , buffer , actual_length )
1029
+
1033
1030
return actual_length
1034
1031
except Exception :
1035
1032
return - 1
@@ -1051,11 +1048,12 @@ def seek_callback(ctx, offset, whence):
1051
1048
Returns:
1052
1049
New position in the stream, or -1 on error
1053
1050
"""
1051
+ file_stream = self ._file_like_stream
1054
1052
if not self ._initialized or self ._closed :
1055
1053
return - 1
1056
1054
try :
1057
- self . _file_like_stream .seek (offset , whence )
1058
- return self . _file_like_stream .tell ()
1055
+ file_stream .seek (offset , whence )
1056
+ return file_stream .tell ()
1059
1057
except Exception :
1060
1058
return - 1
1061
1059
@@ -1158,11 +1156,12 @@ def __del__(self):
1158
1156
try :
1159
1157
# Only cleanup if not already closed and we have a valid stream
1160
1158
if hasattr (self , '_closed' ) and not self ._closed :
1161
- if hasattr (self , '_stream' ) and self ._stream :
1159
+ stream = self ._stream
1160
+ if hasattr (self , '_stream' ) and stream :
1162
1161
# Use internal cleanup to avoid calling close() which could
1163
1162
# cause issues
1164
1163
try :
1165
- _lib .c2pa_release_stream (self . _stream )
1164
+ _lib .c2pa_release_stream (stream )
1166
1165
except Exception :
1167
1166
# Destructors shouldn't raise exceptions
1168
1167
logger .error ("Failed to release Stream" )
@@ -1183,17 +1182,17 @@ def close(self):
1183
1182
Errors during cleanup are logged but not raised to ensure cleanup.
1184
1183
Multiple calls to close() are handled gracefully.
1185
1184
"""
1186
-
1187
1185
if self ._closed :
1188
1186
return
1189
1187
1190
1188
try :
1191
1189
# Clean up stream first as it depends on callbacks
1192
1190
# Note: We don't close self._file_like_stream as we don't own it,
1193
1191
# the opener owns it.
1194
- if self ._stream :
1192
+ stream = self ._stream
1193
+ if stream :
1195
1194
try :
1196
- _lib .c2pa_release_stream (self . _stream )
1195
+ _lib .c2pa_release_stream (stream )
1197
1196
except Exception as e :
1198
1197
logger .error (
1199
1198
Stream ._ERROR_MESSAGES ['stream_error' ].format (
@@ -1415,7 +1414,8 @@ def __init__(self,
1415
1414
# If stream is a string, treat it as a path and try to open it
1416
1415
1417
1416
# format_or_path is a format
1418
- if format_or_path .lower () not in Reader .get_supported_mime_types ():
1417
+ format_lower = format_or_path .lower ()
1418
+ if format_lower not in Reader .get_supported_mime_types ():
1419
1419
raise C2paError .NotSupported (
1420
1420
f"Reader does not support { format_or_path } " )
1421
1421
@@ -1688,28 +1688,6 @@ class Signer:
1688
1688
'encoding_error' : "Invalid UTF-8 characters in input: {}"
1689
1689
}
1690
1690
1691
- def __init__ (self , signer_ptr : ctypes .POINTER (C2paSigner )):
1692
- """Initialize a new Signer instance.
1693
-
1694
- Note: This constructor is not meant to be called directly.
1695
- Use from_info() or from_callback() instead.
1696
-
1697
- Args:
1698
- signer_ptr: Pointer to the native C2PA signer
1699
-
1700
- Raises:
1701
- C2paError: If the signer pointer is invalid
1702
- """
1703
- # Validate pointer before assignment
1704
- if not signer_ptr :
1705
- raise C2paError ("Invalid signer pointer: pointer is null" )
1706
-
1707
- self ._signer = signer_ptr
1708
- self ._closed = False
1709
-
1710
- # Set only for signers which are callback signers
1711
- self ._callback_cb = None
1712
-
1713
1691
@classmethod
1714
1692
def from_info (cls , signer_info : C2paSignerInfo ) -> 'Signer' :
1715
1693
"""Create a new Signer from signer information.
@@ -1876,6 +1854,28 @@ def wrapped_callback(
1876
1854
1877
1855
return signer_instance
1878
1856
1857
+ def __init__ (self , signer_ptr : ctypes .POINTER (C2paSigner )):
1858
+ """Initialize a new Signer instance.
1859
+
1860
+ Note: This constructor is not meant to be called directly.
1861
+ Use from_info() or from_callback() instead.
1862
+
1863
+ Args:
1864
+ signer_ptr: Pointer to the native C2PA signer
1865
+
1866
+ Raises:
1867
+ C2paError: If the signer pointer is invalid
1868
+ """
1869
+ # Validate pointer before assignment
1870
+ if not signer_ptr :
1871
+ raise C2paError ("Invalid signer pointer: pointer is null" )
1872
+
1873
+ self ._signer = signer_ptr
1874
+ self ._closed = False
1875
+
1876
+ # Set only for signers which are callback signers
1877
+ self ._callback_cb = None
1878
+
1879
1879
def __enter__ (self ):
1880
1880
"""Context manager entry."""
1881
1881
self ._ensure_valid_state ()
@@ -1980,15 +1980,6 @@ def reserve_size(self) -> int:
1980
1980
1981
1981
return result
1982
1982
1983
- @property
1984
- def closed (self ) -> bool :
1985
- """Check if the signer is closed.
1986
-
1987
- Returns:
1988
- bool: True if the signer is closed, False otherwise
1989
- """
1990
- return self ._closed
1991
-
1992
1983
1993
1984
class Builder :
1994
1985
"""High-level wrapper for C2PA Builder operations."""
@@ -2075,6 +2066,51 @@ def get_supported_mime_types(cls) -> list[str]:
2075
2066
2076
2067
return cls ._supported_mime_types_cache
2077
2068
2069
+ @classmethod
2070
+ def from_json (cls , manifest_json : Any ) -> 'Builder' :
2071
+ """Create a new Builder from a JSON manifest.
2072
+
2073
+ Args:
2074
+ manifest_json: The JSON manifest definition
2075
+
2076
+ Returns:
2077
+ A new Builder instance
2078
+
2079
+ Raises:
2080
+ C2paError: If there was an error creating the builder
2081
+ """
2082
+ return cls (manifest_json )
2083
+
2084
+ @classmethod
2085
+ def from_archive (cls , stream : Any ) -> 'Builder' :
2086
+ """Create a new Builder from an archive stream.
2087
+
2088
+ Args:
2089
+ stream: The stream containing the archive
2090
+ (any Python stream-like object)
2091
+
2092
+ Returns:
2093
+ A new Builder instance
2094
+
2095
+ Raises:
2096
+ C2paError: If there was an error creating the builder from archive
2097
+ """
2098
+ builder = cls ({})
2099
+ stream_obj = Stream (stream )
2100
+ builder ._builder = _lib .c2pa_builder_from_archive (stream_obj ._stream )
2101
+
2102
+ if not builder ._builder :
2103
+ # Clean up the stream object if builder creation fails
2104
+ stream_obj .close ()
2105
+
2106
+ error = _parse_operation_result_for_error (_lib .c2pa_error ())
2107
+ if error :
2108
+ raise C2paError (error )
2109
+ raise C2paError ("Failed to create builder from archive" )
2110
+
2111
+ builder ._initialized = True
2112
+ return builder
2113
+
2078
2114
def __init__ (self , manifest_json : Any ):
2079
2115
"""Initialize a new Builder instance.
2080
2116
@@ -2119,50 +2155,16 @@ def __init__(self, manifest_json: Any):
2119
2155
2120
2156
self ._initialized = True
2121
2157
2122
- @classmethod
2123
- def from_json (cls , manifest_json : Any ) -> 'Builder' :
2124
- """Create a new Builder from a JSON manifest.
2125
-
2126
- Args:
2127
- manifest_json: The JSON manifest definition
2128
-
2129
- Returns:
2130
- A new Builder instance
2131
-
2132
- Raises:
2133
- C2paError: If there was an error creating the builder
2134
- """
2135
- return cls (manifest_json )
2136
-
2137
- @classmethod
2138
- def from_archive (cls , stream : Any ) -> 'Builder' :
2139
- """Create a new Builder from an archive stream.
2140
-
2141
- Args:
2142
- stream: The stream containing the archive
2143
- (any Python stream-like object)
2144
-
2145
- Returns:
2146
- A new Builder instance
2147
-
2148
- Raises:
2149
- C2paError: If there was an error creating the builder from archive
2150
- """
2151
- builder = cls ({})
2152
- stream_obj = Stream (stream )
2153
- builder ._builder = _lib .c2pa_builder_from_archive (stream_obj ._stream )
2154
-
2155
- if not builder ._builder :
2156
- # Clean up the stream object if builder creation fails
2157
- stream_obj .close ()
2158
+ def __del__ (self ):
2159
+ """Ensure resources are cleaned up if close() wasn't called."""
2160
+ self ._cleanup_resources ()
2158
2161
2159
- error = _parse_operation_result_for_error (_lib .c2pa_error ())
2160
- if error :
2161
- raise C2paError (error )
2162
- raise C2paError ("Failed to create builder from archive" )
2162
+ def __enter__ (self ):
2163
+ self ._ensure_valid_state ()
2164
+ return self
2163
2165
2164
- builder . _initialized = True
2165
- return builder
2166
+ def __exit__ ( self , exc_type , exc_val , exc_tb ):
2167
+ self . close ()
2166
2168
2167
2169
def _ensure_valid_state (self ):
2168
2170
"""Ensure the builder is in a valid state for operations.
@@ -2208,10 +2210,6 @@ def _cleanup_resources(self):
2208
2210
# Ensure we don't raise exceptions during cleanup
2209
2211
pass
2210
2212
2211
- def __del__ (self ):
2212
- """Ensure resources are cleaned up if close() wasn't called."""
2213
- self ._cleanup_resources ()
2214
-
2215
2213
def close (self ):
2216
2214
"""Release the builder resources.
2217
2215
@@ -2234,13 +2232,6 @@ def close(self):
2234
2232
finally :
2235
2233
self ._closed = True
2236
2234
2237
- def __enter__ (self ):
2238
- self ._ensure_valid_state ()
2239
- return self
2240
-
2241
- def __exit__ (self , exc_type , exc_val , exc_tb ):
2242
- self .close ()
2243
-
2244
2235
def set_no_embed (self ):
2245
2236
"""Set the no-embed flag.
2246
2237
@@ -2469,7 +2460,8 @@ def _sign_internal(
2469
2460
if not signer or not hasattr (signer , '_signer' ) or not signer ._signer :
2470
2461
raise C2paError ("Invalid or closed signer" )
2471
2462
2472
- if format .lower () not in Builder .get_supported_mime_types ():
2463
+ format_lower = format .lower ()
2464
+ if format_lower not in Builder .get_supported_mime_types ():
2473
2465
raise C2paError .NotSupported (
2474
2466
f"Builder does not support { format } " )
2475
2467
0 commit comments