speequal
Speech/audio quality metrics as prebuilt Python wheels — pip install speequal. One metric today:
speequal.visqol — ViSQOL v3, Google's objective, full-reference perceptual audio/speech quality metric (MOS-LQO), plus a small numpy API. This is a packaging of Google's code, not a reimplementation: the C++ library, the pybind11 bindings, the SVR and TFLite lattice models and all default mappings are the ones in google/visqol at the commit recorded in UPSTREAM.md, built with Bazel and redistributed under the same Apache-2.0 license (see LICENSE, NOTICE). Upstream is not on PyPI (the name is prohibited there) and needs Bazel plus a TensorFlow Lite source build to install; this wheel needs neither.
pip install speequal
Wheels: Linux x86_64, CPython 3.12 and 3.14, tagged manylinux_2_39 (built on Ubuntu 24.04 with GCC 13; they need glibc ≥ 2.39 and a GCC-13-era libstdc++). Other platforms/interpreters fall back to the sdist, which needs Bazel (see below). Runtime dependencies: numpy, protobuf (≥ 4.21 — the bindings use message_factory.GetMessageClass; tested with 6.33 and 7.36).
API
from speequal import visqol
visqol.speech(ref, deg, fs=16000) # MOS-LQO, speech mode (wideband, 16 kHz)
visqol.audio(ref, deg, fs=48000) # MOS-LQO, audio mode (SVR model, 48 kHz)
visqol.measure(ref, deg, mode="speech", fs=16000) # upstream's raw SimilarityResultMsg
visqol.selftest() # the install gate (see below); speequal.selftest() runs every metric's
ref / deg are 1-d float arrays (numpy or torch, any float dtype, values in [-1, 1]) or paths to 16-bit PCM WAV files. speech() raises unless fs == 16000 and audio() unless fs == 48000 — nothing is resampled for you; get to the right rate first. Lengths may differ (ViSQOL aligns and handles the mismatch itself; nothing is padded or cropped here). Model files are located inside the package, never relative to the working directory. speequal.__version__ is the wheel version (<upstream visqol version>.postN), speequal.visqol.UPSTREAM_COMMIT the vendored git sha.
Options, all mirroring VisqolConfig fields: speech(..., lattice=True) maps NSIM→MOS with the TFLite deep-lattice model (upstream's binary default; its ceiling for an identical pair is ≈ 4.51), lattice=False uses the scaled polynomial mapping (identical pair → 5.0); unscaled_mos_mapping=True (requires lattice=False, as upstream ignores it otherwise) leaves a perfect score at ≈ 4.0. measure() additionally exposes allow_unsupported_sample_rates and search_window_radius, and returns moslqo, vnsim, fvnsim, fvnsim10, fstdnsim, fvdegenergy, center_freq_bands, patch_sims, alignment_lag_s.
Upstream's own modules are untouched and still importable, one level down: from speequal.visqol import visqol_lib_py, from speequal.visqol.pb2 import visqol_config_pb2, similarity_result_pb2.
CLI
python -m speequal.visqol ref.wav deg.wav # speech mode, prints the MOS-LQO
python -m speequal.visqol ref.wav deg.wav --audio # audio mode
python -m speequal.visqol ref.wav deg.wav --verbose # + VNSIM, per-band NSIM, patch count
The sample rate is read from the files (any rate in speech mode, 48 kHz in audio mode, like upstream's binary). --no-lattice, --unscaled, --allow-unsupported-sample-rates as above.
Self-test
python -c "import speequal; speequal.selftest()"
For ViSQOL: (a) speech mode at 16 kHz: a clip vs itself hits the mapper ceiling (≈ 4.5 lattice / 5.0 polynomial) and a −20 dB-noise copy scores lower; (b) audio mode at 48 kHz: a clip vs itself ≥ 4.7; (c) conformance: the 20 reference/degraded/flag cases of upstream's tests/conformance_test.cc with the expected MOS-LQO values of src/include/conformance.h (copied into speequal/visqol/conformance.json) reproduce to 1e-3. The two clean-speech clips ship in the wheel; the other conformance WAVs (~55 MB) are downloaded once from google/visqol at the pinned commit into ~/.cache/visqol/ (VISQOL_TESTDATA=/path/to/upstream/testdata uses a local copy). visqol.selftest(conformance=False) skips the download.
Building from source
The sdist on PyPI is upstream's tree plus the wrapper; pip install from it runs upstream's setup.py, which needs Bazel 5.3.2 (bazelisk picks it up from .bazelversion), a C++17 compiler, and fetches TensorFlow 2.11 sources — so on an unsupported platform expect a long first build. From this repository:
git clone https://github.com/danjacobellis/speequal && cd speequal
pip install numpy setuptools wheel auditwheel patchelf build # in the target interpreter
./build.sh # installs bazelisk into ~/.local/bin if absent; wheel lands in dist/
./build.sh --python=/path/to/other/python # e.g. a 3.14
./install_for_debugging.sh # build + install into ~/g + selftest
build.sh copies upstream/ to build/upstream/, applies patches/, runs Bazel there, builds the wheel with upstream's setup.py, then auditwheel repairs it to the most specific manylinux policy the build host allows. Bazel's cache makes rebuilds fast; --clean expunges it.
What changed vs upstream
Nothing in the metric. Every deviation from google/visqol is a file in patches/:
0001-bazelrc-gcc13-cstdint.patch—build --cxxopt=-include --cxxopt=cstdint: TF 2.11'stensorflow/lite/kernels/internal/spectrogram.ccusesuint32_twithout including<cstdint>, which GCC 13 no longer tolerates.0002-setup-py-version-wrapper-metadata.patch—setup.py: distributionspeequal, version3.3.3.postN, Google'svisqolpackage mapped tospeequal.visqol(.model,.pb2beneath it), PyPI metadata (readme, license,python_requires,install_requires = numpy, protobuf), aDistribution.has_ext_modulesoverride so the wheel is taggedcpXY-<platform>instead ofpy3-none-any, and copying the wrapper (python_wrapper/) plus the two clean-speech WAVs into the package.0003-workspace-pybind11_protobuf-getmessageclass.patch—WORKSPACEapplies a one-hunk patch to the pinnedpybind11_protobuf(2022): protobuf ≥ 5 removedMessageFactory.GetPrototype, which made every proto conversion silently fail; usemessage_factory.GetMessageClasswhen it exists.0004-manifest-in-sdist.patch—MANIFEST.inso the sdist carries the whole Bazel tree (src/,python/,model/,WORKSPACE,BUILD,.bazelrc,.bazelversion, the patches) and never a previous build'sbazel-bin.
Added on top (not patches): the speequal package from src/speequal/ (__init__.py, visqol/{__init__,api,__main__,selftest}.py, visqol/conformance.json).
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file speequal-3.3.3.post1.tar.gz.
File metadata
- Download URL: speequal-3.3.3.post1.tar.gz
- Upload date:
- Size: 1.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fed6e592a1c4eb8f462d34ae62467ae540452d9875aa8050be5fc6969a98d795
|
|
| MD5 |
a2525cfea6d499cbe83092419829ece8
|
|
| BLAKE2b-256 |
c9463f62572fcf8fa5312fd44fee6da1544ed05d49845de2dae3d2d40a430d4e
|
File details
Details for the file speequal-3.3.3.post1-cp314-cp314-manylinux_2_39_x86_64.whl.
File metadata
- Download URL: speequal-3.3.3.post1-cp314-cp314-manylinux_2_39_x86_64.whl
- Upload date:
- Size: 5.1 MB
- Tags: CPython 3.14, manylinux: glibc 2.39+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ba53bc72512a11f0cdbaf5fb9e4f28166d16086a043e4a4df91efc7c30151ff5
|
|
| MD5 |
662e8054c2d0b0e19e162f93b53295fb
|
|
| BLAKE2b-256 |
15212e7989ea9f20d69bff209287e4f4554ed7f6dbd1a929b619a7e690ff8bd7
|
File details
Details for the file speequal-3.3.3.post1-cp312-cp312-manylinux_2_39_x86_64.whl.
File metadata
- Download URL: speequal-3.3.3.post1-cp312-cp312-manylinux_2_39_x86_64.whl
- Upload date:
- Size: 5.1 MB
- Tags: CPython 3.12, manylinux: glibc 2.39+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
edaaf864018c7751dc6571ca874eeb4b2de1b4ec39cfbcb87871d27c8df28fb9
|
|
| MD5 |
4f4d9c4fd797382a7fb9bc7edb5a2f81
|
|
| BLAKE2b-256 |
b18f8e09f9853db6ecbf9d8c0d8f617e9d195d14226082fa7570cd613045d450
|