public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: matiwari <matiwari@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/ibus-speech-to-text] rawhide: update to release 1.1.0
Date: Mon, 21 Sep 2026 21:59:08 GMT [thread overview]
Message-ID: <179002794845.1.11251679282596415016.rpms-ibus-speech-to-text-38f002b367ae@fedoraproject.org> (raw)
A new commit has been pushed.
Repo : rpms/ibus-speech-to-text
Branch : rawhide
Commit : 38f002b367ae842015cc569d091ed0ae9bf17f62
Author : matiwari <matiwari@redhat.com>
Date : 2026-09-22T02:59:29+05:30
Stats : +8/-1469 in 4 file(s)
URL : https://src.fedoraproject.org/rpms/ibus-speech-to-text/c/38f002b367ae842015cc569d091ed0ae9bf17f62?branch=rawhide
Log:
update to release 1.1.0
---
diff --git a/.gitignore b/.gitignore
index 98a27e3..21723c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
/0.8.0.tar.gz
/0.9.0.tar.gz
/1.0.0.tar.gz
+/1.1.0.tar.gz
diff --git a/ibus-speech-to-text.spec b/ibus-speech-to-text.spec
index f856071..9d66ce8 100644
--- a/ibus-speech-to-text.spec
+++ b/ibus-speech-to-text.spec
@@ -1,13 +1,12 @@
%global debug_package %{nil}
Name: ibus-speech-to-text
-Version: 1.0.0
+Version: 1.1.0
Release: 1%{?dist}
Summary: A speech to text IBus Input Method using VOSK
ExcludeArch: %{ix86}
License: GPL-3.0-or-later
URL: https://github.com/Manish7093/IBus-Speech-To-Text
Source0: https://github.com/Manish7093/IBus-Speech-To-Text/archive/refs/tags/%{version}.tar.gz
-Patch0: ibus-stt-1_0_0-backend-notify.patch
BuildRequires: meson
BuildRequires: python3-devel
@@ -27,6 +26,7 @@ Requires: gst-vosk >= 0.3.0
Requires: gtk4
Requires: dconf
Requires: python3-pywhispercpp
+Requires: libadwaita
%description
A speech to text IBus Input Method using VOSK and WhisperCpp
@@ -34,7 +34,6 @@ which can be used to dictate text to any application
%prep
%setup -q -n IBus-Speech-To-Text-%{version}
-%patch 0 -p1 -b .orig~
%build
%meson
@@ -57,6 +56,10 @@ desktop-file-validate %{buildroot}/%{_datadir}/applications/ibus-setup-stt.deskt
%{_datadir}/glib-2.0/schemas/org.freedesktop.ibus.engine.stt.gschema.xml
%changelog
+* Tue Sep 22 2026 Manish Tiwari <matiwari@redhat.com> 1.1.0-1
+- Update to release 1.1.0
+- https://github.com/Manish7093/IBus-Speech-To-Text/releases/tag/1.1.0
+
* Fri Aug 7 2026 Manish Tiwari <matiwari@redhat.com> 1.0.0-1
- Update to release 1.0.0
- Add ibus-stt-1_0_0-backend-notify.patch to support backend notification dialog
diff --git a/ibus-stt-1_0_0-backend-notify.patch b/ibus-stt-1_0_0-backend-notify.patch
deleted file mode 100644
index f1d9c28..0000000
--- a/ibus-stt-1_0_0-backend-notify.patch
+++ /dev/null
@@ -1,1465 +0,0 @@
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/meson.build IBus-Speech-To-Text-1.0.0/engine/meson.build
---- IBus-Speech-To-Text-1.0.0.orig/engine/meson.build 2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/meson.build 2026-08-07 01:42:43.021671212 +0530
-@@ -18,6 +18,7 @@
- 'sttshortcutdialog.py',
- 'sttutterancerow.py',
- 'sttvad.py',
-+ 'sttbackenddeps.py',
- 'sttmodelchooserdialog.py',
- 'sttvoskmodelmanagers.py',
- 'sttwhispermodelmanagers.py',
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttbackenddeps.py IBus-Speech-To-Text-1.0.0/engine/sttbackenddeps.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttbackenddeps.py 1970-01-01 05:30:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttbackenddeps.py 2026-08-07 01:44:45.299598828 +0530
-@@ -0,0 +1,173 @@
-+import importlib
-+import importlib.machinery
-+import importlib.util
-+import logging
-+import sys
-+
-+LOG_MSG = logging.getLogger()
-+
-+class STTBackendDependency:
-+
-+ def __init__(self, module, distribution, extras=None):
-+ self.module = module
-+ self.distribution = distribution
-+ self.extras = tuple(extras) if extras is not None else ()
-+
-+ @property
-+ def requirement(self):
-+ if not self.extras:
-+ return self.distribution
-+ return "%s[%s]" % (self.distribution, ",".join(self.extras))
-+
-+ @property
-+ def pip_argument(self):
-+ if not self.extras:
-+ return self.distribution
-+ return "'%s'" % self.requirement
-+
-+ def available(self):
-+ #True when the module can be found on sys.path right now
-+ try:
-+ spec = importlib.machinery.PathFinder.find_spec(self.module,
-+ sys.path)
-+ except (ImportError, ValueError, TypeError, AttributeError) as error:
-+ LOG_MSG.debug("cannot look up module %s (%s)", self.module, error)
-+ return False
-+
-+ return spec is not None
-+
-+ def __repr__(self):
-+ return "<STTBackendDependency %s>" % self.requirement
-+
-+# NOTE: keep the keys in sync with the "backend" GSettings key and with the
-+# check buttons of sttconfigdialog.ui.
-+_BACKENDS = {
-+ "vosk": {
-+ "name": "Vosk",
-+ "dependencies": (),
-+ "components": {
-+ "engine": ("sttgstvosk", "STTGstVosk"),
-+ "model": ("sttvoskmodel", "STTVoskModel"),
-+ "manager": ("sttvoskmodelmanagers", "stt_vosk_online_model_manager"),
-+ },
-+ },
-+ "whisper": {
-+ "name": "Whisper",
-+ "dependencies": (),
-+ "components": {
-+ "engine": ("sttgstwhisper", "STTGstWhisper"),
-+ "model": ("sttwhispermodel", "STTWhisperModel"),
-+ "manager": ("sttwhispermodelmanagers", "stt_whisper_online_model_manager"),
-+ },
-+ },
-+ "onnxasr": {
-+ "name": "onnx-asr",
-+ "dependencies": (
-+ STTBackendDependency("onnx_asr", "onnx-asr", ("cpu", "hub")),
-+ ),
-+ "components": {
-+ "engine": ("sttgstonnxasr", "STTGstOnnxAsr"),
-+ "model": ("sttonnxasrmodel", "STTOnnxAsrModel"),
-+ "manager": ("sttonnxasrmodelmanagers", "stt_onnxasr_online_model_manager"),
-+ },
-+ },
-+ "moonshine": {
-+ "name": "Moonshine",
-+ "dependencies": (
-+ STTBackendDependency("moonshine_voice", "moonshine-voice"),
-+ ),
-+ "components": {
-+ "engine": ("sttgstmoonshine", "STTGstMoonshine"),
-+ "model": ("sttmoonshinemodel", "STTMoonshineModel"),
-+ "manager": ("sttmoonshinemodelmanagers", "stt_moonshine_online_model_manager"),
-+ },
-+ },
-+}
-+
-+STT_DEFAULT_BACKEND = "vosk"
-+
-+# Cache the lookups: they are queried from callbacks that can run on every
-+# model/locale change. Dropped by stt_invalidate_availability_cache().
-+_availability_cache = {}
-+
-+def stt_backends():
-+ return tuple(_BACKENDS.keys())
-+
-+def stt_backend_display_name(backend):
-+ entry = _BACKENDS.get(backend)
-+ if entry is None:
-+ return backend
-+ return entry["name"]
-+
-+def stt_backend_dependencies(backend):
-+ entry = _BACKENDS.get(backend)
-+ if entry is None:
-+ return ()
-+ return entry["dependencies"]
-+
-+def stt_invalidate_availability_cache():
-+ _availability_cache.clear()
-+ importlib.invalidate_caches()
-+
-+def stt_backend_missing_dependencies(backend, refresh=False):
-+ if refresh:
-+ stt_invalidate_availability_cache()
-+
-+ missing = []
-+ for dependency in stt_backend_dependencies(backend):
-+ available = _availability_cache.get(dependency.module)
-+ if available is None:
-+ available = dependency.available()
-+ _availability_cache[dependency.module] = available
-+ LOG_MSG.debug("module %s available: %s",
-+ dependency.module, available)
-+ if not available:
-+ missing.append(dependency)
-+
-+ return missing
-+
-+def stt_backend_is_available(backend, refresh=False):
-+ return not stt_backend_missing_dependencies(backend, refresh=refresh)
-+
-+def stt_backend_install_command(backend):
-+ missing = stt_backend_missing_dependencies(backend)
-+ if not missing:
-+ return ""
-+
-+ return "pip install %s" % " ".join(
-+ dependency.pip_argument for dependency in missing)
-+
-+def stt_backend_component(backend, component):
-+ entry = _BACKENDS.get(backend)
-+ if entry is None:
-+ LOG_MSG.warning("unknown backend (%s)", backend)
-+ return None
-+
-+ location = entry["components"].get(component)
-+ if location is None:
-+ return None
-+
-+ module_name, attribute = location
-+ try:
-+ module = importlib.import_module(module_name)
-+ except Exception as error:
-+ LOG_MSG.warning("cannot import %s for backend %s (%s)",
-+ module_name, backend, error)
-+ return None
-+
-+ value = getattr(module, attribute, None)
-+ if value is None:
-+ LOG_MSG.warning("%s has no attribute %s", module_name, attribute)
-+ return value
-+
-+def stt_backend_model_manager(backend):
-+ manager = stt_backend_component(backend, "manager")
-+ if manager is None:
-+ return None
-+
-+ try:
-+ return manager()
-+ except Exception as error:
-+ LOG_MSG.warning("cannot create model manager for %s (%s)",
-+ backend, error)
-+ return None
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttconfigdialog.py IBus-Speech-To-Text-1.0.0/engine/sttconfigdialog.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttconfigdialog.py 2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttconfigdialog.py 2026-08-07 01:45:25.241228485 +0530
-@@ -27,27 +27,23 @@
- gi.require_version('Gtk', '4.0')
- gi.require_version('Adw', '1')
-
--from gi.repository import Gtk, Gio, Adw
-+from gi.repository import Gtk, Gio, Gdk, GObject, Adw
-
- from sttutils import *
- from sttshortcutrow import STTShortcutRow
- from sttshortcutdialog import STTShortcutDialog
-
- from sttcurrentlocale import stt_current_locale
--from sttvoskmodelmanagers import stt_vosk_online_model_manager
--from sttwhispermodelmanagers import stt_whisper_online_model_manager
--from sttonnxasrmodelmanagers import stt_onnxasr_online_model_manager
--from sttmoonshinemodelmanagers import stt_moonshine_online_model_manager
--from sttvoskmodel import STTVoskModel
--from sttwhispermodel import STTWhisperModel
--from sttonnxasrmodel import STTOnnxAsrModel
--from sttmoonshinemodel import STTMoonshineModel
- from sttmodelchooserdialog import STTModelChooserDialog
-
--from sttgstvosk import STTGstVosk
--from sttgstwhisper import STTGstWhisper
--from sttgstonnxasr import STTGstOnnxAsr
--from sttgstmoonshine import STTGstMoonshine
-+from sttbackenddeps import (STT_DEFAULT_BACKEND,
-+ stt_backend_component,
-+ stt_backend_display_name,
-+ stt_backend_install_command,
-+ stt_backend_is_available,
-+ stt_backend_missing_dependencies,
-+ stt_backend_model_manager,
-+ stt_invalidate_availability_cache)
-
- LOG_MSG=logging.getLogger()
-
-@@ -64,6 +60,11 @@
- onnxasr_check = Gtk.Template.Child()
- moonshine_check = Gtk.Template.Child()
-
-+ vosk_action_row = Gtk.Template.Child()
-+ whisper_action_row = Gtk.Template.Child()
-+ onnxasr_action_row = Gtk.Template.Child()
-+ moonshine_action_row = Gtk.Template.Child()
-+
- tab_stack = Gtk.Template.Child()
- tab_switcher = Gtk.Template.Child()
-
-@@ -102,18 +103,20 @@
- self._utterances_dict = {}
- self._no_model_toast = None
- self._unsupported_locale_toast = None
-+ self._missing_deps_toast = None
- self._model = None
- self._suppress_language_cb = False
-+ self._suppress_engine_cb = False
- self._engine = None
-
- self._settings=Gio.Settings.new("org.freedesktop.ibus.engine.stt")
- self._settings.bind("preload", self.preload_model_switch, "active", Gio.SettingsBindFlags.DEFAULT)
- self._settings.bind("active-on-start", self.active_on_start_switch, "active", Gio.SettingsBindFlags.DEFAULT)
-
-- stt_vosk_online_model_manager()
-- stt_whisper_online_model_manager()
-- stt_onnxasr_online_model_manager()
-- stt_moonshine_online_model_manager()
-+ self._backend_subtitles = {
-+ backend: (row.get_subtitle() or "")
-+ for backend, row in self._backend_action_rows().items()
-+ }
-
- # Load current locale
- self._current_locale = stt_current_locale()
-@@ -121,25 +124,11 @@
- self._override_file_changed_id=self._current_locale.connect("override-file-changed", self._override_file_changed_cb)
- self._override_file_written=False
-
-- backend = self._settings.get_string("backend")
-- self._suppress_engine_cb = True
-- if backend == "whisper":
-- self.whisper_check.set_active(True)
-- elif backend == "onnxasr":
-- self.onnxasr_check.set_active(True)
-- elif backend == "moonshine":
-- self.moonshine_check.set_active(True)
-- else:
-- self.vosk_check.set_active(True)
-- self._suppress_engine_cb = False
-+ self._select_backend_button(self._backend())
-
- self._locale_list = []
- self._locale_names = Gtk.StringList()
-- self._populate_locale_list()
-- self._suppress_language_cb = True
-- self.language_dropdown.set_model(self._locale_names)
-- self._suppress_language_cb = False
-- self._select_current_locale_in_dropdown()
-+ self._refresh_locale_dropdown()
-
- if self._current_locale.default_locale:
- self._suppress_language_cb = True
-@@ -156,17 +145,81 @@
-
- self._update_voice_commands_visibility()
-
-- if self._model is None or not self._model.available():
-- self._engine_has_no_model()
-- elif self._valid_formatting_file == False:
-- self._unsupported_locale()
--
- self._toast_action=Gio.SimpleAction.new("manage_model", None)
-+ self._install_action=Gio.SimpleAction.new("install_backend", None)
- action_group=Gio.SimpleActionGroup.new()
- action_group.insert(self._toast_action)
-+ action_group.insert(self._install_action)
- self.insert_action_group("toast", action_group)
- self._toast_action.connect("activate",
- self._manage_model_action_activated)
-+ self._install_action.connect("activate",
-+ self._install_action_activated)
-+
-+ self._update_backend_rows()
-+ self._refresh_status_messages()
-+ self._backend_was_available = stt_backend_is_available(self._backend())
-+ self.connect("notify::is-active", self._window_active_cb)
-+
-+ def _window_active_cb(self, *_args):
-+ if not self.get_property("is-active"):
-+ return
-+
-+ stt_invalidate_availability_cache()
-+ available = stt_backend_is_available(self._backend())
-+ if available == self._backend_was_available:
-+ self._update_backend_rows()
-+ return
-+
-+ self._backend_was_available = available
-+ self._reload_backend()
-+
-+ def _backend(self):
-+ backend = self._settings.get_string("backend")
-+ if backend in ("", None):
-+ return STT_DEFAULT_BACKEND
-+ return backend
-+
-+ def _backend_check_buttons(self):
-+ return {
-+ "vosk": self.vosk_check,
-+ "whisper": self.whisper_check,
-+ "onnxasr": self.onnxasr_check,
-+ "moonshine": self.moonshine_check,
-+ }
-+
-+ def _backend_action_rows(self):
-+ return {
-+ "vosk": self.vosk_action_row,
-+ "whisper": self.whisper_action_row,
-+ "onnxasr": self.onnxasr_action_row,
-+ "moonshine": self.moonshine_action_row,
-+ }
-+
-+ def _backend_for_check_button(self, button):
-+ for backend, check in self._backend_check_buttons().items():
-+ if check == button:
-+ return backend
-+ return None
-+
-+ def _select_backend_button(self, backend):
-+ button = self._backend_check_buttons().get(backend)
-+ if button is None:
-+ button = self.vosk_check
-+
-+ self._suppress_engine_cb = True
-+ button.set_active(True)
-+ self._suppress_engine_cb = False
-+
-+ def _update_backend_rows(self):
-+ for backend, row in self._backend_action_rows().items():
-+ subtitle = self._backend_subtitles.get(backend, "")
-+ if stt_backend_is_available(backend):
-+ row.set_subtitle(subtitle)
-+ elif subtitle:
-+ row.set_subtitle(_("%s · Not installed") % subtitle)
-+ else:
-+ row.set_subtitle(_("Not installed"))
-
-
- def _create_engine(self):
-@@ -180,15 +233,23 @@
- self._engine.destroy()
- self._engine = None
-
-- backend = self._settings.get_string("backend")
-- if backend == "whisper":
-- self._engine = STTGstWhisper(current_locale=self._current_locale)
-- elif backend == "onnxasr":
-- self._engine = STTGstOnnxAsr(current_locale=self._current_locale)
-- elif backend == "moonshine":
-- self._engine = STTGstMoonshine(current_locale=self._current_locale)
-- else:
-- self._engine = STTGstVosk(current_locale=self._current_locale)
-+ backend = self._backend()
-+ if not stt_backend_is_available(backend):
-+ LOG_MSG.warning("backend %s is missing its dependencies, "
-+ "no engine created", backend)
-+ return
-+
-+ engine_class = stt_backend_component(backend, "engine")
-+ if engine_class is None:
-+ return
-+
-+ try:
-+ self._engine = engine_class(current_locale=self._current_locale)
-+ except Exception as error:
-+ LOG_MSG.error("cannot create engine for backend %s (%s)",
-+ backend, error)
-+ self._engine = None
-+ return
-
- self._engine.connect("model-changed", self._engine_model_changed_cb)
- self._engine.preload()
-@@ -204,11 +265,13 @@
- and self._current_locale.locale not in self._locale_list):
- self._append_locale_option(self._current_locale.locale)
-
-- backend = self._settings.get_string("backend")
-+ backend = self._backend()
- if backend == "moonshine":
-- supported = stt_moonshine_online_model_manager().supported_locales()
-+ manager = stt_backend_model_manager("moonshine")
- else:
-- supported = stt_vosk_online_model_manager().supported_locales()
-+ manager = stt_backend_model_manager("vosk")
-+
-+ supported = manager.supported_locales() if manager is not None else []
-
- _EXCLUDED = {"multilingual"}
-
-@@ -218,6 +281,16 @@
- if loc not in self._locale_list:
- self._append_locale_option(loc)
-
-+ def _refresh_locale_dropdown(self):
-+ # Rebuild the language list and hand it to the dropdown.
-+ self._populate_locale_list()
-+
-+ self._suppress_language_cb = True
-+ self.language_dropdown.set_model(self._locale_names)
-+ self._suppress_language_cb = False
-+
-+ self._select_current_locale_in_dropdown()
-+
- def _append_locale_option(self, locale_str):
- if locale_str in (None, "", "None", "multilingual"):
- return
-@@ -253,23 +326,36 @@
- self._model.disconnect_by_func(self._model_changed_cb)
- except TypeError:
- pass
-+ self._model = None
-
-- backend = self._settings.get_string("backend")
-+ backend = self._backend()
- locale_str = self._current_locale.locale
-
-- if backend == "whisper":
-- self._model = STTWhisperModel(locale_str=locale_str)
-- elif backend == "onnxasr":
-- self._model = STTOnnxAsrModel(locale_str=locale_str)
-- elif backend == "moonshine":
-- self._model = STTMoonshineModel(locale_str=locale_str)
-- else:
-- self._model = STTVoskModel(locale_str=locale_str)
-+ if stt_backend_is_available(backend):
-+ model_class = stt_backend_component(backend, "model")
-+ if model_class is not None:
-+ try:
-+ self._model = model_class(locale_str=locale_str)
-+ except Exception as error:
-+ LOG_MSG.error("cannot create model for backend %s (%s)",
-+ backend, error)
-+
-+ if self._model is not None:
-+ self._model.connect("changed", self._model_changed_cb)
-
-- self._model.connect("changed", self._model_changed_cb)
- self._update_model_info()
-
- def _update_model_info(self):
-+ backend = self._backend()
-+
-+ if stt_backend_missing_dependencies(backend):
-+ self.model_info_row.set_title(
-+ _("%s is not installed") % stt_backend_display_name(backend))
-+ self.model_info_row.set_subtitle(
-+ stt_backend_install_command(backend))
-+ self.change_model_button.set_label(_("Install…"))
-+ return
-+
- if self._model is None or not self._model.available():
- self.model_info_row.set_title(_("No model downloaded"))
- self.model_info_row.set_subtitle(
-@@ -288,16 +374,8 @@
- else _("Installed manually"))
- return
-
-- backend = self._settings.get_string("backend")
-- if backend == "whisper":
-- manager = stt_whisper_online_model_manager()
-- elif backend == "onnxasr":
-- manager = stt_onnxasr_online_model_manager()
-- elif backend == "moonshine":
-- manager = stt_moonshine_online_model_manager()
-- else:
-- manager = stt_vosk_online_model_manager()
-- desc = manager.get_model_description(model_name)
-+ manager = stt_backend_model_manager(backend)
-+ desc = manager.get_model_description(model_name) if manager else None
-
- self.model_info_row.set_title(model_name)
-
-@@ -347,6 +425,9 @@
- self.model_info_row.set_subtitle(size)
-
- def _auto_prompt_model_download(self):
-+ if not stt_backend_is_available(self._backend()):
-+ return
-+
- if self._model is not None and not self._model.available():
- dialog = STTModelChooserDialog(model=self._model)
- dialog.set_transient_for(self)
-@@ -355,9 +436,134 @@
- def _model_changed_cb(self, model):
- self._update_model_info()
-
-+ def _copy_to_clipboard(self, text):
-+ clipboard = self.get_clipboard()
-+ if clipboard is None:
-+ return
-+
-+ try:
-+ clipboard.set_content(
-+ Gdk.ContentProvider.new_for_value(GObject.Value(str, text)))
-+ except Exception as error:
-+ LOG_MSG.warning("cannot copy to clipboard (%s)", error)
-+
-+ def _install_dialog_body(self, backend, missing):
-+ modules = "\n".join(" • %s" % dep.requirement for dep in missing)
-+ return _("The %(backend)s backend needs python modules that are not "
-+ "installed on this system:\n\n"
-+ "%(modules)s\n\n"
-+ "Install them in a terminal with the following command, "
-+ "then press “Check Again”:\n\n"
-+ " %(command)s") % {
-+ "backend": stt_backend_display_name(backend),
-+ "modules": modules,
-+ "command": stt_backend_install_command(backend)}
-+
-+ def _present_install_dialog(self, backend):
-+ missing = stt_backend_missing_dependencies(backend, refresh=True)
-+ if not missing:
-+ return False
-+
-+ heading = _("%s Is Not Installed") % stt_backend_display_name(backend)
-+ body = self._install_dialog_body(backend, missing)
-+ command = stt_backend_install_command(backend)
-+
-+ # Adw.AlertDialog needs libadwaita 1.5, fall back on Adw.MessageDialog.
-+ if hasattr(Adw, "AlertDialog"):
-+ dialog = Adw.AlertDialog(heading=heading, body=body)
-+ self._setup_install_dialog(dialog, backend, command)
-+ dialog.present(self)
-+ else:
-+ dialog = Adw.MessageDialog(transient_for=self, modal=True,
-+ heading=heading, body=body)
-+ self._setup_install_dialog(dialog, backend, command)
-+ dialog.present()
-+
-+ return True
-+
-+ def _setup_install_dialog(self, dialog, backend, command):
-+ dialog.add_response("close", _("Close"))
-+ dialog.add_response("copy", _("Copy Command"))
-+ dialog.add_response("recheck", _("Check Again"))
-+ dialog.set_response_appearance("recheck",
-+ Adw.ResponseAppearance.SUGGESTED)
-+ dialog.set_default_response("recheck")
-+ dialog.set_close_response("close")
-+ dialog.connect("response", self._install_dialog_response_cb,
-+ backend, command)
-+
-+ def _install_dialog_response_cb(self, dialog, response, backend, command):
-+ if response == "copy":
-+ self._copy_to_clipboard(command)
-+ self.toast_overlay.add_toast(
-+ Adw.Toast(title=_("Command copied to the clipboard"),
-+ timeout=3))
-+ return
-+
-+ if response != "recheck":
-+ return
-+
-+ if not stt_backend_is_available(backend, refresh=True):
-+ self.toast_overlay.add_toast(
-+ Adw.Toast(title=_("%s is still not installed")
-+ % stt_backend_display_name(backend), timeout=5))
-+ return
-+
-+ self._update_backend_rows()
-+ if backend != self._backend():
-+ self._apply_backend(backend)
-+ else:
-+ self._reload_backend()
-+
-+ def _install_action_activated(self, _action, _param):
-+ self._present_install_dialog(self._backend())
-+
-+ def _backend_not_installed(self):
-+ if self._missing_deps_toast != None:
-+ return
-+
-+ if self._no_model_toast != None:
-+ self._no_model_toast.dismiss()
-+ self._no_model_toast = None
-+ if self._unsupported_locale_toast != None:
-+ self._unsupported_locale_toast.dismiss()
-+ self._unsupported_locale_toast = None
-+
-+ self._missing_deps_toast = Adw.Toast(
-+ title=_("%s is not installed")
-+ % stt_backend_display_name(self._backend()),
-+ timeout=0,
-+ button_label=_("How to Install"),
-+ action_name="toast.install_backend")
-+ self._missing_deps_toast.connect("dismissed", self._toast_dismissed)
-+ self.toast_overlay.add_toast(self._missing_deps_toast)
-+
-+ def _refresh_status_messages(self):
-+ if not stt_backend_is_available(self._backend()):
-+ self._backend_not_installed()
-+ return
-+
-+ if self._missing_deps_toast != None:
-+ self._missing_deps_toast.dismiss()
-+ self._missing_deps_toast = None
-+
-+ if (self._model is None or not self._model.available()
-+ or self._engine is None or not self._engine.has_model()):
-+ self._engine_has_no_model()
-+ return
-+
-+ if self._no_model_toast != None:
-+ self._no_model_toast.dismiss()
-+ self._no_model_toast = None
-+
-+ if self._valid_formatting_file == False:
-+ self._unsupported_locale()
-+ elif self._unsupported_locale_toast != None:
-+ self._unsupported_locale_toast.dismiss()
-+ self._unsupported_locale_toast = None
-
- def _update_voice_commands_visibility(self):
-- is_vosk = (self._settings.get_string("backend") == "vosk")
-+ is_vosk = (self._backend() == "vosk")
- self.vc_whisper_warning.set_visible(not is_vosk)
- self.voice_commands_group.set_visible(is_vosk)
-
-@@ -372,40 +578,40 @@
- if getattr(self, '_suppress_engine_cb', False):
- return
-
-- if button == self.vosk_check:
-- backend = "vosk"
-- elif button == self.moonshine_check:
-- backend = "moonshine"
-- elif button == self.whisper_check:
-- backend = "whisper"
-- else:
-- backend = "onnxasr"
-+ backend = self._backend_for_check_button(button)
-+ if backend is None:
-+ return
-
-- current = self._settings.get_string("backend")
-+ current = self._backend()
- if backend == current:
- return
-
-- self._settings.set_string("backend", backend)
-- old_locale = self._current_locale.locale
-- self._populate_locale_list()
-- self._suppress_language_cb = True
-- self.language_dropdown.set_model(self._locale_names)
-- self._suppress_language_cb = False
-+ if not stt_backend_is_available(backend, refresh=True):
-+ self._select_backend_button(current)
-+ self._update_backend_rows()
-+ self._present_install_dialog(backend)
-+ return
-
-- if old_locale in self._locale_list:
-- self._suppress_language_cb = True
-- self.language_dropdown.set_selected(
-- self._locale_list.index(old_locale))
-- self._suppress_language_cb = False
-+ self._apply_backend(backend)
-
-- self._init_model()
-+ def _apply_backend(self, backend):
-+ self._settings.set_string("backend", backend)
-+ self._select_backend_button(backend)
-+ self._reload_backend(prompt_download=True)
-
-+ def _reload_backend(self, prompt_download=False):
-+ self._refresh_locale_dropdown()
-+ self._init_model()
- self._create_engine()
- self._update_voice_commands_visibility()
- self._empty_shortcut_page()
- self._load_utterances()
-+ self._update_backend_rows()
-+ self._refresh_status_messages()
-
-- if self._model is not None and not self._model.available():
-+ if (prompt_download
-+ and self._model is not None
-+ and not self._model.available()):
- self._auto_prompt_model_download()
-
- @Gtk.Template.Callback()
-@@ -441,6 +647,14 @@
-
- @Gtk.Template.Callback()
- def change_model_clicked_cb(self, *_args):
-+ backend = self._backend()
-+ if not stt_backend_is_available(backend, refresh=True):
-+ self._present_install_dialog(backend)
-+ return
-+
-+ if self._model is None:
-+ self._reload_backend()
-+
- if self._model != None:
- dialog = STTModelChooserDialog(model=self._model)
- dialog.set_transient_for(self)
-@@ -458,11 +672,7 @@
- self._select_current_locale_in_dropdown()
-
- if self._current_locale.locale not in self._locale_list:
-- self._populate_locale_list()
-- self._suppress_language_cb = True
-- self.language_dropdown.set_model(self._locale_names)
-- self._suppress_language_cb = False
-- self._select_current_locale_in_dropdown()
-+ self._refresh_locale_dropdown()
-
- self._init_model()
- self._load_current_locale()
-@@ -541,20 +751,7 @@
- def _load_current_locale(self):
- self._empty_shortcut_page()
- self._load_utterances()
--
-- if not self._engine.has_model():
-- self._engine_has_no_model()
-- return
--
-- if self._no_model_toast != None:
-- self._no_model_toast.dismiss()
-- self._no_model_toast = None
--
-- if not self._valid_formatting_file:
-- self._unsupported_locale()
-- elif self._unsupported_locale_toast != None:
-- self._unsupported_locale_toast.dismiss()
-- self._unsupported_locale_toast = None
-+ self._refresh_status_messages()
-
- def _apply_change(self):
- LOG_MSG.debug("override file being written")
-@@ -608,6 +805,10 @@
- self._present_shortcut_dialog(row)
-
- def _present_shortcut_dialog(self, row):
-+ if self._engine is None:
-+ LOG_MSG.warning("no engine available, cannot edit shortcuts")
-+ return
-+
- dialog = STTShortcutDialog(
- row=row, engine=self._engine, transient_for=self)
- dialog.connect("response", self._shortcut_dialog_response_cb)
-@@ -781,7 +982,9 @@
- self._auto_prompt_model_download()
-
- def _toast_dismissed(self, toast):
-- if toast == self._no_model_toast:
-+ if toast == self._missing_deps_toast:
-+ self._missing_deps_toast=None
-+ elif toast == self._no_model_toast:
- self._no_model_toast=None
-
- # Display the other message if needed
-@@ -792,11 +995,14 @@
-
- def _unsupported_locale(self):
- # Careful: we can have no formatting file but an overriding one !!
-+ if self._missing_deps_toast != None:
-+ return
-+
- if self._no_model_toast != None:
- return
-
- # Formatting files only exist for vosk; other engines have no files to find
-- if self._settings.get_string("backend") != "vosk":
-+ if self._backend() != "vosk":
- if self._unsupported_locale_toast is not None:
- self._unsupported_locale_toast.dismiss()
- self._unsupported_locale_toast = None
-@@ -816,6 +1022,9 @@
- self.toast_overlay.add_toast(self._unsupported_locale_toast)
-
- def _engine_has_no_model(self):
-+ if self._missing_deps_toast != None:
-+ return
-+
- if self._no_model_toast != None:
- return
-
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttgstfactory.py IBus-Speech-To-Text-1.0.0/engine/sttgstfactory.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttgstfactory.py 2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttgstfactory.py 2026-08-07 01:46:01.026792604 +0530
-@@ -29,6 +29,10 @@
- from sttgstonnxasr import STTGstOnnxAsr
- from sttgstmoonshine import STTGstMoonshine
-
-+from sttbackenddeps import (stt_backend_display_name,
-+ stt_backend_install_command,
-+ stt_backend_is_available)
-+
- LOG_MSG=logging.getLogger()
-
- class STTGstFactory(GObject.GObject):
-@@ -45,11 +49,21 @@
- self.__update_preloaded_engine()
-
- def new_engine(self):
-- engine=None if self._current_engine is None else self._current_engine()
-+ engine = None if self._current_engine is None else self._current_engine()
-+ if engine is not None and engine.pipeline is None:
-+ LOG_MSG.debug("cached engine was already destroyed, creating a new one")
-+ engine = None
- if engine is None:
- LOG_MSG.debug("new engine")
- # Check backend setting
- backend = self.__settings.get_string("backend")
-+ if not stt_backend_is_available(backend):
-+ LOG_MSG.error("%s backend is selected but not installed. "
-+ "Install it with: %s",
-+ stt_backend_display_name(backend),
-+ stt_backend_install_command(backend))
-+ LOG_MSG.error("falling back to the Vosk backend")
-+ backend = "vosk"
- if backend == "whisper":
- LOG_MSG.info("Using Whisper backend")
- engine=STTGstWhisper()
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttgstmoonshine.py IBus-Speech-To-Text-1.0.0/engine/sttgstmoonshine.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttgstmoonshine.py 2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttgstmoonshine.py 2026-08-07 01:38:55.356082244 +0530
-@@ -13,38 +13,39 @@
-
- SAMPLE_RATE = 16000
-
--try:
-- from moonshine_voice import (
-- Transcriber,
-- TranscriptEventListener,
-- ModelArch,
-- )
-- MOONSHINE_AVAILABLE = True
--except ImportError:
-- LOG_MSG.warning("moonshine_voice not available. Install with: pip install moonshine-voice")
-- MOONSHINE_AVAILABLE = False
-- TranscriptEventListener = object
-+_LISTENER_CLASS = None
-
--class _LineListener(TranscriptEventListener):
-+def _line_listener_class():
-+ #Build the listener subclass on first use
-+ global _LISTENER_CLASS
-+ if _LISTENER_CLASS is not None:
-+ return _LISTENER_CLASS
-
-- def __init__(self, engine):
-- if MOONSHINE_AVAILABLE:
-+ from moonshine_voice import TranscriptEventListener
-+
-+ class _LineListener(TranscriptEventListener):
-+
-+ def __init__(self, engine):
- super().__init__()
-- self._engine = engine
-- self._emitted_lines = []
-+ self._engine = engine
-+ self._emitted_lines = []
-
-- def reset(self):
-- self._emitted_lines.clear()
-+ def reset(self):
-+ self._emitted_lines.clear()
-+
-+ def on_line_completed(self, event):
-+ line = event.line
-+ if any(line is seen for seen in self._emitted_lines):
-+ return
-+ self._emitted_lines.append(line)
-+ text = (line.text or "").strip()
-+ if text:
-+ LOG_MSG.info("Moonshine transcription result: '%s'", text)
-+ GLib.idle_add(self._engine._emit_text, text)
-+
-+ _LISTENER_CLASS = _LineListener
-+ return _LISTENER_CLASS
-
-- def on_line_completed(self, event):
-- line = event.line
-- if any(line is seen for seen in self._emitted_lines):
-- return
-- self._emitted_lines.append(line)
-- text = (line.text or "").strip()
-- if text:
-- LOG_MSG.info("Moonshine transcription result: '%s'", text)
-- GLib.idle_add(self._engine._emit_text, text)
-
- class STTGstMoonshine(STTGstBase):
- __gtype_name__ = 'STTGstMoonshine'
-@@ -93,6 +94,7 @@
- self._tx_lock = threading.Lock()
- self._session_active = False
- self._stopping = False
-+ self._destroyed = False
-
- self._process_queue = queue.Queue()
- self._stop_processing = False
-@@ -103,11 +105,11 @@
-
- def __del__(self):
- try:
-- if LOG_MSG is not None:
-- LOG_MSG.info("Moonshine __del__")
- self._stop_processing = True
-- if self._process_thread is not None:
-- self._process_thread.join(timeout=2.0)
-+ thread = self._process_thread
-+ self._process_thread = None
-+ if thread is not None:
-+ thread.join(timeout=2.0)
- except Exception:
- pass
- try:
-@@ -116,24 +118,36 @@
- pass
-
- def destroy(self):
-+ if self._destroyed:
-+ LOG_MSG.debug("Moonshine engine already destroyed")
-+ return
-+ self._destroyed = True
-+
- self._stop_processing = True
-- if self._process_thread is not None:
-- self._process_thread.join(timeout=2.0)
-
-- self._current_locale.disconnect(self._locale_id)
-- self._locale_id = 0
-+ thread = self._process_thread
-+ self._process_thread = None
-+ if thread is not None:
-+ thread.join(timeout=2.0)
-+ if thread.is_alive():
-+ LOG_MSG.warning("Moonshine worker thread did not stop in time")
-+
-+ if self._locale_id != 0:
-+ self._current_locale.disconnect(self._locale_id)
-+ self._locale_id = 0
-
-- if self._model_id != 0:
-+ if self._model_id != 0 and self._model is not None:
- self._model.disconnect(self._model_id)
- self._model_id = 0
-
- with self._tx_lock:
- self._teardown_transcriber()
-
-- if self._appsink is not None and getattr(self, "_new_sample_id", 0) !=0:
-- self._appsink.disconnect(self._new_sample_id)
-- self._new_sample_id = 0
-+ if self._appsink is not None and self._on_new_sample_id != 0:
-+ self._appsink.disconnect(self._on_new_sample_id)
-+ self._on_new_sample_id = 0
- self._appsink = None
-+
- LOG_MSG.info("Moonshine.destroy() called")
- super().destroy()
-
-@@ -150,14 +164,18 @@
- self._listener = None
-
- def _load_moonshine_model(self, model_path, model_arch):
-- if not MOONSHINE_AVAILABLE:
-- LOG_MSG.error("moonshine_voice not available")
-+ try:
-+ from moonshine_voice import ModelArch, Transcriber
-+ except Exception as e:
-+ LOG_MSG.error("moonshine_voice not available (%s). "
-+ "Install with: pip install moonshine-voice", e)
- return False
-+
- try:
-- arch = model_arch if model_arch is not None else ModelArch.BASE
-+ arch = (ModelArch(model_arch) if model_arch is not None else ModelArch.BASE)
- LOG_MSG.info("Loading Moonshine model: %s (arch=%s)", model_path, arch)
- transcriber = Transcriber(model_path=model_path, model_arch=arch)
-- listener = _LineListener(self)
-+ listener = _line_listener_class()(self)
- transcriber.add_listener(listener)
- with self._tx_lock:
- self._teardown_transcriber()
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttmoonshinemodelmanagers.py IBus-Speech-To-Text-1.0.0/engine/sttmoonshinemodelmanagers.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttmoonshinemodelmanagers.py 2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttmoonshinemodelmanagers.py 2026-08-07 01:46:55.086648952 +0530
-@@ -1,5 +1,9 @@
- import os
-+import sys
-+import importlib.machinery
- import logging
-+import importlib
-+import importlib.util
- import threading
-
- from pathlib import Path
-@@ -9,23 +13,14 @@
-
- LOG_MSG = logging.getLogger()
-
--
--try:
-- from moonshine_voice import ModelArch
-- from moonshine_voice.download import (
-- MODEL_INFO,
-- find_model_info,
-- get_components_for_model_info,
-- get_model_for_language,
-- )
-- from moonshine_voice.download_file import get_cache_dir
-- MOONSHINE_AVAILABLE = True
--except Exception as e:
-- LOG_MSG.warning("moonshine_voice model catalog unavailable (%s). "
-- "Install/upgrade with: pip install -U moonshine-voice", e)
-- MOONSHINE_AVAILABLE = False
-- MODEL_INFO = {}
-- ModelArch = None
-+_ARCH_NAMES = {
-+ 0: "tiny",
-+ 1: "base",
-+ 2: "tiny-streaming",
-+ 3: "base-streaming",
-+ 4: "small-streaming",
-+ 5: "medium-streaming",
-+}
-
- _ARCH_SIZES = {
- "tiny": "~50 MB",
-@@ -46,15 +41,9 @@
- }
-
- def _arch_to_string(model_arch):
-- return {
-- 0: "tiny",
-- 1: "base",
-- 2: "tiny-streaming",
-- 3: "base-streaming",
-- 4: "small-streaming",
-- 5: "medium-streaming",
-- }.get(int(model_arch), "base")
--
-+ if model_arch is None:
-+ return "base"
-+ return _ARCH_NAMES.get(int(model_arch), "base")
-
- class STTDownloadState(float, Enum):
- STOPPED = -1.0
-@@ -67,26 +56,116 @@
- return None
- return locale_str[0:2].lower()
-
--def _all_model_infos():
-- for lang, entry in MODEL_INFO.items():
-- for model in entry.get("models", []):
-- yield model["model_name"], lang, model
--
--def _expected_model_path(model_info):
-- cache_dir = get_cache_dir()
-- folder = model_info["download_url"].replace("https://", "")
-- return Path(cache_dir, folder)
--
--def _model_present(model_info):
-- root = _expected_model_path(model_info)
-- if not root.is_dir():
-+def moonshine_installed():
-+ try:
-+ if importlib.machinery.PathFinder.find_spec("moonshine_voice", sys.path):
-+ return True
-+ importlib.invalidate_caches()
-+ return bool(importlib.machinery.PathFinder.find_spec("moonshine_voice",
-+ sys.path))
-+ except (ImportError, ValueError, TypeError, AttributeError) as error:
-+ LOG_MSG.debug("cannot look up moonshine_voice (%s)", error)
- return False
-+
-+def _moonshine_cache_dir():
-+ #Same directory as moonshine_voice.download_file.get_cache_dir(), computed without importing the package
-+ override = os.environ.get("MOONSHINE_VOICE_CACHE")
-+ if override:
-+ return Path(override)
-+
-+ xdg = os.environ.get("XDG_CACHE_HOME")
-+ if xdg:
-+ return Path(xdg) / "moonshine_voice"
-+
-+ return Path.home() / ".cache" / "moonshine_voice"
-+
-+_CATALOG = None # {model_name: {...}}
-+_CATALOG_LOCALES = None # {lang: [model_name, ...]}
-+
-+def _build_catalog():
-+ from moonshine_voice.download import find_model_info, supported_languages
-+
-+ models = {}
-+ locales = {}
-+
-+ for lang in supported_languages():
-+ for arch_value in sorted(_ARCH_NAMES):
-+ try:
-+ info = find_model_info(lang, arch_value)
-+ except Exception:
-+ # This language simply has no model for that architecture.
-+ continue
-+
-+ arch_name = _ARCH_NAMES[arch_value]
-+ name = "%s-%s" % (arch_name, lang)
-+ models[name] = {
-+ "name": name,
-+ "lang": lang,
-+ "arch": arch_value,
-+ "arch_name": arch_name,
-+ "download_url": info.get("download_url", ""),
-+ }
-+ locales.setdefault(lang, []).append(name)
-+
-+ return models, locales
-+
-+def _catalog():
-+ global _CATALOG, _CATALOG_LOCALES
-+
-+ if not moonshine_installed():
-+ _CATALOG = None
-+ _CATALOG_LOCALES = None
-+ return {}, {}
-+
-+ if _CATALOG is not None:
-+ return _CATALOG, _CATALOG_LOCALES
-+
-+ try:
-+ models, locales = _build_catalog()
-+ except Exception as error:
-+ LOG_MSG.warning("moonshine_voice model catalog unavailable (%s). "
-+ "Install/upgrade with: pip install -U moonshine-voice",
-+ error)
-+ return {}, {}
-+
-+ if not models:
-+ LOG_MSG.warning("moonshine_voice returned an empty model catalog")
-+ return {}, {}
-+
-+ _CATALOG = models
-+ _CATALOG_LOCALES = locales
-+ LOG_MSG.debug("moonshine catalog built (%d models, %d languages)",
-+ len(models), len(locales))
-+ return _CATALOG, _CATALOG_LOCALES
-+
-+def _model_root(entry):
-+ url = entry.get("download_url") or ""
-+ if not url:
-+ return None
-+ return Path(_moonshine_cache_dir(), url.replace("https://", ""))
-+
-+def _model_present(entry):
-+ root = _model_root(entry)
-+ if root is None or not root.is_dir():
-+ return False
-+
-+ components = None
- try:
-- components = get_components_for_model_info(model_info)
-- except Exception:
-- components = ["tokenizer.bin"]
-- return all((root / component).is_file() for component in components)
-+ from moonshine_voice.download import (find_model_info,
-+ get_components_for_model_info)
-+ components = get_components_for_model_info(
-+ find_model_info(entry["lang"], entry["arch"]))
-+ except Exception as error:
-+ LOG_MSG.debug("cannot list components of %s (%s)",
-+ entry["name"], error)
-
-+ if components:
-+ return all((root / component).is_file() for component in components)
-+
-+ try:
-+ return any(root.iterdir())
-+ except OSError:
-+ return False
-
- class STTMoonshineModelDescription(GObject.Object):
- __gtype_name__ = "STTMoonshineModelDescription"
-@@ -106,21 +185,32 @@
- self.quality = init_model.quality if init_model is not None else ""
-
- self._operation = None
-+ self._downloaded_path = None
- self.download_progress = STTDownloadState.STOPPED
-
- def _download_finished(self):
- self._operation = None
- self.download_progress = STTDownloadState.STOPPED
-- info = find_model_info(self.lang, self.arch)
-- if _model_present(info):
-- path = str(_expected_model_path(info))
-+
-+ path = self._downloaded_path
-+ self._downloaded_path = None
-+
-+ if path is None:
-+ entry = _catalog()[0].get(self.name)
-+ if entry is not None and _model_present(entry):
-+ path = str(_model_root(entry))
-+
-+ if path is not None:
- self.paths = [path]
-- stt_moonshine_local_model_manager()._notify_added(self.name, path, self.lang)
-+ stt_moonshine_local_model_manager()._notify_added(
-+ self.name, path, self.lang)
- return False
-
- def _download_thread(self, cancelled):
- try:
-- get_model_for_language(self.lang, self.arch)
-+ from moonshine_voice import get_model_for_language
-+ path, _arch = get_model_for_language(self.lang, self.arch)
-+ self._downloaded_path = str(path)
- except Exception as e:
- LOG_MSG.error("Moonshine download failed (%s): %s", self.name, e)
- if not cancelled.is_set():
-@@ -131,7 +221,7 @@
- def start_downloading(self):
- if self._operation is not None:
- return
-- if not MOONSHINE_AVAILABLE:
-+ if not moonshine_installed():
- LOG_MSG.error("cannot download, moonshine_voice not installed")
- return
-
-@@ -186,16 +276,18 @@
- super().__init__()
- self._present = {}
- self._custom_paths = {}
-- self._scan_present_models()
-+ self._scanned_models = 0
-
- def _scan_present_models(self):
-- if not MOONSHINE_AVAILABLE:
-+ catalog, _locales = _catalog()
-+ if not catalog or self._scanned_models == len(catalog):
- return
-- for model_name, lang, info in _all_model_infos():
-- info = dict(info, language=lang)
-- if _model_present(info):
-- self._present[model_name] = str(_expected_model_path(info))
-- LOG_MSG.debug("moonshine model present on disk (%s)", model_name)
-+
-+ self._scanned_models = len(catalog)
-+ for name, entry in catalog.items():
-+ if _model_present(entry):
-+ self._present[name] = str(_model_root(entry))
-+ LOG_MSG.debug("moonshine model present on disk (%s)", name)
-
- def _notify_added(self, model_name, path, lang=None):
- self._present[model_name] = path
-@@ -206,44 +298,38 @@
- self.emit("removed", model_name, path)
-
- def path_available(self, model_path):
-- return model_path in self._present.values() or model_path in self._custom_paths
-+ self._scan_present_models()
-+ return (model_path in self._present.values()
-+ or model_path in self._custom_paths)
-
- def get_best_path_for_model(self, model_name):
- if model_name is None:
- return None
-+ self._scan_present_models()
- return self._present.get(model_name, None)
-
- def get_arch_for_model(self, model_name):
-- if not MOONSHINE_AVAILABLE:
-- return None
-- for name, lang, info in _all_model_infos():
-- if name == model_name:
-- return info["model_arch"]
-- return None
-+ entry = _catalog()[0].get(model_name)
-+ return entry["arch"] if entry is not None else None
-
- def get_lang_for_model(self, model_name):
-- if not MOONSHINE_AVAILABLE:
-- return None
-- for name, lang, info in _all_model_infos():
-- if name == model_name:
-- return lang
-- return None
-+ entry = _catalog()[0].get(model_name)
-+ return entry["lang"] if entry is not None else None
-
- @staticmethod
- def _infer_arch_for_folder(model_path):
- root = Path(model_path)
-+ name = root.name.lower()
-+
- if (root / "streaming_config.json").is_file():
-- name = root.name.lower()
-- for token, arch in (("medium", ModelArch.MEDIUM_STREAMING),
-- ("small", ModelArch.SMALL_STREAMING),
-- ("base", ModelArch.BASE_STREAMING),
-- ("tiny", ModelArch.TINY_STREAMING)):
-+ for token, arch in (("medium", 5), ("small", 4),
-+ ("base", 3), ("tiny", 2)):
- if token in name:
- return arch
-- return ModelArch.SMALL_STREAMING
-- if "tiny" in root.name.lower():
-- return ModelArch.TINY
-- return ModelArch.BASE
-+ return 4
-+ if "tiny" in name:
-+ return 0
-+ return 1
-
- def register_custom_model_path(self, model_path, locale_str):
- self._custom_paths[model_path] = locale_str
-@@ -252,7 +338,8 @@
- self._custom_paths.pop(model_path, None)
-
- def custom_path_available(self, model_path):
-- return Path(model_path).is_dir() and (Path(model_path) / "tokenizer.bin").is_file()
-+ root = Path(model_path)
-+ return root.is_dir() and (root / "tokenizer.bin").is_file()
-
-
- _GLOBAL_LOCAL_MANAGER = None
-@@ -276,35 +363,43 @@
- super().__init__()
- self._models = {}
- self._locales_dict = {}
-- self._build_catalog()
-
- local = stt_moonshine_local_model_manager()
- local.connect("added", self._model_path_added_cb)
- local.connect("removed", self._model_path_removed_cb)
-
-- def _build_catalog(self):
-- if not MOONSHINE_AVAILABLE:
-+ def _ensure_catalog(self):
-+ catalog, locales = _catalog()
-+ if not catalog:
-+ self._models = {}
-+ self._locales_dict = {}
-+ return
-+ if len(self._models) == len(catalog):
- return
-- for model_name, lang, info in _all_model_infos():
-- arch = info["model_arch"]
-- arch_str = _arch_to_string(arch)
-+
-+ local = stt_moonshine_local_model_manager()
-+ self._models = {}
-+ self._locales_dict = {}
-+
-+ for name, entry in catalog.items():
-+ arch_name = entry["arch_name"]
-
- desc = STTMoonshineModelDescription()
-- desc.name = model_name
-- desc.lang = lang
-- desc.locale = lang
-- desc.arch = arch
-- desc.type = arch_str
-- desc.url = info["download_url"]
-- desc.size = _ARCH_SIZES.get(arch_str, "")
-- desc.quality = _ARCH_QUALITY.get(arch_str, "")
-+ desc.name = name
-+ desc.lang = entry["lang"]
-+ desc.locale = entry["lang"]
-+ desc.arch = entry["arch"]
-+ desc.type = arch_name
-+ desc.url = entry["download_url"]
-+ desc.size = _ARCH_SIZES.get(arch_name, "")
-+ desc.quality = _ARCH_QUALITY.get(arch_name, "")
-
-- path = stt_moonshine_local_model_manager().get_best_path_for_model(model_name)
-+ path = local.get_best_path_for_model(name)
- if path is not None:
- desc.paths = [path]
-
-- self._models[model_name] = desc
-- self._locales_dict.setdefault(lang, []).append(desc)
-+ self._models[name] = desc
-+ self._locales_dict.setdefault(entry["lang"], []).append(desc)
-
- def _model_path_added_cb(self, manager, model_name, model_path):
- desc = self._models.get(model_name, None)
-@@ -322,15 +417,16 @@
- self.emit("changed", desc)
-
- def get_model_description(self, model_name):
-+ self._ensure_catalog()
- return self._models.get(model_name, None)
-
- def get_models_for_locale(self, locale_str):
-+ self._ensure_catalog()
- lang = _lang_of_locale(locale_str)
- return list(self._locales_dict.get(lang, []))
-
- def supported_locales(self):
-- if not MOONSHINE_AVAILABLE:
-- return []
-+ self._ensure_catalog()
- return list(self._locales_dict.keys())
-
- _GLOBAL_ONLINE_MANAGER = None
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttmoonshinemodel.py IBus-Speech-To-Text-1.0.0/engine/sttmoonshinemodel.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttmoonshinemodel.py 2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttmoonshinemodel.py 2026-08-07 01:39:38.932769188 +0530
-@@ -4,10 +4,7 @@
- from pathlib import Path
- from gi.repository import GObject, Gio
-
--from sttmoonshinemodelmanagers import (
-- stt_moonshine_local_model_manager,
-- MOONSHINE_AVAILABLE,
--)
-+from sttmoonshinemodelmanagers import stt_moonshine_local_model_manager
-
- LOG_MSG = logging.getLogger()
-
-@@ -79,7 +76,7 @@
- return
- self._model_name = None
- self._model_path = model
-- self._model_arch = local._infer_arch_for_folder(model) if MOONSHINE_AVAILABLE else None
-+ self._model_arch = local._infer_arch_for_folder(model)
- local.register_custom_model_path(model, self._locale_str)
- self._valid_model = local.custom_path_available(model)
- else:
diff --git a/sources b/sources
index 3d7d1cf..8df341d 100644
--- a/sources
+++ b/sources
@@ -1 +1 @@
-SHA512 (1.0.0.tar.gz) = 7b5d6cbea06cfcdb03420cf7160738ef435726af5192b3d4435a58afe902edb1b8bbc50b5b0b594fc640749f60622a6cf81d79b9fec6c6b2ceb6977551f884c8
+SHA512 (1.1.0.tar.gz) = 8c9fbdbd22443cfb94bc692ee218ef7b982f3469cb0f546b4a1f135a088b92a5c83911a1b6ef808c8bdb2716782eda706f45822c01f6aadc819f9fe2ce49ede9
reply other threads:[~2026-09-21 21:59 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=179002794845.1.11251679282596415016.rpms-ibus-speech-to-text-38f002b367ae@fedoraproject.org \
--to=matiwari@redhat.com \
--cc=git-commits@fedoraproject.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox