Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-SoundFile for
openSUSE:Factory checked in at 2026-08-22 21:36:10
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-SoundFile (Old)
and /work/SRC/openSUSE:Factory/.python-SoundFile.new.1258 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-SoundFile"
Sat Aug 22 21:36:10 2026 rev:11 rq:1373057 version:0.13.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-SoundFile/python-SoundFile.changes
2024-10-27 11:25:01.977802457 +0100
+++
/work/SRC/openSUSE:Factory/.python-SoundFile.new.1258/python-SoundFile.changes
2026-08-22 21:38:17.832841060 +0200
@@ -1,0 +2,18 @@
+Fri Aug 21 18:37:21 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 0.13.1:
+ * Fixed a regression in blocks() when fill_value is set and the
+ block size is longer than the file
+- 0.13.0:
+ * numpy is now a hard dependency (was Recommends)
+ * Add compression_level and bitrate_mode for compressed formats
+ * Fix blocks() on files shorter than the block size
+- Rebase 0001-Fix-libsndfile-versioning.patch
+- Switch to %autosetup
+- Require libsndfile1 directly instead of a parse-time rpm query
+ of libsndfile-devel
+- Use pkgconfig(sndfile) instead of libsndfile-devel
+- Skip test_write_mp3_compression: openSUSE libsndfile is built
+ without MPEG
+
+-------------------------------------------------------------------
Old:
----
soundfile-0.12.1.tar.gz
New:
----
soundfile-0.13.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-SoundFile.spec ++++++
--- /var/tmp/diff_new_pack.DhuByp/_old 2026-08-22 21:38:19.213890526 +0200
+++ /var/tmp/diff_new_pack.DhuByp/_new 2026-08-22 21:38:19.216890634 +0200
@@ -1,12 +1,12 @@
#
# spec file for package python-SoundFile
#
-# Copyright (c) 2023 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
-# file, is the same license as for the pristine package itself (unless the
+# file, is the same license as the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
@@ -18,13 +18,14 @@
%{?sle15_python_module_pythons}
Name: python-SoundFile
-Version: 0.12.1
+Version: 0.13.1
Release: 0
Summary: An audio library based on libsndfile, CFFI and NumPy
License: BSD-3-Clause
URL: https://github.com/bastibe/python-soundfile
Source:
https://files.pythonhosted.org/packages/source/s/soundfile/soundfile-%{version}.tar.gz
Source99: python-SoundFile.rpmlintrc
+# PATCH-FIX-OPENSUSE 0001-Fix-libsndfile-versioning.patch [email protected] --
strip optional -exp suffix from libsndfile version string
Patch0: 0001-Fix-libsndfile-versioning.patch
BuildRequires: %{python_module cffi >= 1.0}
BuildRequires: %{python_module numpy}
@@ -33,20 +34,22 @@
BuildRequires: %{python_module setuptools}
BuildRequires: %{python_module wheel}
BuildRequires: fdupes
-BuildRequires: libsndfile-devel
+BuildRequires: pkgconfig
BuildRequires: python-rpm-macros
-BuildArch: noarch
+BuildRequires: pkgconfig(sndfile)
+# dlopen()s libsndfile via cffi ABI mode (not linked)
+Requires: libsndfile1
Requires: python-cffi >= 1.0
-Requires: %(rpm -q --requires libsndfile-devel | grep -o
'libsndfile[0-9]*')
-Recommends: python-numpy
+Requires: python-numpy
Obsoletes: python-PySoundFile < %{version}
Provides: python-PySoundFile = %{version}
Provides: python-soundfile = %{version}
+BuildArch: noarch
%python_subpackages
%description
PySoundFile is an audio library based on libsndfile, CFFI and NumPy.
-Full documentation is available on http://pysoundfile.readthedocs.org/.
+Full documentation is available on https://python-soundfile.readthedocs.io/.
PySoundFile can read and write sound files. File reading/writing is
supported through libsndfile, which itself is accessed through CFFI,
@@ -54,8 +57,7 @@
represents audio data as NumPy arrays.
%prep
-%setup -q -n soundfile-%{version}
-%autopatch -p1
+%autosetup -p1 -n soundfile-%{version}
%build
# force a pure wheel through unknown platform
@@ -68,7 +70,10 @@
%python_expand %fdupes %{buildroot}%{$python_sitelib}
%check
-%pytest
+# openSUSE libsndfile is built without MPEG/MP3 (no mpg123/lame) on all
+# current products. Skip the MP3 compression test rather than probing
+# available_formats() at runtime, which would hide a later MPEG enablement.
+%pytest -k 'not test_write_mp3_compression'
%files %{python_files}
%doc README.rst
++++++ 0001-Fix-libsndfile-versioning.patch ++++++
--- /var/tmp/diff_new_pack.DhuByp/_old 2026-08-22 21:38:19.256892066 +0200
+++ /var/tmp/diff_new_pack.DhuByp/_new 2026-08-22 21:38:19.263892317 +0200
@@ -9,19 +9,17 @@
soundfile.py | 2 ++
1 file changed, 2 insertions(+)
-diff --git a/soundfile.py b/soundfile.py
-index cc13192..9012d78 100644
--- a/soundfile.py
+++ b/soundfile.py
-@@ -10,6 +10,7 @@ For further information, see
https://python-soundfile.readthedocs.io/.
+@@ -10,6 +10,7 @@
"""
- __version__ = "0.12.1"
+ __version__ = "0.13.1"
+import re as _re
import os as _os
import sys as _sys
from os import SEEK_SET, SEEK_CUR, SEEK_END
-@@ -194,6 +195,7 @@ except (OSError, ImportError, TypeError):
+@@ -214,6 +215,7 @@
__libsndfile_version__ =
_ffi.string(_snd.sf_version_string()).decode('utf-8', 'replace')
if __libsndfile_version__.startswith('libsndfile-'):
__libsndfile_version__ = __libsndfile_version__[len('libsndfile-'):]
@@ -32,4 +30,3 @@
--
2.45.2
-
++++++ soundfile-0.12.1.tar.gz -> soundfile-0.13.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/soundfile-0.12.1/PKG-INFO
new/soundfile-0.13.1/PKG-INFO
--- old/soundfile-0.12.1/PKG-INFO 2023-02-15 16:27:22.906724200 +0100
+++ new/soundfile-0.13.1/PKG-INFO 2025-01-25 10:09:37.205288400 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: soundfile
-Version: 0.12.1
+Version: 0.13.1
Summary: An audio library based on libsndfile, CFFI and NumPy
Home-page: https://github.com/bastibe/python-soundfile
Author: Bastian Bechtold
@@ -21,8 +21,10 @@
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Multimedia :: Sound/Audio
Description-Content-Type: text/x-rst
-Provides-Extra: numpy
License-File: LICENSE
+Requires-Dist: cffi>=1.0
+Requires-Dist: numpy
+Provides-Extra: numpy
python-soundfile
================
@@ -100,8 +102,7 @@
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,
-for example using the Anaconda_ package manager or the `Unofficial Windows
-Binaries for Python Extension Packages
<http://www.lfd.uci.edu/~gohlke/pythonlibs/>`_.
+for example using the Anaconda_ package manager.
.. _Anaconda: https://www.continuum.io/downloads
@@ -246,6 +247,48 @@
from urllib2 import urlopen
+In-memory files
+^^^^^^^^^^^^^^^
+
+Chunks of audio, i.e. `bytes`, can also be read and written without touching
the filesystem.
+In the following example OGG is converted to WAV entirely in memory (without
writing files to the disk):
+
+.. code:: python
+
+ import io
+ import soundfile as sf
+
+ def ogg2wav(ogg: bytes):
+ ogg_buf = io.BytesIO(ogg)
+ ogg_buf.name = 'file.ogg'
+ data, samplerate = sf.read(ogg_buf)
+ wav_buf = io.BytesIO()
+ wav_buf.name = 'file.wav'
+ sf.write(wav_buf, data, samplerate)
+ wav_buf.seek(0) # Necessary for `.read()` to return all bytes
+ return wav_buf.read()
+
+Controlling bitrate mode and compression level
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+For some audio formats, you can control the bitrate and compression level.
+
+`compression_level` is a float between 0 and 1, with 1 being the highest
compression,
+and `bitrate_mode` is 'VARIABLE', 'CONSTANT', or 'AVERAGE'.
+
+.. code:: python
+
+ import soundfile as sf
+
+ # for example, this uncompressed 5 minute wav file with 32 kHz sample rate
is 18 Mb
+ data, samplerate = sf.read('5min_32kHz.wav')
+
+ # maximum mp3 compression results in 1.1 Mb file, with either CONSTANT or
VARIABLE bit rate
+ sf.write('max_compression_vbr.mp3', data, samplerate,
bitrate_mode='VARIABLE', compression_level=.99)
+ sf.write('max_compression_cbr.mp3', data, samplerate,
bitrate_mode='CONSTANT', compression_level=.99)
+
+ # minimum mp3 compression results in 3.5 Mb file
+ sf.write('min_compression_vbr.mp3', data, samplerate,
bitrate_mode='VARIABLE', compression_level=0)
+
Known Issues
------------
@@ -377,4 +420,19 @@
- Fixed typo on library location detection if no packaged lib and
no system lib was found
+2025-01-02 V0.13.0 Bastian Bechtold
+ Thank you, Zhong Jianxin, mcclure, jneuendorf-i4h, aoirint, endolith, Guy
Illes, ytya, Sam Lapp, Benjamin Moody
+
+ - Updated libsndfile to v1.2.2
+ - Linux arm64 builds added
+ - Numpy is now a dependency
+ - Fixed error in blocks, if file is very short
+ - Compression level and bitrate controls added for compressed files
+ - Various README improvements
+ - Various build system improvements
+ - Various improvements to error messages
+
+2025-01-25 V0.13.1 Bastian Bechtold
+ Thank you, Brian McFee and Guy Illes
+ - Fixed regression in blocks
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/soundfile-0.12.1/README.rst
new/soundfile-0.13.1/README.rst
--- old/soundfile-0.12.1/README.rst 2023-02-15 16:23:13.000000000 +0100
+++ new/soundfile-0.13.1/README.rst 2025-01-25 10:04:43.000000000 +0100
@@ -74,8 +74,7 @@
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,
-for example using the Anaconda_ package manager or the `Unofficial Windows
-Binaries for Python Extension Packages
<http://www.lfd.uci.edu/~gohlke/pythonlibs/>`_.
+for example using the Anaconda_ package manager.
.. _Anaconda: https://www.continuum.io/downloads
@@ -220,6 +219,48 @@
from urllib2 import urlopen
+In-memory files
+^^^^^^^^^^^^^^^
+
+Chunks of audio, i.e. `bytes`, can also be read and written without touching
the filesystem.
+In the following example OGG is converted to WAV entirely in memory (without
writing files to the disk):
+
+.. code:: python
+
+ import io
+ import soundfile as sf
+
+ def ogg2wav(ogg: bytes):
+ ogg_buf = io.BytesIO(ogg)
+ ogg_buf.name = 'file.ogg'
+ data, samplerate = sf.read(ogg_buf)
+ wav_buf = io.BytesIO()
+ wav_buf.name = 'file.wav'
+ sf.write(wav_buf, data, samplerate)
+ wav_buf.seek(0) # Necessary for `.read()` to return all bytes
+ return wav_buf.read()
+
+Controlling bitrate mode and compression level
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+For some audio formats, you can control the bitrate and compression level.
+
+`compression_level` is a float between 0 and 1, with 1 being the highest
compression,
+and `bitrate_mode` is 'VARIABLE', 'CONSTANT', or 'AVERAGE'.
+
+.. code:: python
+
+ import soundfile as sf
+
+ # for example, this uncompressed 5 minute wav file with 32 kHz sample rate
is 18 Mb
+ data, samplerate = sf.read('5min_32kHz.wav')
+
+ # maximum mp3 compression results in 1.1 Mb file, with either CONSTANT or
VARIABLE bit rate
+ sf.write('max_compression_vbr.mp3', data, samplerate,
bitrate_mode='VARIABLE', compression_level=.99)
+ sf.write('max_compression_cbr.mp3', data, samplerate,
bitrate_mode='CONSTANT', compression_level=.99)
+
+ # minimum mp3 compression results in 3.5 Mb file
+ sf.write('min_compression_vbr.mp3', data, samplerate,
bitrate_mode='VARIABLE', compression_level=0)
+
Known Issues
------------
@@ -350,3 +391,20 @@
- Fixed typo on library location detection if no packaged lib and
no system lib was found
+
+2025-01-02 V0.13.0 Bastian Bechtold
+ Thank you, Zhong Jianxin, mcclure, jneuendorf-i4h, aoirint, endolith, Guy
Illes, ytya, Sam Lapp, Benjamin Moody
+
+ - Updated libsndfile to v1.2.2
+ - Linux arm64 builds added
+ - Numpy is now a dependency
+ - Fixed error in blocks, if file is very short
+ - Compression level and bitrate controls added for compressed files
+ - Various README improvements
+ - Various build system improvements
+ - Various improvements to error messages
+
+2025-01-25 V0.13.1 Bastian Bechtold
+ Thank you, Brian McFee and Guy Illes
+
+ - Fixed regression in blocks
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/soundfile-0.12.1/setup.py
new/soundfile-0.13.1/setup.py
--- old/soundfile-0.12.1/setup.py 2023-02-09 10:45:21.000000000 +0100
+++ new/soundfile-0.13.1/setup.py 2025-01-25 10:02:16.000000000 +0100
@@ -2,7 +2,6 @@
import os
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
@@ -34,27 +33,7 @@
package_data = None
zip_safe = True
-
-class PyTest(TestCommand):
-
- user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]
-
- def initialize_options(self):
- TestCommand.initialize_options(self)
- self.pytest_args = []
-
- def finalize_options(self):
- TestCommand.finalize_options(self)
- self.test_args = []
- self.test_suite = True
-
- def run_tests(self):
- # import here, cause outside the eggs aren't loaded
- import pytest
- errno = pytest.main(self.pytest_args)
- sys.exit(errno)
-
-cmdclass = {'test': PyTest}
+cmdclass = {}
try:
from wheel.bdist_wheel import bdist_wheel
@@ -73,14 +52,20 @@
else:
oses = 'macosx_11_0_arm64'
elif platform == 'win32':
- if architecture0 == '32bit':
+ if architecture0.lower() == 'arm64' or machine() == 'ARM64':
+ oses = 'win_arm64'
+ elif architecture0 == 'x86' or architecture0 == '32bit':
oses = 'win32'
- else:
+ elif architecture0 == 'x64' or architecture0 == '64bit':
oses = 'win_amd64'
elif platform == 'linux':
- # the oldest mainline github runner available is ubuntu 20.04,
- # which runs glibc 2.31:
- oses = 'manylinux_2_31_x86_64'
+ # using the centos:7 runner with glibc2.17:
+ if architecture0 == 'arm64':
+ pep600_architecture = 'aarch64'
+ else:
+ pep600_architecture = architecture0
+
+ oses = 'manylinux_2_28_{}'.format(pep600_architecture)
else:
pythons = 'py2.py3'
oses = 'any'
@@ -111,9 +96,9 @@
zip_safe=zip_safe,
license='BSD 3-Clause License',
setup_requires=["cffi>=1.0"],
- install_requires=['cffi>=1.0'],
+ install_requires=['cffi>=1.0', 'numpy'],
cffi_modules=["soundfile_build.py:ffibuilder"],
- extras_require={'numpy': ['numpy']},
+ extras_require={'numpy': []}, # This option is no longer relevant, but the
empty entry must be left in to avoid breaking old build scripts.
platforms='any',
classifiers=[
'Development Status :: 5 - Production/Stable',
@@ -131,6 +116,5 @@
],
long_description=open('README.rst').read(),
long_description_content_type="text/x-rst",
- tests_require=['pytest'],
cmdclass=cmdclass,
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/soundfile-0.12.1/soundfile.egg-info/PKG-INFO
new/soundfile-0.13.1/soundfile.egg-info/PKG-INFO
--- old/soundfile-0.12.1/soundfile.egg-info/PKG-INFO 2023-02-15
16:27:22.000000000 +0100
+++ new/soundfile-0.13.1/soundfile.egg-info/PKG-INFO 2025-01-25
10:09:37.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: soundfile
-Version: 0.12.1
+Version: 0.13.1
Summary: An audio library based on libsndfile, CFFI and NumPy
Home-page: https://github.com/bastibe/python-soundfile
Author: Bastian Bechtold
@@ -21,8 +21,10 @@
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Multimedia :: Sound/Audio
Description-Content-Type: text/x-rst
-Provides-Extra: numpy
License-File: LICENSE
+Requires-Dist: cffi>=1.0
+Requires-Dist: numpy
+Provides-Extra: numpy
python-soundfile
================
@@ -100,8 +102,7 @@
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,
-for example using the Anaconda_ package manager or the `Unofficial Windows
-Binaries for Python Extension Packages
<http://www.lfd.uci.edu/~gohlke/pythonlibs/>`_.
+for example using the Anaconda_ package manager.
.. _Anaconda: https://www.continuum.io/downloads
@@ -246,6 +247,48 @@
from urllib2 import urlopen
+In-memory files
+^^^^^^^^^^^^^^^
+
+Chunks of audio, i.e. `bytes`, can also be read and written without touching
the filesystem.
+In the following example OGG is converted to WAV entirely in memory (without
writing files to the disk):
+
+.. code:: python
+
+ import io
+ import soundfile as sf
+
+ def ogg2wav(ogg: bytes):
+ ogg_buf = io.BytesIO(ogg)
+ ogg_buf.name = 'file.ogg'
+ data, samplerate = sf.read(ogg_buf)
+ wav_buf = io.BytesIO()
+ wav_buf.name = 'file.wav'
+ sf.write(wav_buf, data, samplerate)
+ wav_buf.seek(0) # Necessary for `.read()` to return all bytes
+ return wav_buf.read()
+
+Controlling bitrate mode and compression level
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+For some audio formats, you can control the bitrate and compression level.
+
+`compression_level` is a float between 0 and 1, with 1 being the highest
compression,
+and `bitrate_mode` is 'VARIABLE', 'CONSTANT', or 'AVERAGE'.
+
+.. code:: python
+
+ import soundfile as sf
+
+ # for example, this uncompressed 5 minute wav file with 32 kHz sample rate
is 18 Mb
+ data, samplerate = sf.read('5min_32kHz.wav')
+
+ # maximum mp3 compression results in 1.1 Mb file, with either CONSTANT or
VARIABLE bit rate
+ sf.write('max_compression_vbr.mp3', data, samplerate,
bitrate_mode='VARIABLE', compression_level=.99)
+ sf.write('max_compression_cbr.mp3', data, samplerate,
bitrate_mode='CONSTANT', compression_level=.99)
+
+ # minimum mp3 compression results in 3.5 Mb file
+ sf.write('min_compression_vbr.mp3', data, samplerate,
bitrate_mode='VARIABLE', compression_level=0)
+
Known Issues
------------
@@ -377,4 +420,19 @@
- Fixed typo on library location detection if no packaged lib and
no system lib was found
+2025-01-02 V0.13.0 Bastian Bechtold
+ Thank you, Zhong Jianxin, mcclure, jneuendorf-i4h, aoirint, endolith, Guy
Illes, ytya, Sam Lapp, Benjamin Moody
+
+ - Updated libsndfile to v1.2.2
+ - Linux arm64 builds added
+ - Numpy is now a dependency
+ - Fixed error in blocks, if file is very short
+ - Compression level and bitrate controls added for compressed files
+ - Various README improvements
+ - Various build system improvements
+ - Various improvements to error messages
+
+2025-01-25 V0.13.1 Bastian Bechtold
+ Thank you, Brian McFee and Guy Illes
+ - Fixed regression in blocks
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/soundfile-0.12.1/soundfile.egg-info/requires.txt
new/soundfile-0.13.1/soundfile.egg-info/requires.txt
--- old/soundfile-0.12.1/soundfile.egg-info/requires.txt 2023-02-15
16:27:22.000000000 +0100
+++ new/soundfile-0.13.1/soundfile.egg-info/requires.txt 2025-01-25
10:09:37.000000000 +0100
@@ -1,4 +1,4 @@
cffi>=1.0
+numpy
[numpy]
-numpy
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/soundfile-0.12.1/soundfile.py
new/soundfile-0.13.1/soundfile.py
--- old/soundfile-0.12.1/soundfile.py 2023-02-15 16:23:13.000000000 +0100
+++ new/soundfile-0.13.1/soundfile.py 2025-01-25 10:04:53.000000000 +0100
@@ -8,7 +8,7 @@
For further information, see https://python-soundfile.readthedocs.io/.
"""
-__version__ = "0.12.1"
+__version__ = "0.13.1"
import os as _os
import sys as _sys
@@ -145,16 +145,36 @@
'int16': 'short'
}
+_bitrate_modes = {
+ 'CONSTANT': 0,
+ 'AVERAGE': 1,
+ 'VARIABLE': 2,
+}
+
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'
elif _sys.platform == 'win32':
from platform import architecture as _architecture
- _packaged_libname = 'libsndfile_' + _architecture()[0] + '.dll'
+ from platform import machine as _machine
+ # this check can not be completed correctly: for x64 binaries running
on
+ # arm64 Windows report the same values as arm64 binaries. For now,
neither
+ # numpy nor cffi are available for arm64, so we can safely assume we're
+ # in x86 land:
+ if _architecture()[0] == '64bit':
+ _packaged_libname = 'libsndfile_x64.dll'
+ elif _architecture()[0] == '32bit':
+ _packaged_libname = 'libsndfile_x86.dll'
+ else:
+ raise OSError('no packaged library for Windows {} {}'
+ .format(_architecture(), _machine()))
elif _sys.platform == 'linux':
from platform import machine as _machine
- _packaged_libname = 'libsndfile_' + _machine() + '.so'
+ if _machine() in ["aarch64", "aarch64_be", "armv8b", "armv8l"]:
+ _packaged_libname = 'libsndfile_arm64.so'
+ else:
+ _packaged_libname = 'libsndfile_' + _machine() + '.so'
else:
raise OSError('no packaged library for this platform')
@@ -290,7 +310,7 @@
def write(file, data, samplerate, subtype=None, endian=None, format=None,
- closefd=True):
+ closefd=True, compression_level=None, bitrate_mode=None):
"""Write data to a sound file.
.. note:: If *file* exists, it will be truncated and overwritten!
@@ -322,7 +342,7 @@
Other Parameters
----------------
- format, endian, closefd
+ format, endian, closefd, compression_level, bitrate_mode
See `SoundFile`.
Examples
@@ -341,7 +361,8 @@
else:
channels = data.shape[1]
with SoundFile(file, 'w', samplerate, channels,
- subtype, endian, format, closefd) as f:
+ subtype, endian, format, closefd,
+ compression_level, bitrate_mode) as f:
f.write(data)
@@ -554,7 +575,8 @@
"""
def __init__(self, file, mode='r', samplerate=None, channels=None,
- subtype=None, endian=None, format=None, closefd=True):
+ subtype=None, endian=None, format=None, closefd=True,
+ compression_level=None, bitrate_mode=None):
"""Open a sound file.
If a file is opened with `mode` ``'r'`` (the default) or
@@ -623,6 +645,14 @@
closefd : bool, optional
Whether to close the file descriptor on `close()`. Only
applicable if the *file* argument is a file descriptor.
+ compression_level : float, optional
+ The compression level on 'write()'. The compression level
+ should be between 0.0 (minimum compression level) and 1.0
+ (highest compression level).
+ See `libsndfile document
<https://github.com/libsndfile/libsndfile/blob/c81375f070f3c6764969a738eacded64f53a076e/docs/command.md>`__.
+ bitrate_mode : {'CONSTANT', 'AVERAGE', 'VARIABLE'}, optional
+ The bitrate mode on 'write()'.
+ See `libsndfile document
<https://github.com/libsndfile/libsndfile/blob/c81375f070f3c6764969a738eacded64f53a076e/docs/command.md>`__.
Examples
--------
@@ -653,6 +683,8 @@
mode = getattr(file, 'mode', None)
mode_int = _check_mode(mode)
self._mode = mode
+ self._compression_level = compression_level
+ self._bitrate_mode = bitrate_mode
self._info = _create_info_struct(file, mode, samplerate, channels,
format, subtype, endian)
self._file = self._open(file, mode_int, closefd)
@@ -661,6 +693,13 @@
self.seek(0)
_snd.sf_command(self._file, _snd.SFC_SET_CLIPPING, _ffi.NULL,
_snd.SF_TRUE)
+
+ # set compression setting
+ if self._compression_level is not None:
+ # needs to be called before set_bitrate_mode
+ self._set_compression_level(self._compression_level)
+ if self._bitrate_mode is not None:
+ self._set_bitrate_mode(self._bitrate_mode)
name = property(lambda self: self._name)
"""The file name of the sound file."""
@@ -695,6 +734,10 @@
"""Whether the sound file is closed or not."""
_errorcode = property(lambda self: _snd.sf_error(self._file))
"""A pending sndfile error code."""
+ compression_level = property(lambda self: self._compression_level)
+ """The compression level on 'write()'"""
+ bitrate_mode = property(lambda self: self._bitrate_mode)
+ """The bitrate mode on 'write()'"""
@property
def extra_info(self):
@@ -708,10 +751,14 @@
_file = None
def __repr__(self):
+ compression_setting = (",
compression_level={0}".format(self.compression_level)
+ if self.compression_level is not None else "")
+ compression_setting += (",
bitrate_mode='{0}'".format(self.bitrate_mode)
+ if self.bitrate_mode is not None else "")
return ("SoundFile({0.name!r}, mode={0.mode!r}, "
"samplerate={0.samplerate}, channels={0.channels}, "
"format={0.format!r}, subtype={0.subtype!r}, "
- "endian={0.endian!r})".format(self))
+ "endian={0.endian!r}{1})".format(self, compression_setting))
def __del__(self):
self.close()
@@ -1015,6 +1062,7 @@
"""
import numpy as np
+
# no copy is made if data has already the correct memory layout:
data = np.ascontiguousarray(data)
written = self._array_io('write', data, len(data))
@@ -1103,10 +1151,12 @@
if 'r' not in self.mode and '+' not in self.mode:
raise SoundFileRuntimeError("blocks() is not allowed in write-only
mode")
+ frames = self._check_frames(frames, fill_value)
if out is None:
if blocksize is None:
raise TypeError("One of {blocksize, out} must be specified")
- out = self._create_empty_array(blocksize, always_2d, dtype)
+ out_size = blocksize if fill_value is not None else min(blocksize,
frames)
+ out = self._create_empty_array(out_size, always_2d, dtype)
copy_out = True
else:
if blocksize is not None:
@@ -1116,7 +1166,6 @@
copy_out = False
overlap_memory = None
- frames = self._check_frames(frames, fill_value)
while frames > 0:
if overlap_memory is None:
output_offset = 0
@@ -1332,10 +1381,11 @@
def _array_io(self, action, array, frames):
"""Check array and call low-level IO function."""
- if (array.ndim not in (1, 2) or
- array.ndim == 1 and self.channels != 1 or
- array.ndim == 2 and array.shape[1] != self.channels):
- raise ValueError("Invalid shape: {0!r}".format(array.shape))
+ if array.ndim not in (1,2):
+ raise ValueError("Invalid shape: {0!r} ({1})".format(array.shape,
"0 dimensions not supported" if array.ndim < 1 else "too many dimensions"))
+ array_channels = 1 if array.ndim == 1 else array.shape[1]
+ if array_channels != self.channels:
+ raise ValueError("Invalid shape: {0!r} (Expected {1} channels, got
{2})".format(array.shape, self.channels, array_channels))
if not array.flags.c_contiguous:
raise ValueError("Data must be C-contiguous")
ctype = self._check_dtype(array.dtype.name)
@@ -1398,7 +1448,30 @@
if data:
strs[strtype] = _ffi.string(data).decode('utf-8', 'replace')
return strs
+
+ def _set_bitrate_mode(self, bitrate_mode):
+ """Call libsndfile's set bitrate mode function."""
+ assert bitrate_mode in _bitrate_modes
+
+ pointer_bitrate_mode = _ffi.new("int[1]")
+ pointer_bitrate_mode[0] = _bitrate_modes[bitrate_mode]
+ err = _snd.sf_command(self._file, _snd.SFC_SET_BITRATE_MODE,
pointer_bitrate_mode, _ffi.sizeof(pointer_bitrate_mode))
+ if err != _snd.SF_TRUE:
+ err = _snd.sf_error(self._file)
+ raise LibsndfileError(err, f"Error set bitrate mode
{bitrate_mode}")
+
+ def _set_compression_level(self, compression_level):
+ """Call libsndfile's set compression level function."""
+ if not (0 <= compression_level <= 1):
+ raise ValueError("Compression level must be in range [0..1]")
+
+ pointer_compression_level = _ffi.new("double[1]")
+ pointer_compression_level[0] = compression_level
+ err = _snd.sf_command(self._file, _snd.SFC_SET_COMPRESSION_LEVEL,
pointer_compression_level, _ffi.sizeof(pointer_compression_level))
+ if err != _snd.SF_TRUE:
+ err = _snd.sf_error(self._file)
+ raise LibsndfileError(err, f"Error set compression level
{compression_level}")
def _error_check(err, prefix=""):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/soundfile-0.12.1/soundfile_build.py
new/soundfile-0.13.1/soundfile_build.py
--- old/soundfile-0.12.1/soundfile_build.py 2022-08-18 08:08:25.000000000
+0200
+++ new/soundfile-0.13.1/soundfile_build.py 2024-12-30 16:45:16.000000000
+0100
@@ -27,6 +27,9 @@
SFC_SET_SCALE_FLOAT_INT_READ = 0x1014,
SFC_SET_SCALE_INT_FLOAT_WRITE = 0x1015,
+
+ SFC_SET_COMPRESSION_LEVEL = 0x1301,
+ SFC_SET_BITRATE_MODE = 0x1305,
} ;
enum
@@ -38,6 +41,11 @@
SFM_READ = 0x10,
SFM_WRITE = 0x20,
SFM_RDWR = 0x30,
+
+ /* Modes for bitrate. */
+ SF_BITRATE_MODE_CONSTANT = 0,
+ SF_BITRATE_MODE_AVERAGE = 1,
+ SF_BITRATE_MODE_VARIABLE = 2,
} ;
typedef int64_t sf_count_t ;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/soundfile-0.12.1/tests/test_argspec.py
new/soundfile-0.13.1/tests/test_argspec.py
--- old/soundfile-0.12.1/tests/test_argspec.py 2022-08-18 08:08:25.000000000
+0200
+++ new/soundfile-0.13.1/tests/test_argspec.py 2024-12-30 16:45:16.000000000
+0100
@@ -30,6 +30,8 @@
init_defaults = defaults(sf.SoundFile.__init__)
del init_defaults['mode'] # mode is always 'r'
+ del init_defaults['compression_level'] # only write()
+ del init_defaults['bitrate_mode'] # only write()
del func_defaults['start']
del func_defaults['stop']
@@ -59,6 +61,9 @@
meth_defaults = defaults(sf.SoundFile.blocks)
init_defaults = defaults(sf.SoundFile.__init__)
+ del init_defaults['compression_level'] # only write()
+ del init_defaults['bitrate_mode'] # only write()
+
del func_defaults['start']
del func_defaults['stop']
del init_defaults['mode']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/soundfile-0.12.1/tests/test_soundfile.py
new/soundfile-0.13.1/tests/test_soundfile.py
--- old/soundfile-0.12.1/tests/test_soundfile.py 2023-02-06
10:28:25.000000000 +0100
+++ new/soundfile-0.13.1/tests/test_soundfile.py 2025-01-25
10:02:16.000000000 +0100
@@ -22,6 +22,9 @@
filename_mono = 'tests/mono.wav'
filename_raw = 'tests/mono.raw'
filename_new = 'tests/delme.please'
+filename_mp3 = 'tests/stereo.mp3'
+filename_flac = 'tests/stereo.flac'
+filename_opus = 'tests/stereo.opus'
if sys.version_info >= (3, 6):
@@ -295,6 +298,61 @@
assert "file extension" in str(excinfo.value)
+def test_write_mp3_compression():
+ sr = 44100
+ sf.write(filename_mp3, data_stereo, sr, format='MP3',
subtype='MPEG_LAYER_III',
+ compression_level=0, bitrate_mode='CONSTANT')
+ constant_0_size = os.path.getsize(filename_mp3)
+
+ sf.write(filename_mp3, data_stereo, sr, format='MP3',
subtype='MPEG_LAYER_III',
+ compression_level=0, bitrate_mode='VARIABLE')
+ variable_0_size = os.path.getsize(filename_mp3)
+ assert variable_0_size < constant_0_size
+
+ sf.write(filename_mp3, data_stereo, sr, format='MP3',
subtype='MPEG_LAYER_III',
+ compression_level=0, bitrate_mode='AVERAGE')
+ average_0_size = os.path.getsize(filename_mp3)
+ assert (average_0_size < variable_0_size < constant_0_size)
+
+ sf.write(filename_mp3, data_stereo, sr, format='MP3',
subtype='MPEG_LAYER_III',
+ compression_level=0.999, bitrate_mode='CONSTANT')
+ constant_1_size= os.path.getsize(filename_mp3)
+ assert constant_1_size < constant_0_size
+
+ sf.write(filename_mp3, data_stereo, sr, format='MP3',
subtype='MPEG_LAYER_III',
+ compression_level=0.999, bitrate_mode='VARIABLE')
+ variable_1_size = os.path.getsize(filename_mp3)
+ assert constant_1_size <variable_1_size < constant_0_size
+
+ sf.write(filename_mp3, data_stereo, sr, format='MP3',
subtype='MPEG_LAYER_III',
+ compression_level=0.999, bitrate_mode='AVERAGE')
+ average_1_size = os.path.getsize(filename_mp3)
+ assert constant_1_size < average_1_size < constant_0_size
+
+ # This test case should be OK, but an exception is raised at
libsndfile<=1.2.2.
+ with pytest.raises(RuntimeError) as excinfo:
+ sf.write(filename_mp3, data_stereo, sr, format='MP3',
subtype='MPEG_LAYER_III',
+ compression_level=1, bitrate_mode='VARIABLE')
+ assert "compression" in str(excinfo.value)
+
+ # just run one more time so we're left with a valid MP3 in the directory
+ sf.write(filename_mp3, data_stereo, sr, format='MP3',
subtype='MPEG_LAYER_III')
+
+
+def test_write_flac_compression():
+ sr = 44100
+ # Compression requires a certain size
+ data_stereo = np.random.random((sr, 1))
+ data_stereo = np.concatenate([data_stereo, -data_stereo], axis=1)
+
+ sf.write(filename_flac, data_stereo, sr, format='FLAC', subtype='PCM_16',
compression_level=0)
+ low_compression_size = os.path.getsize(filename_flac)
+
+ sf.write(filename_flac, data_stereo, sr, format='FLAC', subtype='PCM_16',
compression_level=1)
+ high_compression_size = os.path.getsize(filename_flac)
+ assert high_compression_size < low_compression_size
+
+
# -----------------------------------------------------------------------------
# Test blocks() function
# -----------------------------------------------------------------------------
@@ -323,7 +381,7 @@
def test_blocks_fill_last_block(file_stereo_r):
blocks = list(sf.blocks(file_stereo_r, blocksize=3, fill_value=0))
- last_block = np.row_stack((data_stereo[3:4], np.zeros((2, 2))))
+ last_block = np.vstack((data_stereo[3:4], np.zeros((2, 2))))
assert_equal_list_of_arrays(blocks, [data_stereo[0:3], last_block])
@@ -373,7 +431,7 @@
def test_blocks_with_frames_and_fill_value(file_stereo_r):
blocks = list(
sf.blocks(file_stereo_r, blocksize=2, frames=3, fill_value=0))
- last_block = np.row_stack((data_stereo[2:3], np.zeros((1, 2))))
+ last_block = np.vstack((data_stereo[2:3], np.zeros((1, 2))))
assert_equal_list_of_arrays(blocks, [data_stereo[0:2], last_block])
@@ -403,11 +461,40 @@
def test_blocks_mono():
+ blocks = list(sf.blocks(filename_mono, blocksize=3, dtype='int16'))
+ assert_equal_list_of_arrays(blocks, [[0, 1, 2], [-2, -1]])
+
+
+def test_blocks_with_fill_value_mono():
blocks = list(sf.blocks(filename_mono, blocksize=3, dtype='int16',
fill_value=0))
assert_equal_list_of_arrays(blocks, [[0, 1, 2], [-2, -1, 0]])
+def test_blocks_with_overlap_and_fill_value_mono():
+ blocks = list(sf.blocks(filename_mono, blocksize=4, dtype='int16',
+ overlap=2, fill_value=0))
+ assert_equal_list_of_arrays(blocks, [[0, 1, 2, -2], [2, -2, -1, 0]])
+
+
+def test_block_longer_than_file_with_overlap_mono():
+ blocks = list(sf.blocks(filename_mono, blocksize=20, dtype='int16',
+ overlap=2))
+ assert_equal_list_of_arrays(blocks, [[0, 1, 2, -2, -1]])
+
+
+def test_block_longer_than_file_with_fill_value_mono():
+ blocks = list(sf.blocks(filename_mono, blocksize=10, dtype='int16',
+ fill_value=0))
+ assert_equal_list_of_arrays(blocks, [[0, 1, 2, -2, -1, 0, 0, 0, 0, 0]])
+
+
+def test_block_longer_than_file_with_overlap_and_fill_value_mono():
+ blocks = list(sf.blocks(filename_mono, blocksize=10, dtype='int16',
+ overlap=2, fill_value=0))
+ assert_equal_list_of_arrays(blocks, [[0, 1, 2, -2, -1, 0, 0, 0, 0, 0]])
+
+
def test_blocks_rplus(sf_stereo_rplus):
blocks = list(sf_stereo_rplus.blocks(blocksize=2))
assert_equal_list_of_arrays(blocks, [data_stereo[0:2], data_stereo[2:4]])
@@ -615,6 +702,14 @@
"samplerate=44100, channels=2, "
"format='WAV', subtype='FLOAT', "
"endian='FILE')").format(sf_stereo_r)
+
+ sf_stereo_r._compression_level = 0
+ sf_stereo_r._bitrate_mode = "CONSTANT"
+ assert repr(sf_stereo_r) == ("SoundFile({0.name!r}, mode='r', "
+ "samplerate=44100, channels=2, "
+ "format='WAV', subtype='FLOAT', "
+ "endian='FILE', compression_level=0, "
+
"bitrate_mode='CONSTANT')").format(sf_stereo_r)
def test_extra_info(sf_stereo_r):