Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion soundfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1325,7 +1325,7 @@ def vio_read(ptr, count, user_data):
try:
buf = _ffi.buffer(ptr, count)
data_read = file.readinto(buf)
except AttributeError:
except (AttributeError, NotImplementedError):
data = file.read(count)
data_read = len(data)
buf = _ffi.buffer(ptr, data_read)
Expand Down
65 changes: 65 additions & 0 deletions tests/test_read_fallback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import io

import numpy as np
import pytest
from numpy.testing import assert_array_equal

import soundfile as sf


class ReadAdapter(io.RawIOBase):
def __init__(self, contents):
self.source = io.BytesIO(contents)
self.read_calls = 0

def readinto(self, buffer):
# PyPy does not expose RawIOBase's stub; exercise the same capability failure.
raise NotImplementedError

def read(self, size=-1):
self.read_calls += 1
return self.source.read(size)

def seek(self, offset, whence=0):
return self.source.seek(offset, whence)

def tell(self):
return self.source.tell()

def readable(self):
return True

def seekable(self):
return True


@pytest.mark.parametrize('format', ['WAV', 'FLAC'])
@pytest.mark.parametrize('channels', [1, 2])
def test_virtual_io_read_adapter_with_unimplemented_readinto(format, channels):
values = np.array([1, 256, -2345, 42], dtype='int16')
if channels == 2:
values = np.column_stack((values, -values))
encoded = io.BytesIO()
sf.write(encoded, values, 8000, format=format, subtype='PCM_16')
adapter = ReadAdapter(encoded.getvalue())
# The method exists, but is deliberately not implemented by this adapter.
with pytest.raises(NotImplementedError):
adapter.readinto(bytearray(1))
decoded, rate = sf.read(adapter, dtype='int16')
assert rate == 8000
assert_array_equal(decoded, values)
assert adapter.read_calls > 0


def test_virtual_io_prefers_working_readinto():
class ReadIntoAdapter(ReadAdapter):
def readinto(self, buffer):
return self.source.readinto(buffer)

values = np.array([1, 256, -2345, 42], dtype='int16')
encoded = io.BytesIO()
sf.write(encoded, values, 8000, format='WAV', subtype='PCM_16')
adapter = ReadIntoAdapter(encoded.getvalue())
decoded, _ = sf.read(adapter, dtype='int16')
assert_array_equal(decoded, values)
assert adapter.read_calls == 0
Loading