# 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 . 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)