Files
gajim-plugins/stt_voice_messages/gtk/sttbox.py
T
2026-08-07 04:23:44 -03:00

156 lines
4.9 KiB
Python

# This file is part of Gajim.
#
# Gajim is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import Any
import logging
from pathlib import Path
from gi.repository import Adw
from gi.repository import Gio
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gtk
from gajim.plugins.plugins_i18n import _
from ..model import OnnxAsrModel
log = logging.getLogger("gajim.p.stt_voice_messages_sttbox")
class STTBox(Gtk.Box):
def __init__(self, model: OnnxAsrModel, audio_file: Path) -> None:
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.set_margin_top(6)
self.set_halign(Gtk.Align.CENTER)
self.set_visible(False)
self._model = model
self._audio_file = audio_file
self._task: Gio.Task | None = None
self._transcribe_button = Gtk.Button.new_from_icon_name(
"lucide-square-pen-symbolic"
)
self._transcribe_button.set_tooltip_text(_("Transcribe voice message"))
self._transcribe_button.set_valign(Gtk.Align.CENTER)
self._transcribe_button.connect("clicked", self._on_transcribe_clicked)
self._spinner = Adw.Spinner(valign=Gtk.Align.CENTER, visible=False)
self._transcription_label = Gtk.Label()
self._transcription_label.set_max_width_chars(40)
self._transcription_label.set_wrap(True)
self._transcription_label.set_xalign(0)
self._transcription_label.set_selectable(True)
self.append(self._spinner)
self.append(self._transcription_label)
@property
def button(self) -> Gtk.Button:
return self._transcribe_button
def transcribe(self) -> None:
self._on_transcribe_clicked(self._transcribe_button)
def _on_transcribe_clicked(self, _button: Gtk.Button) -> None:
if not self._model.available():
self._show_status(
_('onnx-asr is not installed. Run: pip install "onnx-asr[hub]"'),
busy=False,
)
return
if self._model.is_loaded:
text = _("Transcribing…")
elif self._model.will_download:
text = _("Downloading model…")
else:
text = _("Loading model…")
self._show_status(text, busy=True)
self._run_bg(self._model.load, self._on_load_done)
def _on_load_done(self) -> None:
try:
self._finish()
except Exception as e:
self._show_status(_("Error: {}").format(e), busy=False)
return
self._show_status(_("Transcribing…"), busy=True)
self._run_bg(
lambda: self._model.recognize(self._audio_file, self._on_partial),
self._show_result,
)
def _on_partial(self, text: str) -> None:
GLib.idle_add(self._set_partial, text)
def _set_partial(self, text: str) -> None:
self._transcription_label.set_text(text)
def _show_result(self) -> None:
try:
text = self._finish()
except Exception as e:
self._show_status(_("Error: {}").format(e), busy=False)
return
text = text.strip()
if not text:
text = _("No speech detected.")
self._show_status(text, busy=False)
def _run_bg(self, fn, on_done) -> None:
self._bg_fn = fn
def _cb(_source: GObject.Object, _result: Gio.AsyncResult, _data: None) -> None:
on_done()
self._task = Gio.Task.new(self, None, _cb, None)
self._task.run_in_thread(self._thread_cb)
@staticmethod
def _thread_cb(
task: Gio.Task,
source: "STTBox",
_data: None,
_cancel: Gio.Cancellable,
) -> None:
try:
task.return_value(source._bg_fn())
except Exception as e:
task.return_value(e)
def _finish(self) -> Any:
task = self._task
self._task = None
value = task.propagate_value().value
if isinstance(value, Exception):
raise value
return value
def _show_status(self, text: str, busy: bool) -> None:
self._transcription_label.set_text(text)
self._transcription_label.set_visible(True)
self.set_visible(True)
self._spinner.set_visible(busy)
self._transcribe_button.set_sensitive(not busy)