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):

Reply via email to