fix(vosk): add missing Italian and English-India entries to medium/large model tables (#551)
Fixes #550. Root cause: VOSK_MODEL_INFO["medium"]["languages"] and VOSK_MODEL_INFO["large"]["languages"] in vosk_model_info.py were missing entries for "it" (Italian) and "en-in" (English-India), even though both languages are present in the "small" table and VOSK actually publishes bigger models for them (vosk-model-it-0.22, vosk-model-en-in-0.5 at alphacephei.com/vosk/models). When a user selected Italian + the medium (or large) VOSK model, _init_vosk() built vosk_model_map["medium"] = None (via .get() on a missing key), and _get_vosk_model_path() then called os.path.join(MODELS_DIR, None), crashing with: join() argument must be str, bytes, or os.PathLike object, not 'NoneType' before any download was even attempted. This matches the exact debug log in the issue and the settings-dialog download flow the reporter exercised. Fix: - Add the missing "it"/"en-in" entries to the medium/large tables so all 10 supported languages now resolve consistently across all three sizes. - Add a guard in _get_vosk_model_path() that raises a clear ValueError ("No VOSK '<size>' model available for language '<lang>'...") instead of crashing deep inside os.path.join, so any future data gap fails loudly and helpfully rather than with a cryptic TypeError.
This commit is contained in:
@@ -1824,6 +1824,13 @@ class SpeechRecognitionManager:
|
||||
"""Get the path to the VOSK model based on the selected size and language."""
|
||||
model_name = self.vosk_model_map.get(self.model_size, self.vosk_model_map["small"])
|
||||
|
||||
if not model_name:
|
||||
vosk_language = "en-us" if self.language == "auto" else self.language
|
||||
raise ValueError(
|
||||
f"No VOSK '{self.model_size}' model available for language '{vosk_language}'. "
|
||||
"Try the 'small' model size or a different language."
|
||||
)
|
||||
|
||||
# First, check user's local models directory
|
||||
user_model_path = os.path.join(MODELS_DIR, model_name)
|
||||
if os.path.exists(user_model_path):
|
||||
|
||||
@@ -78,8 +78,10 @@ VOSK_MODEL_INFO = {
|
||||
"desc": "Balanced accuracy/speed",
|
||||
"languages": {
|
||||
"en-us": "vosk-model-en-us-0.22",
|
||||
"en-in": "vosk-model-en-in-0.5",
|
||||
"fr": "vosk-model-fr-0.22",
|
||||
"de": "vosk-model-de-0.21",
|
||||
"it": "vosk-model-it-0.22",
|
||||
"ru": "vosk-model-ru-0.22",
|
||||
"hi": "vosk-model-hi-0.22",
|
||||
"es": "vosk-model-es-0.42",
|
||||
@@ -92,8 +94,10 @@ VOSK_MODEL_INFO = {
|
||||
"desc": "Same as medium (best available)",
|
||||
"languages": {
|
||||
"en-us": "vosk-model-en-us-0.22",
|
||||
"en-in": "vosk-model-en-in-0.5",
|
||||
"fr": "vosk-model-fr-0.22",
|
||||
"de": "vosk-model-de-0.21",
|
||||
"it": "vosk-model-it-0.22",
|
||||
"ru": "vosk-model-ru-0.22",
|
||||
"hi": "vosk-model-hi-0.22",
|
||||
"es": "vosk-model-es-0.42",
|
||||
|
||||
@@ -69,6 +69,7 @@ from conftest import mock_audio_feedback # noqa: E402
|
||||
# Update import paths to use the new package structure
|
||||
from vocalinux.common_types import RecognitionState # noqa: E402
|
||||
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager # noqa: E402
|
||||
from vocalinux.utils.vosk_model_info import VOSK_MODEL_INFO # noqa: E402
|
||||
|
||||
|
||||
class TestSpeechRecognition(unittest.TestCase):
|
||||
@@ -251,6 +252,62 @@ class TestSpeechRecognition(unittest.TestCase):
|
||||
# Verify the large model path is constructed correctly
|
||||
mock_get_path.assert_called_with()
|
||||
|
||||
def test_vosk_medium_and_large_cover_all_small_languages(self):
|
||||
"""Regression test for issue #550.
|
||||
|
||||
The 'medium' and 'large' VOSK model tables must map every language
|
||||
that 'small' supports. Italian ('it') and English-India ('en-in')
|
||||
were previously missing from 'medium'/'large', which caused
|
||||
vosk_model_map[size] to resolve to None and crash later with
|
||||
"join() argument must be str, bytes, or os.PathLike object, not
|
||||
'NoneType'" instead of downloading the (existing) big model.
|
||||
"""
|
||||
small_languages = set(VOSK_MODEL_INFO["small"]["languages"])
|
||||
for size in ("medium", "large"):
|
||||
size_languages = set(VOSK_MODEL_INFO[size]["languages"])
|
||||
missing = small_languages - size_languages
|
||||
self.assertEqual(
|
||||
missing,
|
||||
set(),
|
||||
f"VOSK_MODEL_INFO['{size}']['languages'] is missing languages "
|
||||
f"that 'small' supports: {missing}. Any language present in "
|
||||
"'small' must also resolve to a model for 'medium'/'large', "
|
||||
"otherwise initialization crashes with a NoneType path error.",
|
||||
)
|
||||
|
||||
def test_vosk_init_italian_medium_model_resolves_correctly(self):
|
||||
"""Selecting Italian + the 'medium' VOSK model must not crash (issue #550)."""
|
||||
manager = SpeechRecognitionManager(engine="vosk", language="it", model_size="medium")
|
||||
|
||||
self.assertEqual(
|
||||
manager.vosk_model_map["medium"],
|
||||
VOSK_MODEL_INFO["medium"]["languages"]["it"],
|
||||
)
|
||||
self.assertIn("vosk-model-it-0.22", manager.vosk_model_path)
|
||||
|
||||
def test_get_vosk_model_path_raises_clear_error_for_unmapped_language(self):
|
||||
"""An unmapped (size, language) combination should raise a clear,
|
||||
actionable error instead of crashing deep inside os.path.join with a
|
||||
cryptic TypeError (the actual failure mode reported in issue #550,
|
||||
before the VOSK_MODEL_INFO data gap was fixed)."""
|
||||
manager = SpeechRecognitionManager(engine="vosk", language="en-us", model_size="small")
|
||||
|
||||
# Simulate a language that has no mapping for the requested size,
|
||||
# mirroring what happened for "it"/"medium" prior to the data fix.
|
||||
manager.model_size = "medium"
|
||||
manager.language = "it"
|
||||
manager.vosk_model_map = {
|
||||
"small": "vosk-model-small-it-0.22",
|
||||
"medium": None,
|
||||
"large": None,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
manager._get_vosk_model_path()
|
||||
|
||||
self.assertIn("medium", str(ctx.exception))
|
||||
self.assertIn("it", str(ctx.exception))
|
||||
|
||||
def test_download_vosk_model(self):
|
||||
"""Test downloading VOSK model."""
|
||||
# Make os.path.exists return False to trigger download
|
||||
|
||||
Reference in New Issue
Block a user