Skip to content

Commit

Permalink
Merge pull request #326 from bastibe/bastibe/0.11.0
Browse files Browse the repository at this point in the history
Release 0.11.0
  • Loading branch information
bastibe authored Sep 27, 2022
2 parents 8737cfb + c426f56 commit 7deb23a
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 72 deletions.
17 changes: 17 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,20 @@ News
- Improves performance of `blocks()` and `SoundFile.blocks()`.
- Improves import time by using CFFI's out of line mode.
- Adds a build script for building distributions.

2022-06-02 V0.11.0 Bastian Bechtold:
Thank you, tennies, Hannes Helmholz, Christoph Boeddeker, Matt
Vollrath, Matthias Geier, Jacek Konieczny, Boris Verkhovskiy,
Jonas Haag, Eduardo Moguillansky, Panos Laganakos, Jarvy Jarvison,
Domingo Ramirez, Tim Chagnon, Kyle Benesch, Fabian-Robert Stöter,
Joe Todd

- MP3 support
- Adds binary wheels for macOS M1
- Improves compatibility with macOS, specifically for M1 machines
- Fixes file descriptor open for binary wheels on Windows and Python 3.5+
- Updates libsndfile to v1.1.0
- Adds get_strings method for retrieving all metadata at once
- Improves documentation, error messages and tests
- Displays length of very short files in samples
- Supports the file system path protocol (pathlib et al)
11 changes: 7 additions & 4 deletions build_wheels.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import os
import shutil

