@@ -1807,7 +1807,24 @@ def read_callback(ctx, data, length):
18071807 if not data or length <= 0 :
18081808 return - 1
18091809
1810- buffer = self ._file_like_stream .read (length )
1810+ src = self ._file_like_stream
1811+ # For larger reads, fill the buffer directly
1812+ # via a memoryview, avoiding the intermediate
1813+ # `bytes` allocation. BytesIO and binary file objects
1814+ # implement readinto. For smaller reads the per-call
1815+ # ctypes setup cost (from_address+memoryview) outweighs
1816+ # the savings, so we keep the bytes path for those.
1817+ READINTO_THRESHOLD = 4096
1818+ if length >= READINTO_THRESHOLD :
1819+ readinto = getattr (src , "readinto" , None )
1820+ if readinto is not None :
1821+ arr = (ctypes .c_ubyte * length ).from_address (
1822+ ctypes .addressof (data .contents )
1823+ )
1824+ n = readinto (memoryview (arr ))
1825+ return n or 0
1826+
1827+ buffer = src .read (length )
18111828 if not buffer : # EOF
18121829 return 0
18131830
@@ -1870,17 +1887,8 @@ def write_callback(ctx, data, length):
18701887 if not data or length <= 0 :
18711888 return - 1
18721889
1873- # Create a temporary buffer to safely handle the data
1874- temp_buffer = (ctypes .c_ubyte * length )()
1875- try :
1876- # Copy data to our temporary buffer
1877- ctypes .memmove (temp_buffer , data , length )
1878- # Write from our safe buffer
1879- self ._file_like_stream .write (bytes (temp_buffer ))
1880- return length
1881- finally :
1882- # Ensure temporary buffer is cleared
1883- ctypes .memset (temp_buffer , 0 , length )
1890+ self ._file_like_stream .write (ctypes .string_at (data , length ))
1891+ return length
18841892 except Exception :
18851893 return - 1
18861894
@@ -2890,10 +2898,7 @@ def wrapped_callback(
28902898 if data_len > 1024 * 1024 : # 1MB limit
28912899 return - 1
28922900
2893- # Recover signed data (copy, to avoid lifetime issues)
2894- temp_buffer = (ctypes .c_ubyte * data_len )()
2895- ctypes .memmove (temp_buffer , data_ptr , data_len )
2896- data = bytes (temp_buffer )
2901+ data = ctypes .string_at (data_ptr , data_len )
28972902
28982903 if not data :
28992904 # Error: empty data, invalid so return -1,
@@ -3558,10 +3563,7 @@ def _sign_internal(
35583563 manifest_bytes = b""
35593564 if manifest_bytes_ptr and result > 0 :
35603565 try :
3561- # Convert the C pointer to Python bytes
3562- temp_buffer = (ctypes .c_ubyte * result )()
3563- ctypes .memmove (temp_buffer , manifest_bytes_ptr , result )
3564- manifest_bytes = bytes (temp_buffer )
3566+ manifest_bytes = ctypes .string_at (manifest_bytes_ptr , result )
35653567 except Exception :
35663568 manifest_bytes = b""
35673569 finally :
@@ -3660,7 +3662,13 @@ def sign(
36603662 context's signer is used.
36613663 format: The MIME type of the content.
36623664 source: The source stream.
3663- dest: The destination stream (optional).
3665+ dest: The destination stream (optional). When
3666+ omitted, the signed asset is buffered into
3667+ an in-memory `BytesIO` sized to the full
3668+ output. For assets larger than a few MB,
3669+ pass a file or other writable stream to
3670+ avoid buffering the whole signed payload
3671+ in memory.
36643672
36653673 Returns:
36663674 Manifest bytes
@@ -3758,7 +3766,9 @@ def format_embeddable(format: str, manifest_bytes: bytes) -> tuple[int, bytes]:
37583766 _clear_error_state ()
37593767
37603768 format_str = format .encode ('utf-8' )
3761- manifest_array = (ctypes .c_ubyte * len (manifest_bytes ))(* manifest_bytes )
3769+ manifest_array = (ctypes .c_ubyte * len (manifest_bytes )).from_buffer_copy (
3770+ manifest_bytes
3771+ )
37623772 result_bytes_ptr = ctypes .POINTER (ctypes .c_ubyte )()
37633773
37643774 result = _lib .c2pa_format_embeddable (
@@ -3771,10 +3781,9 @@ def format_embeddable(format: str, manifest_bytes: bytes) -> tuple[int, bytes]:
37713781 _check_ffi_operation_result (result ,
37723782 "Failed to format embeddable manifest" , check = lambda r : r < 0 )
37733783
3774- # Convert the result bytes to a Python bytes object
37753784 size = result
37763785 try :
3777- result_bytes = bytes (result_bytes_ptr [: size ] )
3786+ result_bytes = ctypes . string_at (result_bytes_ptr , size )
37783787 except Exception as e :
37793788 raise C2paError (
37803789 f"Failed to convert embeddable manifest bytes: { e } "
0 commit comments