mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Ask whisper.cpp for the detected language when auto mode runs it
The spoken language is only knowable when the transcription model reports it, and only whisper.cpp does: the hosted endpoints accept auto but never say what they heard. transcribe_detected() asks for detection exactly where it can be answered, the local server in auto mode, by requesting verbose_json with no_language_probabilities switched back on for that one request (the server runs with -nlp, which keeps the sweep off everything else).
This commit is contained in:
+45
-1
@@ -356,7 +356,8 @@ def local_failure(service, server, exc):
|
||||
|
||||
|
||||
def _transcribe_request(target, audio_path, language, prompt, response_format,
|
||||
granularity=None, timeout=300, aborter=None):
|
||||
granularity=None, timeout=300, aborter=None,
|
||||
detect_language=False):
|
||||
if target.provider == "local":
|
||||
# The timeouts here are sized for a hosted API, where a slow answer is a
|
||||
# bill running. Locally the only thing being spent is time.
|
||||
@@ -368,6 +369,12 @@ def _transcribe_request(target, audio_path, language, prompt, response_format,
|
||||
fields = [("model", target.model), ("response_format", response_format)]
|
||||
if language and language != "auto":
|
||||
fields.append(("language", language))
|
||||
if detect_language:
|
||||
# whisper.cpp was started with -nlp, which keeps the language
|
||||
# probability sweep off every request. Detection is only worth that
|
||||
# sweep for the run that asked for it, so it is switched back on here,
|
||||
# per request, and reported in the verbose_json answer.
|
||||
fields.append(("no_language_probabilities", "false"))
|
||||
# OpenRouter takes the hint field and throws it away, so spare it the bytes.
|
||||
# The same words still reach the cleanup model as a glossary. whisper.cpp
|
||||
# takes it as the initial prompt, the way OpenAI does.
|
||||
@@ -440,6 +447,43 @@ def transcribe(target, audio_path, language="", prompt="", timeout=300, aborter=
|
||||
return text
|
||||
|
||||
|
||||
# whisper.cpp reports what it heard as a lowercase full name ("turkish",
|
||||
# "english", "german"…); the settings and the cleanup prompt speak in two-letter
|
||||
# codes. Only the handful Dikte offers as a fixed choice get a code; anything
|
||||
# else is left as the empty string, which the caller reads as "unknown" rather
|
||||
# than guessing at a language it has no label for.
|
||||
_DETECTED_TO_CODE = {
|
||||
"english": "en", "turkish": "tr", "german": "de",
|
||||
"french": "fr", "spanish": "es", "arabic": "ar",
|
||||
}
|
||||
|
||||
|
||||
def transcribe_detected(target, audio_path, language="", prompt="", timeout=300,
|
||||
aborter=None):
|
||||
"""(text, code) with the language the model heard.
|
||||
|
||||
The spoken language is only knowable when the transcription model reports
|
||||
it, and only whisper.cpp does: the hosted endpoints accept "auto" but never
|
||||
say what they heard. So detection is asked for exactly where it can be
|
||||
answered — the local server, in auto mode — and every other run transcribes
|
||||
as before and hands back an empty code.
|
||||
"""
|
||||
if target.provider == "local" and language == "auto":
|
||||
data = _transcribe_request(
|
||||
target, audio_path, language, prompt, "verbose_json",
|
||||
detect_language=True, timeout=timeout, aborter=aborter,
|
||||
)
|
||||
text = _local_text(data.get("text") or "").strip()
|
||||
if not text:
|
||||
raise ApiError(t("Transcript came back empty."))
|
||||
code = _DETECTED_TO_CODE.get(
|
||||
(data.get("detected_language") or "").strip().lower(), "")
|
||||
return text, code
|
||||
text = transcribe(target, audio_path, language=language, prompt=prompt,
|
||||
timeout=timeout, aborter=aborter)
|
||||
return text, ""
|
||||
|
||||
|
||||
def transcribe_segments(target, audio_path, language="", prompt="", timeout=300,
|
||||
aborter=None):
|
||||
"""[(start_seconds, end_seconds, text)] using whisper-1's verbose response."""
|
||||
|
||||
@@ -691,6 +691,40 @@ class TranscribeHere(DikteTest):
|
||||
api.transcribe_segments(LOCAL, self.wav)
|
||||
self.assertEqual(multipart_fields(calls[0])["model"], "ggml-base.bin")
|
||||
|
||||
# ---- the detected language --------------------------------------------
|
||||
|
||||
def test_auto_mode_asks_whisper_for_the_detected_language(self):
|
||||
# The -nlp the server was started with is switched back on for this one
|
||||
# request, so whisper's verbose_json reports what it heard.
|
||||
reply = {"text": " Merhaba dünya. ", "detected_language": "turkish"}
|
||||
with fake_urlopen(reply) as calls:
|
||||
text, code = api.transcribe_detected(LOCAL, self.wav, language="auto")
|
||||
fields = multipart_fields(calls[0])
|
||||
self.assertEqual(fields["response_format"], "verbose_json")
|
||||
self.assertEqual(fields["no_language_probabilities"], "false")
|
||||
self.assertNotIn("language", fields)
|
||||
self.assertEqual(text, "Merhaba dünya.")
|
||||
self.assertEqual(code, "tr")
|
||||
|
||||
def test_a_fixed_language_reports_no_detection(self):
|
||||
with fake_urlopen({"text": "hello"}) as calls:
|
||||
text, code = api.transcribe_detected(LOCAL, self.wav, language="tr")
|
||||
self.assertNotIn("no_language_probabilities", multipart_fields(calls[0]))
|
||||
self.assertEqual(text, "hello")
|
||||
self.assertEqual(code, "")
|
||||
|
||||
def test_a_detected_language_without_a_code_stays_unknown(self):
|
||||
with fake_urlopen({"text": "hello", "detected_language": "somali"}):
|
||||
_text, code = api.transcribe_detected(LOCAL, self.wav, language="auto")
|
||||
self.assertEqual(code, "")
|
||||
|
||||
def test_a_hosted_auto_run_transcribes_without_detection(self):
|
||||
with fake_urlopen({"text": "hi"}) as calls:
|
||||
text, code = api.transcribe_detected(OPENAI, self.wav, language="auto")
|
||||
self.assertNotIn("no_language_probabilities", multipart_fields(calls[0]))
|
||||
self.assertEqual(text, "hi")
|
||||
self.assertEqual(code, "")
|
||||
|
||||
|
||||
class Stopping(unittest.TestCase):
|
||||
"""The Stop button, from the far end: a request already blocked on a reply.
|
||||
|
||||
Reference in New Issue
Block a user