feat: per-engine timeout field, WhisperX default 120s (v2.03.07)
- STTEngine gains timeout: int = 30 field; used as HTTP timeout per engine - Engines tab shows "Timeout (s)" field; blank = 30s default - WhisperX quickstart template pre-sets 120s (diarization is slow) - config load/save: only written when != 30 to keep config clean - RAM column header tooltip explains "server" for Docker/remote engines Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2326f20a28
commit
d5e512a812
@ -6,4 +6,4 @@ counterpart to the macOS Blitztext menu bar app: it runs natively on the host
|
||||
(not in a container) so it can type into any application via xdotool.
|
||||
"""
|
||||
|
||||
__version__ = "2.03.06"
|
||||
__version__ = "2.03.07"
|
||||
|
||||
@ -276,6 +276,7 @@ def load(path: Path = CONFIG_PATH) -> Config:
|
||||
url=e.get("url", "").rstrip("/"),
|
||||
model=e.get("model", ""),
|
||||
api_key_env=e.get("api_key_env", ""),
|
||||
timeout=int(e.get("timeout", 30)),
|
||||
)
|
||||
for e in data.get("stt_engine", [])
|
||||
] or [STTEngine("Local faster-whisper", "local", model=cfg.model)]
|
||||
@ -397,7 +398,8 @@ def save(cfg: Config, path: Path = CONFIG_PATH) -> None:
|
||||
{k: v for k, v in {
|
||||
"name": e.name, "type": e.type, "url": e.url,
|
||||
"model": e.model, "api_key_env": e.api_key_env,
|
||||
}.items() if v or k in ("name", "type")}
|
||||
"timeout": e.timeout if e.timeout != 30 else None,
|
||||
}.items() if v is not None and (v or k in ("name", "type"))}
|
||||
for e in cfg.stt_engines
|
||||
],
|
||||
"llm": {"active": cfg.llm_active},
|
||||
|
||||
@ -67,23 +67,24 @@ _LLM_TEMPLATES: list[tuple[str, str, str, str, str]] = [
|
||||
("vLLM", "http://localhost:8000/v1", "", "local", ""),
|
||||
("llama-swap", "http://localhost:28080/v1", "", "local", ""),
|
||||
]
|
||||
_STT_TEMPLATES: list[tuple[str, str, str, str, str]] = [
|
||||
_STT_TEMPLATES: list[tuple[str, str, str, str, str, int]] = [
|
||||
# (name, url, api_key_env, type, model, timeout_s)
|
||||
# ── Cloud / API services ──────────────────────────────────────────────────
|
||||
("OpenAI Whisper", "https://api.openai.com/v1", "OPENAI_API_KEY", "openai", "whisper-1"),
|
||||
("Groq Whisper (free tier)","https://api.groq.com/openai/v1", "GROQ_API_KEY", "openai", "whisper-large-v3-turbo"),
|
||||
("OpenAI Whisper", "https://api.openai.com/v1", "OPENAI_API_KEY", "openai", "whisper-1", 30),
|
||||
("Groq Whisper (free tier)","https://api.groq.com/openai/v1", "GROQ_API_KEY", "openai", "whisper-large-v3-turbo", 30),
|
||||
# ── Local servers ─────────────────────────────────────────────────────────
|
||||
("faster-whisper-server", "http://localhost:8010/v1", "", "openai", ""),
|
||||
("Speaches (docker)", "http://localhost:8080/v1", "", "openai", ""),
|
||||
("whisper.cpp server", "http://localhost:8081/v1", "", "openai", ""),
|
||||
("WhisperX server", "http://localhost:8081/transcribe","", "openai", "whisper-large-v3"),
|
||||
("NVIDIA NIM / Parakeet", "http://localhost:8007/v1", "", "openai", ""),
|
||||
("Realtime (Riva / NIM)", "http://localhost:8006/v1", "", "riva_realtime", ""),
|
||||
("faster-whisper-server", "http://localhost:8010/v1", "", "openai", "", 30),
|
||||
("Speaches (docker)", "http://localhost:8080/v1", "", "openai", "", 30),
|
||||
("whisper.cpp server", "http://localhost:8081/v1", "", "openai", "", 30),
|
||||
("WhisperX server", "http://localhost:8081/transcribe","", "openai", "whisper-large-v3", 120),
|
||||
("NVIDIA NIM / Parakeet", "http://localhost:8007/v1", "", "openai", "", 30),
|
||||
("Realtime (Riva / NIM)", "http://localhost:8006/v1", "", "riva_realtime", "", 30),
|
||||
# ── Built-in local models (no server needed) ──────────────────────────────
|
||||
("Local tiny — fastest, ~39 MB", "", "", "local", "tiny"),
|
||||
("Local base — fast, ~74 MB", "", "", "local", "base"),
|
||||
("Local small — balanced, ~244 MB", "", "", "local", "small"),
|
||||
("Local medium — accurate, ~769 MB","", "", "local", "medium"),
|
||||
("Local large-v3 — best, ~1.5 GB", "", "", "local", "large-v3"),
|
||||
("Local tiny — fastest, ~39 MB", "", "", "local", "tiny", 30),
|
||||
("Local base — fast, ~74 MB", "", "", "local", "base", 30),
|
||||
("Local small — balanced, ~244 MB", "", "", "local", "small", 30),
|
||||
("Local medium — accurate, ~769 MB","", "", "local", "medium", 30),
|
||||
("Local large-v3 — best, ~1.5 GB", "", "", "local", "large-v3", 30),
|
||||
]
|
||||
|
||||
# (cat_emoji, label, [emojis]) — standard Unicode categories, WhatsApp-style
|
||||
@ -1192,6 +1193,10 @@ notebook.bt-nb tab:checked label {
|
||||
"For a server, press ⟳ to fetch available models from its URL.")
|
||||
self.stt_key = _labeled(cfg_card, "API key env", _entry(placeholder="e.g. GROQ_API_KEY (optional)"),
|
||||
tooltip="Name of the environment variable that holds your API key. Leave blank for local servers.")
|
||||
self.stt_timeout = _labeled(cfg_card, "Timeout (s)", _entry(placeholder="30"),
|
||||
tooltip="Seconds to wait for a transcription response. "
|
||||
"Increase for slow servers (e.g. WhisperX with diarization can take 60–120 s). "
|
||||
"Default: 30.")
|
||||
self.stt_url.connect("changed", lambda _e: self._schedule_models("stt"))
|
||||
self.stt_key.connect("changed", lambda _e: self._schedule_models("stt"))
|
||||
self.stt_type.connect("changed", self._stt_type_changed)
|
||||
@ -1284,6 +1289,7 @@ notebook.bt-nb tab:checked label {
|
||||
self.stt_name.set_text(e.name)
|
||||
self.stt_type.set_active(next((i for i, (k, _) in enumerate(_STT_TYPES) if k == e.type), 0))
|
||||
self.stt_url.set_text(e.url); self.stt_key.set_text(e.api_key_env)
|
||||
self.stt_timeout.set_text("" if e.timeout == 30 else str(e.timeout))
|
||||
if e.type == "local":
|
||||
_fill_combo(self.stt_model, ["tiny", "base", "small", "medium", "large-v3"], e.model or self.cfg.model)
|
||||
elif e.type == "openai":
|
||||
@ -1320,6 +1326,10 @@ notebook.bt-nb tab:checked label {
|
||||
e.url = self.stt_url.get_text().strip().rstrip("/")
|
||||
e.model = _combo_text(self.stt_model)
|
||||
e.api_key_env = self.stt_key.get_text().strip()
|
||||
try:
|
||||
e.timeout = max(5, int(self.stt_timeout.get_text().strip() or "30"))
|
||||
except ValueError:
|
||||
e.timeout = 30
|
||||
if self.stt_combo.get_active_text() != new_name:
|
||||
self.stt_combo.remove(idx); self.stt_combo.insert_text(idx, new_name); self.stt_combo.set_active(idx)
|
||||
|
||||
@ -1452,11 +1462,12 @@ notebook.bt-nb tab:checked label {
|
||||
menu.popup_at_widget(btn, Gdk.Gravity.SOUTH_WEST, Gdk.Gravity.NORTH_WEST, None)
|
||||
|
||||
def _stt_apply_template(self, tpl: tuple) -> None:
|
||||
name, url, key, type_key, model = tpl
|
||||
name, url, key, type_key, model, timeout = tpl
|
||||
self.stt_name.set_text(name)
|
||||
self.stt_url.set_text(url)
|
||||
self.stt_key.set_text(key)
|
||||
self.stt_type.set_active(next((i for i, (k, _) in enumerate(_STT_TYPES) if k == type_key), 0))
|
||||
self.stt_timeout.set_text("" if timeout == 30 else str(timeout))
|
||||
if model:
|
||||
_fill_combo(self.stt_model, [model], model)
|
||||
elif url:
|
||||
@ -2087,6 +2098,12 @@ notebook.bt-nb tab:checked label {
|
||||
col.set_expand(expand)
|
||||
if max_w:
|
||||
col.set_max_width(max_w)
|
||||
if i == 8:
|
||||
col.set_widget(Gtk.Label(label="RAM (MB)", tooltip_text=
|
||||
"RAM loaded by this engine during the benchmark run (local engines only).\n"
|
||||
"'server' = model runs in a container or remote server — memory is not visible here.\n"
|
||||
"'—' = local model was already loaded, no delta to measure."))
|
||||
col.get_widget().show()
|
||||
tree.append_column(col)
|
||||
if i in (6, 7, 8): # Time, Accuracy, RAM — sort numerically
|
||||
bench_sort.set_sort_func(i, _num_cmp, i)
|
||||
|
||||
@ -27,6 +27,7 @@ class STTEngine:
|
||||
url: str = "" # base URL incl. /v1 for remote, e.g. http://localhost:8010/v1
|
||||
model: str = "" # remote model id, or local whisper size override
|
||||
api_key_env: str = "" # env var holding a bearer key (optional)
|
||||
timeout: int = 30 # HTTP timeout in seconds; raise for slow servers (WhisperX, etc.)
|
||||
|
||||
@property
|
||||
def is_local(self) -> bool:
|
||||
@ -204,7 +205,8 @@ def transcribe(
|
||||
return local_transcriber.transcribe(audio_path, language=language, hotwords=hotwords)
|
||||
if engine.is_streaming:
|
||||
raise STTError("Streaming STT engines are live-only. Use a workflow with mode = \"stream\".")
|
||||
return _transcribe_remote(engine, audio_path, language=language, prompt=hotwords, timeout=timeout)
|
||||
return _transcribe_remote(engine, audio_path, language=language, prompt=hotwords,
|
||||
timeout=engine.timeout if engine.timeout > 0 else timeout)
|
||||
|
||||
|
||||
def _transcribe_remote(engine: STTEngine, audio_path: Path, *, language: str, prompt: str, timeout: int) -> str:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user