Skip to content
Closed
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
68 changes: 25 additions & 43 deletions pysoundfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,43 +334,18 @@ def __init__(self, file, mode='r', samplerate=None, channels=None,
except KeyError:
raise ValueError("Invalid mode: %s" % repr(mode))

original_format = format
filename = getattr(file, 'name', file)
file_extension = str(filename).rsplit('.', 1)[-1].upper()
if format is None and ('w' in self.mode or
file_extension == 'RAW'):
if file_extension not in _formats:
if self.mode == 'w':
raise TypeError(
"No format specified and unable to get format from "
"file extension: %s" % repr(filename))
else:
format = file_extension
if format is None:
ext = str(getattr(file, 'name', file)).rsplit('.', 1)[-1].upper()
if ext in _formats:
format = ext

self._info = _ffi.new("SF_INFO*")
if self.mode == 'w' or str(format).upper() == 'RAW':
if samplerate is None:
raise TypeError("samplerate must be specified")
if samplerate is not None:
self._info.samplerate = samplerate
if channels is None:
raise TypeError("channels must be specified")
if channels is not None:
self._info.channels = channels
if str(format).upper() == 'RAW' and subtype is None:
raise TypeError("RAW files must specify a subtype")
if format is not None:
self._info.format = _format_int(format, subtype, endian)
elif self.mode == 'rw':
if samplerate is not None:
self._info.samplerate = samplerate
if channels is not None:
self._info.channels = channels
if format is not None:
self._info.format = _format_int(format, subtype, endian)
else:
if [samplerate, channels, original_format, subtype, endian] != \
[None] * 5:
raise TypeError("Only allowed if mode='w' or format='RAW': "
"samplerate, channels, "
"format, subtype, endian")

self._name = file
if isinstance(file, str):
Expand All @@ -383,10 +358,23 @@ def __init__(self, file, mode='r', samplerate=None, channels=None,
self._init_virtual_io(file), mode_int, self._info, _ffi.NULL)
self._name = str(file)
else:
raise RuntimeError("file must be a filename, a file descriptor or "
"a file-like object with the methods "
"'seek()', 'read()', 'write()' and 'tell()'")
self._handle_error()
raise TypeError("Invalid file: %s" % repr(file))

err = _snd.sf_error(self._file)
if err != 0 and (self.mode != 'r' or self.format == 'RAW'):
missing = []
if self._info.samplerate == 0:
missing.append("samplerate")
if self._info.channels == 0:
missing.append("channels")
if self._info.format & _snd.SF_FORMAT_TYPEMASK == 0:
missing.append("format")
if missing:
raise RuntimeError(
"Error opening %r. Missing %s? (libsndfile: %s)" % (
self.name, "/".join(missing),
_ffi.string(_snd.sf_error_number(err)).decode()))
self._handle_error_number(err)

name = property(lambda self: self._name)
mode = property(lambda self: self._mode)
Expand Down Expand Up @@ -502,9 +490,6 @@ def __setattr__(self, name, value):
# access text data in the sound file through properties
if name in _str_types:
self._check_if_closed()
if self.mode == 'r':
raise RuntimeError("Can not change %s of file in read mode" %
repr(name))
data = _ffi.new('char[]', value.encode())
err = _snd.sf_set_string(self._file, _str_types[name], data)
self._handle_error_number(err)
Expand Down Expand Up @@ -557,8 +542,6 @@ def __setitem__(self, frame, data):
# access the file as if it where a one-dimensional Numpy
# array. Data must be in the form (frames x channels).
# Both open slice bounds and negative values are allowed.
if self.mode == 'r':
raise RuntimeError("Cannot write to file opened in read mode")
start, stop = self._get_slice_bounds(frame)
if stop - start != len(data):
raise IndexError(
Expand Down Expand Up @@ -634,6 +617,7 @@ def _create_empty_array(self, frames, always_2d, dtype):

def _read_or_write(self, funcname, array, frames):
# Call into libsndfile
self._check_if_closed()
ffi_type = _ffi_types[array.dtype]
assert array.flags.c_contiguous
assert array.dtype.itemsize == _ffi.sizeof(ffi_type)
Expand Down Expand Up @@ -671,7 +655,6 @@ def read(self, frames=-1, dtype='float64', always_2d=True,
containing all valid frames is returned.

"""
self._check_if_closed()
if self.mode == 'w':
raise RuntimeError("Cannot read from file opened in write mode")

Expand Down Expand Up @@ -709,7 +692,6 @@ def write(self, data):
array or as one-dimensional array for mono signals.

"""
self._check_if_closed()
if self.mode == 'r':
raise RuntimeError("Cannot write to file opened in read mode")

Expand Down
68 changes: 62 additions & 6 deletions tests/test_pysoundfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,59 @@ def test_blocks_write(sf_stereo_w):
list(sf_stereo_w.blocks(blocksize=2))


# -----------------------------------------------------------------------------
# Test open()
# -----------------------------------------------------------------------------


def test_open_with_invalid_file():
with pytest.raises(TypeError) as excinfo:
sf.open(3.1415)
assert "Invalid file" in str(excinfo.value)


def test_open_with_invalid_mode():
with pytest.raises(ValueError) as excinfo:
sf.open(filename_stereo, 42)
assert "Invalid mode" in str(excinfo.value)
with pytest.raises(ValueError) as excinfo:
sf.open(filename_stereo, 'wr')
assert "Invalid mode" in str(excinfo.value)


def test_open_with_more_invalid_arguments():
with pytest.raises(TypeError) as excinfo:
sf.open(filename_new, 'w', samplerate=3.1415, channels=2)
assert "integer" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
sf.open(filename_new, 'w', samplerate=44100, channels=3.1415)
assert "integer" in str(excinfo.value)
with pytest.raises(ValueError) as excinfo:
sf.open(filename_new, 'w', 44100, 2, format='WAF')
assert "Invalid format string" in str(excinfo.value)
with pytest.raises(ValueError) as excinfo:
sf.open(filename_new, 'w', 44100, 2, subtype='PCM16')
assert "Invalid subtype string" in str(excinfo.value)
with pytest.raises(ValueError) as excinfo:
sf.open(filename_new, 'w', 44100, 2, endian='BOTH')
assert "Invalid endian-ness" in str(excinfo.value)


def test_open_w_and_rw_new_with_too_few_arguments(tmpdir):
with tmpdir.as_cwd():
filename = 'not_existing.xyz'
for mode in 'w', 'rw':
with pytest.raises(RuntimeError) as excinfo:
sf.open(filename, mode, samplerate=44100, channels=2)
assert "Missing format" in str(excinfo.value)
with pytest.raises(RuntimeError) as excinfo:
sf.open(filename, mode, samplerate=44100, format='WAV')
assert "channels" in str(excinfo.value)
with pytest.raises(RuntimeError) as excinfo:
sf.open(filename, mode, channels=2, format='WAV')
assert "samplerate" in str(excinfo.value)


# -----------------------------------------------------------------------------
# Test file metadata
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -548,17 +601,20 @@ def test_non_file_attributes_should_not_save_to_disk():


def test_read_raw_files_should_read_data():
with sf.open(filename_raw, samplerate=44100,
channels=1, subtype='PCM_16') as f:
with sf.open(filename_raw, 'r', 44100, 1, 'PCM_16') as f:
assert np.all(f.read(dtype='int16') == data_mono)


def test_read_raw_files_with_too_few_arguments_should_fail():
with pytest.raises(TypeError): # missing everything
with pytest.raises(TypeError) as excinfo: # missing everything
sf.open(filename_raw)
with pytest.raises(TypeError): # missing subtype
assert "No default subtype" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo: # missing subtype
sf.open(filename_raw, samplerate=44100, channels=2)
with pytest.raises(TypeError): # missing channels
assert "No default subtype" in str(excinfo.value)
with pytest.raises(RuntimeError) as excinfo: # missing channels
sf.open(filename_raw, samplerate=44100, subtype='PCM_16')
with pytest.raises(TypeError): # missing samplerate
assert "channels" in str(excinfo.value)
with pytest.raises(RuntimeError) as excinfo: # missing samplerate
sf.open(filename_raw, channels=2, subtype='PCM_16')
assert "samplerate" in str(excinfo.value)