architectures = dict(darwin=['64bit'],
architectures = dict(darwin=['x86_64', 'arm64'],
win32=['32bit', '64bit'],
noplatform='noarch')

def cleanup():
os.environ['PYSOUNDFILE_PLATFORM'] = ''
os.environ['PYSOUNDFILE_ARCHITECTURE'] = ''
shutil.rmtree('build', ignore_errors=True)
shutil.rmtree('soundfile.egg-info', ignore_errors=True)
try:
os.remove('_soundfile.py')
except:
pass

for platform, archs in architectures.items():
os.environ['PYSOUNDFILE_PLATFORM'] = platform
for arch in archs:
os.environ['PYSOUNDFILE_PLATFORM'] = platform
os.environ['PYSOUNDFILE_ARCHITECTURE'] = arch
os.system('python3 setup.py bdist_wheel')
cleanup()
os.system('python setup.py bdist_wheel')

os.system('python3 setup.py sdist')
cleanup()
os.system('python setup.py sdist')
26 changes: 8 additions & 18 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,14 @@
from setuptools.command.test import test as TestCommand
import sys

PYTHON_INTERPRETERS = '.'.join([
'cp26', 'cp27',
'cp32', 'cp33', 'cp34', 'cp35', 'cp36',
'pp27',
'pp32', 'pp33',
])
MACOSX_VERSIONS = '.'.join([
'macosx_10_5_x86_64',
'macosx_10_6_intel',
'macosx_10_9_intel',
'macosx_10_9_x86_64',
])

# environment variables for cross-platform package creation
platform = os.environ.get('PYSOUNDFILE_PLATFORM', sys.platform)
architecture0 = os.environ.get('PYSOUNDFILE_ARCHITECTURE', architecture()[0])

if platform == 'darwin':
libname = 'libsndfile.dylib'
libname = 'libsndfile_' + architecture0 + '.dylib'
elif platform == 'win32':
libname = 'libsndfile' + architecture0 + '.dll'
libname = 'libsndfile_' + architecture0 + '.dll'
else:
libname = None

Expand Down Expand Up @@ -70,9 +57,12 @@ class bdist_wheel_half_pure(bdist_wheel):
"""Create OS-dependent, but Python-independent wheels."""

def get_tag(self):
pythons = 'py2.py3.' + PYTHON_INTERPRETERS
pythons = 'py2.py3'
if platform == 'darwin':
oses = MACOSX_VERSIONS
if architecture0 == 'x86_64':
oses = 'macosx_10_9_x86_64.macosx_11_0_x86_64'
else:
oses = 'macosx_10_9_arm64.macosx_11_0_arm64'
elif platform == 'win32':
if architecture0 == '32bit':
oses = 'win32'
Expand All @@ -87,7 +77,7 @@ def get_tag(self):

setup(
name='soundfile',
version='0.10.3post1',
version='0.11.0',
description='An audio library based on libsndfile, CFFI and NumPy',
author='Bastian Bechtold',
author_email='basti@bastibe.de',
Expand Down
103 changes: 60 additions & 43 deletions soundfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
For further information, see https://python-soundfile.readthedocs.io/.
"""
__version__ = "0.10.3"
__version__ = "0.11.0"

import os as _os
import sys as _sys
from platform import machine as _machine
from os import SEEK_SET, SEEK_CUR, SEEK_END
from ctypes.util import find_library as _find_library
from _soundfile import ffi as _ffi
Expand Down Expand Up @@ -62,36 +61,44 @@
'OGG': 0x200000, # Xiph OGG container
'MPC2K': 0x210000, # Akai MPC 2000 sampler
'RF64': 0x220000, # RF64 WAV file
'MP3': 0x230000, # MPEG-1/2 audio stream
}

_subtypes = {
'PCM_S8': 0x0001, # Signed 8 bit data
'PCM_16': 0x0002, # Signed 16 bit data
'PCM_24': 0x0003, # Signed 24 bit data
'PCM_32': 0x0004, # Signed 32 bit data
'PCM_U8': 0x0005, # Unsigned 8 bit data (WAV and RAW only)
'FLOAT': 0x0006, # 32 bit float data
'DOUBLE': 0x0007, # 64 bit float data
'ULAW': 0x0010, # U-Law encoded.
'ALAW': 0x0011, # A-Law encoded.
'IMA_ADPCM': 0x0012, # IMA ADPCM.
'MS_ADPCM': 0x0013, # Microsoft ADPCM.
'GSM610': 0x0020, # GSM 6.10 encoding.
'VOX_ADPCM': 0x0021, # OKI / Dialogix ADPCM
'G721_32': 0x0030, # 32kbs G721 ADPCM encoding.
'G723_24': 0x0031, # 24kbs G723 ADPCM encoding.
'G723_40': 0x0032, # 40kbs G723 ADPCM encoding.
'DWVW_12': 0x0040, # 12 bit Delta Width Variable Word encoding.
'DWVW_16': 0x0041, # 16 bit Delta Width Variable Word encoding.
'DWVW_24': 0x0042, # 24 bit Delta Width Variable Word encoding.
'DWVW_N': 0x0043, # N bit Delta Width Variable Word encoding.
'DPCM_8': 0x0050, # 8 bit differential PCM (XI only)
'DPCM_16': 0x0051, # 16 bit differential PCM (XI only)
'VORBIS': 0x0060, # Xiph Vorbis encoding.
'ALAC_16': 0x0070, # Apple Lossless Audio Codec (16 bit).
'ALAC_20': 0x0071, # Apple Lossless Audio Codec (20 bit).
'ALAC_24': 0x0072, # Apple Lossless Audio Codec (24 bit).
'ALAC_32': 0x0073, # Apple Lossless Audio Codec (32 bit).
'PCM_S8': 0x0001, # Signed 8 bit data
'PCM_16': 0x0002, # Signed 16 bit data
'PCM_24': 0x0003, # Signed 24 bit data
'PCM_32': 0x0004, # Signed 32 bit data
'PCM_U8': 0x0005, # Unsigned 8 bit data (WAV and RAW only)
'FLOAT': 0x0006, # 32 bit float data
'DOUBLE': 0x0007, # 64 bit float data
'ULAW': 0x0010, # U-Law encoded.
'ALAW': 0x0011, # A-Law encoded.
'IMA_ADPCM': 0x0012, # IMA ADPCM.
'MS_ADPCM': 0x0013, # Microsoft ADPCM.
'GSM610': 0x0020, # GSM 6.10 encoding.
'VOX_ADPCM': 0x0021, # OKI / Dialogix ADPCM
'NMS_ADPCM_16': 0x0022, # 16kbs NMS G721-variant encoding.
'NMS_ADPCM_24': 0x0023, # 24kbs NMS G721-variant encoding.
'NMS_ADPCM_32': 0x0024, # 32kbs NMS G721-variant encoding.
'G721_32': 0x0030, # 32kbs G721 ADPCM encoding.
'G723_24': 0x0031, # 24kbs G723 ADPCM encoding.
'G723_40': 0x0032, # 40kbs G723 ADPCM encoding.
'DWVW_12': 0x0040, # 12 bit Delta Width Variable Word encoding.
'DWVW_16': 0x0041, # 16 bit Delta Width Variable Word encoding.
'DWVW_24': 0x0042, # 24 bit Delta Width Variable Word encoding.
'DWVW_N': 0x0043, # N bit Delta Width Variable Word encoding.
'DPCM_8': 0x0050, # 8 bit differential PCM (XI only)
'DPCM_16': 0x0051, # 16 bit differential PCM (XI only)
'VORBIS': 0x0060, # Xiph Vorbis encoding.
'OPUS': 0x0064, # Xiph/Skype Opus encoding.
'ALAC_16': 0x0070, # Apple Lossless Audio Codec (16 bit).
'ALAC_20': 0x0071, # Apple Lossless Audio Codec (20 bit).
'ALAC_24': 0x0072, # Apple Lossless Audio Codec (24 bit).
'ALAC_32': 0x0073, # Apple Lossless Audio Codec (32 bit).
'MPEG_LAYER_I': 0x0080, # MPEG-1 Audio Layer I.
'MPEG_LAYER_II': 0x0081, # MPEG-1 Audio Layer II.
'MPEG_LAYER_III': 0x0082, # MPEG-2 Audio Layer III.
}

_endians = {
Expand Down Expand Up @@ -128,6 +135,7 @@
'OGG': 'VORBIS',
'MPC2K': 'PCM_16',
'RF64': 'PCM_16',
'MP3': 'MPEG_LAYER_III',
}

_ffi_types = {
Expand All @@ -144,10 +152,16 @@
_snd = _ffi.dlopen(_libname)
except OSError:
if _sys.platform == 'darwin':
from platform import machine as _machine
_packaged_libname = 'libsndfile_' + _machine() + '.dylib'
_libname = 'libsndfile.dylib'
elif _sys.platform == 'win32':
from platform import architecture as _architecture
_libname = 'libsndfile' + _architecture()[0] + '.dll'
_packaged_libname = 'libsndfile_' + _architecture()[0] + '.dll'
_libname = 'libsndfile.dll'
elif _sys.platform == 'linux':
_packaged_libname = 'libsndfile.so' # not provided!
_libname = 'libsndfile.so'
else:
raise

Expand All @@ -160,16 +174,19 @@
while not _os.path.isdir(_path):
_path = _os.path.abspath(_os.path.join(_path, '..'))

# Homebrew on Apple M1 uses a `/opt/homebrew/lib` instead of
# `/usr/local/lib`. We are making sure we pick that up.
if _sys.platform == 'darwin' and _machine() == 'arm64':
_hbrew_path = '/opt/homebrew/lib/' if _os.path.isdir('/opt/homebrew/lib/') \
else '/usr/local/lib/'
_snd = _ffi.dlopen(_os.path.join(
_hbrew_path, _libname))
else:
_snd = _ffi.dlopen(_os.path.join(
_path, '_soundfile_data', _libname))
try: # packaged libsndfile:
_snd = _ffi.dlopen(_os.path.join(_path, '_soundfile_data', _packaged_libname))
except OSError: # try system-wide libsndfile:
# Homebrew on Apple M1 uses a `/opt/homebrew/lib` instead of
# `/usr/local/lib`. We are making sure we pick that up.
from platform import machine as _machine
if _sys.platform == 'darwin' and _machine() == 'arm64':
_hbrew_path = '/opt/homebrew/lib/' if _os.path.isdir('/opt/homebrew/lib/') \
else '/usr/local/lib/'
_snd = _ffi.dlopen(_os.path.join(_hbrew_path, _libname))
else:
# Try explicit file name, if the general does not work (e.g. on nixos)
_snd = _ffi.dlopen(_libname)

__libsndfile_version__ = _ffi.string(_snd.sf_version_string()).decode('utf-8', 'replace')
if __libsndfile_version__.startswith('libsndfile-'):
Expand Down Expand Up @@ -1368,9 +1385,9 @@ def copy_metadata(self):
-------
metadata: dict[str, str]
A dict with all metadata. Possible keys are: 'title', 'copyright',
'software', 'artist', 'comment', 'date', 'album', 'license',
'tracknumber' and 'genre'.
A dict with all metadata. Possible keys are: 'title', 'copyright',
'software', 'artist', 'comment', 'date', 'album', 'license',
'tracknumber' and 'genre'.
"""
strs = {}
for strtype, strid in _str_types.items():
Expand Down
13 changes: 7 additions & 6 deletions tests/test_soundfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,31 +101,31 @@ def file_wplus(request):
return _file_new(request, os.O_CREAT | os.O_RDWR, 'w+b')


@pytest.yield_fixture
@pytest.fixture
def file_inmemory():
with io.BytesIO() as f:
yield f


@pytest.yield_fixture
@pytest.fixture
def sf_stereo_r(file_stereo_r):
with sf.SoundFile(file_stereo_r) as f:
yield f


@pytest.yield_fixture
@pytest.fixture
def sf_stereo_w(file_w):
with sf.SoundFile(file_w, 'w', 44100, 2, format='WAV') as f:
yield f


@pytest.yield_fixture
@pytest.fixture
def sf_stereo_rplus(file_stereo_rplus):
with sf.SoundFile(file_stereo_rplus, 'r+') as f:
yield f


@pytest.yield_fixture
@pytest.fixture
def sf_stereo_wplus(file_wplus):
with sf.SoundFile(file_wplus, 'w+', 44100, 2,
format='WAV', subtype='FLOAT') as f:
Expand Down Expand Up @@ -592,7 +592,8 @@ def test_file_content(sf_stereo_r):

def test_file_attributes_in_read_mode(sf_stereo_r):
if isinstance(sf_stereo_r.name, str):
assert sf_stereo_r.name == filename_stereo
# wrap in pathlib, to make tests pass on Windows:
assert pathlib.Path(sf_stereo_r.name) == pathlib.Path(filename_stereo)
elif not isinstance(sf_stereo_r.name, int):
assert sf_stereo_r.name.name == filename_stereo
assert sf_stereo_r.mode == 'r'
Expand Down

0 comments on commit 7deb23a

Please sign in to comment.