fix(languages): map English (India) to Whisper code en (#617)
* fix(languages): map en-in to Whisper code en English (India) used the catalog id as the Whisper language argument, which is invalid. Resolve Whisper/whisper.cpp/remote_api codes via the SUPPORTED_LANGUAGES whisper field so en-us and en-in both become en. Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> * test(languages): cover whisper language resolver fallbacks Exercise unknown catalog ids and legacy en-us fallback paths so codecov/patch covers resolve_whisper_language fully. Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
This commit is contained in:
co-authored by
Jatin K Malik
Cursor Agent
parent
28bed4c1fd
commit
cc69c7ebda
@@ -21,13 +21,31 @@ from typing import Callable, Optional
|
||||
from ..common_types import RecognitionState
|
||||
from ..ui.audio_feedback import play_error_sound, play_start_sound, play_stop_sound
|
||||
from ..utils.paths import models_dir
|
||||
from ..utils.vosk_model_info import VOSK_MODEL_INFO
|
||||
from ..utils.vosk_model_info import SUPPORTED_LANGUAGES, VOSK_MODEL_INFO
|
||||
from ..utils.whispercpp_model_info import WHISPERCPP_MODEL_INFO, get_model_path, is_model_downloaded
|
||||
from ..version import __version__
|
||||
from .command_processor import CommandProcessor
|
||||
from .silero_vad import SILERO_CHUNK_SIZE, load_silero_vad
|
||||
|
||||
|
||||
def resolve_whisper_language(language: str) -> Optional[str]:
|
||||
"""Map a catalog language id to a Whisper / whisper.cpp language code.
|
||||
|
||||
Catalog keys like ``en-us`` / ``en-in`` are not valid Whisper codes; the
|
||||
``whisper`` field on ``SUPPORTED_LANGUAGES`` holds the ISO-style code
|
||||
(``en``). ``auto`` resolves to ``None`` for model auto-detect.
|
||||
"""
|
||||
if language == "auto":
|
||||
return None
|
||||
info = SUPPORTED_LANGUAGES.get(language)
|
||||
if info is not None and "whisper" in info:
|
||||
return info["whisper"]
|
||||
# Legacy fallback for codes not present in the catalog.
|
||||
if language == "en-us":
|
||||
return "en"
|
||||
return language
|
||||
|
||||
|
||||
# ALSA error handler to suppress warnings during PyAudio initialization
|
||||
def _setup_alsa_error_handler():
|
||||
"""Set up an error handler to suppress ALSA warnings."""
|
||||
@@ -1137,11 +1155,7 @@ class SpeechRecognitionManager:
|
||||
import torch
|
||||
use_fp16 = self.model.device != torch.device("cpu")
|
||||
|
||||
lang = self.language
|
||||
if self.language == "en-us":
|
||||
lang = "en"
|
||||
elif self.language == "auto":
|
||||
lang = None # Auto-detect
|
||||
lang = resolve_whisper_language(self.language)
|
||||
|
||||
# Transcribe with Whisper (handles variable length audio automatically)
|
||||
result = self.model.transcribe(
|
||||
@@ -1488,11 +1502,7 @@ class SpeechRecognitionManager:
|
||||
)
|
||||
|
||||
# Prepare language parameter
|
||||
lang = self.language
|
||||
if self.language == "en-us":
|
||||
lang = "en"
|
||||
elif self.language == "auto":
|
||||
lang = None # Auto-detect
|
||||
lang = resolve_whisper_language(self.language)
|
||||
|
||||
logger.debug(f"whisper.cpp using language: {lang or 'auto-detect'}")
|
||||
|
||||
@@ -1625,11 +1635,7 @@ class SpeechRecognitionManager:
|
||||
)
|
||||
|
||||
# Prepare language parameters
|
||||
lang = self.language
|
||||
if lang == "en-us":
|
||||
lang = "en"
|
||||
elif lang == "auto":
|
||||
lang = None
|
||||
lang = resolve_whisper_language(self.language)
|
||||
|
||||
# Prepare HTTP request headers
|
||||
headers = {}
|
||||
|
||||
@@ -366,6 +366,18 @@ class TestRemoteAPITranscription(unittest.TestCase):
|
||||
# Should have been called with "en" (converted from "en-us")
|
||||
mock_server.assert_called_once_with(ANY, "en", ANY, ANY)
|
||||
|
||||
def test_transcribe_with_language_en_in_conversion(self):
|
||||
"""English (India) catalog id must map to Whisper code 'en'."""
|
||||
from unittest.mock import patch
|
||||
|
||||
with patch.object(self.manager, "_try_whispercpp_server_api") as mock_server:
|
||||
mock_server.return_value = "test"
|
||||
|
||||
self.manager.language = "en-in"
|
||||
self.manager._transcribe_with_remote_api([b"audio"], self.manager._http_session)
|
||||
|
||||
mock_server.assert_called_once_with(ANY, "en", ANY, ANY)
|
||||
|
||||
def test_transcribe_with_language_auto(self):
|
||||
"""Test with auto language detection."""
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -305,6 +305,35 @@ class TestSpeechRecognition(unittest.TestCase):
|
||||
self.assertIsNone(hu["vosk"])
|
||||
self.assertNotIn("hu", VOSK_MODEL_INFO["small"]["languages"])
|
||||
|
||||
def test_resolve_whisper_language_maps_english_variants(self):
|
||||
"""Catalog ids like en-us/en-in must become Whisper's 'en' code."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from vocalinux.speech_recognition.recognition_manager import resolve_whisper_language
|
||||
|
||||
self.assertEqual(resolve_whisper_language("en-us"), "en")
|
||||
self.assertEqual(resolve_whisper_language("en-in"), "en")
|
||||
self.assertEqual(resolve_whisper_language("hu"), "hu")
|
||||
self.assertEqual(resolve_whisper_language("ja"), "ja")
|
||||
self.assertIsNone(resolve_whisper_language("auto"))
|
||||
# Unknown catalog ids pass through unchanged.
|
||||
self.assertEqual(resolve_whisper_language("zz-unknown"), "zz-unknown")
|
||||
|
||||
# Legacy fallback when a code is missing from the catalog entirely.
|
||||
with patch(
|
||||
"vocalinux.speech_recognition.recognition_manager.SUPPORTED_LANGUAGES",
|
||||
{},
|
||||
):
|
||||
self.assertEqual(resolve_whisper_language("en-us"), "en")
|
||||
self.assertEqual(resolve_whisper_language("fr"), "fr")
|
||||
|
||||
# Entry present but without a whisper field still falls through.
|
||||
with patch(
|
||||
"vocalinux.speech_recognition.recognition_manager.SUPPORTED_LANGUAGES",
|
||||
{"legacy": {"name": "Legacy", "vosk": None}},
|
||||
):
|
||||
self.assertEqual(resolve_whisper_language("legacy"), "legacy")
|
||||
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user