From 8d33b9fdd9a74a439fa00e1f65b75d6fc02efc73 Mon Sep 17 00:00:00 2001 From: Jon Peirce Date: Tue, 31 Jan 2023 10:10:45 +0000 Subject: [PATCH 01/15] More robust check for _soundfile_data package Rather than trying to create the location for `_soundfile_data/` manually, simply import it and use its reported `__path__` The existing method fails if using py2app because: - starting path is `/lib/python38.zip/soundfile.py` - desired path is `/lib/python3.8/_soundfile_data` - the previous method of stepping back from the starting path never reaches this desired path (it tries `/lib/_soundfile_data` and fails) --- soundfile.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/soundfile.py b/soundfile.py index 22f5bba..60a4298 100644 --- a/soundfile.py +++ b/soundfile.py @@ -148,7 +148,7 @@ try: _libname = _find_library('sndfile') if _libname is None: - raise OSError('sndfile library not found') + raise OSError('sndfile library not found using ctypes.util.find_library') _snd = _ffi.dlopen(_libname) except OSError: if _sys.platform == 'darwin': @@ -165,18 +165,12 @@ else: raise - # hack for packaging tools like cx_Freeze, which - # compress all scripts into a zip file - # which causes __file__ to be inside this zip file - - _path = _os.path.dirname(_os.path.abspath(__file__)) - - while not _os.path.isdir(_path): - _path = _os.path.abspath(_os.path.join(_path, '..')) - + # try packaged lib (in _soundfile_data which should be on python path) try: # packaged libsndfile: - _snd = _ffi.dlopen(_os.path.join(_path, '_soundfile_data', _packaged_libname)) - except OSError: # try system-wide libsndfile: + import _soundfile_data # ImportError if this doesn't exist + _path = _os.path.dirname(_soundfile_data.__path__) + _snd = _ffi.dlopen(_os.path.join(_path, _packaged_libname)) + except (OSError, ImportError): # 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 From e366a085bfaae725913cac9a1ac5da7d7872c130 Mon Sep 17 00:00:00 2001 From: Jon Peirce Date: Wed, 1 Feb 2023 13:32:39 +0000 Subject: [PATCH 02/15] further fix to finding the _soundfile_data path The `__path__` is a _Namespacepath object and it's a list of strings not a single string --- soundfile.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/soundfile.py b/soundfile.py index 60a4298..4535654 100644 --- a/soundfile.py +++ b/soundfile.py @@ -168,8 +168,9 @@ # try packaged lib (in _soundfile_data which should be on python path) try: # packaged libsndfile: import _soundfile_data # ImportError if this doesn't exist - _path = _os.path.dirname(_soundfile_data.__path__) - _snd = _ffi.dlopen(_os.path.join(_path, _packaged_libname)) + _path = _os.path.dirname(_soundfile_data.__path__[0]) + _full_path = _os.path.join(_path, _packaged_libname) + _snd = _ffi.dlopen(_full_path) except (OSError, ImportError): # 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 3c612a4c48be48a904826224af2a5c0d5f128c0a Mon Sep 17 00:00:00 2001 From: Jon Peirce Date: Wed, 1 Feb 2023 20:42:21 +0000 Subject: [PATCH 03/15] Further attempt to fetch libsndfile in reliably Adding an empty `__init__.py` to the `_soundfile_data` package means that the __file__ attribute exists so we can do ``` import _soundfile_data _path = os.path.dirname(_soundfile_data.__file__) _full_path = _os.path.join(_path, _packaged_libname) _snd = _ffi.dlopen(_full_path) ``` --- _soundfile_data | 2 +- soundfile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/_soundfile_data b/_soundfile_data index 4bd2073..db2a7fb 160000 --- a/_soundfile_data +++ b/_soundfile_data @@ -1 +1 @@ -Subproject commit 4bd2073585337484c287b5f6da51702855228b69 +Subproject commit db2a7fb6cddb94a10a033bc99a2b8bd58217f1eb diff --git a/soundfile.py b/soundfile.py index 4535654..919356c 100644 --- a/soundfile.py +++ b/soundfile.py @@ -168,7 +168,7 @@ # try packaged lib (in _soundfile_data which should be on python path) try: # packaged libsndfile: import _soundfile_data # ImportError if this doesn't exist - _path = _os.path.dirname(_soundfile_data.__path__[0]) + _path = _os.path.dirname(_soundfile_data.__file__) _full_path = _os.path.join(_path, _packaged_libname) _snd = _ffi.dlopen(_full_path) except (OSError, ImportError): # try system-wide libsndfile: From 556b1375f36c9ee847f970801bd80ad16db76bb6 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Fri, 4 Nov 2022 11:25:39 +0100 Subject: [PATCH 04/15] adds support for binary linux wheels --- build_wheels.py | 1 + setup.py | 4 ++++ soundfile.py | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/build_wheels.py b/build_wheels.py index c312af4..a2af99c 100644 --- a/build_wheels.py +++ b/build_wheels.py @@ -3,6 +3,7 @@ architectures = dict(darwin=['x86_64', 'arm64'], win32=['32bit', '64bit'], + linux=['x86_64'], noplatform='noarch') def cleanup(): diff --git a/setup.py b/setup.py index 655f753..b1a94d8 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,8 @@ libname = 'libsndfile_' + architecture0 + '.dylib' elif platform == 'win32': libname = 'libsndfile_' + architecture0 + '.dll' +elif platform == 'linux': + libname = 'libsndfile_' + architecture0 + '.so' else: libname = None @@ -68,6 +70,8 @@ def get_tag(self): oses = 'win32' else: oses = 'win_amd64' + elif platform == 'linux': + oses = 'manylinux2014_x86_64' else: pythons = 'py2.py3' oses = 'any' diff --git a/soundfile.py b/soundfile.py index 919356c..0aa3406 100644 --- a/soundfile.py +++ b/soundfile.py @@ -160,7 +160,8 @@ _packaged_libname = 'libsndfile_' + _architecture()[0] + '.dll' _libname = 'libsndfile.dll' elif _sys.platform == 'linux': - _packaged_libname = 'libsndfile.so' # not provided! + from platform import machine as _machine + _packaged_libname = 'libsndfile_' + _machine() + '.so' _libname = 'libsndfile.so' else: raise From 7028b5af5c90c48362d5aa87b8f8e644962c951d Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Fri, 4 Nov 2022 13:34:15 +0100 Subject: [PATCH 05/15] corrects manylinux tag --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b1a94d8..3be11cf 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,9 @@ def get_tag(self): else: oses = 'win_amd64' elif platform == 'linux': - oses = 'manylinux2014_x86_64' + # the oldest mainline github runner available is ubuntu 20.04, + # which runs glibc 2.31: + oses = 'manylinux_2_31_x86_64' else: pythons = 'py2.py3' oses = 'any' From b8d1e3dbc2dd90d8e98bb0cae005c6e86d1b8dc2 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Thu, 2 Feb 2023 16:25:07 +0100 Subject: [PATCH 06/15] preparations for v0.12.0 --- README.rst | 17 +++++++++++++---- _soundfile_data | 2 +- setup.py | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 647a644..86fdc25 100644 --- a/README.rst +++ b/README.rst @@ -59,10 +59,11 @@ The ``soundfile`` module depends on the Python packages CFFI and NumPy, and the system library libsndfile. In a modern Python, you can use ``pip install soundfile`` to download -and install the latest release of the ``soundfile`` module and its dependencies. -On Windows and OS X, this will also install the library libsndfile. -On Linux, you need to install libsndfile using your distribution's -package manager, for example ``sudo apt-get install libsndfile1``. +and install the latest release of the ``soundfile`` module and its +dependencies. On Windows and OS X and Linux 64, this will also install +the library libsndfile. On Linux, you need to install libsndfile using +your distribution's package manager, for example ``sudo apt-get +install libsndfile1``. If you are running on an unusual platform or if you are using an older version of Python, you might need to install NumPy and CFFI separately, @@ -309,3 +310,11 @@ News - Improves documentation, error messages and tests - Displays length of very short files in samples - Supports the file system path protocol (pathlib et al) + +2023-02-02 V0.12.0 Bastian Bechtold + Thank you, Barabazs, Andrew Murray, Jon Peirce, for contributions + to this release. + + - Updated libsndfile to v1.2.0 + - Improves precompiled library location, especially with py2app or cx-freeze. + - Now provide binary wheels for Linux x86_64 diff --git a/_soundfile_data b/_soundfile_data index db2a7fb..6dc01b9 160000 --- a/_soundfile_data +++ b/_soundfile_data @@ -1 +1 @@ -Subproject commit db2a7fb6cddb94a10a033bc99a2b8bd58217f1eb +Subproject commit 6dc01b9f343fe1313c70acee2b89bb510cf24d08 diff --git a/setup.py b/setup.py index 3be11cf..698ac2b 100644 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ def get_tag(self): setup( name='soundfile', - version='0.11.0', + version='0.12.0', description='An audio library based on libsndfile, CFFI and NumPy', author='Bastian Bechtold', author_email='basti@bastibe.de', From cb9d7996908be9b5a320bd5c614c99ca4c39c9a3 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Mon, 6 Feb 2023 10:27:18 +0100 Subject: [PATCH 07/15] fix OS tags for macOS apparently, multi-OS tags are no longer allowed. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 698ac2b..be5ed76 100644 --- a/setup.py +++ b/setup.py @@ -62,9 +62,9 @@ def get_tag(self): pythons = 'py2.py3' if platform == 'darwin': if architecture0 == 'x86_64': - oses = 'macosx_10_9_x86_64.macosx_11_0_x86_64' + oses = 'macosx_10_9_x86_64' else: - oses = 'macosx_10_9_arm64.macosx_11_0_arm64' + oses = 'macosx_11_0_arm64' elif platform == 'win32': if architecture0 == '32bit': oses = 'win32' From 27ae6062e36be9ea5b0200c1bb71e240a1479ff5 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Mon, 6 Feb 2023 10:30:35 +0100 Subject: [PATCH 08/15] adds libsndfile v1.2.0 binaries --- _soundfile_data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_soundfile_data b/_soundfile_data index 6dc01b9..d9887ef 160000 --- a/_soundfile_data +++ b/_soundfile_data @@ -1 +1 @@ -Subproject commit 6dc01b9f343fe1313c70acee2b89bb510cf24d08 +Subproject commit d9887ef926bb11cf1a2526be4ab6f9dc690234c0 From c26ddbf76db7e56af797ff36f65e200912aa037a Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Tue, 7 Feb 2023 07:56:46 +0100 Subject: [PATCH 09/15] updates soundfile.__version__ --- soundfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soundfile.py b/soundfile.py index 0aa3406..63df1c0 100644 --- a/soundfile.py +++ b/soundfile.py @@ -8,7 +8,7 @@ For further information, see https://python-soundfile.readthedocs.io/. """ -__version__ = "0.11.0" +__version__ = "0.12.0" import os as _os import sys as _sys From 077de1f3e34a9b82bf1e236386d7886cc3d4d23a Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Tue, 7 Feb 2023 08:04:23 +0100 Subject: [PATCH 10/15] ensure soundfile.__version__ is always up to date --- setup.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index be5ed76..20d28f2 100644 --- a/setup.py +++ b/setup.py @@ -81,9 +81,18 @@ def get_tag(self): cmdclass['bdist_wheel'] = bdist_wheel_half_pure +with open('soundfile.py') as f: + for line in f: + if line.startswith('__version__'): + _, soundfile_version = line.split('=') + soundfile_version = soundfile_version.strip(' "\'\n') + break + else: + raise RuntimeError("Could not find __version__ in soundfile.py") + setup( name='soundfile', - version='0.12.0', + version=soundfile_version, description='An audio library based on libsndfile, CFFI and NumPy', author='Bastian Bechtold', author_email='basti@bastibe.de', From 41bd664ecbeb100c022314249ddf7a2ebe9d5daa Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Thu, 9 Feb 2023 09:58:57 +0100 Subject: [PATCH 11/15] updated libsndfile dependency description in README --- README.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 86fdc25..3803098 100644 --- a/README.rst +++ b/README.rst @@ -38,7 +38,7 @@ interface for Python calling C code. CFFI is supported for CPython 2.6+, Breaking Changes ---------------- -The ``soundfile`` module has evolved rapidly during the last few releases. Most +The ``soundfile`` module has evolved rapidly in the past. Most notably, we changed the import name from ``import pysoundfile`` to ``import soundfile`` in 0.7. In 0.6, we cleaned up many small inconsistencies, particularly in the the ordering and naming of @@ -60,10 +60,11 @@ system library libsndfile. In a modern Python, you can use ``pip install soundfile`` to download and install the latest release of the ``soundfile`` module and its -dependencies. On Windows and OS X and Linux 64, this will also install -the library libsndfile. On Linux, you need to install libsndfile using -your distribution's package manager, for example ``sudo apt-get -install libsndfile1``. +dependencies. On Windows (64/32) and OS X (Intel/ARM) and Linux 64, +this will also install a current version of the library libsndfile. If +you install the source module, you need to install libsndfile using +your distribution's package manager, for example ``sudo apt install +libsndfile1``. If you are running on an unusual platform or if you are using an older version of Python, you might need to install NumPy and CFFI separately, From b1271c70bec19d80f1152e9665161d89f505f965 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Thu, 9 Feb 2023 10:27:12 +0100 Subject: [PATCH 12/15] prefer packaged libsndfile over system libsndfile Previously, we tried to load the system libsndfile before trying our own packaged libsndfile, to give users the option to use custom, potentially newer versions of libsndfile. However, this situation has reversed in the last few versions of libsndfile. These introduced some new features and much-needed bug fixes, and soundfile has been updated to provide feature-complete, up-to-date versions of libsndfile on most platforms. Linux package repositories, however, have largely not yet updated to these versions, making it desirable to use our libsndfile over the system-provided ones. Should the system-provided libsndfile be preferred over the bundled libsndfile, one should install the source packages or source wheels (or indeed, a system-provided soundfile). --- README.rst | 7 +++++++ soundfile.py | 49 ++++++++++++++++++++++++++++--------------------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/README.rst b/README.rst index 3803098..36eec8f 100644 --- a/README.rst +++ b/README.rst @@ -52,6 +52,12 @@ In 0.9.0, we changed the ``ctype`` arguments of the ``buffer_*`` methods to ``dtype``, using the Numpy ``dtype`` notation. The old ``ctype`` arguments still work, but are now officially deprecated. +In 0.12.0, we changed the load order of the libsndfile library. Now, +the packaged libsndfile in the platform-specific wheels is tried +before falling back to any system-provided libsndfile. If you would +prefer using the system-provided libsndfile, install the source +package or source wheel instead of the platform-specific wheels. + Installation ------------ @@ -319,3 +325,4 @@ News - Updated libsndfile to v1.2.0 - Improves precompiled library location, especially with py2app or cx-freeze. - Now provide binary wheels for Linux x86_64 + - Now prefers packaged libsndfile over system-installed libsndfile diff --git a/soundfile.py b/soundfile.py index 63df1c0..1e0e44b 100644 --- a/soundfile.py +++ b/soundfile.py @@ -145,44 +145,51 @@ 'int16': 'short' } -try: - _libname = _find_library('sndfile') - if _libname is None: - raise OSError('sndfile library not found using ctypes.util.find_library') - _snd = _ffi.dlopen(_libname) -except OSError: +try: # packaged lib (in _soundfile_data which should be on python path) 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 _packaged_libname = 'libsndfile_' + _architecture()[0] + '.dll' - _libname = 'libsndfile.dll' elif _sys.platform == 'linux': from platform import machine as _machine _packaged_libname = 'libsndfile_' + _machine() + '.so' - _libname = 'libsndfile.so' else: - raise - - # try packaged lib (in _soundfile_data which should be on python path) - try: # packaged libsndfile: - import _soundfile_data # ImportError if this doesn't exist - _path = _os.path.dirname(_soundfile_data.__file__) - _full_path = _os.path.join(_path, _packaged_libname) - _snd = _ffi.dlopen(_full_path) - except (OSError, ImportError): # try system-wide libsndfile: + raise OSError('no packaged library for this platform') + + import _soundfile_data # ImportError if this doesn't exist + _path = _os.path.dirname(_soundfile_data.__file__) + _full_path = _os.path.join(_path, _packaged_libname) + _snd = _ffi.dlopen(_full_path) # OSError if file doesn't exist or can't be loaded + +except (OSError, ImportError): + try: # system-wide libsndfile: + _libname = _find_library('sndfile') + if _libname is None: + raise OSError('sndfile library not found using ctypes.util.find_library') + _snd = _ffi.dlopen(_libname) + + except OSError: + # Try explicit file name, if the general does not work (e.g. on nixos) + if _sys.platform == 'darwin': + _explicit_libname = 'libsndfile.dylib' + elif _sys.platform == 'win32': + _explicit_libname = 'libsndfile.dll' + elif _sys.plaform == 'linux': + _explicit_libname = 'libsndfile.so' + else: + raise + # 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)) + _snd = _ffi.dlopen(_os.path.join(_hbrew_path, _explicit_libname)) else: - # Try explicit file name, if the general does not work (e.g. on nixos) - _snd = _ffi.dlopen(_libname) + _snd = _ffi.dlopen(_explicit_libname) __libsndfile_version__ = _ffi.string(_snd.sf_version_string()).decode('utf-8', 'replace') if __libsndfile_version__.startswith('libsndfile-'): From e2b403e685c4979c96ee2a736c47b715cdd797e0 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Thu, 9 Feb 2023 09:46:24 +0100 Subject: [PATCH 13/15] fix bug in setup.py on macOS where the machine was incorrectly identified. --- setup.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 20d28f2..ef99bcc 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,20 @@ #!/usr/bin/env python import os -from platform import architecture +from platform import architecture, machine from setuptools import setup from setuptools.command.test import test as TestCommand import sys # environment variables for cross-platform package creation platform = os.environ.get('PYSOUNDFILE_PLATFORM', sys.platform) -architecture0 = os.environ.get('PYSOUNDFILE_ARCHITECTURE', architecture()[0]) +architecture0 = os.environ.get('PYSOUNDFILE_ARCHITECTURE') +if architecture0 is None: + # follow the same decision tree as in soundfile.py after + # _find_library('sndfile') fails: + if sys.platform == 'win32': + architecture0 = architecture()[0] # 64bit or 32bit + else: + architecture0 = machine() # x86_64 or arm64 if platform == 'darwin': libname = 'libsndfile_' + architecture0 + '.dylib' From aa5ea86fee525cdf5ae287106551c675513a9015 Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Fri, 10 Feb 2023 08:09:16 +0100 Subject: [PATCH 14/15] improves import error handling no longer dies with a TypeError if _soundfile_data is empty, but falls back to the system library as it should. --- soundfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/soundfile.py b/soundfile.py index 1e0e44b..9c083f0 100644 --- a/soundfile.py +++ b/soundfile.py @@ -159,11 +159,11 @@ raise OSError('no packaged library for this platform') import _soundfile_data # ImportError if this doesn't exist - _path = _os.path.dirname(_soundfile_data.__file__) + _path = _os.path.dirname(_soundfile_data.__file__) # TypeError if __file__ is None _full_path = _os.path.join(_path, _packaged_libname) _snd = _ffi.dlopen(_full_path) # OSError if file doesn't exist or can't be loaded -except (OSError, ImportError): +except (OSError, ImportError, TypeError): try: # system-wide libsndfile: _libname = _find_library('sndfile') if _libname is None: From 07ae8fd5de3b4fb763efd073e3bf9a05f91ca0bb Mon Sep 17 00:00:00 2001 From: Bastian Bechtold Date: Fri, 10 Feb 2023 08:10:05 +0100 Subject: [PATCH 15/15] adds instructions on compiling the wheels to README --- README.rst | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 36eec8f..295ab39 100644 --- a/README.rst +++ b/README.rst @@ -62,7 +62,7 @@ Installation ------------ The ``soundfile`` module depends on the Python packages CFFI and NumPy, and the -system library libsndfile. +library libsndfile. In a modern Python, you can use ``pip install soundfile`` to download and install the latest release of the ``soundfile`` module and its @@ -79,6 +79,24 @@ Binaries for Python Extension Packages