# 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 import logging import typing from pathlib import Path from typing import TYPE_CHECKING try: import whisper except ModuleNotFoundError: if typing.TYPE_CHECKING: import whisper try: import faster_whisper as fwhisper except ModuleNotFoundError: if typing.TYPE_CHECKING: import faster_whisper as fwhisper from gi.repository import Gtk from gajim.common import app from gajim.common.app import Any from gajim.gtk.builder import get_builder from gajim.gtk.const import Setting, SettingKind, SettingType from gajim.gtk.settings import SettingsBox from gajim.gtk.sidebar_switcher import SideBarSwitcher from gajim.plugins.helpers import get_builder from gajim.plugins.plugins_i18n import _ from ..models import faster_whisper, openai_whisper from ..models.model_settings import * if TYPE_CHECKING: from ..stt_voice_messages import STTVoiceMessagesPlugin log = logging.getLogger('gajim.p.sttvm_config_dialog') @dataclass class Model: name: str required_moduls: list[str] klass: object config: Any instance: typing.Optional[object] = None SUPPORTED_MODELS: dict[str, Model] = { 'model_openaiwhisper': Model('OpenAI Whisper', ['whisper'], openai_whisper.WhisperModel, OpenAIWhisperSettings), 'model_faster-whisper': Model('Faster-Whisper', ['faster_whisper'], faster_whisper.FasterWhisperModel, FasterWhisperSettings) } class Configuration: def __init__(self, plugin: STTVoiceMessagesPlugin): self._plugin = plugin self._available_models: dict[str, Model] = {} self.check_available_moduls() log.debug('config = %s', self._plugin.config) @property def plugin(self) -> STTVoiceMessagesPlugin: return self._plugin @property def available_models(self) -> dict[str, Model]: return self._available_models def on_setting(self, value: Any, data: Any) -> None: if isinstance(value, str): value.strip() log.debug('plugin config before:\n %s', self.plugin.config.data) self.plugin.config[data] = value log.debug('plugin config after:\n %s', self.plugin.config.data) def on_config_model(self, model: str, value: Any, data: Any) -> None: if isinstance(value, str): value.strip() log.debug('plugin config before:\n %s', self.plugin.config.data[model]) setattr(self.plugin.config.data[model], data, value) log.debug('plugin config after:\n %s', self.plugin.config.data[model]) self._plugin.config.data[model].instance.set_config(self.plugin.config.data[model]) def create_model(self, model: Any) -> None: if (self.plugin.config.data[model].instance is None and self._available_models[model].klass is not None): self.plugin.config.data[model].instance = \ self._available_models[model].klass() else: log.debug('Could not create model %s', model) def on_set_model(self, model: Any, data: str = 'model') -> None: if isinstance(model, str): model.strip() self.plugin.config['model'] = model log.debug('Created model %s with config %s', model, self.plugin.config.data[model]) def check_available_moduls(self): def is_module_available(module: str) -> bool: try: __import__(module) return True except ModuleNotFoundError: log.debug('Could not find module %s', module) return False except ImportError as ex: log.debug(str(ex)) return False for model in SUPPORTED_MODELS: available = True for modul in SUPPORTED_MODELS[model].required_moduls: if not is_module_available(modul): available = False continue if available: self._available_models[model] = SUPPORTED_MODELS[model] if SUPPORTED_MODELS[model].config is not None: log.debug('created config for model = %s: %s', model, self._available_models[model]) log.debug('plugin config for model = %s', self.plugin.config[model]) self.plugin.config.data[model].instance = None self._available_models[model].config = self.plugin.config[model] self.create_model(model) self.on_set_model(self._plugin.config['model']) log.debug('models = %s', self._available_models) class PreferenceBox(SettingsBox): def __init__(self, settings: list[Setting]) -> None: SettingsBox.__init__(self, None) self.get_style_context().add_class('border') self.set_selection_mode(Gtk.SelectionMode.NONE) self.set_vexpand(False) self.set_valign(Gtk.Align.END) for setting in settings: self.add_setting(setting) self.update_states() class STTVoiceMessagesConfigDialog(Gtk.ApplicationWindow): def __init__(self, config: Configuration, parent: Gtk.Window) -> None: Gtk.ApplicationWindow.__init__(self) self.set_application(app.app) self.set_position(Gtk.WindowPosition.CENTER) self.set_show_menubar(False) self.set_name('PreferencesWindow') self.set_default_size(900, 650) self.set_resizable(True) self.set_title(_('STT Voice Messages - Preferences')) ui_path = Path(__file__).parent self._ui = get_builder(str(ui_path.resolve() / 'config_dialog.ui')) self._prefs: dict[str, PreferenceBox] = {} side_bar_switcher = SideBarSwitcher() side_bar_switcher.set_stack(self._ui.stack) self._ui.grid.attach(side_bar_switcher, 0, 0, 1, 1) self.add(self._ui.grid) self.config = config self.plugin = self.config.plugin prefs: list[tuple[str, type[PreferenceBox]]] = [ ('stt_behaviour', self.STTBehaviour), ('models', self.Models), ] # TODO: Refactor this if 'model_openaiwhisper' in config.available_models: prefs.append(('openaiwhisper_general', self.OpenAIWhisperGeneral)) else: self._disable_pref('openai-whisper-viewport') # does not work yet if 'model_faster-whisper' in config.available_models: prefs.append(('fasterwhisper_general', self.FasterWhisperGeneral)) else: self._disable_pref('faster-whisper') # does not work yet self._add_prefs(prefs) self.show_all() def _add_prefs(self, prefs: list[tuple[str, type[PreferenceBox]]]): for ui_name, klass in prefs: pref_box = getattr(self._ui, ui_name) pref = klass(self) # pyright: ignore log.debug('ui_name = %s, klass = %s, pref_box = %s', ui_name, klass, pref_box) pref_box.add(pref) self._prefs[ui_name] = pref def _disable_pref(self, pref: str): # TODO: Not scrolling to setting does not work! pref_box = getattr(self._ui, pref) log.debug('Disable Settings Page for %s', pref_box) adj = Gtk.Adjustment(0, 0, 0) pref_box.set_focus_hadjustment(adj) pref_box.set_focus_vadjustment(adj) ############################################################################ # General Settings ############################################################################ class STTBehaviour(PreferenceBox): def __init__(self, config_dialog: STTVoiceMessagesConfigDialog) -> None: settings = [ Setting(SettingKind.SWITCH, _('Auto Transcribe'), SettingType.VALUE, value=config_dialog.plugin.config['auto_transcribe'], data='auto_transcribe', callback=config_dialog.config.on_setting) ] PreferenceBox.__init__(self, settings) class Models(PreferenceBox): def __init__(self, config_dialog: STTVoiceMessagesConfigDialog) -> None: models: list[tuple[str, str]] = [] for key, value in config_dialog.config.available_models.items(): models.append( (key, str(value.name)) ) settings = [ Setting(SettingKind.COMBO, _('Speech To Text Model'), SettingType.VALUE, value=config_dialog.plugin.config['model'], data='model', callback=config_dialog.config.on_set_model, props={'combo_items': models}, desc=_('Choose Model to use')), ] PreferenceBox.__init__(self, settings) ############################################################################ # OpenAI Whisper Settings ############################################################################ class OpenAIWhisperGeneral(PreferenceBox): def __init__(self, config_dialog: STTVoiceMessagesConfigDialog) -> None: self._model = 'model_openaiwhisper' self._config_dialog = config_dialog settings = [ Setting(SettingKind.POPOVER, _('Language Model Size'), SettingType.VALUE, value=config_dialog.config.available_models[self._model].config.model_size, data='model_size', callback=self._set_config, props={'entries': whisper.available_models()}), Setting(SettingKind.SWITCH, _('Translate'), SettingType.VALUE, value=config_dialog.config.available_models[self._model].config.translate_to_english, data='translate_to_english', callback=self._set_config) ] PreferenceBox.__init__(self, settings) def _set_config(self, value: Any, data: Any): self._config_dialog.config.on_config_model(self._model, value, data) ############################################################################ # Faster Whisper Settings ############################################################################ class FasterWhisperGeneral(PreferenceBox): def __init__(self, config_dialog: STTVoiceMessagesConfigDialog) -> None: self._model = 'model_faster-whisper' self._config_dialog = config_dialog settings = [ Setting(SettingKind.POPOVER, _('Language Model Size'), SettingType.VALUE, value=config_dialog.config.available_models[ self._model].config.model_size, data='model_size', callback=self._set_config, props={'entries': fwhisper.available_models()}), Setting(SettingKind.SWITCH, _('Translate'), SettingType.VALUE, value=config_dialog.config.available_models[ self._model].config.translate_to_english, data='translate_to_english', callback=self._set_config) ] PreferenceBox.__init__(self, settings) def _set_config(self, value: Any, data: Any): self._config_dialog.config.on_config_model(self._model, value, data)