@@ -1024,12 +1024,9 @@ def read_callback(ctx, data, length):
10241024
10251025 # Ensure we don't write beyond the allocated memory
10261026 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+
10331030 return actual_length
10341031 except Exception :
10351032 return - 1
@@ -1051,11 +1048,12 @@ def seek_callback(ctx, offset, whence):
10511048 Returns:
10521049 New position in the stream, or -1 on error
10531050 """
1051+ file_stream = self ._file_like_stream
10541052 if not self ._initialized or self ._closed :
10551053 return - 1
10561054 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 ()
10591057 except Exception :
10601058 return - 1
10611059
@@ -1158,11 +1156,12 @@ def __del__(self):
11581156 try :
11591157 # Only cleanup if not already closed and we have a valid stream
11601158 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 :
11621161 # Use internal cleanup to avoid calling close() which could
11631162 # cause issues
11641163 try :
1165- _lib .c2pa_release_stream (self . _stream )
1164+ _lib .c2pa_release_stream (stream )
11661165 except Exception :
11671166 # Destructors shouldn't raise exceptions
11681167 logger .error ("Failed to release Stream" )
@@ -1183,17 +1182,17 @@ def close(self):
11831182 Errors during cleanup are logged but not raised to ensure cleanup.
11841183 Multiple calls to close() are handled gracefully.
11851184 """
1186-
11871185 if self ._closed :
11881186 return
11891187
11901188 try :
11911189 # Clean up stream first as it depends on callbacks
11921190 # Note: We don't close self._file_like_stream as we don't own it,
11931191 # the opener owns it.
1194- if self ._stream :
1192+ stream = self ._stream
1193+ if stream :
11951194 try :
1196- _lib .c2pa_release_stream (self . _stream )
1195+ _lib .c2pa_release_stream (stream )
11971196 except Exception as e :
11981197 logger .error (
11991198 Stream ._ERROR_MESSAGES ['stream_error' ].format (
@@ -1415,7 +1414,8 @@ def __init__(self,
14151414 # If stream is a string, treat it as a path and try to open it
14161415
14171416 # 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 ():
14191419 raise C2paError .NotSupported (
14201420 f"Reader does not support { format_or_path } " )
14211421
@@ -1688,28 +1688,6 @@ class Signer:
16881688 'encoding_error' : "Invalid UTF-8 characters in input: {}"
16891689 }
16901690
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-
17131691 @classmethod
17141692 def from_info (cls , signer_info : C2paSignerInfo ) -> 'Signer' :
17151693 """Create a new Signer from signer information.
@@ -1876,6 +1854,28 @@ def wrapped_callback(
18761854
18771855 return signer_instance
18781856
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+
18791879 def __enter__ (self ):
18801880 """Context manager entry."""
18811881 self ._ensure_valid_state ()
@@ -1980,15 +1980,6 @@ def reserve_size(self) -> int:
19801980
19811981 return result
19821982
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-
19921983
19931984class Builder :
19941985 """High-level wrapper for C2PA Builder operations."""
@@ -2075,6 +2066,51 @@ def get_supported_mime_types(cls) -> list[str]:
20752066
20762067 return cls ._supported_mime_types_cache
20772068
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+
20782114 def __init__ (self , manifest_json : Any ):
20792115 """Initialize a new Builder instance.
20802116
@@ -2119,50 +2155,16 @@ def __init__(self, manifest_json: Any):
21192155
21202156 self ._initialized = True
21212157
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 ()
21582161
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
21632165
2164- builder . _initialized = True
2165- return builder
2166+ def __exit__ ( self , exc_type , exc_val , exc_tb ):
2167+ self . close ()
21662168
21672169 def _ensure_valid_state (self ):
21682170 """Ensure the builder is in a valid state for operations.
@@ -2208,10 +2210,6 @@ def _cleanup_resources(self):
22082210 # Ensure we don't raise exceptions during cleanup
22092211 pass
22102212
2211- def __del__ (self ):
2212- """Ensure resources are cleaned up if close() wasn't called."""
2213- self ._cleanup_resources ()
2214-
22152213 def close (self ):
22162214 """Release the builder resources.
22172215
@@ -2234,13 +2232,6 @@ def close(self):
22342232 finally :
22352233 self ._closed = True
22362234
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-
22442235 def set_no_embed (self ):
22452236 """Set the no-embed flag.
22462237
@@ -2469,7 +2460,8 @@ def _sign_internal(
24692460 if not signer or not hasattr (signer , '_signer' ) or not signer ._signer :
24702461 raise C2paError ("Invalid or closed signer" )
24712462
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 ():
24732465 raise C2paError .NotSupported (
24742466 f"Builder does not support { format } " )
24752467
0 commit comments