visqol-python 3.3.3__tar.gz → 3.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {visqol_python-3.3.3/visqol_python.egg-info → visqol_python-3.3.4}/PKG-INFO +26 -18
- {visqol_python-3.3.3 → visqol_python-3.3.4}/README.md +21 -12
- {visqol_python-3.3.3 → visqol_python-3.3.4}/pyproject.toml +12 -3
- visqol_python-3.3.4/tests/test_conformance.py +125 -0
- visqol_python-3.3.4/tests/test_quick.py +89 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/__init__.py +1 -1
- {visqol_python-3.3.3 → visqol_python-3.3.4/visqol_python.egg-info}/PKG-INFO +26 -18
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol_python.egg-info/SOURCES.txt +0 -1
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol_python.egg-info/requires.txt +3 -0
- visqol_python-3.3.3/setup.py +0 -58
- visqol_python-3.3.3/tests/test_conformance.py +0 -173
- visqol_python-3.3.3/tests/test_quick.py +0 -81
- {visqol_python-3.3.3 → visqol_python-3.3.4}/LICENSE +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/MANIFEST.in +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/requirements.txt +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/setup.cfg +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/__main__.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/alignment.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/analysis_window.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/api.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/audio_utils.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/gammatone.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/model/libsvm_nu_svr_model.txt +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/nsim.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/patch_creator.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/patch_selector.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/quality_mapper.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/signal_utils.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/visqol_core.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol/visqol_manager.py +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol_python.egg-info/dependency_links.txt +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol_python.egg-info/entry_points.txt +0 -0
- {visqol_python-3.3.3 → visqol_python-3.3.4}/visqol_python.egg-info/top_level.txt +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: visqol-python
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.4
|
|
4
4
|
Summary: ViSQOL - Virtual Speech Quality Objective Listener (Pure Python)
|
|
5
|
-
Home-page: https://github.com/talker93/visqol-python
|
|
6
5
|
Author: Shan Jiang
|
|
7
6
|
License-Expression: Apache-2.0
|
|
8
7
|
Project-URL: Homepage, https://github.com/talker93/visqol-python
|
|
8
|
+
Project-URL: Changelog, https://github.com/talker93/visqol-python/blob/main/CHANGELOG.md
|
|
9
9
|
Project-URL: Bug Reports, https://github.com/talker93/visqol-python/issues
|
|
10
10
|
Project-URL: Source, https://github.com/talker93/visqol-python
|
|
11
11
|
Project-URL: Original C++, https://github.com/google/visqol
|
|
@@ -14,7 +14,6 @@ Classifier: Development Status :: 4 - Beta
|
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Intended Audience :: Science/Research
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -22,19 +21,24 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
22
|
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
24
23
|
Classifier: Topic :: Scientific/Engineering
|
|
25
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.9
|
|
26
25
|
Description-Content-Type: text/markdown
|
|
27
26
|
License-File: LICENSE
|
|
28
27
|
Requires-Dist: numpy>=1.20
|
|
29
28
|
Requires-Dist: scipy>=1.7
|
|
30
29
|
Requires-Dist: soundfile>=0.10
|
|
31
30
|
Requires-Dist: libsvm-official>=3.25
|
|
32
|
-
|
|
31
|
+
Provides-Extra: test
|
|
32
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
33
33
|
Dynamic: license-file
|
|
34
|
-
Dynamic: requires-python
|
|
35
34
|
|
|
36
35
|
# ViSQOL (Python)
|
|
37
36
|
|
|
37
|
+
[](https://pypi.org/project/visqol-python/)
|
|
38
|
+
[](https://github.com/talker93/visqol-python/actions/workflows/ci.yml)
|
|
39
|
+
[](https://pypi.org/project/visqol-python/)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
|
|
38
42
|
A pure Python implementation of [Google's ViSQOL](https://github.com/google/visqol) (Virtual Speech Quality Objective Listener) v3.3.3 for objective audio/speech quality assessment.
|
|
39
43
|
|
|
40
44
|
ViSQOL compares a reference audio signal with a degraded version and outputs a **MOS-LQO** (Mean Opinion Score - Listening Quality Objective) score on a scale of **1.0 – 5.0**.
|
|
@@ -52,10 +56,10 @@ ViSQOL compares a reference audio signal with a degraded version and outputs a *
|
|
|
52
56
|
## Installation
|
|
53
57
|
|
|
54
58
|
```bash
|
|
55
|
-
pip install
|
|
59
|
+
pip install visqol-python
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
Or install
|
|
62
|
+
Or install from source:
|
|
59
63
|
|
|
60
64
|
```bash
|
|
61
65
|
git clone https://github.com/talker93/visqol-python.git
|
|
@@ -167,8 +171,8 @@ Measured on Apple M-series, Python 3.13:
|
|
|
167
171
|
```
|
|
168
172
|
visqol-python/
|
|
169
173
|
├── visqol/ # Main package
|
|
170
|
-
│ ├── __init__.py # Package exports
|
|
171
|
-
│ ├── api.py # Public API
|
|
174
|
+
│ ├── __init__.py # Package exports & version
|
|
175
|
+
│ ├── api.py # Public API (VisqolApi)
|
|
172
176
|
│ ├── visqol_manager.py # Pipeline orchestrator
|
|
173
177
|
│ ├── visqol_core.py # Core algorithm
|
|
174
178
|
│ ├── audio_utils.py # Audio I/O & SPL normalization
|
|
@@ -180,14 +184,18 @@ visqol-python/
|
|
|
180
184
|
│ ├── alignment.py # Global alignment via cross-correlation
|
|
181
185
|
│ ├── nsim.py # NSIM similarity metric
|
|
182
186
|
│ ├── quality_mapper.py # SVR & exponential quality mapping
|
|
183
|
-
│
|
|
184
|
-
|
|
185
|
-
│
|
|
186
|
-
├── tests/ #
|
|
187
|
-
│ ├──
|
|
188
|
-
│
|
|
189
|
-
|
|
190
|
-
├──
|
|
187
|
+
│ ├── __main__.py # CLI entry point
|
|
188
|
+
│ └── model/ # Bundled SVR model
|
|
189
|
+
│ └── libsvm_nu_svr_model.txt
|
|
190
|
+
├── tests/ # Tests (pytest)
|
|
191
|
+
│ ├── conftest.py # Shared fixtures & CLI options
|
|
192
|
+
│ ├── test_quick.py # Smoke tests (no external data needed)
|
|
193
|
+
│ └── test_conformance.py # Full conformance tests (needs testdata)
|
|
194
|
+
├── .github/workflows/
|
|
195
|
+
│ ├── ci.yml # CI: test on Python 3.9–3.13
|
|
196
|
+
│ └── publish.yml # Auto-publish to PyPI on tag push
|
|
197
|
+
├── pyproject.toml # Package metadata & build config
|
|
198
|
+
├── CHANGELOG.md
|
|
191
199
|
├── LICENSE
|
|
192
200
|
└── README.md
|
|
193
201
|
```
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# ViSQOL (Python)
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/visqol-python/)
|
|
4
|
+
[](https://github.com/talker93/visqol-python/actions/workflows/ci.yml)
|
|
5
|
+
[](https://pypi.org/project/visqol-python/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
3
8
|
A pure Python implementation of [Google's ViSQOL](https://github.com/google/visqol) (Virtual Speech Quality Objective Listener) v3.3.3 for objective audio/speech quality assessment.
|
|
4
9
|
|
|
5
10
|
ViSQOL compares a reference audio signal with a degraded version and outputs a **MOS-LQO** (Mean Opinion Score - Listening Quality Objective) score on a scale of **1.0 – 5.0**.
|
|
@@ -17,10 +22,10 @@ ViSQOL compares a reference audio signal with a degraded version and outputs a *
|
|
|
17
22
|
## Installation
|
|
18
23
|
|
|
19
24
|
```bash
|
|
20
|
-
pip install
|
|
25
|
+
pip install visqol-python
|
|
21
26
|
```
|
|
22
27
|
|
|
23
|
-
Or install
|
|
28
|
+
Or install from source:
|
|
24
29
|
|
|
25
30
|
```bash
|
|
26
31
|
git clone https://github.com/talker93/visqol-python.git
|
|
@@ -132,8 +137,8 @@ Measured on Apple M-series, Python 3.13:
|
|
|
132
137
|
```
|
|
133
138
|
visqol-python/
|
|
134
139
|
├── visqol/ # Main package
|
|
135
|
-
│ ├── __init__.py # Package exports
|
|
136
|
-
│ ├── api.py # Public API
|
|
140
|
+
│ ├── __init__.py # Package exports & version
|
|
141
|
+
│ ├── api.py # Public API (VisqolApi)
|
|
137
142
|
│ ├── visqol_manager.py # Pipeline orchestrator
|
|
138
143
|
│ ├── visqol_core.py # Core algorithm
|
|
139
144
|
│ ├── audio_utils.py # Audio I/O & SPL normalization
|
|
@@ -145,14 +150,18 @@ visqol-python/
|
|
|
145
150
|
│ ├── alignment.py # Global alignment via cross-correlation
|
|
146
151
|
│ ├── nsim.py # NSIM similarity metric
|
|
147
152
|
│ ├── quality_mapper.py # SVR & exponential quality mapping
|
|
148
|
-
│
|
|
149
|
-
|
|
150
|
-
│
|
|
151
|
-
├── tests/ #
|
|
152
|
-
│ ├──
|
|
153
|
-
│
|
|
154
|
-
|
|
155
|
-
├──
|
|
153
|
+
│ ├── __main__.py # CLI entry point
|
|
154
|
+
│ └── model/ # Bundled SVR model
|
|
155
|
+
│ └── libsvm_nu_svr_model.txt
|
|
156
|
+
├── tests/ # Tests (pytest)
|
|
157
|
+
│ ├── conftest.py # Shared fixtures & CLI options
|
|
158
|
+
│ ├── test_quick.py # Smoke tests (no external data needed)
|
|
159
|
+
│ └── test_conformance.py # Full conformance tests (needs testdata)
|
|
160
|
+
├── .github/workflows/
|
|
161
|
+
│ ├── ci.yml # CI: test on Python 3.9–3.13
|
|
162
|
+
│ └── publish.yml # Auto-publish to PyPI on tag push
|
|
163
|
+
├── pyproject.toml # Package metadata & build config
|
|
164
|
+
├── CHANGELOG.md
|
|
156
165
|
├── LICENSE
|
|
157
166
|
└── README.md
|
|
158
167
|
```
|
|
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "visqol-python"
|
|
7
|
-
|
|
7
|
+
dynamic = ["version"]
|
|
8
8
|
description = "ViSQOL - Virtual Speech Quality Objective Listener (Pure Python)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
11
|
-
requires-python = ">=3.
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "Shan Jiang"},
|
|
14
14
|
]
|
|
@@ -21,7 +21,6 @@ classifiers = [
|
|
|
21
21
|
"Intended Audience :: Developers",
|
|
22
22
|
"Intended Audience :: Science/Research",
|
|
23
23
|
"Programming Language :: Python :: 3",
|
|
24
|
-
"Programming Language :: Python :: 3.8",
|
|
25
24
|
"Programming Language :: Python :: 3.9",
|
|
26
25
|
"Programming Language :: Python :: 3.10",
|
|
27
26
|
"Programming Language :: Python :: 3.11",
|
|
@@ -37,8 +36,12 @@ dependencies = [
|
|
|
37
36
|
"libsvm-official>=3.25",
|
|
38
37
|
]
|
|
39
38
|
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
test = ["pytest>=7.0"]
|
|
41
|
+
|
|
40
42
|
[project.urls]
|
|
41
43
|
Homepage = "https://github.com/talker93/visqol-python"
|
|
44
|
+
Changelog = "https://github.com/talker93/visqol-python/blob/main/CHANGELOG.md"
|
|
42
45
|
"Bug Reports" = "https://github.com/talker93/visqol-python/issues"
|
|
43
46
|
Source = "https://github.com/talker93/visqol-python"
|
|
44
47
|
"Original C++" = "https://github.com/google/visqol"
|
|
@@ -46,8 +49,14 @@ Source = "https://github.com/talker93/visqol-python"
|
|
|
46
49
|
[project.scripts]
|
|
47
50
|
visqol = "visqol.__main__:main"
|
|
48
51
|
|
|
52
|
+
[tool.setuptools.dynamic]
|
|
53
|
+
version = {attr = "visqol.__version__"}
|
|
54
|
+
|
|
49
55
|
[tool.setuptools.packages.find]
|
|
50
56
|
exclude = ["tests*"]
|
|
51
57
|
|
|
52
58
|
[tool.setuptools.package-data]
|
|
53
59
|
visqol = ["model/*.txt"]
|
|
60
|
+
|
|
61
|
+
[tool.pytest.ini_options]
|
|
62
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ViSQOL Python conformance tests.
|
|
3
|
+
|
|
4
|
+
Requires the official ViSQOL testdata directory. Provide it via:
|
|
5
|
+
pytest tests/test_conformance.py --testdata /path/to/visqol/testdata
|
|
6
|
+
|
|
7
|
+
The testdata directory should contain:
|
|
8
|
+
conformance_testdata_subset/ (audio test WAV files)
|
|
9
|
+
clean_speech/ (speech test WAV files)
|
|
10
|
+
|
|
11
|
+
You can obtain these from the official ViSQOL repository:
|
|
12
|
+
https://github.com/google/visqol
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from visqol import VisqolApi
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ── Fixtures ──
|
|
23
|
+
|
|
24
|
+
@pytest.fixture(scope="session")
|
|
25
|
+
def testdata_dir(request):
|
|
26
|
+
"""Resolve testdata directory from --testdata or auto-detect."""
|
|
27
|
+
td = request.config.getoption("--testdata")
|
|
28
|
+
if td and os.path.isdir(td):
|
|
29
|
+
return td
|
|
30
|
+
# Fallback: look relative to this file (when inside the original visqol repo)
|
|
31
|
+
candidate = os.path.join(os.path.dirname(__file__), "..", "..", "testdata")
|
|
32
|
+
if os.path.isdir(candidate):
|
|
33
|
+
return candidate
|
|
34
|
+
pytest.skip("testdata directory not found — use --testdata /path/to/visqol/testdata")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture(scope="session")
|
|
38
|
+
def conf_dir(testdata_dir):
|
|
39
|
+
return os.path.join(testdata_dir, "conformance_testdata_subset")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture(scope="session")
|
|
43
|
+
def speech_dir(testdata_dir):
|
|
44
|
+
return os.path.join(testdata_dir, "clean_speech")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture(scope="session")
|
|
48
|
+
def audio_api():
|
|
49
|
+
api = VisqolApi()
|
|
50
|
+
api.create(mode="audio")
|
|
51
|
+
return api
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.fixture(scope="session")
|
|
55
|
+
def speech_api():
|
|
56
|
+
api = VisqolApi()
|
|
57
|
+
api.create(mode="speech")
|
|
58
|
+
return api
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ── Test data ──
|
|
62
|
+
|
|
63
|
+
TOLERANCE = 0.05
|
|
64
|
+
|
|
65
|
+
AUDIO_CASES = [
|
|
66
|
+
("strauss48_stereo.wav", "strauss48_stereo_lp35.wav",
|
|
67
|
+
1.3888791489130758, "strauss_lp35"),
|
|
68
|
+
("steely48_stereo.wav", "steely48_stereo_lp7.wav",
|
|
69
|
+
2.2501683734385183, "steely_lp7"),
|
|
70
|
+
("sopr48_stereo.wav", "sopr48_stereo_256kbps_aac.wav",
|
|
71
|
+
4.68228969737946, "sopr_256aac"),
|
|
72
|
+
("ravel48_stereo.wav", "ravel48_stereo_128kbps_opus.wav",
|
|
73
|
+
4.465141897255348, "ravel_128opus"),
|
|
74
|
+
("moonlight48_stereo.wav", "moonlight48_stereo_128kbps_aac.wav",
|
|
75
|
+
4.684292801646114, "moonlight_128aac"),
|
|
76
|
+
("harpsichord48_stereo.wav", "harpsichord48_stereo_96kbps_mp3.wav",
|
|
77
|
+
4.22374532766003, "harpsichord_96mp3"),
|
|
78
|
+
("guitar48_stereo.wav", "guitar48_stereo_64kbps_aac.wav",
|
|
79
|
+
4.349722308064298, "guitar_64aac"),
|
|
80
|
+
("glock48_stereo.wav", "glock48_stereo_48kbps_aac.wav",
|
|
81
|
+
4.332452943882108, "glock_48aac"),
|
|
82
|
+
("contrabassoon48_stereo.wav", "contrabassoon48_stereo_24kbps_aac.wav",
|
|
83
|
+
2.346868205375293, "contrabassoon_24aac"),
|
|
84
|
+
("castanets48_stereo.wav", "castanets48_stereo.wav",
|
|
85
|
+
4.732101253042348, "castanets_identity"),
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
SPEECH_CASES = [
|
|
89
|
+
("CA01_01.wav", "transcoded_CA01_01.wav",
|
|
90
|
+
3.374505555111911, "CA01_transcoded"),
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ── Audio mode tests ──
|
|
95
|
+
|
|
96
|
+
@pytest.mark.parametrize(
|
|
97
|
+
"ref_name, deg_name, expected_mos, test_id",
|
|
98
|
+
AUDIO_CASES,
|
|
99
|
+
ids=[c[3] for c in AUDIO_CASES],
|
|
100
|
+
)
|
|
101
|
+
def test_audio_conformance(audio_api, conf_dir, ref_name, deg_name, expected_mos, test_id):
|
|
102
|
+
ref_path = os.path.join(conf_dir, ref_name)
|
|
103
|
+
deg_path = os.path.join(conf_dir, deg_name)
|
|
104
|
+
result = audio_api.measure(ref_path, deg_path)
|
|
105
|
+
diff = abs(result.moslqo - expected_mos)
|
|
106
|
+
assert diff < TOLERANCE, (
|
|
107
|
+
f"[{test_id}] MOS={result.moslqo:.6f}, expected={expected_mos:.6f}, diff={diff:.6f}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ── Speech mode tests ──
|
|
112
|
+
|
|
113
|
+
@pytest.mark.parametrize(
|
|
114
|
+
"ref_name, deg_name, expected_mos, test_id",
|
|
115
|
+
SPEECH_CASES,
|
|
116
|
+
ids=[c[3] for c in SPEECH_CASES],
|
|
117
|
+
)
|
|
118
|
+
def test_speech_conformance(speech_api, speech_dir, ref_name, deg_name, expected_mos, test_id):
|
|
119
|
+
ref_path = os.path.join(speech_dir, ref_name)
|
|
120
|
+
deg_path = os.path.join(speech_dir, deg_name)
|
|
121
|
+
result = speech_api.measure(ref_path, deg_path)
|
|
122
|
+
diff = abs(result.moslqo - expected_mos)
|
|
123
|
+
assert diff < TOLERANCE, (
|
|
124
|
+
f"[{test_id}] MOS={result.moslqo:.6f}, expected={expected_mos:.6f}, diff={diff:.6f}"
|
|
125
|
+
)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quick smoke tests for ViSQOL Python.
|
|
3
|
+
|
|
4
|
+
These tests verify basic API functionality without requiring external testdata.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from visqol import VisqolApi
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestApiCreation:
|
|
14
|
+
"""Test that VisqolApi can be created in different modes."""
|
|
15
|
+
|
|
16
|
+
def test_create_audio_mode(self):
|
|
17
|
+
api = VisqolApi()
|
|
18
|
+
api.create(mode="audio")
|
|
19
|
+
|
|
20
|
+
def test_create_speech_mode(self):
|
|
21
|
+
api = VisqolApi()
|
|
22
|
+
api.create(mode="speech")
|
|
23
|
+
|
|
24
|
+
def test_create_default_mode(self):
|
|
25
|
+
"""Default mode (no argument) should work as audio mode."""
|
|
26
|
+
api = VisqolApi()
|
|
27
|
+
api.create()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestMeasureFromArrays:
|
|
31
|
+
"""Test measure_from_arrays with synthetic signals."""
|
|
32
|
+
|
|
33
|
+
def test_identical_signal_high_score(self):
|
|
34
|
+
"""Identical signals should produce a high MOS score."""
|
|
35
|
+
api = VisqolApi()
|
|
36
|
+
api.create(mode="speech")
|
|
37
|
+
sr = 16000
|
|
38
|
+
duration = 3.0
|
|
39
|
+
t = np.linspace(0, duration, int(sr * duration), endpoint=False)
|
|
40
|
+
signal = 0.5 * np.sin(2 * np.pi * 440 * t)
|
|
41
|
+
result = api.measure_from_arrays(signal, signal, sample_rate=sr)
|
|
42
|
+
assert result.moslqo >= 4.0, (
|
|
43
|
+
f"Identical signal should give MOS >= 4.0, got {result.moslqo:.4f}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def test_degraded_signal_lower_score(self):
|
|
47
|
+
"""Adding noise to a signal should produce a lower MOS score."""
|
|
48
|
+
api = VisqolApi()
|
|
49
|
+
api.create(mode="speech")
|
|
50
|
+
sr = 16000
|
|
51
|
+
duration = 3.0
|
|
52
|
+
t = np.linspace(0, duration, int(sr * duration), endpoint=False)
|
|
53
|
+
ref = 0.5 * np.sin(2 * np.pi * 440 * t)
|
|
54
|
+
rng = np.random.default_rng(42)
|
|
55
|
+
deg = ref + 0.3 * rng.standard_normal(len(ref))
|
|
56
|
+
result = api.measure_from_arrays(ref, deg, sample_rate=sr)
|
|
57
|
+
assert 1.0 <= result.moslqo <= 5.0, (
|
|
58
|
+
f"MOS should be in [1, 5], got {result.moslqo:.4f}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TestResultFields:
|
|
63
|
+
"""Test that SimilarityResult has all expected fields."""
|
|
64
|
+
|
|
65
|
+
def test_result_has_expected_fields(self):
|
|
66
|
+
api = VisqolApi()
|
|
67
|
+
api.create(mode="speech")
|
|
68
|
+
sr = 16000
|
|
69
|
+
duration = 3.0
|
|
70
|
+
t = np.linspace(0, duration, int(sr * duration), endpoint=False)
|
|
71
|
+
signal = 0.5 * np.sin(2 * np.pi * 440 * t)
|
|
72
|
+
result = api.measure_from_arrays(signal, signal, sample_rate=sr)
|
|
73
|
+
assert hasattr(result, "moslqo")
|
|
74
|
+
assert hasattr(result, "vnsim")
|
|
75
|
+
assert hasattr(result, "fvnsim")
|
|
76
|
+
assert hasattr(result, "fstdnsim")
|
|
77
|
+
assert hasattr(result, "fvdegenergy")
|
|
78
|
+
assert hasattr(result, "patch_sims")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TestVersion:
|
|
82
|
+
"""Test package version is accessible."""
|
|
83
|
+
|
|
84
|
+
def test_version_string(self):
|
|
85
|
+
import visqol
|
|
86
|
+
assert hasattr(visqol, "__version__")
|
|
87
|
+
assert isinstance(visqol.__version__, str)
|
|
88
|
+
parts = visqol.__version__.split(".")
|
|
89
|
+
assert len(parts) >= 2, "Version should have at least major.minor"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: visqol-python
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.4
|
|
4
4
|
Summary: ViSQOL - Virtual Speech Quality Objective Listener (Pure Python)
|
|
5
|
-
Home-page: https://github.com/talker93/visqol-python
|
|
6
5
|
Author: Shan Jiang
|
|
7
6
|
License-Expression: Apache-2.0
|
|
8
7
|
Project-URL: Homepage, https://github.com/talker93/visqol-python
|
|
8
|
+
Project-URL: Changelog, https://github.com/talker93/visqol-python/blob/main/CHANGELOG.md
|
|
9
9
|
Project-URL: Bug Reports, https://github.com/talker93/visqol-python/issues
|
|
10
10
|
Project-URL: Source, https://github.com/talker93/visqol-python
|
|
11
11
|
Project-URL: Original C++, https://github.com/google/visqol
|
|
@@ -14,7 +14,6 @@ Classifier: Development Status :: 4 - Beta
|
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Intended Audience :: Science/Research
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -22,19 +21,24 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
22
|
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
24
23
|
Classifier: Topic :: Scientific/Engineering
|
|
25
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.9
|
|
26
25
|
Description-Content-Type: text/markdown
|
|
27
26
|
License-File: LICENSE
|
|
28
27
|
Requires-Dist: numpy>=1.20
|
|
29
28
|
Requires-Dist: scipy>=1.7
|
|
30
29
|
Requires-Dist: soundfile>=0.10
|
|
31
30
|
Requires-Dist: libsvm-official>=3.25
|
|
32
|
-
|
|
31
|
+
Provides-Extra: test
|
|
32
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
33
33
|
Dynamic: license-file
|
|
34
|
-
Dynamic: requires-python
|
|
35
34
|
|
|
36
35
|
# ViSQOL (Python)
|
|
37
36
|
|
|
37
|
+
[](https://pypi.org/project/visqol-python/)
|
|
38
|
+
[](https://github.com/talker93/visqol-python/actions/workflows/ci.yml)
|
|
39
|
+
[](https://pypi.org/project/visqol-python/)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
|
|
38
42
|
A pure Python implementation of [Google's ViSQOL](https://github.com/google/visqol) (Virtual Speech Quality Objective Listener) v3.3.3 for objective audio/speech quality assessment.
|
|
39
43
|
|
|
40
44
|
ViSQOL compares a reference audio signal with a degraded version and outputs a **MOS-LQO** (Mean Opinion Score - Listening Quality Objective) score on a scale of **1.0 – 5.0**.
|
|
@@ -52,10 +56,10 @@ ViSQOL compares a reference audio signal with a degraded version and outputs a *
|
|
|
52
56
|
## Installation
|
|
53
57
|
|
|
54
58
|
```bash
|
|
55
|
-
pip install
|
|
59
|
+
pip install visqol-python
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
Or install
|
|
62
|
+
Or install from source:
|
|
59
63
|
|
|
60
64
|
```bash
|
|
61
65
|
git clone https://github.com/talker93/visqol-python.git
|
|
@@ -167,8 +171,8 @@ Measured on Apple M-series, Python 3.13:
|
|
|
167
171
|
```
|
|
168
172
|
visqol-python/
|
|
169
173
|
├── visqol/ # Main package
|
|
170
|
-
│ ├── __init__.py # Package exports
|
|
171
|
-
│ ├── api.py # Public API
|
|
174
|
+
│ ├── __init__.py # Package exports & version
|
|
175
|
+
│ ├── api.py # Public API (VisqolApi)
|
|
172
176
|
│ ├── visqol_manager.py # Pipeline orchestrator
|
|
173
177
|
│ ├── visqol_core.py # Core algorithm
|
|
174
178
|
│ ├── audio_utils.py # Audio I/O & SPL normalization
|
|
@@ -180,14 +184,18 @@ visqol-python/
|
|
|
180
184
|
│ ├── alignment.py # Global alignment via cross-correlation
|
|
181
185
|
│ ├── nsim.py # NSIM similarity metric
|
|
182
186
|
│ ├── quality_mapper.py # SVR & exponential quality mapping
|
|
183
|
-
│
|
|
184
|
-
|
|
185
|
-
│
|
|
186
|
-
├── tests/ #
|
|
187
|
-
│ ├──
|
|
188
|
-
│
|
|
189
|
-
|
|
190
|
-
├──
|
|
187
|
+
│ ├── __main__.py # CLI entry point
|
|
188
|
+
│ └── model/ # Bundled SVR model
|
|
189
|
+
│ └── libsvm_nu_svr_model.txt
|
|
190
|
+
├── tests/ # Tests (pytest)
|
|
191
|
+
│ ├── conftest.py # Shared fixtures & CLI options
|
|
192
|
+
│ ├── test_quick.py # Smoke tests (no external data needed)
|
|
193
|
+
│ └── test_conformance.py # Full conformance tests (needs testdata)
|
|
194
|
+
├── .github/workflows/
|
|
195
|
+
│ ├── ci.yml # CI: test on Python 3.9–3.13
|
|
196
|
+
│ └── publish.yml # Auto-publish to PyPI on tag push
|
|
197
|
+
├── pyproject.toml # Package metadata & build config
|
|
198
|
+
├── CHANGELOG.md
|
|
191
199
|
├── LICENSE
|
|
192
200
|
└── README.md
|
|
193
201
|
```
|
visqol_python-3.3.3/setup.py
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
from setuptools import setup, find_packages
|
|
2
|
-
import os
|
|
3
|
-
|
|
4
|
-
here = os.path.abspath(os.path.dirname(__file__))
|
|
5
|
-
|
|
6
|
-
with open(os.path.join(here, "README.md"), encoding="utf-8") as f:
|
|
7
|
-
long_description = f.read()
|
|
8
|
-
|
|
9
|
-
setup(
|
|
10
|
-
name="visqol-python",
|
|
11
|
-
version="3.3.3",
|
|
12
|
-
description="ViSQOL - Virtual Speech Quality Objective Listener (Pure Python)",
|
|
13
|
-
long_description=long_description,
|
|
14
|
-
long_description_content_type="text/markdown",
|
|
15
|
-
url="https://github.com/talker93/visqol-python",
|
|
16
|
-
author="Shan Jiang",
|
|
17
|
-
license="Apache-2.0",
|
|
18
|
-
packages=find_packages(exclude=["tests"]),
|
|
19
|
-
python_requires=">=3.8",
|
|
20
|
-
install_requires=[
|
|
21
|
-
"numpy>=1.20",
|
|
22
|
-
"scipy>=1.7",
|
|
23
|
-
"soundfile>=0.10",
|
|
24
|
-
"libsvm-official>=3.25",
|
|
25
|
-
],
|
|
26
|
-
entry_points={
|
|
27
|
-
"console_scripts": [
|
|
28
|
-
"visqol=visqol.__main__:main",
|
|
29
|
-
],
|
|
30
|
-
},
|
|
31
|
-
package_data={
|
|
32
|
-
"visqol": ["model/*.txt"],
|
|
33
|
-
},
|
|
34
|
-
include_package_data=True,
|
|
35
|
-
keywords=[
|
|
36
|
-
"audio-quality", "speech-quality", "MOS", "PESQ", "POLQA",
|
|
37
|
-
"visqol", "objective-metric", "perceptual-quality",
|
|
38
|
-
],
|
|
39
|
-
classifiers=[
|
|
40
|
-
"Development Status :: 4 - Beta",
|
|
41
|
-
"Intended Audience :: Developers",
|
|
42
|
-
"Intended Audience :: Science/Research",
|
|
43
|
-
"Programming Language :: Python :: 3",
|
|
44
|
-
"Programming Language :: Python :: 3.8",
|
|
45
|
-
"Programming Language :: Python :: 3.9",
|
|
46
|
-
"Programming Language :: Python :: 3.10",
|
|
47
|
-
"Programming Language :: Python :: 3.11",
|
|
48
|
-
"Programming Language :: Python :: 3.12",
|
|
49
|
-
"Programming Language :: Python :: 3.13",
|
|
50
|
-
"Topic :: Multimedia :: Sound/Audio :: Analysis",
|
|
51
|
-
"Topic :: Scientific/Engineering",
|
|
52
|
-
],
|
|
53
|
-
project_urls={
|
|
54
|
-
"Bug Reports": "https://github.com/talker93/visqol-python/issues",
|
|
55
|
-
"Source": "https://github.com/talker93/visqol-python",
|
|
56
|
-
"Original C++": "https://github.com/google/visqol",
|
|
57
|
-
},
|
|
58
|
-
)
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
ViSQOL Python conformance tests.
|
|
4
|
-
|
|
5
|
-
Usage:
|
|
6
|
-
python tests/test_conformance.py --testdata /path/to/visqol/testdata
|
|
7
|
-
|
|
8
|
-
The testdata directory should contain:
|
|
9
|
-
conformance_testdata_subset/ (audio test WAV files)
|
|
10
|
-
clean_speech/ (speech test WAV files)
|
|
11
|
-
|
|
12
|
-
You can obtain these from the official ViSQOL repository:
|
|
13
|
-
https://github.com/google/visqol
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
import argparse
|
|
17
|
-
import time
|
|
18
|
-
import sys
|
|
19
|
-
import os
|
|
20
|
-
|
|
21
|
-
# Add project root for imports
|
|
22
|
-
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
23
|
-
|
|
24
|
-
from visqol.api import VisqolApi
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def _get_testdata_dir():
|
|
28
|
-
parser = argparse.ArgumentParser(add_help=False)
|
|
29
|
-
parser.add_argument('--testdata', default=None)
|
|
30
|
-
args, _ = parser.parse_known_args()
|
|
31
|
-
if args.testdata:
|
|
32
|
-
return args.testdata
|
|
33
|
-
# Fallback: look relative to this file (when inside the original visqol repo)
|
|
34
|
-
candidate = os.path.join(os.path.dirname(__file__), '..', '..', 'testdata')
|
|
35
|
-
if os.path.isdir(candidate):
|
|
36
|
-
return candidate
|
|
37
|
-
print("ERROR: testdata directory not found.")
|
|
38
|
-
print("Usage: python tests/test_conformance.py --testdata /path/to/visqol/testdata")
|
|
39
|
-
sys.exit(1)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
TD = _get_testdata_dir()
|
|
43
|
-
CONF = os.path.join(TD, 'conformance_testdata_subset')
|
|
44
|
-
SPEECH = os.path.join(TD, 'clean_speech')
|
|
45
|
-
|
|
46
|
-
TOLERANCE = 0.05
|
|
47
|
-
|
|
48
|
-
AUDIO_TESTS = [
|
|
49
|
-
('strauss48_stereo.wav', 'strauss48_stereo_lp35.wav',
|
|
50
|
-
1.3888791489130758, 'strauss_lp35'),
|
|
51
|
-
('steely48_stereo.wav', 'steely48_stereo_lp7.wav',
|
|
52
|
-
2.2501683734385183, 'steely_lp7'),
|
|
53
|
-
('sopr48_stereo.wav', 'sopr48_stereo_256kbps_aac.wav',
|
|
54
|
-
4.68228969737946, 'sopr_256aac'),
|
|
55
|
-
('ravel48_stereo.wav', 'ravel48_stereo_128kbps_opus.wav',
|
|
56
|
-
4.465141897255348, 'ravel_128opus'),
|
|
57
|
-
('moonlight48_stereo.wav', 'moonlight48_stereo_128kbps_aac.wav',
|
|
58
|
-
4.684292801646114, 'moonlight_128aac'),
|
|
59
|
-
('harpsichord48_stereo.wav', 'harpsichord48_stereo_96kbps_mp3.wav',
|
|
60
|
-
4.22374532766003, 'harpsichord_96mp3'),
|
|
61
|
-
('guitar48_stereo.wav', 'guitar48_stereo_64kbps_aac.wav',
|
|
62
|
-
4.349722308064298, 'guitar_64aac'),
|
|
63
|
-
('glock48_stereo.wav', 'glock48_stereo_48kbps_aac.wav',
|
|
64
|
-
4.332452943882108, 'glock_48aac'),
|
|
65
|
-
('contrabassoon48_stereo.wav', 'contrabassoon48_stereo_24kbps_aac.wav',
|
|
66
|
-
2.346868205375293, 'contrabassoon_24aac'),
|
|
67
|
-
('castanets48_stereo.wav', 'castanets48_stereo.wav',
|
|
68
|
-
4.732101253042348, 'castanets_identity'),
|
|
69
|
-
]
|
|
70
|
-
|
|
71
|
-
SPEECH_TESTS = [
|
|
72
|
-
('CA01_01.wav', 'transcoded_CA01_01.wav',
|
|
73
|
-
3.374505555111911, 'CA01_transcoded_exp'),
|
|
74
|
-
]
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def run_audio_tests():
|
|
78
|
-
print("=" * 70)
|
|
79
|
-
print("AUDIO MODE CONFORMANCE TESTS")
|
|
80
|
-
print("=" * 70)
|
|
81
|
-
|
|
82
|
-
api = VisqolApi()
|
|
83
|
-
api.create(mode="audio")
|
|
84
|
-
|
|
85
|
-
pass_count = 0
|
|
86
|
-
fail_count = 0
|
|
87
|
-
total_time = 0
|
|
88
|
-
|
|
89
|
-
for ref, deg, expected, name in AUDIO_TESTS:
|
|
90
|
-
ref_path = os.path.join(CONF, ref)
|
|
91
|
-
deg_path = os.path.join(CONF, deg)
|
|
92
|
-
|
|
93
|
-
t0 = time.time()
|
|
94
|
-
result = api.measure(ref_path, deg_path)
|
|
95
|
-
elapsed = time.time() - t0
|
|
96
|
-
total_time += elapsed
|
|
97
|
-
|
|
98
|
-
diff = abs(result.moslqo - expected)
|
|
99
|
-
passed = diff < TOLERANCE
|
|
100
|
-
status = "PASS" if passed else "FAIL"
|
|
101
|
-
|
|
102
|
-
if passed:
|
|
103
|
-
pass_count += 1
|
|
104
|
-
else:
|
|
105
|
-
fail_count += 1
|
|
106
|
-
|
|
107
|
-
print(f" {status} {name:30s} "
|
|
108
|
-
f"MOS={result.moslqo:.4f} "
|
|
109
|
-
f"exp={expected:.4f} "
|
|
110
|
-
f"diff={diff:.6f} "
|
|
111
|
-
f"({elapsed:.1f}s)")
|
|
112
|
-
|
|
113
|
-
print(f"\nAudio: {pass_count}/{len(AUDIO_TESTS)} passed "
|
|
114
|
-
f"(total: {total_time:.1f}s)")
|
|
115
|
-
return fail_count
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def run_speech_tests():
|
|
119
|
-
print("\n" + "=" * 70)
|
|
120
|
-
print("SPEECH MODE CONFORMANCE TESTS (exponential mapping)")
|
|
121
|
-
print("=" * 70)
|
|
122
|
-
|
|
123
|
-
api = VisqolApi()
|
|
124
|
-
api.create(mode="speech")
|
|
125
|
-
|
|
126
|
-
pass_count = 0
|
|
127
|
-
fail_count = 0
|
|
128
|
-
total_time = 0
|
|
129
|
-
|
|
130
|
-
for ref, deg, expected, name in SPEECH_TESTS:
|
|
131
|
-
ref_path = os.path.join(SPEECH, ref)
|
|
132
|
-
deg_path = os.path.join(SPEECH, deg)
|
|
133
|
-
|
|
134
|
-
t0 = time.time()
|
|
135
|
-
result = api.measure(ref_path, deg_path)
|
|
136
|
-
elapsed = time.time() - t0
|
|
137
|
-
total_time += elapsed
|
|
138
|
-
|
|
139
|
-
diff = abs(result.moslqo - expected)
|
|
140
|
-
passed = diff < TOLERANCE
|
|
141
|
-
status = "PASS" if passed else "FAIL"
|
|
142
|
-
|
|
143
|
-
if passed:
|
|
144
|
-
pass_count += 1
|
|
145
|
-
else:
|
|
146
|
-
fail_count += 1
|
|
147
|
-
|
|
148
|
-
print(f" {status} {name:30s} "
|
|
149
|
-
f"MOS={result.moslqo:.4f} "
|
|
150
|
-
f"exp={expected:.4f} "
|
|
151
|
-
f"diff={diff:.6f} "
|
|
152
|
-
f"({elapsed:.1f}s)")
|
|
153
|
-
|
|
154
|
-
print(f"\nSpeech: {pass_count}/{len(SPEECH_TESTS)} passed "
|
|
155
|
-
f"(total: {total_time:.1f}s)")
|
|
156
|
-
return fail_count
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if __name__ == "__main__":
|
|
160
|
-
audio_fails = run_audio_tests()
|
|
161
|
-
speech_fails = run_speech_tests()
|
|
162
|
-
|
|
163
|
-
total_tests = len(AUDIO_TESTS) + len(SPEECH_TESTS)
|
|
164
|
-
total_fails = audio_fails + speech_fails
|
|
165
|
-
|
|
166
|
-
print("\n" + "=" * 70)
|
|
167
|
-
if total_fails == 0:
|
|
168
|
-
print(f"ALL {total_tests} CONFORMANCE TESTS PASSED!")
|
|
169
|
-
else:
|
|
170
|
-
print(f"FAILED: {total_fails}/{total_tests} tests")
|
|
171
|
-
print("=" * 70)
|
|
172
|
-
|
|
173
|
-
sys.exit(total_fails)
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Quick ViSQOL conformance tests (subset of 3 audio + 1 speech).
|
|
4
|
-
|
|
5
|
-
Usage:
|
|
6
|
-
python tests/test_quick.py --testdata /path/to/visqol/testdata
|
|
7
|
-
"""
|
|
8
|
-
import argparse
|
|
9
|
-
import time
|
|
10
|
-
import sys
|
|
11
|
-
import os
|
|
12
|
-
|
|
13
|
-
# Add project root for imports
|
|
14
|
-
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
15
|
-
|
|
16
|
-
from visqol.api import VisqolApi
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _get_testdata_dir():
|
|
20
|
-
parser = argparse.ArgumentParser(add_help=False)
|
|
21
|
-
parser.add_argument('--testdata', default=None)
|
|
22
|
-
args, _ = parser.parse_known_args()
|
|
23
|
-
if args.testdata:
|
|
24
|
-
return args.testdata
|
|
25
|
-
candidate = os.path.join(os.path.dirname(__file__), '..', '..', 'testdata')
|
|
26
|
-
if os.path.isdir(candidate):
|
|
27
|
-
return candidate
|
|
28
|
-
print("ERROR: testdata directory not found.")
|
|
29
|
-
print("Usage: python tests/test_quick.py --testdata /path/to/visqol/testdata")
|
|
30
|
-
sys.exit(1)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
TD = _get_testdata_dir()
|
|
34
|
-
CONF = os.path.join(TD, 'conformance_testdata_subset')
|
|
35
|
-
SPEECH = os.path.join(TD, 'clean_speech')
|
|
36
|
-
|
|
37
|
-
# Test 3 audio + 1 speech
|
|
38
|
-
api_audio = VisqolApi()
|
|
39
|
-
api_audio.create(mode="audio")
|
|
40
|
-
|
|
41
|
-
tests = [
|
|
42
|
-
(CONF, 'guitar48_stereo.wav', 'guitar48_stereo_64kbps_aac.wav',
|
|
43
|
-
4.349722, 'guitar_64aac'),
|
|
44
|
-
(CONF, 'strauss48_stereo.wav', 'strauss48_stereo_lp35.wav',
|
|
45
|
-
1.388879, 'strauss_lp35'),
|
|
46
|
-
(CONF, 'castanets48_stereo.wav', 'castanets48_stereo.wav',
|
|
47
|
-
4.732101, 'castanets_id'),
|
|
48
|
-
]
|
|
49
|
-
|
|
50
|
-
all_pass = True
|
|
51
|
-
for td, ref, deg, expected, name in tests:
|
|
52
|
-
t0 = time.time()
|
|
53
|
-
r = api_audio.measure(os.path.join(td, ref), os.path.join(td, deg))
|
|
54
|
-
dt = time.time() - t0
|
|
55
|
-
d = abs(r.moslqo - expected)
|
|
56
|
-
ok = d < 0.05
|
|
57
|
-
if not ok:
|
|
58
|
-
all_pass = False
|
|
59
|
-
print(f"{'PASS' if ok else 'FAIL'} {name:20s} "
|
|
60
|
-
f"MOS={r.moslqo:.4f} exp={expected:.4f} "
|
|
61
|
-
f"diff={d:.6f} ({dt:.1f}s)")
|
|
62
|
-
sys.stdout.flush()
|
|
63
|
-
|
|
64
|
-
# Speech test
|
|
65
|
-
api_speech = VisqolApi()
|
|
66
|
-
api_speech.create(mode="speech")
|
|
67
|
-
t0 = time.time()
|
|
68
|
-
r = api_speech.measure(
|
|
69
|
-
os.path.join(SPEECH, 'CA01_01.wav'),
|
|
70
|
-
os.path.join(SPEECH, 'transcoded_CA01_01.wav'))
|
|
71
|
-
dt = time.time() - t0
|
|
72
|
-
d = abs(r.moslqo - 3.374506)
|
|
73
|
-
ok = d < 0.05
|
|
74
|
-
if not ok:
|
|
75
|
-
all_pass = False
|
|
76
|
-
print(f"{'PASS' if ok else 'FAIL'} {'speech_CA01':20s} "
|
|
77
|
-
f"MOS={r.moslqo:.4f} exp=3.3745 "
|
|
78
|
-
f"diff={d:.6f} ({dt:.1f}s)")
|
|
79
|
-
|
|
80
|
-
print(f"\n{'ALL PASS' if all_pass else 'SOME FAILED'}")
|
|
81
|
-
sys.exit(0 if all_pass else 1)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|