From 9f21ed7e137e3131a58d1d2c8b47023bc1ec8a60 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Fri, 29 Dec 2023 22:14:09 +0000 Subject: [PATCH 001/129] packaging/update imports (#203) less deprecation warnings --- ovos_plugin_manager/templates/audio.py | 2 +- ovos_plugin_manager/templates/tts.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ovos_plugin_manager/templates/audio.py b/ovos_plugin_manager/templates/audio.py index 3bd0d2b1..f9dac1ed 100644 --- a/ovos_plugin_manager/templates/audio.py +++ b/ovos_plugin_manager/templates/audio.py @@ -6,7 +6,7 @@ from abc import ABCMeta, abstractmethod from ovos_utils import classproperty -from ovos_utils.messagebus import FakeBus +from ovos_utils.fakebus import FakeBus from ovos_utils.process_utils import RuntimeRequirements diff --git a/ovos_plugin_manager/templates/tts.py b/ovos_plugin_manager/templates/tts.py index 019a1931..d2763c29 100644 --- a/ovos_plugin_manager/templates/tts.py +++ b/ovos_plugin_manager/templates/tts.py @@ -42,7 +42,7 @@ from ovos_utils.file_utils import get_cache_directory from ovos_utils.lang.visimes import VISIMES from ovos_utils.log import LOG -from ovos_utils.messagebus import FakeBus as BUS +from ovos_utils.fakebus import FakeBus from ovos_utils.metrics import Stopwatch from ovos_utils.process_utils import RuntimeRequirements @@ -153,7 +153,7 @@ def __init__(self, lang="en-us", config=None, validator=None, self.stopwatch = Stopwatch() self.tts_name = self.__class__.__name__ - self.bus = BUS() # initialized in "init" step + self.bus = FakeBus() # initialized in "init" step self.lang = lang or self.config.get("lang") or 'en-us' self.validator = validator or TTSValidator(self) self.phonetic_spelling = phonetic_spelling @@ -314,7 +314,7 @@ def init(self, bus=None, playback=None): Arguments: bus: OpenVoiceOS messagebus connection """ - self.bus = bus or BUS() + self.bus = bus or FakeBus() if playback is None: LOG.warning("PlaybackThread should be inited by ovos-audio, initing via plugin has been deprecated, " "please pass playback=PlaybackThread() to TTS.init") From 6652c15d5905ef408a0a6c98d757e733a52cc778 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Fri, 29 Dec 2023 22:14:23 +0000 Subject: [PATCH 002/129] Increment Version to 0.0.26a1 --- ovos_plugin_manager/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 6cdbd02a..7f0fb33a 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -2,6 +2,6 @@ # START_VERSION_BLOCK VERSION_MAJOR = 0 VERSION_MINOR = 0 -VERSION_BUILD = 25 -VERSION_ALPHA = 0 +VERSION_BUILD = 26 +VERSION_ALPHA = 1 # END_VERSION_BLOCK From 3d50b883b55d11cb50afe3822b04a42af7664ac9 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Fri, 29 Dec 2023 22:14:49 +0000 Subject: [PATCH 003/129] Update Changelog --- CHANGELOG.md | 41 +++-------------------------------------- 1 file changed, 3 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d25b1f6..c8260397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,49 +1,14 @@ # Changelog -## [0.0.25](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.25) (2023-12-29) +## [0.0.26a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a1) (2023-12-29) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a4...0.0.25) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25...0.0.26a1) **Merged pull requests:** +- packaging/update imports [\#203](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/203) ([JarbasAl](https://github.com/JarbasAl)) - Update requirements.txt [\#201](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/201) ([JarbasAl](https://github.com/JarbasAl)) -## [V0.0.25a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a4) (2023-12-29) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a3...V0.0.25a4) - -**Merged pull requests:** - -- update imports [\#198](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/198) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a3) (2023-12-29) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a2...V0.0.25a3) - -**Merged pull requests:** - -- Update requirements.txt [\#197](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/197) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a2) (2023-12-28) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a1...V0.0.25a2) - -**Closed issues:** - -- module 'inspect' has no attribute 'formatargspec' [\#189](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/189) - -**Merged pull requests:** - -- refactor/move\_from\_utils [\#196](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/196) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a1) (2023-12-09) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24...V0.0.25a1) - -**Implemented enhancements:** - -- feat/disable\_cache [\#194](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/194) ([JarbasAl](https://github.com/JarbasAl)) - \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* From bd4a0cbf423be2669d01656118794568cd889bba Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 30 Dec 2023 01:38:24 +0000 Subject: [PATCH 004/129] fix gui extensions in ovos-utils >= 0.1.0a2 (#204) --- ovos_plugin_manager/templates/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/templates/gui.py b/ovos_plugin_manager/templates/gui.py index fad98e19..e3c020cc 100644 --- a/ovos_plugin_manager/templates/gui.py +++ b/ovos_plugin_manager/templates/gui.py @@ -1,6 +1,6 @@ from ovos_bus_client import Message from ovos_bus_client import MessageBusClient -from ovos_utils.gui import GUIInterface +from ovos_bus_client.apis.gui import GUIInterface from ovos_utils.log import LOG from ovos_config import Configuration From 09c1d40f4898feeaa20be669ae2687a1c65f2af6 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 30 Dec 2023 01:38:38 +0000 Subject: [PATCH 005/129] Increment Version to 0.0.26a2 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 7f0fb33a..057fdf73 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 1 +VERSION_ALPHA = 2 # END_VERSION_BLOCK From 6d84fc6d24304fef12147b714be3693bc39212d5 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 30 Dec 2023 01:39:01 +0000 Subject: [PATCH 006/129] Update Changelog --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8260397..fe4bb0c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog -## [0.0.26a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a1) (2023-12-29) +## [0.0.26a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a2) (2023-12-30) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25...0.0.26a1) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a1...0.0.26a2) + +**Fixed bugs:** + +- fix gui extensions in ovos-utils \>= 0.1.0a2 [\#204](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/204) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.26a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.26a1) (2023-12-29) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25...V0.0.26a1) **Merged pull requests:** From 076830e5bd013920fbf7efc2cd7c0a4f893416f5 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 30 Dec 2023 02:06:02 +0000 Subject: [PATCH 007/129] fix phal plugins in ovos-utils >= 0.1.0a2 (#205) import moved in utils 0.0.37 and removed in 0.1.0 --- ovos_plugin_manager/templates/phal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/templates/phal.py b/ovos_plugin_manager/templates/phal.py index aed65f3a..8a8fb471 100644 --- a/ovos_plugin_manager/templates/phal.py +++ b/ovos_plugin_manager/templates/phal.py @@ -5,7 +5,7 @@ from ovos_utils import camel_case_split from ovos_utils import classproperty from ovos_utils.log import LOG -from ovos_utils.messagebus import get_mycroft_bus +from ovos_bus_client.util import get_mycroft_bus from ovos_utils.process_utils import RuntimeRequirements from ovos_plugin_manager.utils.config import get_plugin_config From 31e1ef2826037c069b0ac87b7bc76f2f8dbcc64b Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 30 Dec 2023 02:06:18 +0000 Subject: [PATCH 008/129] Increment Version to 0.0.26a3 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 057fdf73..13b12be0 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 2 +VERSION_ALPHA = 3 # END_VERSION_BLOCK From 858cea8900060abd89d95c0aa85981ae242902eb Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 30 Dec 2023 02:06:47 +0000 Subject: [PATCH 009/129] Update Changelog --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe4bb0c6..53c56a55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog -## [0.0.26a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a2) (2023-12-30) +## [0.0.26a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a3) (2023-12-30) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a1...0.0.26a2) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a2...0.0.26a3) + +**Fixed bugs:** + +- fix phal plugins in ovos-utils \>= 0.1.0a2 [\#205](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/205) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.26a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.26a2) (2023-12-30) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a1...V0.0.26a2) **Fixed bugs:** From 79f71e06e61dea55fe112c09ced728b1ffd3e650 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:15:59 +0000 Subject: [PATCH 010/129] feat/sentence_tokenize_before_TTS (#206) * feat/sentence_tokenize_before_TTS split large responses into sub-sentences, this improves the TTS synth speed eg, with this PR the sentence below would have been 3 synths instead of 1, and the first one would start playing while it synthed the next 2 > Jan 03 14:55:37 rpi4b hivemind-voice-sat[10648]: 2024-01-03 14:55:37.257 - HiveMind-voice-sat - ovos_audio.service:execute_tts:348 - INFO - Speak: Quantum computing is a field that focuses on developing computer systems that utilize the principles of quantum mechanics. These systems, known as quantum computers, use quantum bits or qubits to process and store information. Unlike classical computers which use bits that can represent either a 0 or a 1, qubits can exist in multiple states simultaneously, thanks to a property called superposition. This allows quantum computers to perform certain calculations much faster than classical computers, potentially enabling breakthroughs in fields like cryptography, optimization, and molecular simulations. However, quantum computing is still a developing field and many practical challenges need to be overcome before fully functional quantum computers become widely available. ChatGPT answered quickly but the TTS server took some time to convert it. It took about 20 seconds with ryan-high on Piper, sure it will be faster with a lower voice but still an issue * Update tts.py --- ovos_plugin_manager/templates/tts.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ovos_plugin_manager/templates/tts.py b/ovos_plugin_manager/templates/tts.py index d2763c29..d90b99dd 100644 --- a/ovos_plugin_manager/templates/tts.py +++ b/ovos_plugin_manager/templates/tts.py @@ -25,6 +25,7 @@ import random import re import subprocess +import quebra_frases from os.path import isfile, join from pathlib import Path from queue import Queue @@ -456,7 +457,8 @@ def validate_ssml(self, utterance): return utterance.replace(" ", " ") def _preprocess_sentence(self, sentence): - """Default preprocessing is no preprocessing. + """Default preprocessing is a sentence_tokenizer, + ie. splits the utterance into sub-sentences using quebra_frases This method can be overridden to create chunks suitable to the TTS engine in question. @@ -467,6 +469,8 @@ def _preprocess_sentence(self, sentence): Returns: list: list of sentence parts """ + if self.config.get("sentence_tokenize"): # TODO default to True on next major release + return quebra_frases.sentence_tokenize(sentence) return [sentence] def execute(self, sentence, ident=None, listen=False, **kwargs): From fe6d167dd4e049498470c660455dc43de9205b5b Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Wed, 3 Jan 2024 20:16:13 +0000 Subject: [PATCH 011/129] Increment Version to 0.0.26a4 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 13b12be0..2d154dda 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 3 +VERSION_ALPHA = 4 # END_VERSION_BLOCK From 2c1bf86dfc8e89731d645c9d83227e56c1c1f196 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Wed, 3 Jan 2024 20:16:44 +0000 Subject: [PATCH 012/129] Update Changelog --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53c56a55..3b43478d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog -## [0.0.26a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a3) (2023-12-30) +## [0.0.26a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a4) (2024-01-03) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a2...0.0.26a3) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a3...0.0.26a4) + +**Implemented enhancements:** + +- feat/sentence\_tokenize\_before\_TTS [\#206](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/206) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.26a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.26a3) (2023-12-30) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a2...V0.0.26a3) **Fixed bugs:** From 3848f7dfc341517f277bf9d0eda137d3c4f85572 Mon Sep 17 00:00:00 2001 From: NeonJarbas <59943014+NeonJarbas@users.noreply.github.com> Date: Mon, 8 Jan 2024 06:47:23 +0000 Subject: [PATCH 013/129] feat/OCP_backends (#207) * feat/OCP_backends --------- Co-authored-by: JarbasAi Co-authored-by: JarbasAI <33701864+JarbasAl@users.noreply.github.com> --- .github/workflows/unit_tests.yml | 6 +- ovos_plugin_manager/audio.py | 4 + ovos_plugin_manager/ocp.py | 54 +++-- ovos_plugin_manager/templates/audio.py | 170 ++++----------- ovos_plugin_manager/templates/media.py | 277 +++++++++++++++++++++++++ ovos_plugin_manager/utils/__init__.py | 8 +- requirements/requirements.txt | 4 +- test/unittests/test_audio.py | 4 +- test/unittests/test_ocp.py | 5 +- 9 files changed, 373 insertions(+), 159 deletions(-) create mode 100644 ovos_plugin_manager/templates/media.py diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 62da0901..89dc6e84 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -46,12 +46,12 @@ jobs: sudo apt-get update sudo apt install python3-dev python -m pip install build wheel - - name: Install core repo - run: | - pip install . - name: Install test dependencies run: | pip install -r requirements/test.txt + - name: Install core repo + run: | + pip install . - name: Run unittests run: | pytest --cov=ovos_plugin_manager --cov-report xml test/unittests diff --git a/ovos_plugin_manager/audio.py b/ovos_plugin_manager/audio.py index 3ca5d0d0..a60ff0db 100644 --- a/ovos_plugin_manager/audio.py +++ b/ovos_plugin_manager/audio.py @@ -2,6 +2,10 @@ from ovos_utils.log import LOG from ovos_bus_client.util import get_mycroft_bus from ovos_config import Configuration +from ovos_utils.log import log_deprecation + +log_deprecation("ovos_plugin_manager.audio has been deprecated on ovos-audio, " + "move to ovos_plugin_manager.media", "0.1.0") def find_plugins(*args, **kwargs): diff --git a/ovos_plugin_manager/ocp.py b/ovos_plugin_manager/ocp.py index 5b88976f..5749cae5 100644 --- a/ovos_plugin_manager/ocp.py +++ b/ovos_plugin_manager/ocp.py @@ -1,31 +1,42 @@ -from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes +from ovos_plugin_manager.utils import PluginTypes from ovos_plugin_manager.templates.ocp import OCPStreamExtractor +from ovos_plugin_manager.templates.media import AudioPlayerBackend, VideoPlayerBackend, WebPlayerBackend from ovos_utils.log import LOG +from functools import lru_cache +from ovos_plugin_manager.utils import find_plugins -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) +def find_ocp_plugins() -> dict: + """ + Find all installed plugins + @return: dict plugin names to entrypoints + """ + return find_plugins(PluginTypes.STREAM_EXTRACTOR) -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) +def find_ocp_audio_plugins() -> dict: + """ + Find all installed plugins + @return: dict plugin names to entrypoints + """ + return find_plugins(PluginTypes.AUDIO_PLAYER) -def find_ocp_plugins() -> dict: + +def find_ocp_video_plugins() -> dict: """ Find all installed plugins @return: dict plugin names to entrypoints """ - from ovos_plugin_manager.utils import find_plugins - return find_plugins(PluginTypes.STREAM_EXTRACTOR) + return find_plugins(PluginTypes.VIDEO_PLAYER) + + +def find_ocp_web_plugins() -> dict: + """ + Find all installed plugins + @return: dict plugin names to entrypoints + """ + return find_plugins(PluginTypes.WEB_PLAYER) class StreamHandler: @@ -92,3 +103,14 @@ def extract_stream(self, uri, video=True): # no extractor available, return raw url return meta or {"uri": uri} + + +@lru_cache() # to avoid loading StreamHandler more than once +def load_stream_extractors(): + return StreamHandler() + + +def available_extractors(): + xtract = load_stream_extractors() + return ["/", "http:", "https:", "file:"] + \ + [f"{sei}//" for sei in xtract.supported_seis] diff --git a/ovos_plugin_manager/templates/audio.py b/ovos_plugin_manager/templates/audio.py index f9dac1ed..fbb1e330 100644 --- a/ovos_plugin_manager/templates/audio.py +++ b/ovos_plugin_manager/templates/audio.py @@ -3,14 +3,19 @@ These classes can be used to create an Audioservice plugin extending OpenVoiceOS's media playback options. """ -from abc import ABCMeta, abstractmethod +from ovos_bus_client.message import Message +from ovos_plugin_manager.templates.media import AudioPlayerBackend as _AB from ovos_utils import classproperty -from ovos_utils.fakebus import FakeBus +from ovos_utils.log import log_deprecation +from ovos_utils.ocp import PlaybackType, TrackState from ovos_utils.process_utils import RuntimeRequirements +log_deprecation("ovos_plugin_manager.templates.audio has been deprecated on ovos-audio, " + "move to ovos_plugin_manager.templates.media", "0.1.0") -class AudioBackend(metaclass=ABCMeta): + +class AudioBackend(_AB): """Base class for all audio backend implementations. Arguments: @@ -18,12 +23,6 @@ class AudioBackend(metaclass=ABCMeta): bus (MessageBusClient): OpenVoiceOS messagebus emitter """ - def __init__(self, config=None, bus=None): - self._track_start_callback = None - self.supports_mime_hints = False - self.config = config or {} - self.bus = bus or FakeBus() - @classproperty def runtime_requirements(self): """ skill developers should override this if they do not require connectivity @@ -59,143 +58,52 @@ def runtime_requirements(self): no_internet_fallback=True, no_network_fallback=True) - @property - def playback_time(self): - return 0 - - @abstractmethod - def supported_uris(self): - """List of supported uri types. - - Returns: - list: Supported uri's - """ - - @abstractmethod + # methods below deprecated and handled by OCP directly + # playlists are no longer managed plugin side + # this is just a compat layer forwarding commands to OCP def clear_list(self): """Clear playlist.""" + msg = Message('ovos.common_play.playlist.clear') + self.bus.emit(msg) - @abstractmethod def add_list(self, tracks): """Add tracks to backend's playlist. Arguments: tracks (list): list of tracks. """ - - @abstractmethod - def play(self, repeat=False): - """Start playback. - - Starts playing the first track in the playlist and will contiune - until all tracks have been played. - - Arguments: - repeat (bool): Repeat playlist, defaults to False - """ - - @abstractmethod - def stop(self): - """Stop playback. - - Stops the current playback. - - Returns: - bool: True if playback was stopped, otherwise False - """ - - def set_track_start_callback(self, callback_func): - """Register callback on track start. - - This method should be called as each track in a playlist is started. - """ - self._track_start_callback = callback_func - - def pause(self): - """Pause playback. - - Stops playback but may be resumed at the exact position the pause - occured. - """ - - def resume(self): - """Resume paused playback. - - Resumes playback after being paused. - """ + tracks = tracks or [] + if isinstance(tracks, (str, tuple)): + tracks = [tracks] + elif not isinstance(tracks, list): + raise ValueError + tracks = [self._uri2meta(t) for t in tracks] + msg = Message('ovos.common_play.playlist.queue', + {'tracks': tracks}) + self.bus.emit(msg) + + @staticmethod + def _uri2meta(uri): + if isinstance(uri, list): + uri = uri[0] + try: + from ovos_ocp_files_plugin.plugin import OCPFilesMetadataExtractor + return OCPFilesMetadataExtractor.extract_metadata(uri) + except: + meta = {"uri": uri, + "skill_id": "mycroft.audio_interface", + "playback": PlaybackType.AUDIO, # TODO mime type check + "status": TrackState.QUEUED_AUDIO, + } + return meta def next(self): """Skip to next track in playlist.""" + self.bus.emit(Message("ovos.common_play.next")) def previous(self): """Skip to previous track in playlist.""" - - def lower_volume(self): - """Lower volume. - - This method is used to implement audio ducking. It will be called when - OpenVoiceOS is listening or speaking to make sure the media playing isn't - interfering. - """ - - def restore_volume(self): - """Restore normal volume. - - Called when to restore the playback volume to previous level after - OpenVoiceOS has lowered it using lower_volume(). - """ - - def get_track_length(self): - """ - getting the duration of the audio in milliseconds - NOTE: not yet supported by mycroft-core - """ - - def get_track_position(self): - """ - get current position in milliseconds - NOTE: not yet supported by mycroft-core - """ - - def set_track_position(self, milliseconds): - """ - go to position in milliseconds - NOTE: not yet supported by mycroft-core - Args: - milliseconds (int): number of milliseconds of final position - """ - - def seek_forward(self, seconds=1): - """Skip X seconds. - - Arguments: - seconds (int): number of seconds to seek, if negative rewind - """ - - def seek_backward(self, seconds=1): - """Rewind X seconds. - - Arguments: - seconds (int): number of seconds to seek, if negative jump forward. - """ - - def track_info(self): - """Get info about current playing track. - - Returns: - dict: Track info containing atleast the keys artist and album. - """ - ret = {} - ret['artist'] = '' - ret['album'] = '' - return ret - - def shutdown(self): - """Perform clean shutdown. - - Implements any audio backend specific shutdown procedures. - """ - self.stop() + self.bus.emit(Message("ovos.common_play.previous")) class RemoteAudioBackend(AudioBackend): diff --git a/ovos_plugin_manager/templates/media.py b/ovos_plugin_manager/templates/media.py new file mode 100644 index 00000000..bd1c385a --- /dev/null +++ b/ovos_plugin_manager/templates/media.py @@ -0,0 +1,277 @@ +from abc import ABCMeta, abstractmethod + +from ovos_bus_client.message import Message +from ovos_utils.log import LOG +from ovos_utils.messagebus import FakeBus + +from ovos_utils.ocp import MediaState, PlayerState, TrackState + + +class MediaBackend(metaclass=ABCMeta): + """Base class for all OCP media backend implementations. + + Media backends are single-track, playlists are handled by OCP + + Arguments: + config (dict): configuration dict for the instance + bus (MessageBusClient): Mycroft messagebus emitter + """ + + def __init__(self, config=None, bus=None): + self._now_playing = None # single uri + self._track_start_callback = None + self.supports_mime_hints = False + self.config = config or {} + self.bus = bus or FakeBus() + + def set_track_start_callback(self, callback_func): + """Register callback on track start. + + This method should be called as each track in a playlist is started. + """ + self._track_start_callback = callback_func + + def load_track(self, uri): + self._now_playing = uri + LOG.debug(f"queuing for {self.__class__.__name__} playback: {uri}") + self.bus.emit(Message("ovos.common_play.media.state", + {"state": MediaState.LOADED_MEDIA})) + + def ocp_start(self): + """Emit OCP status events for play""" + self.bus.emit(Message("ovos.common_play.player.state", + {"state": PlayerState.PLAYING})) + self.bus.emit(Message("ovos.common_play.media.state", + {"state": MediaState.LOADED_MEDIA})) + self.play() + + def ocp_error(self): + """Emit OCP status events for playback error""" + if self._now_playing: + self._now_playing = None + self.bus.emit(Message("ovos.common_play.media.state", + {"state": MediaState.INVALID_MEDIA})) + self.bus.emit(Message("ovos.common_play.player.state", + {"state": PlayerState.STOPPED})) + + def ocp_stop(self): + """Emit OCP status events for stop""" + if self._now_playing: + self._now_playing = None + self.bus.emit(Message("ovos.common_play.player.state", + {"state": PlayerState.STOPPED})) + self.bus.emit(Message("ovos.common_play.media.state", + {"state": MediaState.END_OF_MEDIA})) + self.stop() + + def ocp_pause(self): + """Emit OCP status events for pause""" + if self._now_playing: + self.bus.emit(Message("ovos.common_play.player.state", + {"state": PlayerState.PAUSED})) + self.pause() + + def ocp_resume(self): + """Emit OCP status events for resume""" + if self._now_playing: + self.bus.emit(Message("ovos.common_play.player.state", + {"state": PlayerState.PLAYING})) + self.bus.emit(Message("ovos.common_play.track.state", + {"state": TrackState.PLAYING_AUDIO})) + self.resume() + + @property + def playback_time(self): + return 0 + + @abstractmethod + def supported_uris(self): + """List of supported uri types. + + Returns: + list: Supported uri's + """ + + @abstractmethod + def play(self): + """Start playback. + + Starts playing the first track in the playlist and will contiune + until all tracks have been played. + """ + + @abstractmethod + def stop(self): + """Stop playback. + + Stops the current playback. + + Returns: + bool: True if playback was stopped, otherwise False + """ + + @abstractmethod + def pause(self): + """Pause playback. + + Stops playback but may be resumed at the exact position the pause + occured. + """ + + @abstractmethod + def resume(self): + """Resume paused playback. + + Resumes playback after being paused. + """ + + @abstractmethod + def lower_volume(self): + """Lower volume. + + This method is used to implement audio ducking. It will be called when + OpenVoiceOS is listening or speaking to make sure the media playing isn't + interfering. + """ + + @abstractmethod + def restore_volume(self): + """Restore normal volume. + + Called when to restore the playback volume to previous level after + OpenVoiceOS has lowered it using lower_volume(). + """ + + @abstractmethod + def get_track_length(self) -> int: + """ + getting the duration of the audio in milliseconds + """ + + @abstractmethod + def get_track_position(self) -> int: + """ + get current position in milliseconds + """ + + @abstractmethod + def set_track_position(self, milliseconds): + """ + go to position in milliseconds + Args: + milliseconds (int): number of milliseconds of final position + """ + + def seek_forward(self, seconds=1): + """Skip X seconds. + + Arguments: + seconds (int): number of seconds to seek, if negative rewind + """ + miliseconds = seconds * 1000 + new_pos = self.get_track_position() + miliseconds + self.set_track_position(new_pos) + + def seek_backward(self, seconds=1): + """Rewind X seconds. + + Arguments: + seconds (int): number of seconds to seek, if negative jump forward. + """ + miliseconds = seconds * 1000 + new_pos = self.get_track_position() - miliseconds + self.set_track_position(new_pos) + + def track_info(self): + """Get info about current playing track. + + Returns: + dict: Track info containing atleast the keys artist and album. + """ + ret = {} + ret['artist'] = '' + ret['album'] = '' + ret['title'] = self._now_playing + return ret + + def shutdown(self): + """Perform clean shutdown. + + Implements any audio backend specific shutdown procedures. + """ + self.stop() + + +class AudioPlayerBackend(MediaBackend): + """ for audio""" + + def load_track(self, uri): + super().load_track(uri) + self.bus.emit(Message("ovos.common_play.track.state", + {"state": TrackState.QUEUED_AUDIO})) + + def ocp_start(self): + """Emit OCP status events for play""" + super().ocp_start() + self.bus.emit(Message("ovos.common_play.track.state", + {"state": TrackState.PLAYING_AUDIO})) + + +class RemoteAudioPlayerBackend(AudioPlayerBackend): + """Base class for remote audio backends. + + RemoteAudioBackends will always be checked after the normal + AudioBackends to make playback start locally by default. + + An example of a RemoteAudioBackend would be things like mopidy servers, etc. + """ + + +class VideoPlayerBackend(MediaBackend): + """ for video""" + def load_track(self, uri): + super().load_track(uri) + self.bus.emit(Message("ovos.common_play.track.state", + {"state": TrackState.QUEUED_VIDEO})) + + def ocp_start(self): + """Emit OCP status events for play""" + super().ocp_start() + self.bus.emit(Message("ovos.common_play.track.state", + {"state": TrackState.PLAYING_VIDEO})) + + +class RemoteVideoPlayerBackend(VideoPlayerBackend): + """Base class for remote audio backends. + + RemoteVideoBackends will always be checked after the normal + VideoBackends to make playback start locally by default. + + An example of a RemoteVideoBackend would be things like Chromecasts, etc. + """ + + +class WebPlayerBackend(MediaBackend): + """ for web pages""" + + def load_track(self, uri): + super().load_track(uri) + self.bus.emit(Message("ovos.common_play.track.state", + {"state": TrackState.QUEUED_WEBVIEW})) + + def ocp_start(self): + """Emit OCP status events for play""" + super().ocp_start() + self.bus.emit(Message("ovos.common_play.track.state", + {"state": TrackState.PLAYING_WEBVIEW})) + + +class RemoteWebPlayerBackend(WebPlayerBackend): + """Base class for remote web backends. + + RemoteWebBackends will always be checked after the normal + VideoBackends to make playback start locally by default. + + An example of a RemoteWebBackend would be + things that can render a webpage in a different machine + """ diff --git a/ovos_plugin_manager/utils/__init__.py b/ovos_plugin_manager/utils/__init__.py index 26f3bd51..441390bf 100644 --- a/ovos_plugin_manager/utils/__init__.py +++ b/ovos_plugin_manager/utils/__init__.py @@ -30,7 +30,7 @@ class PluginTypes(str, Enum): VAD = "ovos.plugin.VAD" PHONEME = "ovos.plugin.g2p" AUDIO2IPA = "ovos.plugin.audio2ipa" - AUDIO = 'mycroft.plugin.audioservice' + AUDIO = 'mycroft.plugin.audioservice' # DEPRECATED STT = 'mycroft.plugin.stt' TTS = 'mycroft.plugin.tts' WAKEWORD = 'mycroft.plugin.wake_word' @@ -52,6 +52,9 @@ class PluginTypes(str, Enum): TOKENIZATION = "intentbox.tokenization" POSTAG = "intentbox.postag" STREAM_EXTRACTOR = "ovos.ocp.extractor" + AUDIO_PLAYER = "opm.media.audio" + VIDEO_PLAYER = "opm.media.video" + WEB_PLAYER = "opm.media.web" PERSONA = "opm.plugin.persona" # personas are a dict, they have no config because they ARE a config @@ -86,6 +89,9 @@ class PluginConfigTypes(str, Enum): TOKENIZATION = "intentbox.tokenization.config" POSTAG = "intentbox.postag.config" STREAM_EXTRACTOR = "ovos.ocp.extractor.config" + AUDIO_PLAYER = "opm.media.audio.config" + VIDEO_PLAYER = "opm.media.video.config" + WEB_PLAYER = "opm.media.web.config" def find_plugins(plug_type: PluginTypes = None) -> dict: diff --git a/requirements/requirements.txt b/requirements/requirements.txt index b10d2770..3f1f5ef2 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,5 +1,5 @@ -ovos-utils < 0.2.0, >=0.0.37 -ovos-bus-client < 0.2.0, >=0.0.8 +ovos-utils < 0.2.0, >=0.1.0a8 +ovos-bus-client < 0.2.0, >=0.0.9a3 ovos-config < 0.2.0, >=0.0.12 combo_lock~=0.2 requests~=2.26 diff --git a/test/unittests/test_audio.py b/test/unittests/test_audio.py index 1ad550ff..59db48d8 100644 --- a/test/unittests/test_audio.py +++ b/test/unittests/test_audio.py @@ -6,11 +6,11 @@ class TestAudioTemplate(unittest.TestCase): def test_audio_backend(self): - from ovos_plugin_manager.templates.audio import AudioBackend + from ovos_plugin_manager.templates.media import AudioPlayerBackend # TODO def test_remote_audio_backend(self): - from ovos_plugin_manager.templates.audio import RemoteAudioBackend + from ovos_plugin_manager.templates.media import RemoteAudioPlayerBackend class TestAudio(unittest.TestCase): diff --git a/test/unittests/test_ocp.py b/test/unittests/test_ocp.py index 61cbf058..3305dd43 100644 --- a/test/unittests/test_ocp.py +++ b/test/unittests/test_ocp.py @@ -22,10 +22,7 @@ def test_find_plugins(self, find_plugins): find_ocp_plugins() find_plugins.assert_called_once_with(self.PLUGIN_TYPE) - @patch("ovos_plugin_manager.utils.find_plugins") - def test_stream_handler(self, find_plugins): + def test_stream_handler(self): from ovos_plugin_manager.ocp import StreamHandler handler = StreamHandler() self.assertIsInstance(handler.extractors, dict) - find_plugins.assert_called_once_with(PluginTypes.STREAM_EXTRACTOR) - # TODO: More tests From c1b782bba6d305c70e80be603eb4844877803247 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 8 Jan 2024 06:47:39 +0000 Subject: [PATCH 014/129] Increment Version to 0.0.26a5 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 2d154dda..84d36437 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 4 +VERSION_ALPHA = 5 # END_VERSION_BLOCK From e265403c48f40efac8978fad4e890c89ead865fd Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 8 Jan 2024 06:48:13 +0000 Subject: [PATCH 015/129] Update Changelog --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b43478d..0ce02481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog -## [0.0.26a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a4) (2024-01-03) +## [0.0.26a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a5) (2024-01-08) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a3...0.0.26a4) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a4...0.0.26a5) + +**Implemented enhancements:** + +- feat/OCP\_backends [\#207](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/207) ([NeonJarbas](https://github.com/NeonJarbas)) + +## [V0.0.26a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.26a4) (2024-01-03) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a3...V0.0.26a4) **Implemented enhancements:** From 36b67e9ced100e9075c60017549a97e69a666b81 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sun, 28 Jan 2024 13:47:47 +0000 Subject: [PATCH 016/129] Create dependabot.yml --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..26e59a20 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "pip" # See documentation for possible values + directory: "/requirements" # Location of package manifests + schedule: + interval: "weekly" From 8a72bc37bd09d52363306c7696608e130a76beda Mon Sep 17 00:00:00 2001 From: NeonJarbas <59943014+NeonJarbas@users.noreply.github.com> Date: Wed, 7 Feb 2024 19:15:12 +0000 Subject: [PATCH 017/129] feat/track_meta (#211) Co-authored-by: JarbasAi --- ovos_plugin_manager/templates/media.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/ovos_plugin_manager/templates/media.py b/ovos_plugin_manager/templates/media.py index bd1c385a..8d94132a 100644 --- a/ovos_plugin_manager/templates/media.py +++ b/ovos_plugin_manager/templates/media.py @@ -23,6 +23,7 @@ def __init__(self, config=None, bus=None): self.supports_mime_hints = False self.config = config or {} self.bus = bus or FakeBus() + self.meta = {} def set_track_start_callback(self, callback_func): """Register callback on track start. @@ -31,8 +32,9 @@ def set_track_start_callback(self, callback_func): """ self._track_start_callback = callback_func - def load_track(self, uri): + def load_track(self, uri: str, metadata: dict = None): self._now_playing = uri + self.meta.update(metadata or {}) LOG.debug(f"queuing for {self.__class__.__name__} playback: {uri}") self.bus.emit(Message("ovos.common_play.media.state", {"state": MediaState.LOADED_MEDIA})) @@ -188,11 +190,7 @@ def track_info(self): Returns: dict: Track info containing atleast the keys artist and album. """ - ret = {} - ret['artist'] = '' - ret['album'] = '' - ret['title'] = self._now_playing - return ret + return self.meta def shutdown(self): """Perform clean shutdown. @@ -205,8 +203,8 @@ def shutdown(self): class AudioPlayerBackend(MediaBackend): """ for audio""" - def load_track(self, uri): - super().load_track(uri) + def load_track(self, uri, metadata: dict = None): + super().load_track(uri, metadata) self.bus.emit(Message("ovos.common_play.track.state", {"state": TrackState.QUEUED_AUDIO})) @@ -229,8 +227,8 @@ class RemoteAudioPlayerBackend(AudioPlayerBackend): class VideoPlayerBackend(MediaBackend): """ for video""" - def load_track(self, uri): - super().load_track(uri) + def load_track(self, uri, metadata: dict = None): + super().load_track(uri, metadata) self.bus.emit(Message("ovos.common_play.track.state", {"state": TrackState.QUEUED_VIDEO})) @@ -254,8 +252,8 @@ class RemoteVideoPlayerBackend(VideoPlayerBackend): class WebPlayerBackend(MediaBackend): """ for web pages""" - def load_track(self, uri): - super().load_track(uri) + def load_track(self, uri, metadata: dict = None): + super().load_track(uri, metadata) self.bus.emit(Message("ovos.common_play.track.state", {"state": TrackState.QUEUED_WEBVIEW})) From e6eb77a508e94b22a7a60b04854672f438464144 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Wed, 7 Feb 2024 19:15:28 +0000 Subject: [PATCH 018/129] Increment Version to 0.0.26a6 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 84d36437..1d1f42cb 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 5 +VERSION_ALPHA = 6 # END_VERSION_BLOCK From 5b89ccb7fe338fc6aba84c3354d46902d5a2da24 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Wed, 7 Feb 2024 19:15:54 +0000 Subject: [PATCH 019/129] Update Changelog --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce02481..5ec87c0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog -## [0.0.26a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a5) (2024-01-08) +## [0.0.26a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a6) (2024-02-07) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a4...0.0.26a5) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a5...0.0.26a6) + +**Implemented enhancements:** + +- feat/track\_meta [\#211](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/211) ([NeonJarbas](https://github.com/NeonJarbas)) + +## [V0.0.26a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.26a5) (2024-01-08) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a4...V0.0.26a5) **Implemented enhancements:** From 650a5a86dff24913b6685b9c154dbf66e2c31b76 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 10 Feb 2024 17:02:30 +0000 Subject: [PATCH 020/129] feat/streaming_tts (#212) * feat/streaming_tts * Update tts.py * docstr * Update tts.py * Update tts.py * Update tts.py * Update tts.py * Update tts.py * Update tts.py * Update tts.py * Update tts.py * fix * use cache in streaming_tts * kwargs fix * docstrs and kwarg cleanup * add logs --- ovos_plugin_manager/templates/tts.py | 216 ++++++++++++++++++++++++--- 1 file changed, 198 insertions(+), 18 deletions(-) diff --git a/ovos_plugin_manager/templates/tts.py b/ovos_plugin_manager/templates/tts.py index d90b99dd..771c0c7e 100644 --- a/ovos_plugin_manager/templates/tts.py +++ b/ovos_plugin_manager/templates/tts.py @@ -21,32 +21,37 @@ # would hang here engine.playback.stop() """ +import abc +import asyncio import inspect import random import re import subprocess -import quebra_frases from os.path import isfile, join from pathlib import Path from queue import Queue from threading import Thread +from typing import AsyncIterable + +import quebra_frases import requests +from ovos_bus_client.apis.enclosure import EnclosureAPI from ovos_bus_client.message import Message, dig_for_message from ovos_config import Configuration -from ovos_plugin_manager.g2p import OVOSG2PFactory, find_g2p_plugins -from ovos_plugin_manager.templates.g2p import OutOfVocabulary -from ovos_plugin_manager.utils.config import get_plugin_config -from ovos_plugin_manager.utils.tts_cache import TextToSpeechCache, hash_sentence from ovos_utils import classproperty -from ovos_utils.file_utils import resolve_resource_file -from ovos_bus_client.apis.enclosure import EnclosureAPI +from ovos_utils.fakebus import FakeBus from ovos_utils.file_utils import get_cache_directory +from ovos_utils.file_utils import resolve_resource_file from ovos_utils.lang.visimes import VISIMES from ovos_utils.log import LOG -from ovos_utils.fakebus import FakeBus from ovos_utils.metrics import Stopwatch from ovos_utils.process_utils import RuntimeRequirements +from ovos_plugin_manager.g2p import OVOSG2PFactory, find_g2p_plugins +from ovos_plugin_manager.templates.g2p import OutOfVocabulary +from ovos_plugin_manager.utils.config import get_plugin_config +from ovos_plugin_manager.utils.tts_cache import TextToSpeechCache, hash_sentence + EMPTY_PLAYBACK_QUEUE_TUPLE = (None, None, None, None, None) SSML_TAGS = re.compile(r'<[^>]*>') @@ -297,7 +302,8 @@ def load_spellings(self, config=None): return {} def begin_audio(self): - """Helper function for child classes to call in execute()""" + """Helper function for child classes to call in execute()""" + self.stopwatch.start() self.add_metric({"metric_type": "tts.start"}) def end_audio(self, listen=False): @@ -347,6 +353,7 @@ def enclosure(self): def enclosure(self, val): TTS.playback.enclosure = val + @abc.abstractmethod def get_tts(self, sentence, wav_file, lang=None): """Abstract method that a tts implementation needs to implement. @@ -485,9 +492,11 @@ def execute(self, sentence, ident=None, listen=False, **kwargs): listen: (bool) True if listen should be triggered at the end of the utterance. """ + self.begin_audio() sentence = self.validate_ssml(sentence) self.add_metric({"metric_type": "tts.ssml.validated"}) self._execute(sentence, ident, listen, **kwargs) + self.end_audio() def _replace_phonetic_spellings(self, sentence): if self.phonetic_spelling: @@ -497,15 +506,17 @@ def _replace_phonetic_spellings(self, sentence): sentence = sentence.replace(word, spelled) return sentence - def _execute(self, sentence, ident, listen, **kwargs): - self.stopwatch.start() - sentence = self._replace_phonetic_spellings(sentence) - chunks = self._preprocess_sentence(sentence) - # Apply the listen flag to the last chunk, set the rest to False - chunks = [(chunks[i], listen if i == len(chunks) - 1 else False) - for i in range(len(chunks))] - self.add_metric({"metric_type": "tts.preprocessed", - "n_chunks": len(chunks)}) + def _execute(self, sentence, ident, listen, preprocess=True, **kwargs): + if preprocess: + sentence = self._replace_phonetic_spellings(sentence) + chunks = self._preprocess_sentence(sentence) + # Apply the listen flag to the last chunk, set the rest to False + chunks = [(chunks[i], listen if i == len(chunks) - 1 else False) + for i in range(len(chunks))] + self.add_metric({"metric_type": "tts.preprocessed", + "n_chunks": len(chunks)}) + else: + chunks = [(sentence, listen)] lang, voice = self.context.get(kwargs) tts_id = join(self.tts_name, voice, lang) @@ -751,6 +762,7 @@ def __init__(self, *args, **kwargs): self.channels = self.config.get("channels", "1") self.rate = self.config.get("rate", "16000") + @abc.abstractmethod def sentence_to_files(self, sentence): """ list of ordered files to concatenate and form final wav file return files (list) , phonemes (list) @@ -801,6 +813,174 @@ class RemoteTTSTimeoutException(RemoteTTSException): pass +class StreamingTTSCallbacks: + """handle the playback of streaming TTS, can be overrided in StreamingTTS""" + def __init__(self, bus, play_args=None, tts_config=None): + self.bus = bus + self.config = tts_config or {} + self.play_args = play_args or ["paplay"] + self._process = None + + def stream_start(self, message=None): + """prepare anything needed to playback streamed audio + events: + - "ovos.common_play.duck" + - "recognizer_loop:audio_output_start" + """ + LOG.info(f"TTS stream start: {self.__class__.__name__}") + message = message or \ + dig_for_message() or \ + Message("speak") + + # we don't use the regular PlaybackThread here, we need to handle recognizer_loop:audio_output_start + if not self.config.get("pulse_duck", False): + self.bus.emit(message.forward("ovos.common_play.duck")) + self.bus.emit(message.forward("recognizer_loop:audio_output_start")) + + if self._process: + self.stream_stop() + LOG.debug(f"stream playback command: {self.play_args}") + self._process = subprocess.Popen(self.play_args, stdin=subprocess.PIPE) + + def stream_chunk(self, chunk): + """play streamed chunk of audio""" + LOG.debug(f"TTS stream chunk: {self.__class__.__name__} - {len(chunk)} bytes") + if self._process: + self._process.stdin.write(chunk) + self._process.stdin.flush() + + def stream_stop(self, listen=False, message=None): + """got all streamed audio, cleanup state + events: + - "ovos.common_play.unduck" + - "recognizer_loop:audio_output_end" + - 'mycroft.mic.listen' + """ + LOG.info(f"TTS stream stop: {self.__class__.__name__}") + message = message or \ + dig_for_message() or \ + Message("speak") + + if self._process: + self._process.stdin.close() + self._process.wait() + self._process = None + + # we don't use the regular PlaybackThread here, we need to handle recognizer_loop:audio_output_end and listen flag + if not self.config.get("pulse_duck", False): + self.bus.emit(message.forward("ovos.common_play.unduck")) + self.bus.emit(message.forward("recognizer_loop:audio_output_end")) + if listen: + self.bus.emit(message.forward('mycroft.mic.listen')) + + +class StreamingTTS(TTS): + """ + Abstract class for a Streaming TTS engine implementation. + Audio is streamed in chunks as it becomes available instead of waiting the full sentence to be synthesized + + this plugin can be used in a synchronous way like any other plugin via self.get_tts(sentence, wav_file) + + to play audio as it becomes available use self.generate_audio(sentence, wav_file) + + NOTE: StreamingTTS does not support phonemes + """ + + def init(self, bus=None, playback=None, callbacks=None): + """ Performs intial setup of TTS object. + + Arguments: + bus: OpenVoiceOS messagebus connection + playback: PlaybackThread + callbacks: StreamingTTSCallbacks + """ + super().init(bus, playback) + self.callbacks = callbacks or StreamingTTSCallbacks(self.bus, + tts_config=self.config) + + @abc.abstractmethod + async def stream_tts(self, sentence) -> AsyncIterable[bytes]: + """yield chunks of TTS audio as they become available""" + raise NotImplementedError + + async def generate_audio(self, sentence, wav_file, play_streaming=True, + listen=False, message=None, plugin_kwargs=None): + """save streamed TTS to wav file, if configured also play TTS as it becomes available""" + plugin_kwargs = plugin_kwargs or {} + if play_streaming: + self.callbacks.stream_start(message) + with open(wav_file, "wb") as f: + try: + async for chunk in self.stream_tts(sentence, **plugin_kwargs): + f.write(chunk) + if play_streaming: + self.callbacks.stream_chunk(chunk) + finally: + if play_streaming: + self.callbacks.stream_stop(listen, message) + return wav_file + + def _execute(self, sentence, ident, listen, **kwargs): + sentence = self._replace_phonetic_spellings(sentence) + self.add_metric({"metric_type": "tts.preprocessed"}) + + sentence_hash = hash_sentence(sentence) + + # parse requested language for this TTS request + lang, voice = self.context.get(kwargs) + kwargs["lang"] = lang + kwargs["voice"] = voice + + # get path to cache final synthesized file + cache = self.get_cache(voice, lang) # cache per tts_id (lang/voice combo) + + # if cached, play existing file instead + if self.enable_cache and sentence_hash in cache: + super()._execute(sentence, ident, listen, preprocess=False, **kwargs) + return + + wav_file = str(cache.define_audio_file(sentence_hash)) + + message = kwargs.get("message") or \ + dig_for_message() or \ + Message("speak") + + # filter kwargs per plugin, different plugins expose different options + plugin_kwargs = {k: v for k, v in kwargs.items() + if k in inspect.signature(self.stream_tts).parameters + and k not in ["sentence", "wav_file", "play_streaming"]} + + # handle streaming TTS + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + self.add_metric({"metric_type": "tts.stream.start"}) + loop.run_until_complete( + self.generate_audio(sentence, wav_file, + play_streaming=True, + listen=listen, + message=message, + plugin_kwargs=plugin_kwargs) + ) + finally: + loop.close() + self.add_metric({"metric_type": "tts.stream.end"}) + + def get_tts(self, sentence, wav_file, **kwargs): + """wrap streaming TTS into sync usage""" + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + wav_file = loop.run_until_complete( + self.generate_audio(sentence, wav_file, + play_streaming=False, + plugin_kwargs=kwargs) + ) + finally: + loop.close() + return wav_file, None # No phonemes + + class RemoteTTS(TTS): """ Abstract class for a Remote TTS engine implementation. From 0d1cd44024d45b535dbbd0a32824c337f533faa6 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 10 Feb 2024 17:02:43 +0000 Subject: [PATCH 021/129] Increment Version to 0.0.26a7 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 1d1f42cb..e55ed5ed 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 6 +VERSION_ALPHA = 7 # END_VERSION_BLOCK From 2e046b4ec257135511357c8c504a5cc538612e91 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 10 Feb 2024 17:03:07 +0000 Subject: [PATCH 022/129] Update Changelog --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ec87c0a..7be4bf88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog -## [0.0.26a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a6) (2024-02-07) +## [0.0.26a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a7) (2024-02-10) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a5...0.0.26a6) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a6...0.0.26a7) + +**Implemented enhancements:** + +- feat/streaming\_tts [\#212](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/212) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.26a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.26a6) (2024-02-07) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a5...V0.0.26a6) **Implemented enhancements:** From 71883ce816b32ad5e0481d3b24421075fdec5721 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sun, 11 Feb 2024 05:44:00 +0000 Subject: [PATCH 023/129] fix/load_persistent_cache() (#209) permanent cache wasnt being loaded on cache init --- ovos_plugin_manager/utils/tts_cache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ovos_plugin_manager/utils/tts_cache.py b/ovos_plugin_manager/utils/tts_cache.py index 0b2ef5bf..177feebe 100644 --- a/ovos_plugin_manager/utils/tts_cache.py +++ b/ovos_plugin_manager/utils/tts_cache.py @@ -241,6 +241,7 @@ def __init__(self, tts_config, tts_name, audio_file_type): # only persist if utterance is spoken >= N times self.persist_thresh = self.config.get("persist_thresh", 1) self._sentence_count = {} + self.load_persistent_cache() def __contains__(self, sha): """The cache contains a SHA if it knows of it and it exists on disk.""" From 8dab1efa8221eeda1ae73563bdd4042dbcb2394d Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 11 Feb 2024 05:44:14 +0000 Subject: [PATCH 024/129] Increment Version to 0.0.26a8 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index e55ed5ed..769470e8 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 7 +VERSION_ALPHA = 8 # END_VERSION_BLOCK From 3fbf78826f9dc0f0a55e184310a56ec976781994 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 11 Feb 2024 05:44:38 +0000 Subject: [PATCH 025/129] Update Changelog --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be4bf88..5c8520b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog -## [0.0.26a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a7) (2024-02-10) +## [0.0.26a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a8) (2024-02-11) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a6...0.0.26a7) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a7...0.0.26a8) + +**Fixed bugs:** + +- fix/load\_persistent\_cache\(\) [\#209](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/209) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.26a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.26a7) (2024-02-10) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a6...V0.0.26a7) **Implemented enhancements:** From 78fa444e42ac5bc6541665b9170d0f6ff7216fa1 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:21:20 +0000 Subject: [PATCH 026/129] feat/streaming_solver (#213) * @abc.abstractmethod ensure all plugins implement mandatory methods * typing * feat/streaming_solver default implementation with fake streaming --- ovos_plugin_manager/templates/solvers.py | 88 +++++++++++++++++------- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/ovos_plugin_manager/templates/solvers.py b/ovos_plugin_manager/templates/solvers.py index a517a1b1..f08dd084 100644 --- a/ovos_plugin_manager/templates/solvers.py +++ b/ovos_plugin_manager/templates/solvers.py @@ -27,12 +27,14 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Solver service can be found at: https://github.com/Neongeckocom/neon_solvers +import abc +from typing import Optional, List, Iterable from json_database import JsonStorageXDG +from ovos_plugin_manager.language import OVOSLangTranslationFactory +from ovos_utils.log import LOG from ovos_utils.xdg_utils import xdg_cache_home from quebra_frases import sentence_tokenize -from ovos_utils.log import LOG -from ovos_plugin_manager.language import OVOSLangTranslationFactory class AbstractSolver: @@ -64,16 +66,18 @@ def __init__(self, config=None, translator=None, *args, **kwargs): self.translator = translator or OVOSLangTranslationFactory.create() @staticmethod - def sentence_split(text, max_sentences=25): + def sentence_split(text: str, max_sentences: int=25) -> List[str]: return sentence_tokenize(text)[:max_sentences] - def _get_user_lang(self, context, lang=None): + def _get_user_lang(self, context: Optional[dict] = None, + lang: Optional[str] = None) -> str: context = context or {} lang = lang or context.get("lang") or self.default_lang lang = lang.split("-")[0] return lang - def _tx_query(self, query, context=None, lang=None): + def _tx_query(self, query: str, + context: Optional[dict] = None, lang: Optional[str] = None): if not self.enable_tx: return query, context, lang context = context or {} @@ -119,28 +123,42 @@ def __init__(self, config=None, translator=None, *args, **kwargs): self.cache = self.spoken_cache = {} # plugin methods to override - def get_spoken_answer(self, query, context): + @abc.abstractmethod + def get_spoken_answer(self, query: str, + context: Optional[dict] = None) -> str: """ query assured to be in self.default_lang return a single sentence text response """ raise NotImplementedError - def get_data(self, query, context): + def stream_utterances(self, query: str, + context: Optional[dict] = None) -> Iterable[str]: + """streaming api, yields utterances as they become available + each utterance can be sent to TTS before we have a full answer + this is particularly helpful with LLMs""" + ans = self.get_spoken_answer(query, context) + for utt in self.sentence_split(ans): + yield utt + + def get_data(self, query: str, + context: Optional[dict] = None) -> dict: """ query assured to be in self.default_lang return a dict response """ - raise NotImplementedError + return {"answer": self.get_spoken_answer(query, context)} - def get_image(self, query, context=None): + def get_image(self, query: str, + context: Optional[dict] = None) -> str: """ query assured to be in self.default_lang return path/url to a single image to acompany spoken_answer """ - raise NotImplementedError + return None - def get_expanded_answer(self, query, context=None): + def get_expanded_answer(self, query: str, + context: Optional[dict] = None) -> List[dict]: """ query assured to be in self.default_lang return a list of ordered steps to expand the answer, eg, "tell me more" @@ -151,10 +169,13 @@ def get_expanded_answer(self, query, context=None): } :return: """ - raise NotImplementedError + return [{"title": query, + "summary": self.get_spoken_answer(query, context), + "img": self.get_image(query, context)}] # user facing methods - def search(self, query, context=None, lang=None): + def search(self, query: str, + context: Optional[dict] = None, lang: Optional[str] = None) -> dict: """ cache and auto translate query if needed returns translated response from self.get_data @@ -181,7 +202,8 @@ def search(self, query, context=None, lang=None): return self.translator.translate_dict(data, user_lang, lang) return data - def visual_answer(self, query, context=None, lang=None): + def visual_answer(self, query: str, + context: Optional[dict] = None, lang: Optional[str] = None) -> str: """ cache and auto translate query if needed returns image that answers query @@ -189,7 +211,8 @@ def visual_answer(self, query, context=None, lang=None): query, context, lang = self._tx_query(query, context, lang) return self.get_image(query, context) - def spoken_answer(self, query, context=None, lang=None): + def spoken_answer(self, query: str, + context: Optional[dict] = None, lang: Optional[str] = None) -> str: """ cache and auto translate query if needed returns chunked and translated response from self.get_spoken_answer @@ -216,7 +239,8 @@ def spoken_answer(self, query, context=None, lang=None): else: return summary - def long_answer(self, query, context=None, lang=None): + def long_answer(self, query: str, + context: Optional[dict] = None, lang: Optional[str] = None) -> List[dict]: """ return a list of ordered steps to expand the answer, eg, "tell me more" step0 is always self.spoken_answer and self.get_image @@ -250,7 +274,10 @@ class TldrSolver(AbstractSolver): handling automatic translation back and forth as needed""" # plugin methods to override - def get_tldr(self, document, context): + + @abc.abstractmethod + def get_tldr(self, document: str, + context: Optional[dict] = None) -> str: """ document assured to be in self.default_lang returns summary of provided document @@ -258,7 +285,8 @@ def get_tldr(self, document, context): raise NotImplementedError # user facing methods - def tldr(self, document, context=None, lang=None): + def tldr(self, document: str, + context: Optional[dict] = None, lang: Optional[str] = None) -> str: """ cache and auto translate query if needed returns summary of provided document @@ -280,7 +308,10 @@ class EvidenceSolver(AbstractSolver): handling automatic translation back and forth as needed""" # plugin methods to override - def get_best_passage(self, evidence, question, context): + + @abc.abstractmethod + def get_best_passage(self, evidence: str, question: str, + context: Optional[dict] = None) -> str: """ evidence and question assured to be in self.default_lang returns summary of provided document @@ -288,7 +319,8 @@ def get_best_passage(self, evidence, question, context): raise NotImplementedError # user facing methods - def extract_answer(self, evidence, question, context=None, lang=None): + def extract_answer(self, evidence: str, question: str, + context: Optional[dict] = None, lang: Optional[str] = None) -> str: """ cache and auto translate evidence and question if needed returns passage from evidence that answers question @@ -311,7 +343,10 @@ class MultipleChoiceSolver(AbstractSolver): handling automatic translation back and forth as needed""" # plugin methods to override - def select_answer(self, query, options, context): + + @abc.abstractmethod + def select_answer(self, query: str, options: List[str], + context: Optional[dict] = None) -> str: """ query and options assured to be in self.default_lang return best answer from options list @@ -319,7 +354,8 @@ def select_answer(self, query, options, context): raise NotImplementedError # user facing methods - def solve(self, query, options, context=None, lang=None): + def solve(self, query: str, options: List[str], + context: Optional[dict] = None, lang: Optional[str] = None) -> str: """ cache and auto translate query and options if needed returns best answer from provided options @@ -341,7 +377,10 @@ class EntailmentSolver(AbstractSolver): handling automatic translation back and forth as needed""" # plugin methods to override - def check_entailment(self, premise, hypothesis, context): + + @abc.abstractmethod + def check_entailment(self, premise: str, hypothesis: str, + context: Optional[dict] = None) -> bool: """ premise and hyopithesis assured to be in self.default_lang return Bool, True if premise entails the hypothesis False otherwise @@ -349,7 +388,8 @@ def check_entailment(self, premise, hypothesis, context): raise NotImplementedError # user facing methods - def entails(self, premise, hypothesis, context=None, lang=None): + def entails(self, premise: str, hypothesis: str, + context: Optional[dict] = None, lang: Optional[str] = None) -> bool: """ cache and auto translate premise and hypothesis if needed return Bool, True if premise entails the hypothesis False otherwise From 8154147ccbba47a2586a14d8442421c56609e085 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Tue, 13 Feb 2024 17:21:36 +0000 Subject: [PATCH 027/129] Increment Version to 0.0.26a9 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 769470e8..b01c46a8 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 8 +VERSION_ALPHA = 9 # END_VERSION_BLOCK From 46b8a6005fa149521bd41095d0a9bba805dc0d34 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Tue, 13 Feb 2024 17:21:59 +0000 Subject: [PATCH 028/129] Update Changelog --- CHANGELOG.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c8520b6..fa6dcda6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,20 @@ # Changelog -## [0.0.26a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a8) (2024-02-11) +## [0.0.26a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a9) (2024-02-13) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a7...0.0.26a8) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a8...0.0.26a9) + +**Implemented enhancements:** + +- feat/streaming\_solver [\#213](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/213) ([JarbasAl](https://github.com/JarbasAl)) + +**Fixed bugs:** + +- Messages from ovos\_utils.log.LOG are swallowed and not sent to STDOUT [\#173](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/173) + +## [V0.0.26a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.26a8) (2024-02-11) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a7...V0.0.26a8) **Fixed bugs:** From 8619ac60ac6ef703bc1c84716a4229798e66a6fa Mon Sep 17 00:00:00 2001 From: builderjer <34875857+builderjer@users.noreply.github.com> Date: Sat, 17 Feb 2024 11:01:27 -0700 Subject: [PATCH 029/129] fix: AdminPlugin (#214) * fix: AdminPlugin The AdminPlugins would not load * simplified Admin classes * Fixed tests? --- ovos_plugin_manager/templates/phal.py | 7 ++++--- test/unittests/test_phal.py | 15 ++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ovos_plugin_manager/templates/phal.py b/ovos_plugin_manager/templates/phal.py index 8a8fb471..4ec7fa53 100644 --- a/ovos_plugin_manager/templates/phal.py +++ b/ovos_plugin_manager/templates/phal.py @@ -515,7 +515,8 @@ def _deactivate_mouth_events(self, message=None): """Disable movement of the mouth with speech""" self._mouth_events = False +class AdminPlugin(PHALPlugin): + """Running as Admin""" -# Just for api consistency -AdminPlugin = PHALPlugin -AdminValidator = PHALValidator +class AdminValidator(PHALValidator): + """Running as Admin""" diff --git a/test/unittests/test_phal.py b/test/unittests/test_phal.py index 3f3f0ccf..89232d01 100644 --- a/test/unittests/test_phal.py +++ b/test/unittests/test_phal.py @@ -17,12 +17,17 @@ def test_PHAL_Plugin(self): from ovos_plugin_manager.templates.phal import PHALValidator # TODO - def test_admin_classes(self): - from ovos_plugin_manager.templates.phal import AdminPlugin, \ - AdminValidator, PHALPlugin, PHALValidator - self.assertEqual(AdminPlugin, PHALPlugin) - self.assertEqual(AdminValidator, PHALValidator) + def test_Admin_Validator(self): + from ovos_plugin_manager.templates.phal import AdminValidator + self.assertTrue(AdminValidator.validate()) + self.assertTrue(AdminValidator.validate({"test": "val"})) + self.assertTrue(AdminValidator.validate({"enabled": True})) + self.assertFalse(AdminValidator.validate({"enabled": False})) + self.assertFalse(AdminValidator.validate({"enabled": None})) + def test_Admin_Plugin(self): + from ovos_plugin_manager.templates.phal import AdminPlugin + # TODO class TestPHAL(unittest.TestCase): PLUGIN_TYPE = PluginTypes.PHAL From 2b678aeef0235249a595591e4f8be7dbf1073059 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 17 Feb 2024 18:01:43 +0000 Subject: [PATCH 030/129] Increment Version to 0.0.26a10 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index b01c46a8..f1d9a553 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 9 +VERSION_ALPHA = 10 # END_VERSION_BLOCK From 1465f7c8e93d64f3ce615840f4aea37129deaa9e Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 17 Feb 2024 18:02:12 +0000 Subject: [PATCH 031/129] Update Changelog --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa6dcda6..149a12c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog -## [0.0.26a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a9) (2024-02-13) +## [0.0.26a10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a10) (2024-02-17) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a8...0.0.26a9) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a9...0.0.26a10) + +**Fixed bugs:** + +- fix: AdminPlugin [\#214](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/214) ([builderjer](https://github.com/builderjer)) + +## [V0.0.26a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.26a9) (2024-02-13) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a8...V0.0.26a9) **Implemented enhancements:** From 85b1a3b2074a2e8710e6eb9bdba09e9aea0fe33c Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 9 Mar 2024 22:32:11 -0600 Subject: [PATCH 032/129] chore(docs): add a long description to PyPi (#215) Partially fufills https://github.com/OpenVoiceOS/ovos-core/issues/390 --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8137f04a..f73a96d9 100644 --- a/setup.py +++ b/setup.py @@ -56,6 +56,8 @@ def required(requirements_file): STT_PLUGIN_ENTRY_POINT = 'ovos-stt-plugin-dummy=ovos_plugin_manager.templates.stt:STT' WW_PLUGIN_ENTRY_POINT = 'ovos-ww-plugin-dummy=ovos_plugin_manager.templates.hotwords:HotWordEngine' +with open("README.md", "r") as f: + long_description = f.read() setup( name='ovos-plugin-manager', @@ -70,8 +72,10 @@ def required(requirements_file): author='jarbasAi', install_requires=required("requirements/requirements.txt"), package_data={'': package_files('ovos-plugin-manager')}, - author_email='jarbasai@mailfence.com', + author_email='jarbas@openvoiceos.com', description='OpenVoiceOS plugin manager', + long_description=long_description, + long_description_content_type="text/markdown", entry_points={ 'intentbox.segmentation': SEG_PLUGIN_ENTRY_POINT, 'intentbox.tokenization': TOK_PLUGIN_ENTRY_POINT, From 9d773bb555de49c312332b58099a90f704de9058 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 10 Mar 2024 04:32:31 +0000 Subject: [PATCH 033/129] Increment Version to --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index f1d9a553..43b0687a 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 10 +VERSION_ALPHA = 11 # END_VERSION_BLOCK From 8244ec591f95c950a1fe7309e4c516faa5206e3e Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 10 Mar 2024 04:32:55 +0000 Subject: [PATCH 034/129] Update Changelog --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 149a12c8..7ca50974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog -## [0.0.26a10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a10) (2024-02-17) +## [Unreleased](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/HEAD) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a9...0.0.26a10) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a10...HEAD) + +**Merged pull requests:** + +- chore\(docs\): add a long description to PyPi [\#215](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/215) ([mikejgray](https://github.com/mikejgray)) + +## [V0.0.26a10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.26a10) (2024-02-17) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a9...V0.0.26a10) **Fixed bugs:** From c0172f4c57595281a7a38e0dea9cfd6248d44e0b Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 20 Apr 2024 00:23:05 +0100 Subject: [PATCH 035/129] fix/tts_reload (#219) fixes https://github.com/OpenVoiceOS/ovos-audio/issues/50 --- ovos_plugin_manager/templates/tts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/templates/tts.py b/ovos_plugin_manager/templates/tts.py index 771c0c7e..6f91085b 100644 --- a/ovos_plugin_manager/templates/tts.py +++ b/ovos_plugin_manager/templates/tts.py @@ -339,7 +339,7 @@ def _init_playback(self, playback): if not TTS.playback.enclosure: TTS.playback.enclosure = EnclosureAPI(self.bus) - if not TTS.playback.is_running: + if not TTS.playback.is_alive(): TTS.playback.start() @property From 4daa91398d2e4d4655aaf7c8faaa9481ddd1d59a Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Fri, 19 Apr 2024 23:23:20 +0000 Subject: [PATCH 036/129] Increment Version to --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 43b0687a..32daec8b 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 11 +VERSION_ALPHA = 12 # END_VERSION_BLOCK From f0ea727146d3d07fa7436a9f657403e75b75d4ca Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Fri, 19 Apr 2024 23:23:43 +0000 Subject: [PATCH 037/129] Update Changelog --- CHANGELOG.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ca50974..28314e00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,19 @@ ## [Unreleased](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/HEAD) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a10...HEAD) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...HEAD) + +**Fixed bugs:** + +- fix/tts\_reload [\#219](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/219) ([JarbasAl](https://github.com/JarbasAl)) + +**Closed issues:** + +- Error about Azure TTS plugin [\#193](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/193) + +## [V](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V) (2024-03-10) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a10...V) **Merged pull requests:** From 460027a27145d043778ae133a68d6d52e234c667 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 20 Apr 2024 07:18:04 +0100 Subject: [PATCH 038/129] feat/lang_detection_plugin (#220) --- ovos_plugin_manager/audio_transformers.py | 15 +++++++++-- ovos_plugin_manager/templates/transformers.py | 26 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/ovos_plugin_manager/audio_transformers.py b/ovos_plugin_manager/audio_transformers.py index 3e9d6db0..3f4e3b8c 100644 --- a/ovos_plugin_manager/audio_transformers.py +++ b/ovos_plugin_manager/audio_transformers.py @@ -1,7 +1,8 @@ -from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes -from ovos_plugin_manager.templates.transformers import AudioTransformer from ovos_utils.log import LOG +from ovos_plugin_manager.templates.transformers import AudioTransformer, AudioLanguageDetector +from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes + def find_plugins(*args, **kwargs): # TODO: Deprecate in 0.1.0 @@ -57,3 +58,13 @@ def get_audio_transformer_module_configs(module_name: str): """ from ovos_plugin_manager.utils.config import load_plugin_configs return load_plugin_configs(module_name, PluginConfigTypes.AUDIO_TRANSFORMER) + + +def find_audio_lang_detector_plugins() -> dict: + """ + Find all installed audio language detector plugins + @return: dict plugin names to entrypoints + """ + from ovos_plugin_manager.utils import find_plugins + return {k: p for k, p in find_plugins(PluginTypes.AUDIO_TRANSFORMER).items() + if issubclass(p, AudioLanguageDetector)} diff --git a/ovos_plugin_manager/templates/transformers.py b/ovos_plugin_manager/templates/transformers.py index d2e48005..ead4282a 100644 --- a/ovos_plugin_manager/templates/transformers.py +++ b/ovos_plugin_manager/templates/transformers.py @@ -1,7 +1,10 @@ -from typing import List, Tuple +import abc +from typing import List, Tuple, Optional -from ovos_config.config import Configuration from ovos_bus_client.util import get_mycroft_bus +from ovos_config.config import Configuration +from ovos_config.locale import get_default_lang +from ovos_utils.log import LOG from ovos_plugin_manager.utils import ReadWriteStream @@ -233,3 +236,22 @@ def transform(self, wav_file: str, context: dict = None) -> Tuple[str, dict]: def default_shutdown(self): """ perform any shutdown actions """ pass + + +class AudioLanguageDetector(AudioTransformer): + + @property + def valid_langs(self) -> List[str]: + return list( + set([get_default_lang()] + Configuration().get("secondary_langs", [])) + ) + + @abc.abstractmethod + def detect(self, audio_data: bytes, valid_langs: Optional[List] = None) -> Tuple[str, float]: + raise NotImplementedError + + # plugin api + def transform(self, audio_data: bytes): + lang, prob = self.detect(audio_data) + LOG.info(f"Detected speech language '{lang}' with probability {prob}") + return audio_data, {"stt_lang": lang, "lang_probability": prob} From b4d88595667a3b1bb3328aeb882b8121777185f3 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Apr 2024 06:18:18 +0000 Subject: [PATCH 039/129] Increment Version to --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 32daec8b..b8520073 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 12 +VERSION_ALPHA = 13 # END_VERSION_BLOCK From f675ab3d6345aad51a0a9e9d0d90af8436aae133 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Apr 2024 06:18:41 +0000 Subject: [PATCH 040/129] Update Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28314e00..7f46e18d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ [Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...HEAD) +**Implemented enhancements:** + +- feat/lang\_detection\_plugin [\#220](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/220) ([JarbasAl](https://github.com/JarbasAl)) + **Fixed bugs:** - fix/tts\_reload [\#219](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/219) ([JarbasAl](https://github.com/JarbasAl)) From 264cdf7fa4373de230694605078e08666026b6a9 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Apr 2024 06:30:05 +0000 Subject: [PATCH 041/129] Increment Version to --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index b8520073..80160124 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 13 +VERSION_ALPHA = 14 # END_VERSION_BLOCK From b54c0a64ba50aa8c3dcf864b43c66b0185f31633 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sun, 21 Apr 2024 00:15:37 +0100 Subject: [PATCH 042/129] hotfix/skip_gh_release the neon automation stopped reporting version causing breakage TODO: move to our own --- .github/workflows/publish_alpha.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/publish_alpha.yml b/.github/workflows/publish_alpha.yml index 5091a6f3..83ed44cc 100644 --- a/.github/workflows/publish_alpha.yml +++ b/.github/workflows/publish_alpha.yml @@ -34,20 +34,6 @@ jobs: runs-on: ubuntu-latest needs: update_version steps: - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token - with: - tag_name: V${{ needs.update_version.outputs.version }} - release_name: Release ${{ needs.update_version.outputs.version }} - body: | - Changes in this Release - ${{ needs.update_version.outputs.changelog }} - draft: false - prerelease: true - commitish: dev - name: Checkout Repository uses: actions/checkout@v2 with: From 30141f150734a9d1c7ec4fbb78b8fd6ea841dc5c Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Apr 2024 23:17:54 +0000 Subject: [PATCH 043/129] Increment Version to --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 80160124..401f6ae7 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 14 +VERSION_ALPHA = 15 # END_VERSION_BLOCK From bf6524f9c2d2c3fe9b75c7d980b7a2e47cdc739a Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Apr 2024 23:18:23 +0000 Subject: [PATCH 044/129] Update Changelog --- CHANGELOG.md | 844 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 844 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f46e18d..f8840d7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,850 @@ - packaging/update imports [\#203](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/203) ([JarbasAl](https://github.com/JarbasAl)) - Update requirements.txt [\#201](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/201) ([JarbasAl](https://github.com/JarbasAl)) +## [V0.0.25](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25) (2023-12-29) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a4...V0.0.25) + +## [V0.0.25a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a4) (2023-12-29) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a3...V0.0.25a4) + +**Merged pull requests:** + +- update imports [\#198](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/198) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.25a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a3) (2023-12-29) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a2...V0.0.25a3) + +**Merged pull requests:** + +- Update requirements.txt [\#197](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/197) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.25a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a2) (2023-12-28) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a1...V0.0.25a2) + +**Closed issues:** + +- module 'inspect' has no attribute 'formatargspec' [\#189](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/189) + +**Merged pull requests:** + +- refactor/move\_from\_utils [\#196](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/196) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.25a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a1) (2023-12-09) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24...V0.0.25a1) + +**Implemented enhancements:** + +- feat/disable\_cache [\#194](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/194) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.24](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24) (2023-10-26) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a18...V0.0.24) + +## [V0.0.24a18](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a18) (2023-10-25) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a17...V0.0.24a18) + +**Fixed bugs:** + +- STT class init with wrong config [\#132](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/132) +- Fix STT and TTS configuration handling [\#187](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/187) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.24a17](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a17) (2023-10-25) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a16...V0.0.24a17) + +**Merged pull requests:** + +- Update test dependencies [\#190](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/190) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.24a16](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a16) (2023-10-24) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a15...V0.0.24a16) + +**Fixed bugs:** + +- remove log spam [\#188](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/188) ([JarbasAl](https://github.com/JarbasAl)) + +**Closed issues:** + +- support ovos-translate-server [\#182](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/182) + +## [V0.0.24a15](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a15) (2023-10-12) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a14...V0.0.24a15) + +**Fixed bugs:** + +- Language Module Factory Tests/Fixes [\#184](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/184) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.24a14](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a14) (2023-10-11) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a13...V0.0.24a14) + +**Fixed bugs:** + +- No module named 'mycroft\_bus\_client' [\#146](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/146) +- Circular import while importing `StreamHandler` [\#130](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/130) + +**Merged pull requests:** + +- Add license notice and link to plugins from neon\_solvers package [\#185](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/185) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.24a13](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a13) (2023-10-10) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a12...V0.0.24a13) + +**Implemented enhancements:** + +- feat/translate\_plug\_as\_arg [\#183](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/183) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.24a12](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a12) (2023-10-08) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a11...V0.0.24a12) + +**Fixed bugs:** + +- fix/playback\_thread startup [\#181](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/181) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.24a11](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a11) (2023-10-08) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a10...V0.0.24a11) + +**Fixed bugs:** + +- fix/playback\_thread startup [\#180](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/180) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.24a10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a10) (2023-10-07) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a9...V0.0.24a10) + +**Implemented enhancements:** + +- feat/ovos\_dialog\_tts\_transformers [\#179](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/179) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.24a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a9) (2023-09-22) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a8...V0.0.24a9) + +**Fixed bugs:** + +- fix/deprecation\_warnings [\#178](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/178) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.24a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a8) (2023-09-17) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a6...V0.0.24a8) + +**Merged pull requests:** + +- Don't swallow plugin load errors [\#176](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/176) ([strugee](https://github.com/strugee)) + +## [V0.0.24a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a6) (2023-09-08) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a5...V0.0.24a6) + +**Implemented enhancements:** + +- feat/extract\_speech [\#139](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/139) ([JarbasAl](https://github.com/JarbasAl)) + +**Fixed bugs:** + +- fix/audio\_config [\#174](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/174) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.24a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a5) (2023-07-07) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a4...V0.0.24a5) + +**Merged pull requests:** + +- Updates logging around language plugin errors [\#171](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/171) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.24a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a4) (2023-07-07) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a3...V0.0.24a4) + +**Merged pull requests:** + +- Replace `sleep` with `Event.wait` [\#170](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/170) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.24a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a3) (2023-07-04) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a2...V0.0.24a3) + +**Fixed bugs:** + +- feat/optional\_g2p [\#169](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/169) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.24a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a2) (2023-07-04) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a1...V0.0.24a2) + +**Fixed bugs:** + +- fix/on\_mouth\_viseme\_list [\#168](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/168) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.24a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a1) (2023-06-21) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23...V0.0.24a1) + +**Implemented enhancements:** + +- feat/tts\_session\_context [\#167](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/167) ([JarbasAl](https://github.com/JarbasAl)) + +**Fixed bugs:** + +- Automation fixes [\#164](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/164) ([NeonDaniel](https://github.com/NeonDaniel)) + +**Closed issues:** + +- ImportError: Wake Word marvin with module ovos-ww-plugin-precise-lite failed to load [\#166](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/166) + +## [V0.0.23](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23) (2023-06-03) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a27...V0.0.23) + +## [V0.0.23a27](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a27) (2023-06-02) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a26...V0.0.23a27) + +**Fixed bugs:** + +- Update `get_plugin_config` and tests for GUI support [\#163](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/163) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.23a26](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a26) (2023-06-02) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a25...V0.0.23a26) + +**Fixed bugs:** + +- Logged exception when cache directory doesn't exist [\#133](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/133) +- Add error handling and tests to cache curation [\#162](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/162) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.23a25](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a25) (2023-06-02) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a24...V0.0.23a25) + +**Merged pull requests:** + +- Troubleshoot default STT config handling [\#161](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/161) ([NeonDaniel](https://github.com/NeonDaniel)) +- Add trivial test case to `test_ocp` [\#160](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/160) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.23a24](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a24) (2023-05-31) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a23...V0.0.23a24) + +**Implemented enhancements:** + +- Add persona plugins [\#159](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/159) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.23a23](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a23) (2023-05-31) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a22...V0.0.23a23) + +**Merged pull requests:** + +- Release Automation and Stable Dependencies [\#142](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/142) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.23a22](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a22) (2023-05-26) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a21...V0.0.23a22) + +**Implemented enhancements:** + +- Outline Unit Tests [\#158](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/158) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.23a21](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a21) (2023-05-26) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a20...V0.0.23a21) + +**Implemented enhancements:** + +- Refactor to consolidate common logic [\#157](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/157) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.23a20](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a20) (2023-05-24) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a19...V0.0.23a20) + +**Merged pull requests:** + +- Add VAD module tests with bugfixes and doc updates [\#156](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/156) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.23a19](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a19) (2023-05-22) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a18...V0.0.23a19) + +**Merged pull requests:** + +- Fix MicrophoneFactory config handling [\#155](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/155) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.23a18](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a18) (2023-05-20) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a17...V0.0.23a18) + +**Implemented enhancements:** + +- Dinkum listener compat fixes with tests [\#154](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/154) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.23a17](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a17) (2023-05-18) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a16...V0.0.23a17) + +**Fixed bugs:** + +- Fix Hotword Plugin Load Compat [\#153](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/153) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.23a16](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a16) (2023-05-17) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a15...V0.0.23a16) + +**Implemented enhancements:** + +- feat/microphone\_plugs [\#152](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/152) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a15](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a15) (2023-05-16) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a14...V0.0.23a15) + +**Implemented enhancements:** + +- :tada: - GUI plugin [\#151](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/151) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a14](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a14) (2023-05-12) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a13...V0.0.23a14) + +**Fixed bugs:** + +- fix/ww\_json [\#150](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/150) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a13](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a13) (2023-05-12) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a12...V0.0.23a13) + +**Merged pull requests:** + +- refactor/solvers\_init [\#149](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/149) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a12](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a12) (2023-05-12) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a11...V0.0.23a12) + +**Implemented enhancements:** + +- feat/more\_solvers [\#148](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/148) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a11](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a11) (2023-05-06) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a10...V0.0.23a11) + +**Implemented enhancements:** + +- feat/audio2ipa [\#147](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/147) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a10) (2023-04-29) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a9...V0.0.23a10) + +**Fixed bugs:** + +- better translate fallback module handling [\#145](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/145) ([emphasize](https://github.com/emphasize)) + +**Closed issues:** + +- translator: api key from configuration [\#143](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/143) + +## [V0.0.23a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a9) (2023-04-13) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a8...V0.0.23a9) + +**Implemented enhancements:** + +- feat/neon\_transformers [\#141](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/141) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a8) (2023-04-13) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a7...V0.0.23a8) + +**Implemented enhancements:** + +- feat/question\_solvers [\#140](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/140) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a7) (2023-04-09) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a6...V0.0.23a7) + +**Fixed bugs:** + +- fix plugin requirements conflicts [\#138](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/138) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a6) (2023-04-07) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a5...V0.0.23a6) + +**Merged pull requests:** + +- migrate to ovos-bus-client [\#136](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/136) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a5) (2023-04-05) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a4...V0.0.23a5) + +## [V0.0.23a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a4) (2023-03-31) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a3...V0.0.23a4) + +**Fixed bugs:** + +- more missing imports [\#135](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/135) ([builderjer](https://github.com/builderjer)) + +## [V0.0.23a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a3) (2023-03-31) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a2...V0.0.23a3) + +**Fixed bugs:** + +- fix/missing\_imports md5 [\#134](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/134) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a2) (2023-03-31) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a1...V0.0.23a2) + +**Implemented enhancements:** + +- feat/voice\_configs [\#131](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/131) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.23a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a1) (2023-03-10) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22...V0.0.23a1) + +**Fixed bugs:** + +- fix/dummy\_stt\_plugin [\#129](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/129) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.22](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22) (2023-03-02) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a9...V0.0.22) + +## [V0.0.22a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a9) (2023-03-01) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a8...V0.0.22a9) + +**Merged pull requests:** + +- Update load exception logging [\#128](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/128) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.22a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a8) (2023-02-25) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a7...V0.0.22a8) + +**Merged pull requests:** + +- Update ovos\_utils dependency to stable release [\#127](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/127) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.22a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a7) (2023-02-23) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a6...V0.0.22a7) + +**Fixed bugs:** + +- fix/chained\_sei\_extract [\#124](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/124) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.22a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a6) (2023-02-14) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a5...V0.0.22a6) + +## [V0.0.22a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a5) (2023-02-14) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a4...V0.0.22a5) + +**Fixed bugs:** + +- fix/logspam [\#123](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/123) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.22a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a4) (2023-02-14) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a3...V0.0.22a4) + +**Fixed bugs:** + +- feat/improve\_ocp\_plugin\_error\_handling [\#122](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/122) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.22a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a3) (2023-02-09) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a2...V0.0.22a3) + +**Implemented enhancements:** + +- feat/generalize runtime requirements [\#118](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/118) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.22a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a2) (2023-02-02) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a1...V0.0.22a2) + +**Fixed bugs:** + +- fix/entrypoint\_loading [\#117](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/117) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.22a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a1) (2023-01-28) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21...V0.0.22a1) + +**Merged pull requests:** + +- help out with misconfigured TTS modules [\#101](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/101) ([osheroff](https://github.com/osheroff)) + +## [V0.0.21](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21) (2023-01-24) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a8...V0.0.21) + +## [V0.0.21a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a8) (2023-01-22) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a7...V0.0.21a8) + +**Fixed bugs:** + +- fixed bug with 'emit' [\#103](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/103) ([builderjer](https://github.com/builderjer)) + +## [V0.0.21a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a7) (2023-01-20) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a6...V0.0.21a7) + +**Implemented enhancements:** + +- feat/ww fallback [\#99](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/99) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.21a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a6) (2022-12-28) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a5...V0.0.21a6) + +**Implemented enhancements:** + +- Feat/ocp stream extractors [\#100](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/100) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.21a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a5) (2022-12-10) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a4...V0.0.21a5) + +**Merged pull requests:** + +- Add alternating LED animation [\#98](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/98) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.21a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a4) (2022-12-05) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a3...V0.0.21a4) + +**Fixed bugs:** + +- fix - phal plugins should be threads [\#96](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/96) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.0.21a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a3) (2022-11-22) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a2...V0.0.21a3) + +**Merged pull requests:** + +- Fix typo in `get_tts_config` to get tts section from passed config [\#95](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/95) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.21a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a2) (2022-11-19) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a1...V0.0.21a2) + +**Merged pull requests:** + +- One-shot LED animations [\#93](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/93) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.21a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a1) (2022-11-18) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20...V0.0.21a1) + +**Fixed bugs:** + +- Fix typo in `RefillLedAnimation` [\#92](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/92) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [V0.0.20](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20) (2022-11-15) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a6...V0.0.20) + +## [V0.0.20a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a6) (2022-11-10) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a5...V0.0.20a6) + +## [V0.0.20a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a5) (2022-11-09) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a4...V0.0.20a5) + +## [V0.0.20a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a4) (2022-11-08) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a3...V0.0.20a4) + +## [V0.0.20a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a3) (2022-11-07) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a2...V0.0.20a3) + +## [V0.0.20a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a2) (2022-11-01) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a1...V0.0.20a2) + +## [V0.0.20a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a1) (2022-10-28) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19...V0.0.20a1) + +## [V0.0.19](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19) (2022-10-25) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a12...V0.0.19) + +## [V0.0.19a12](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a12) (2022-10-24) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a11...V0.0.19a12) + +## [V0.0.19a11](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a11) (2022-10-21) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a10...V0.0.19a11) + +## [V0.0.19a10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a10) (2022-10-21) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a9...V0.0.19a10) + +## [V0.0.19a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a9) (2022-10-20) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a8...V0.0.19a9) + +## [V0.0.19a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a8) (2022-10-18) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a7...V0.0.19a8) + +## [V0.0.19a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a7) (2022-10-18) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a6...V0.0.19a7) + +## [V0.0.19a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a6) (2022-10-18) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a5...V0.0.19a6) + +## [V0.0.19a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a5) (2022-10-17) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a4...V0.0.19a5) + +## [V0.0.19a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a4) (2022-09-18) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a3...V0.0.19a4) + +## [V0.0.19a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a3) (2022-09-17) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a2...V0.0.19a3) + +## [V0.0.19a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a2) (2022-09-16) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a1...V0.0.19a2) + +## [V0.0.19a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a1) (2022-08-26) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18...V0.0.19a1) + +## [V0.0.18](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18) (2022-08-17) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a13...V0.0.18) + +## [V0.0.18a13](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a13) (2022-08-17) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a12...V0.0.18a13) + +## [V0.0.18a12](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a12) (2022-08-02) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a11...V0.0.18a12) + +## [V0.0.18a11](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a11) (2022-07-29) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a10...V0.0.18a11) + +## [V0.0.18a10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a10) (2022-07-22) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a9...V0.0.18a10) + +## [V0.0.18a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a9) (2022-07-07) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a8...V0.0.18a9) + +## [V0.0.18a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a8) (2022-06-17) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a7...V0.0.18a8) + +## [V0.0.18a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a7) (2022-06-17) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a6...V0.0.18a7) + +## [V0.0.18a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a6) (2022-06-15) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a5...V0.0.18a6) + +## [V0.0.18a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a5) (2022-06-03) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a4...V0.0.18a5) + +## [V0.0.18a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a4) (2022-06-01) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a3...V0.0.18a4) + +## [V0.0.18a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a3) (2022-05-22) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a2...V0.0.18a3) + +## [V0.0.18a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a2) (2022-05-22) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a1...V0.0.18a2) + +## [V0.0.18a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a1) (2022-05-22) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.17...V0.0.18a1) + +## [V0.0.17](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.17) (2022-04-26) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.17a2...V0.0.17) + +## [V0.0.17a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.17a2) (2022-04-26) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.17a1...V0.0.17a2) + +## [V0.0.17a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.17a1) (2022-04-20) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.16...V0.0.17a1) + +## [V0.0.16](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.16) (2022-03-30) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.16a3...V0.0.16) + +## [V0.0.16a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.16a3) (2022-03-30) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.16a2...V0.0.16a3) + +## [V0.0.16a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.16a2) (2022-03-30) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.16a1...V0.0.16a2) + +## [V0.0.16a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.16a1) (2022-03-30) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.15...V0.0.16a1) + +## [V0.0.15](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.15) (2022-03-26) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.15a2...V0.0.15) + +## [V0.0.15a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.15a2) (2022-03-26) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.15a1...V0.0.15a2) + +## [V0.0.15a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.15a1) (2022-03-26) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14...V0.0.15a1) + +## [V0.0.14](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14) (2022-03-26) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14a5...V0.0.14) + +## [V0.0.14a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14a5) (2022-03-26) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14a4...V0.0.14a5) + +## [V0.0.14a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14a4) (2022-03-18) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14a3...V0.0.14a4) + +## [V0.0.14a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14a3) (2022-03-14) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14a2...V0.0.14a3) + +## [V0.0.14a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14a2) (2022-03-14) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14a1...V0.0.14a2) + +## [V0.0.14a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14a1) (2022-03-14) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.13...V0.0.14a1) + +## [V0.0.13](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.13) (2022-03-12) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.13a1...V0.0.13) + +## [V0.0.13a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.13a1) (2022-03-12) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.12...V0.0.13a1) + +## [V0.0.12](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.12) (2022-03-05) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.12a1...V0.0.12) + +## [V0.0.12a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.12a1) (2022-03-05) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11...V0.0.12a1) + +## [V0.0.11](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11) (2022-03-05) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11a5...V0.0.11) + +## [V0.0.11a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11a5) (2022-03-05) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11a4...V0.0.11a5) + +## [V0.0.11a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11a4) (2022-03-03) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11a3...V0.0.11a4) + +## [V0.0.11a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11a3) (2022-03-03) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11a2...V0.0.11a3) + +## [V0.0.11a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11a2) (2022-03-03) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11a1...V0.0.11a2) + +## [V0.0.11a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11a1) (2022-03-01) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.10...V0.0.11a1) + +## [V0.0.10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.10) (2022-03-01) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.10a1...V0.0.10) + +## [V0.0.10a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.10a1) (2022-03-01) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.9...V0.0.10a1) + +## [V0.0.9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.9) (2022-03-01) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.8a3...V0.0.9) + +## [V0.0.8a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.8a3) (2022-02-28) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.8a2...V0.0.8a3) + +## [V0.0.8a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.8a2) (2022-02-25) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.8a1...V0.0.8a2) + +## [V0.0.8a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.8a1) (2022-02-24) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.8...V0.0.8a1) + +## [V0.0.8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.8) (2022-02-24) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.7a1...V0.0.8) + +## [V0.0.7a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.7a1) (2022-02-24) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/0.0.4...V0.0.7a1) + +## [0.0.4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.4) (2022-02-17) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/0.0.2...0.0.4) + +## [0.0.2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.2) (2021-11-05) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/c73ba5973781e871f9434113b84bd5d2845ae1d6...0.0.2) + \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* From f29fed1c48e971d6b79db9ef05bf2eba0ccec06a Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Mon, 29 Apr 2024 05:44:09 +0100 Subject: [PATCH 045/129] feat/restore phonetic spellings (#195) * refactor/tts_cache fix kwargs handling in synth method move methods around for readability and group them based on functionality add more deprecation warnings lang from session move cache to TTSContext * voice from Session * save a call to find_tts_plugins * save a call to find_tts_plugins * streaming TTS too * tests * docstrs * unittests for session * unittests for session * drop tts_prefs from session * drop tts_prefs from session * support phonetic spellings again per lang used to refer to a hardcoded nglish file in mycroft-core specific to mimic1 now generalized to be per TTS plugin and live in a "locale" folder like everything else * fix root_dir * avoid reinits of playback thread * add curate_caches helper for ovos-audio usage --- .github/workflows/unit_tests.yml | 2 +- ovos_plugin_manager/templates/tts.py | 1080 +++++++++++++++----------- ovos_plugin_manager/tts.py | 4 +- test/unittests/test_tts.py | 143 +++- 4 files changed, 776 insertions(+), 453 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 89dc6e84..69a78d3a 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -33,7 +33,7 @@ jobs: strategy: max-parallel: 2 matrix: - python-version: [ 3.7, 3.8, 3.9, "3.10" ] + python-version: [ 3.8, 3.9, "3.10" ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/ovos_plugin_manager/templates/tts.py b/ovos_plugin_manager/templates/tts.py index 6f91085b..fe24bf86 100644 --- a/ovos_plugin_manager/templates/tts.py +++ b/ovos_plugin_manager/templates/tts.py @@ -1,49 +1,29 @@ -""" -this module is meant to enable usage of mycroft plugins inside and outside -mycroft, importing from here will make things work as planned in mycroft, -but if outside mycroft things will still work - -The main use case is for plugins to be used across different projects - -## Differences from upstream - -TTS: -- added automatic guessing of phonemes/visime calculation, enabling mouth -movements for all TTS engines (only mimic implements this in upstream) -- playback start call has been omitted and moved to init method -- init is called by mycroft, but non mycroft usage wont call it -- outside mycroft the enclosure is not set, bus is dummy and playback thread is not used - - playback queue is not wanted when some module is calling get_tts - - if playback was started on init then python scripts would never stop - from mycroft.tts import TTSFactory - engine = TTSFactory.create() - engine.get_tts("hello world", "hello_world." + engine.audio_ext) - # would hang here - engine.playback.stop() -""" import abc import asyncio import inspect -import random +import os.path import re +import sys import subprocess from os.path import isfile, join from pathlib import Path from queue import Queue from threading import Thread -from typing import AsyncIterable +from typing import AsyncIterable, List, Dict import quebra_frases import requests from ovos_bus_client.apis.enclosure import EnclosureAPI from ovos_bus_client.message import Message, dig_for_message +from ovos_bus_client.session import SessionManager from ovos_config import Configuration +from ovos_config.locations import get_xdg_cache_save_path from ovos_utils import classproperty from ovos_utils.fakebus import FakeBus from ovos_utils.file_utils import get_cache_directory from ovos_utils.file_utils import resolve_resource_file from ovos_utils.lang.visimes import VISIMES -from ovos_utils.log import LOG +from ovos_utils.log import LOG, deprecated, log_deprecation from ovos_utils.metrics import Stopwatch from ovos_utils.process_utils import RuntimeRequirements @@ -56,83 +36,101 @@ SSML_TAGS = re.compile(r'<[^>]*>') -class PlaybackThread(Thread): - """ PlaybackThread moved to ovos_audio.playback - standalone plugin usage should rely on self.get_tts - ovos-audio relies on self.execute and needs this class +class TTSContext: + """ + A context manager for handling Text-To-Speech (TTS) operations and caching. - this class was only in ovos-plugin-manager in order to - patch usage of our plugins in mycroft-core""" + Attributes: + plugin_id (str): Identifier for the TTS plugin being used. + lang (str): Language code for the TTS operation. + voice (str): Identifier for the voice type in use. + synth_kwargs (dict): Optional dictionary containing additional keyword arguments for the TTS synthesizer. - def __new__(self, *args, **kwargs): - LOG.warning("PlaybackThread moved to ovos_audio.playback") - try: - from ovos_audio.playback import PlaybackThread - return PlaybackThread(*args, **kwargs) - except ImportError: - raise ImportError("please install ovos-audio for playback handling") + Class Attributes: + _caches (dict): A class-level dictionary acting as a cache store for different TTS contexts. + """ + _caches: Dict[str, TextToSpeechCache] = {} -class TTSContext: - """ parses kwargs for valid signatures and extracts voice/lang optional parameters - it will look for a requested voice in kwargs and inside the source Message data if available. - voice can also be defined by a combination of language and gender, - in that case the helper method get_voice will be used to resolve the final voice_id - """ + def __init__(self, plugin_id: str, lang: str, voice: str, synth_kwargs: dict = None): + """ + Initializes the TTSContext instance. - def __init__(self, engine): - self.engine = engine + Parameters: + plugin_id (str): The unique identifier for the TTS plugin. + lang (str): The language in which the text will be synthesized. + voice (str): The voice model to be used for text synthesis. + synth_kwargs (dict, optional): Additional keyword arguments for the synthesizer. + """ + self.plugin_id = plugin_id + self.lang = lang + self.voice = voice + self.synth_kwargs = synth_kwargs or {} - def get_message(self, kwargs): - msg = kwargs.get("message") or dig_for_message() - if msg and isinstance(msg, Message): - return msg + @property + def tts_id(self): + """ + Constructs a unique identifier for the TTS context based on plugin, voice, and language. - def get_lang(self, kwargs): - # parse requested language for this TTS request - # NOTE: this is ovos only functionality, not in mycroft-core! - lang = kwargs.get("lang") - message = self.get_message(kwargs) - if not lang and message: - # get lang from message object if possible - lang = message.data.get("lang") or \ - message.context.get("lang") - return lang or self.engine.lang - - def get_gender(self, kwargs): - gender = kwargs.get("gender") - message = self.get_message(kwargs) - if not gender and message: - # get gender from message object if possible - gender = message.data.get("gender") or \ - message.context.get("gender") - return gender + Returns: + str: A unique identifier that represents the TTS context. + """ + return join(self.plugin_id, self.voice, self.lang) - def get_voice(self, kwargs): - # parse requested voice for this TTS request - # NOTE: this is ovos only functionality, not in mycroft-core! - voice = kwargs.get("voice") - message = self.get_message(kwargs) - if not voice and message: - # get voice from message object if possible - voice = message.data.get("voice") or \ - message.context.get("voice") - - if not voice: - gender = self.get_gender(kwargs) - if gender: - lang = self.get_lang(kwargs) - voice = self.engine.get_voice(gender, lang) - - return voice or self.engine.voice - - def get(self, kwargs=None): - kwargs = kwargs or {} - return self.get_lang(kwargs), self.get_voice(kwargs) + def get_cache(self, audio_ext="wav", cache_config=None): + """ + Retrieves or creates a cache instance for the current TTS context. - def get_cache(self, kwargs=None): - lang, voice = self.get(kwargs) - return self.engine.get_cache(voice, lang) + Parameters: + audio_ext (str, optional): The file extension for the audio files (default is 'wav'). + cache_config (dict, optional): Configuration settings for the cache, including parameters like + minimum free percent, persistence settings, and cache directory path. + + Returns: + TextToSpeechCache: The cache instance associated with the current TTS context. + """ + cache_config = cache_config or { + "min_free_percent": 75, + "persist_cache": False, + "persist_thresh": 1, + "preloaded_cache": f"{get_xdg_cache_save_path()}/{self.tts_id}" + } + if self.tts_id not in TTSContext._caches: + TTSContext._caches[self.tts_id] = TextToSpeechCache( + cache_config, self.tts_id, audio_ext + ) + return self._caches[self.tts_id] + + def get_from_cache(self, sentence, audio_ext="wav", cache_config=None): + """ + Retrieves an audio file and phoneme data from the cache, based on the input sentence. + + Parameters: + sentence (str): The sentence for which to retrieve audio data. + audio_ext (str, optional): The file extension of the audio file (default is 'wav'). + cache_config (dict, optional): Configuration settings for the cache. + + Returns: + tuple: A tuple containing the path to the cached audio file and optionally the phoneme data. + + Raises: + FileNotFoundError: If the sentence is not found in the cache. + """ + sentence_hash = hash_sentence(sentence) + phonemes = None + cache = self.get_cache(audio_ext, cache_config) + if sentence_hash not in cache: + raise FileNotFoundError(f"sentence is not cached, {sentence_hash}.{audio_ext}") + audio_file, pho_file = cache.cached_sentences[sentence_hash] + LOG.info(f"Found {audio_file.name} in TTS cache") + if pho_file: + phonemes = pho_file.load() + return audio_file, phonemes + + @classmethod + def curate_caches(cls): + for cache in TTSContext._caches.values(): + cache.curate() class TTS: @@ -141,26 +139,44 @@ class TTS: It aggregates the minimum required parameters and exposes ``execute(sentence)`` and ``validate_ssml(sentence)`` functions. - Arguments: - lang (str): - config (dict): Configuration for this specific tts engine - validator (TTSValidator): Used to verify proper installation - phonetic_spelling (bool): Whether to spell certain words phonetically - ssml_tags (list): Supported ssml properties. Ex. ['speak', 'prosody'] + Attributes: + queue (Queue): A queue for managing TTS playback tasks. + playback (PlaybackThread): The playback thread used for TTS audio output. + + Args: + lang (str): The language code for the TTS engine. + config (dict): Configuration settings for the specific TTS engine. + validator (TTSValidator): Validator used to verify proper installation. + audio_ext (str): The default audio file extension (default is 'wav'). + phonetic_spelling (bool): Whether to spell certain words phonetically. + ssml_tags (list): Supported SSML properties (e.g., ['speak', 'prosody']). """ queue = None playback = None - def __init__(self, lang="en-us", config=None, validator=None, + def __init__(self, lang=None, config=None, validator=None, audio_ext='wav', phonetic_spelling=True, ssml_tags=None): - self.log_timestamps = False + """ + Initializes the TTS engine with specified parameters. + Args: + lang (str): The language code (deprecated). + config (dict): Configuration settings for the TTS engine. + validator (TTSValidator): Validator for verifying installation. + audio_ext (str): Default audio file extension (default is 'wav'). + phonetic_spelling (bool): Whether to use phonetic spelling (default is True). + ssml_tags (list): Supported SSML tags (default is None). + """ + if lang is not None: + log_deprecation("lang argument for TTS has been deprecated! it will be ignored, " + "pass lang to get_tts directly instead") + self.log_timestamps = False + self.root_dir = os.path.dirname(os.path.abspath(sys.modules[self.__module__].__file__)) self.config = config or get_plugin_config(config, "tts") self.stopwatch = Stopwatch() self.tts_name = self.__class__.__name__ - self.bus = FakeBus() # initialized in "init" step - self.lang = lang or self.config.get("lang") or 'en-us' + self.validator = validator or TTSValidator(self) self.phonetic_spelling = phonetic_spelling self.audio_ext = audio_ext @@ -169,205 +185,85 @@ def __init__(self, lang="en-us", config=None, validator=None, self.enable_cache = self.config.get("enable_cache", True) - self.voice = self.config.get("voice") or "default" - # TODO can self.filename be deprecated ? is it used anywhere at all? - cache_dir = get_cache_directory(self.tts_name) - self.filename = join(cache_dir, 'tts.' + self.audio_ext) - - random.seed() - if TTS.queue is None: TTS.queue = Queue() - self.context = TTSContext(self) - - # NOTE: self.playback.start() was moved to init method - # playback queue is not wanted if we only care about get_tts - # init is called by mycroft, but non mycroft usage wont call it, - # outside mycroft the enclosure is not set, bus is dummy and - # playback thread is not used - self.spellings = self.load_spellings() - - self.caches = { - self.tts_id: TextToSpeechCache( - self.config, self.tts_id, self.audio_ext - )} + self.spellings: Dict[str, dict] = self.load_spellings() + self._init_g2p() - cfg = Configuration() - g2pm = self.config.get("g2p_module") - if g2pm: - if g2pm in find_g2p_plugins(): - cfg.setdefault("g2p", {}) - globl = cfg["g2p"].get("module") or g2pm - if globl != g2pm: - LOG.info(f"TTS requested {g2pm} explicitly, ignoring global module {globl} ") - cfg["g2p"]["module"] = g2pm - else: - LOG.warning(f"TTS selected {g2pm}, but it is not available!") + self.add_metric({"metric_type": "tts.init"}) - try: - self.g2p = OVOSG2PFactory.create(cfg) - except: - LOG.exception("G2P plugin not loaded, there will be no mouth movements") - self.g2p = None + # unused by plugins, assigned in init method by ovos-audio, + # only present for backwards compat reasons + self.bus = None - self.cache.curate() + self._plugin_id = "" # the plugin name - self.add_metric({"metric_type": "tts.init"}) + @property + def plugin_id(self) -> str: + """ + Retrieves the plugin ID for the TTS engine. + Returns: + str: The plugin ID associated with the TTS engine. + """ + if not self._plugin_id: + from ovos_plugin_manager.tts import find_tts_plugins + for tts_id, clazz in find_tts_plugins().items(): + if isinstance(self, clazz): + self._plugin_id = tts_id + break + return self._plugin_id + + # methods for individual plugins to override @classproperty def runtime_requirements(self): - """ skill developers should override this if they do not require connectivity - some examples: - IOT plugin that controls devices via LAN could return: - scans_on_init = True - RuntimeRequirements(internet_before_load=False, - network_before_load=scans_on_init, - requires_internet=False, - requires_network=True, - no_internet_fallback=True, - no_network_fallback=False) - online search plugin with a local cache: - has_cache = False - RuntimeRequirements(internet_before_load=not has_cache, - network_before_load=not has_cache, - requires_internet=True, - requires_network=True, - no_internet_fallback=True, - no_network_fallback=True) - a fully offline plugin: - RuntimeRequirements(internet_before_load=False, - network_before_load=False, - requires_internet=False, - requires_network=False, - no_internet_fallback=True, - no_network_fallback=True) - """ + """ WIP - currently unused, + placeholder to allow plugins to request internet/gui before load + refer to skills to see how it is used""" return RuntimeRequirements() @property - def tts_id(self): - lang, voice = self.context.get() - return join(self.tts_name, voice, lang) - - @property - def cache(self): - return self.caches.get(self.tts_id) or \ - self.get_cache() - - @cache.setter - def cache(self, val): - self.caches[self.tts_id] = val - - def get_cache(self, voice=None, lang=None): - lang = lang or self.lang - voice = voice or self.voice or "default" - tts_id = join(self.tts_name, voice, lang) - if tts_id not in self.caches: - self.caches[tts_id] = TextToSpeechCache( - self.config, tts_id, self.audio_ext - ) - return self.caches[tts_id] - - def handle_metric(self, metadata=None): - """ receive timing metrics for diagnostics - does nothing by default but plugins might use it, eg, NeonCore""" - - def add_metric(self, metadata=None): - """ wraps handle_metric to catch exceptions and log timestamps """ - try: - self.handle_metric(metadata) - if self.log_timestamps: - LOG.debug(f"time delta: {self.stopwatch.delta} metric: {metadata}") - except Exception as e: - LOG.exception(e) - - def load_spellings(self, config=None): - """Load phonetic spellings of words as dictionary.""" - path = join('text', self.lang.lower(), 'phonetic_spellings.txt') - try: - spellings_file = resolve_resource_file(path, config=config or Configuration()) - except: - LOG.debug('Failed to locate phonetic spellings resouce file.') - return {} - if not spellings_file: - return {} - try: - with open(spellings_file) as f: - lines = filter(bool, f.read().split('\n')) - lines = [i.split(':') for i in lines] - return {key.strip(): value.strip() for key, value in lines} - except ValueError: - LOG.exception('Failed to load phonetic spellings.') - return {} - - def begin_audio(self): - """Helper function for child classes to call in execute()""" - self.stopwatch.start() - self.add_metric({"metric_type": "tts.start"}) - - def end_audio(self, listen=False): - """Helper cleanup function for child classes to call in execute(). - - Arguments: - listen (bool): DEPRECATED: indication if listening trigger should be sent. - """ - self.add_metric({"metric_type": "tts.end"}) - self.stopwatch.stop() - - def init(self, bus=None, playback=None): - """ Performs intial setup of TTS object. - - Arguments: - bus: OpenVoiceOS messagebus connection + def available_languages(self) -> set: + """Return languages supported by this TTS implementation in this state + This property should be overridden by the derived class to advertise + what languages that engine supports. + Returns: + set: A set of supported language codes. """ - self.bus = bus or FakeBus() - if playback is None: - LOG.warning("PlaybackThread should be inited by ovos-audio, initing via plugin has been deprecated, " - "please pass playback=PlaybackThread() to TTS.init") - if TTS.playback: - playback.shutdown() - playback = PlaybackThread(TTS.queue, self.bus) # compat - playback.start() - self._init_playback(playback) - self.add_metric({"metric_type": "tts.setup"}) - - def _init_playback(self, playback): - TTS.playback = playback - TTS.playback.set_bus(self.bus) - TTS.playback.attach_tts(self) - if not TTS.playback.enclosure: - TTS.playback.enclosure = EnclosureAPI(self.bus) + return set() - if not TTS.playback.is_alive(): - TTS.playback.start() + @abc.abstractmethod + def get_tts(self, sentence, wav_file, lang=None, voice=None): + """Abstract method that a tts implementation needs to implement. - @property - def enclosure(self): - if not TTS.playback.enclosure: - bus = TTS.playback.bus or self.bus - TTS.playback.enclosure = EnclosureAPI(bus) - return TTS.playback.enclosure + Args: + sentence (str): The input sentence to synthesize. + wav_file (str): The output file path for the synthesized audio. + lang (str, optional): The requested language (defaults to self.lang). + voice (str, optional): The requested voice (defaults to self.voice). - @enclosure.setter - def enclosure(self, val): - TTS.playback.enclosure = val + Returns: + tuple: (wav_file, phoneme) + """ + return "", None - @abc.abstractmethod - def get_tts(self, sentence, wav_file, lang=None): - """Abstract method that a tts implementation needs to implement. + def preprocess_sentence(self, sentence: str) -> List[str]: + """Default preprocessing is a sentence_tokenizer, + ie. splits the utterance into sub-sentences using quebra_frases - Should get data from tts. + This method can be overridden to create chunks suitable to the + TTS engine in question. Arguments: - sentence(str): Sentence to synthesize - wav_file(str): output file - lang(str): requested language (optional), defaults to self.lang + sentence (str): sentence to preprocess Returns: - tuple: (wav_file, phoneme) + list: list of sentence parts """ - return "", None + if self.config.get("sentence_tokenize"): # TODO default to True on next major release + return quebra_frases.sentence_tokenize(sentence) + return [sentence] def modify_tag(self, tag): """Override to modify each supported ssml tag. @@ -377,6 +273,19 @@ def modify_tag(self, tag): """ return tag + def handle_metric(self, metadata=None): + """ receive timing metrics for diagnostics + does nothing by default but plugins might use it, eg, NeonCore""" + + @property + def voice(self): + return self.config.get("voice") or "default" + + @voice.setter + def voice(self, val): + self.config["voice"] = val + + # SSML helpers @staticmethod def remove_ssml(text): """Removes SSML tags from a string. @@ -463,22 +372,121 @@ def validate_ssml(self, utterance): # return text with supported ssml tags only return utterance.replace(" ", " ") - def _preprocess_sentence(self, sentence): - """Default preprocessing is a sentence_tokenizer, - ie. splits the utterance into sub-sentences using quebra_frases + # init helpers + def _init_g2p(self): + """ + Initializes the grapheme-to-phoneme (G2P) conversion for the TTS engine. + """ + cfg = Configuration() + g2pm = self.config.get("g2p_module") + if g2pm: + if g2pm in find_g2p_plugins(): + cfg.setdefault("g2p", {}) + globl = cfg["g2p"].get("module") or g2pm + if globl != g2pm: + LOG.info(f"TTS requested {g2pm} explicitly, ignoring global module {globl} ") + cfg["g2p"]["module"] = g2pm + else: + LOG.warning(f"TTS selected {g2pm}, but it is not available!") - This method can be overridden to create chunks suitable to the - TTS engine in question. + try: + self.g2p = OVOSG2PFactory.create(cfg) + except: + LOG.debug("G2P plugin not loaded, there will be no mouth movements") + self.g2p = None + + def init(self, bus=None, playback=None): + """ Connects TTS object to PlaybackQueue in ovos-audio. + + This method needs to be called in order for self.execute to do anything + + not needed if using get_tts / synth methods directly as intended in standalone usage Arguments: - sentence (str): sentence to preprocess + bus: OpenVoiceOS messagebus connection + """ + self.bus = bus or FakeBus() + if playback is None: + LOG.warning("PlaybackThread should be inited by ovos-audio, initing via plugin has been deprecated, " + "please pass playback=PlaybackThread() to TTS.init") + if not TTS.playback: + playback = PlaybackThread(TTS.queue, self.bus) # compat + playback.start() + self._init_playback(playback) + self.add_metric({"metric_type": "tts.setup"}) + + def _init_playback(self, playback): + """ + Initializes the playback functionality for the TTS engine. + + Args: + playback: PlaybackThread instance. + """ + + TTS.playback = playback + TTS.playback.set_bus(self.bus) + if not TTS.playback.enclosure: + TTS.playback.enclosure = EnclosureAPI(self.bus) + + if not TTS.playback.is_alive(): + TTS.playback.start() + + def load_spellings(self, config=None) -> Dict[str, dict]: + """ + Loads phonetic spellings of words as a dictionary. + + Args: + config (dict, optional): Configuration settings. Returns: - list: list of sentence parts + dict: A dictionary of phonetic spellings. """ - if self.config.get("sentence_tokenize"): # TODO default to True on next major release - return quebra_frases.sentence_tokenize(sentence) - return [sentence] + if config: + LOG.warning("config argument is deprecated and unused!") + spellings_data = {} + locale = f"{self.root_dir}/locale" + if os.path.isdir(locale): + for lang in os.listdir(locale): + spellings_file = f"{locale}/{lang}/phonetic_spellings.txt" + if not os.path.isfile(spellings_file): + continue + try: + with open(spellings_file) as f: + lines = filter(bool, f.read().split('\n')) + lines = [i.split(':') for i in lines] + spellings_data[lang] = {key.strip(): value.strip() for key, value in lines} + except ValueError: + LOG.exception(f'Failed to load {lang} phonetic spellings.') + return spellings_data + + ## execution events + def add_metric(self, metadata=None): + """ + Wraps handle_metric to catch exceptions and log timestamps. + + Args: + metadata (dict, optional): Additional metadata for the metric. + """ + try: + self.handle_metric(metadata) + if self.log_timestamps: + LOG.debug(f"time delta: {self.stopwatch.delta} metric: {metadata}") + except Exception as e: + LOG.exception(e) + + def begin_audio(self): + """Helper function for child classes to call in execute()""" + self.stopwatch.start() + self.add_metric({"metric_type": "tts.start"}) + + def end_audio(self, listen=False): + """Helper cleanup function for child classes to call in execute(). + + Arguments: + listen (bool): DEPRECATED: indication if listening trigger should be sent. + """ + self.add_metric({"metric_type": "tts.end"}) + self.stopwatch.stop() def execute(self, sentence, ident=None, listen=False, **kwargs): """Convert sentence to speech, preprocessing out unsupported ssml @@ -498,142 +506,135 @@ def execute(self, sentence, ident=None, listen=False, **kwargs): self._execute(sentence, ident, listen, **kwargs) self.end_audio() - def _replace_phonetic_spellings(self, sentence): - if self.phonetic_spelling: + ## synth + def _replace_phonetic_spellings(self, sentence:str, lang: str) -> str: + if self.phonetic_spelling and lang in self.spellings: for word in re.findall(r"[\w']+", sentence): - if word.lower() in self.spellings: - spelled = self.spellings[word.lower()] + if word.lower() in self.spellings[lang]: + spelled = self.spellings[lang][word.lower()] sentence = sentence.replace(word, spelled) return sentence + def _get_visemes(self, phonemes, sentence, ctxt): + # get visemes/mouth movements + viseme = [] + if phonemes: + viseme = self.viseme(phonemes) + elif self.g2p is not None: + try: + viseme = self.g2p.utterance2visemes(sentence, ctxt.lang) + except OutOfVocabulary: + pass + except: + # this one is unplanned, let devs know all the info so they can fix it + LOG.exception(f"Unexpected failure in G2P plugin: {self.g2p}") + + if not viseme: + # Debug level because this is expected in default installs + LOG.debug(f"no mouth movements available! unknown visemes for {sentence}") + return viseme + + def _get_ctxt(self, kwargs=None) -> TTSContext: + """create a TTSContext from arbitrary kwargs passed to synth/execute methods + takes lang from Session into account if a message is present + """ + # get request specific synth params + kwargs = kwargs or {} + message = kwargs.get("message") or dig_for_message() + + # update kwargs from session + if message and "lang" not in kwargs: + sess = SessionManager.get(message) + kwargs["lang"] = sess.lang + + # filter kwargs accepted by this specific plugin + kwargs = {k: v for k, v in kwargs.items() + if k in inspect.signature(self.get_tts).parameters + and k not in ["sentence", "wav_file"]} + + LOG.debug(f"TTS kwargs: {kwargs}") + return TTSContext(plugin_id=self.plugin_id, + lang=kwargs.get("lang") or Configuration().get("lang", "en-us"), + voice=kwargs.get("voice") or self.voice, + synth_kwargs=kwargs) + def _execute(self, sentence, ident, listen, preprocess=True, **kwargs): + # get request specific synth params + ctxt = self._get_ctxt(kwargs) + if preprocess: - sentence = self._replace_phonetic_spellings(sentence) - chunks = self._preprocess_sentence(sentence) + # pre-process + sentence = self._replace_phonetic_spellings(sentence, ctxt.lang) + chunks = self.preprocess_sentence(sentence) # Apply the listen flag to the last chunk, set the rest to False chunks = [(chunks[i], listen if i == len(chunks) - 1 else False) for i in range(len(chunks))] + + # metrics timing callback self.add_metric({"metric_type": "tts.preprocessed", "n_chunks": len(chunks)}) else: chunks = [(sentence, listen)] - lang, voice = self.context.get(kwargs) - tts_id = join(self.tts_name, voice, lang) + message = kwargs.get("message") or \ + dig_for_message() or \ + Message("speak", context={"session": {"session_id": ident}}) # synth -> queue for playback for sentence, l in chunks: # load from cache or synth + cache - audio_file, phonemes = self.synth(sentence, **kwargs) + audio_file, phonemes = self.synth(sentence, ctxt) # get visemes/mouth movements - viseme = [] - if phonemes: - viseme = self.viseme(phonemes) - elif self.g2p is not None: - try: - viseme = self.g2p.utterance2visemes(sentence, lang) - except OutOfVocabulary: - pass - except: - # this one is unplanned, let devs know all the info so they can fix it - LOG.exception(f"Unexpected failure in G2P plugin: {self.g2p}") - - if not viseme: - # Debug level because this is expected in default installs - LOG.debug(f"no mouth movements available! unknown visemes for {sentence}") - - message = kwargs.get("message") or \ - dig_for_message() or \ - Message("speak", context={"session": {"session_id": ident}}) + viseme = self._get_visemes(phonemes, sentence, ctxt) + + # queue audio for playback TTS.queue.put( - (str(audio_file), viseme, l, tts_id, message) + (str(audio_file), viseme, l, ctxt.tts_id, message) ) + + # metrics timing callback self.add_metric({"metric_type": "tts.queued"}) - def synth(self, sentence, **kwargs): - """ This method wraps get_tts - several optional keyword arguments are supported - sentence will be read/saved to cache""" + def synth(self, sentence, ctxt: TTSContext = None, **kwargs): + """ + Synthesizes speech for the given sentence. wraps get_tts + + sentence will be read/saved to cache + + Args: + sentence (str): The sentence to synthesize. + ctxt (TTSContext): The TTS context. + **kwargs: Additional synth arguments for get_tts. + + Returns: + tuple: A tuple containing the path to the synthesized audio file and phoneme data. + """ self.add_metric({"metric_type": "tts.synth.start"}) sentence_hash = hash_sentence(sentence) - # parse requested language for this TTS request - # NOTE: this is ovos/neon only functionality, not in mycroft-core! - lang, voice = self.context.get(kwargs) - kwargs["lang"] = lang - kwargs["voice"] = voice - - cache = self.get_cache(voice, lang) # cache per tts_id (lang/voice combo) + # parse kwargs for this TTS request + ctxt = ctxt or self._get_ctxt(kwargs) + cache = ctxt.get_cache(self.audio_ext, self.config) # load from cache if self.enable_cache and sentence_hash in cache: - audio, phonemes = self.get_from_cache(sentence, **kwargs) + audio, phonemes = ctxt.get_from_cache(sentence, cache) self.add_metric({"metric_type": "tts.synth.finished", "cache": True}) return audio, phonemes # synth + cache audio = cache.define_audio_file(sentence_hash) - - # filter kwargs per plugin, different plugins expose different options - # mycroft-core -> no kwargs - # ovos -> lang + voice optional kwargs - # neon-core -> message - kwargs = {k: v for k, v in kwargs.items() - if k in inspect.signature(self.get_tts).parameters - and k not in ["sentence", "wav_file"]} - - # finally do the TTS synth - audio.path, phonemes = self.get_tts(sentence, str(audio), **kwargs) + audio.path, phonemes = self.get_tts(sentence, str(audio), + **ctxt.synth_kwargs) self.add_metric({"metric_type": "tts.synth.finished"}) # cache sentence + phonemes if self.enable_cache: - self._cache_sentence(sentence, audio, phonemes, sentence_hash, - voice=voice, lang=lang) + self._cache_sentence(sentence, ctxt.lang, audio, cache, + phonemes, sentence_hash) return audio, phonemes - def _cache_phonemes(self, sentence, phonemes=None, sentence_hash=None): - sentence_hash = sentence_hash or hash_sentence(sentence) - if not phonemes and self.g2p is not None: - try: - phonemes = self.g2p.utterance2arpa(sentence, self.lang) - self.add_metric({"metric_type": "tts.phonemes.g2p"}) - except Exception as e: - self.add_metric({"metric_type": "tts.phonemes.g2p.error", "error": str(e)}) - if phonemes: - return self.save_phonemes(sentence_hash, phonemes) - return None - - def _cache_sentence(self, sentence, audio_file, phonemes=None, sentence_hash=None, - voice=None, lang=None): - sentence_hash = sentence_hash or hash_sentence(sentence) - # RANT: why do you hate strings ChrisV? - if isinstance(audio_file.path, str): - audio_file.path = Path(audio_file.path) - pho_file = self._cache_phonemes(sentence, phonemes, sentence_hash) - cache = self.get_cache(voice=voice, lang=lang) - cache.cached_sentences[sentence_hash] = (audio_file, pho_file) - self.add_metric({"metric_type": "tts.synth.cached"}) - - def get_from_cache(self, sentence, **kwargs): - sentence_hash = hash_sentence(sentence) - phonemes = None - cache = self.context.get_cache(kwargs) - audio_file, pho_file = cache.cached_sentences[sentence_hash] - LOG.info(f"Found {audio_file.name} in TTS cache") - if not pho_file: - # guess phonemes from sentence + cache them - pho_file = self._cache_phonemes(sentence, sentence_hash) - if pho_file: - phonemes = pho_file.load() - return audio_file, phonemes - - def get_voice(self, gender, lang=None): - """ map a language and gender to a valid voice for this TTS engine """ - lang = lang or self.lang - return gender - def viseme(self, phonemes): """Create visemes from phonemes. @@ -660,31 +661,52 @@ def viseme(self, phonemes): float(0.2))) return visimes or None - def clear_cache(self): - """ Remove all cached files. """ - self.cache.clear() - - def save_phonemes(self, key, phonemes): - """Cache phonemes + ## cache + def _cache_phonemes(self, sentence, lang: str, cache: TextToSpeechCache = None, phonemes=None, sentence_hash=None): + """ + Caches phonemes for the given sentence. - Arguments: - key (str): Hash key for the sentence - phonemes (str): phoneme string to save + Args: + sentence (str): The sentence to cache phonemes for. + cache (TextToSpeechCache): The cache instance. + phonemes (str, optional): The phonemes for the sentence. + sentence_hash (str, optional): The hash of the sentence. """ - phoneme_file = self.cache.define_phoneme_file(key) - phoneme_file.save(phonemes) - return phoneme_file + sentence_hash = sentence_hash or hash_sentence(sentence) + if not phonemes and self.g2p is not None: + try: + phonemes = self.g2p.utterance2arpa(sentence, lang) + self.add_metric({"metric_type": "tts.phonemes.g2p"}) + except Exception as e: + self.add_metric({"metric_type": "tts.phonemes.g2p.error", "error": str(e)}) + if phonemes: + phoneme_file = cache.define_phoneme_file(sentence_hash) + phoneme_file.save(phonemes) + return phoneme_file + return None - def load_phonemes(self, key): - """Load phonemes from cache file. + def _cache_sentence(self, sentence, lang: str, audio_file, cache, phonemes=None, sentence_hash=None): + """ + Caches the sentence along with associated audio and phonemes. - Arguments: - key (str): Key identifying phoneme cache + Args: + sentence (str): The sentence to cache. + audio_file (AudioFile): The audio file associated with the sentence. + cache (TextToSpeechCache): The cache instance. + phonemes (str, optional): The phonemes for the sentence. + sentence_hash (str, optional): The hash of the sentence. """ - phoneme_file = self.cache.define_phoneme_file(key) - return phoneme_file.load() + sentence_hash = sentence_hash or hash_sentence(sentence) + # RANT: why do you hate strings ChrisV? + if isinstance(audio_file.path, str): + audio_file.path = Path(audio_file.path) + pho_file = self._cache_phonemes(sentence, lang, cache, phonemes, sentence_hash) + cache.cached_sentences[sentence_hash] = (audio_file, pho_file) + self.add_metric({"metric_type": "tts.synth.cached"}) + ## shutdown def stop(self): + """Stops the TTS playback.""" if TTS.playback: try: TTS.playback.stop() @@ -693,22 +715,192 @@ def stop(self): self.add_metric({"metric_type": "tts.stop"}) def shutdown(self): + """Shuts down the TTS engine.""" self.stop() if TTS.playback: TTS.playback.detach_tts(self) def __del__(self): + """Destructor for the TTS object.""" self.shutdown() + # below code is all deprecated and marked for removal in next stable release @property - def available_languages(self) -> set: - """Return languages supported by this TTS implementation in this state - This property should be overridden by the derived class to advertise - what languages that engine supports. + @deprecated("self.enclosure has been deprecated, use EnclosureAPI directly decoupled from the plugin code", + "0.1.0") + def enclosure(self): + """Deprecated. Accessor for the enclosure property. + Returns: - set: supported languages + EnclosureAPI: The EnclosureAPI instance associated with the TTS playback. """ - return set() + if not TTS.playback.enclosure: + bus = TTS.playback.bus or self.bus + TTS.playback.enclosure = EnclosureAPI(bus) + return TTS.playback.enclosure + + @enclosure.setter + @deprecated("self.enclosure has been deprecated, use EnclosureAPI directly decoupled from the plugin code", + "0.1.0") + def enclosure(self, val): + """Deprecated. Setter for the enclosure property. + + Arguments: + val (EnclosureAPI): The EnclosureAPI instance to set. + """ + TTS.playback.enclosure = val + + @property + @deprecated("self.filename has been deprecated, unused for a long time now", + "0.1.0") + def filename(self): + """Deprecated. Accessor for the filename property. + + Returns: + str: The filename for the TTS audio. + """ + cache_dir = get_cache_directory(self.tts_name) + return join(cache_dir, 'tts.' + self.audio_ext) + + @filename.setter + @deprecated("self.filename has been deprecated, unused for a long time now", + "0.1.0") + def filename(self, val): + """Deprecated. Setter for the filename property. + + Arguments: + val (str): The filename to set. + """ + + @property + @deprecated("self.tts_id has been deprecated, use TTSContext().tts_id", + "0.1.0") + def tts_id(self): + """Deprecated. Accessor for the tts_id property. + + Returns: + str: The ID associated with the TTS context. + """ + return self._get_ctxt().tts_id + + @property + @deprecated("self.cache has been deprecated, use TTSContext().get_cache", + "0.1.0") + def cache(self): + """Deprecated. Accessor for the cache property. + + Returns: + TextToSpeechCache: The cache associated with the TTS context. + """ + return TTSContext._caches.get(self.tts_id) or \ + self.get_cache() + + @cache.setter + @deprecated("self.cache has been deprecated, use TTSContext().get_cache", + "0.1.0") + def cache(self, val): + """Deprecated. Setter for the cache property. + + Arguments: + val (TextToSpeechCache): The cache to set. + """ + TTSContext._caches[self.tts_id] = val + + @deprecated("get_voice was never formally adopted and is unused, it will be removed", + "0.1.0") + def get_voice(self, gender, lang=None): + """Deprecated. Get a valid voice for the TTS engine. + + Arguments: + gender (str): Gender of the voice. + lang (str, optional): Language for the voice. Defaults to None. + + Returns: + str: The selected voice. + """ + return gender + + @deprecated("get_cache has been deprecated, use TTSContext().get_cache directly", + "0.1.0") + def get_cache(self, voice=None, lang=None): + """Deprecated. Get the cache associated with the TTS context. + + Arguments: + voice (str, optional): Voice for the cache. Defaults to None. + lang (str, optional): Language for the cache. Defaults to None. + + Returns: + TextToSpeechCache: The cache associated with the TTS context. + """ + return self._get_ctxt().get_cache(self.audio_ext, self.config) + + @deprecated("clear_cache has been deprecated, use TTSContext().get_cache directly", + "0.1.0") + def clear_cache(self): + """Deprecated. Clear all cached files.""" + cache = self._get_ctxt().get_cache(self.audio_ext, self.config) + cache.clear() + + @deprecated("save_phonemes has been deprecated, use TTSContext().get_cache directly", + "0.1.0") + def save_phonemes(self, key, phonemes): + """Deprecated. Cache phonemes. + + Arguments: + key (str): Hash key for the sentence. + phonemes (str): Phoneme string to save. + + Returns: + PhonemeFile: The PhonemeFile instance. + """ + cache = self._get_ctxt().get_cache(self.audio_ext, self.config) + phoneme_file = cache.define_phoneme_file(key) + phoneme_file.save(phonemes) + return phoneme_file + + @deprecated("load_phonemes has been deprecated, use TTSContext().get_cache directly", + "0.1.0") + def load_phonemes(self, key): + """Deprecated. Load phonemes from cache file. + + Arguments: + key (str): Key identifying phoneme cache. + + Returns: + str: Phonemes loaded from the cache file. + """ + cache = self._get_ctxt().get_cache(self.audio_ext, self.config) + phoneme_file = cache.define_phoneme_file(key) + return phoneme_file.load() + + @deprecated("get_from_cache has been deprecated, use TTSContext().get_from_cache directly", + "0.1.0") + def get_from_cache(self, sentence): + """Deprecated. Get data from the cache. + + Arguments: + sentence (str): Sentence used as cache key. + + Returns: + tuple: Tuple containing the audio and phonemes. + """ + return self._get_ctxt().get_from_cache(sentence, self.audio_ext, self.config) + + @property + @deprecated("language is defined per request in get_tts, self.lang is not used", + "0.1.0") + def lang(self): + message = dig_for_message() + if message: + sess = SessionManager.get(message) + return sess.lang + return self.config.get("lang") or 'en-us' + + @lang.setter + @deprecated("language is defined per request in get_tts, self.lang is not used", + "0.1.0") + def lang(self, val): + LOG.warning("self.lang can not be set! it comes from the bus message") class TTSValidator: @@ -815,6 +1007,7 @@ class RemoteTTSTimeoutException(RemoteTTSException): class StreamingTTSCallbacks: """handle the playback of streaming TTS, can be overrided in StreamingTTS""" + def __init__(self, bus, play_args=None, tts_config=None): self.bus = bus self.config = tts_config or {} @@ -831,12 +1024,12 @@ def stream_start(self, message=None): message = message or \ dig_for_message() or \ Message("speak") - + # we don't use the regular PlaybackThread here, we need to handle recognizer_loop:audio_output_start if not self.config.get("pulse_duck", False): self.bus.emit(message.forward("ovos.common_play.duck")) self.bus.emit(message.forward("recognizer_loop:audio_output_start")) - + if self._process: self.stream_stop() LOG.debug(f"stream playback command: {self.play_args}") @@ -860,7 +1053,7 @@ def stream_stop(self, listen=False, message=None): message = message or \ dig_for_message() or \ Message("speak") - + if self._process: self._process.stdin.close() self._process.wait() @@ -869,7 +1062,7 @@ def stream_stop(self, listen=False, message=None): # we don't use the regular PlaybackThread here, we need to handle recognizer_loop:audio_output_end and listen flag if not self.config.get("pulse_duck", False): self.bus.emit(message.forward("ovos.common_play.unduck")) - self.bus.emit(message.forward("recognizer_loop:audio_output_end")) + self.bus.emit(message.forward("recognizer_loop:audio_output_end")) if listen: self.bus.emit(message.forward('mycroft.mic.listen')) @@ -878,14 +1071,14 @@ class StreamingTTS(TTS): """ Abstract class for a Streaming TTS engine implementation. Audio is streamed in chunks as it becomes available instead of waiting the full sentence to be synthesized - + this plugin can be used in a synchronous way like any other plugin via self.get_tts(sentence, wav_file) - + to play audio as it becomes available use self.generate_audio(sentence, wav_file) NOTE: StreamingTTS does not support phonemes """ - + def init(self, bus=None, playback=None, callbacks=None): """ Performs intial setup of TTS object. @@ -899,10 +1092,10 @@ def init(self, bus=None, playback=None, callbacks=None): tts_config=self.config) @abc.abstractmethod - async def stream_tts(self, sentence) -> AsyncIterable[bytes]: + async def stream_tts(self, sentence, **kwargs) -> AsyncIterable[bytes]: """yield chunks of TTS audio as they become available""" raise NotImplementedError - + async def generate_audio(self, sentence, wav_file, play_streaming=True, listen=False, message=None, plugin_kwargs=None): """save streamed TTS to wav file, if configured also play TTS as it becomes available""" @@ -921,22 +1114,20 @@ async def generate_audio(self, sentence, wav_file, play_streaming=True, return wav_file def _execute(self, sentence, ident, listen, **kwargs): - sentence = self._replace_phonetic_spellings(sentence) - self.add_metric({"metric_type": "tts.preprocessed"}) - - sentence_hash = hash_sentence(sentence) # parse requested language for this TTS request - lang, voice = self.context.get(kwargs) - kwargs["lang"] = lang - kwargs["voice"] = voice + ctxt = self._get_ctxt(kwargs) + cache = ctxt.get_cache(self.audio_ext, self.config) - # get path to cache final synthesized file - cache = self.get_cache(voice, lang) # cache per tts_id (lang/voice combo) + sentence = self._replace_phonetic_spellings(sentence, ctxt.lang) + self.add_metric({"metric_type": "tts.preprocessed"}) + + sentence_hash = hash_sentence(sentence) # if cached, play existing file instead if self.enable_cache and sentence_hash in cache: - super()._execute(sentence, ident, listen, preprocess=False, **kwargs) + super()._execute(sentence, ident, listen, + preprocess=False, **ctxt.synth_kwargs) return wav_file = str(cache.define_audio_file(sentence_hash)) @@ -945,10 +1136,10 @@ def _execute(self, sentence, ident, listen, **kwargs): dig_for_message() or \ Message("speak") - # filter kwargs per plugin, different plugins expose different options - plugin_kwargs = {k: v for k, v in kwargs.items() - if k in inspect.signature(self.stream_tts).parameters - and k not in ["sentence", "wav_file", "play_streaming"]} + # filter kwargs accepted by this specific plugin + ctxt.synth_kwargs = {k: v for k, v in kwargs.items() + if k in inspect.signature(self.stream_tts).parameters + and k not in ["sentence"]} # handle streaming TTS loop = asyncio.new_event_loop() @@ -956,16 +1147,16 @@ def _execute(self, sentence, ident, listen, **kwargs): try: self.add_metric({"metric_type": "tts.stream.start"}) loop.run_until_complete( - self.generate_audio(sentence, wav_file, + self.generate_audio(sentence, wav_file, play_streaming=True, listen=listen, message=message, - plugin_kwargs=plugin_kwargs) + plugin_kwargs=ctxt.synth_kwargs) ) finally: loop.close() self.add_metric({"metric_type": "tts.stream.end"}) - + def get_tts(self, sentence, wav_file, **kwargs): """wrap streaming TTS into sync usage""" loop = asyncio.new_event_loop() @@ -981,6 +1172,8 @@ def get_tts(self, sentence, wav_file, **kwargs): return wav_file, None # No phonemes +# below classes are deprecated and will be removed in 0.1.0 + class RemoteTTS(TTS): """ Abstract class for a Remote TTS engine implementation. @@ -988,6 +1181,8 @@ class RemoteTTS(TTS): Usage is discouraged """ + @deprecated("RemoteTTS has been deprecated, please use the regular TTS class", + "0.1.0") def __init__(self, lang, config, url, api_path, validator): super(RemoteTTS, self).__init__(lang, config, validator) self.api_path = api_path @@ -1006,3 +1201,20 @@ def get_tts(self, sentence, wav_file, lang=None): with open(wav_file, 'wb') as f: f.write(r.content) return wav_file, None + + +class PlaybackThread(Thread): + """ PlaybackThread moved to ovos_audio.playback + standalone plugin usage should rely on self.get_tts + ovos-audio relies on self.execute and needs this class + + this class was only in ovos-plugin-manager in order to + patch usage of our plugins in mycroft-core""" + + def __new__(self, *args, **kwargs): + LOG.warning("PlaybackThread moved to ovos_audio.playback") + try: + from ovos_audio.playback import PlaybackThread + return PlaybackThread(*args, **kwargs) + except ImportError: + raise ImportError("please install ovos-audio for playback handling") diff --git a/ovos_plugin_manager/tts.py b/ovos_plugin_manager/tts.py index ba1983a2..fe453423 100644 --- a/ovos_plugin_manager/tts.py +++ b/ovos_plugin_manager/tts.py @@ -203,8 +203,8 @@ def create(config=None): clazz = OVOSTTSFactory.get_class(tts_config) if clazz: LOG.info(f'Found plugin {tts_module}') - tts = clazz(lang=None, # explicitly read lang from config - config=tts_config) + tts = clazz(config=tts_config) + tts._plugin_id = tts_module tts.validator.validate() LOG.info(f'Loaded plugin {tts_module}') else: diff --git a/test/unittests/test_tts.py b/test/unittests/test_tts.py index 27fe6e3d..88b7a29f 100644 --- a/test/unittests/test_tts.py +++ b/test/unittests/test_tts.py @@ -1,7 +1,13 @@ import unittest +from unittest.mock import MagicMock from unittest.mock import patch, Mock + +from ovos_bus_client.session import Session +from ovos_config import Configuration +from ovos_utils.fakebus import FakeBus, Message + +from ovos_plugin_manager.templates.tts import TTS, TTSContext from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes -from ovos_plugin_manager.templates.tts import TTS class TestTTSTemplate(unittest.TestCase): @@ -114,23 +120,23 @@ def test_format_speak_tags_with_speech_no_tags(self): self.assertEqual(tagged_with_exclusion, valid_output) def test_playback_thread(self): - from ovos_plugin_manager.templates.tts import PlaybackThread + pass # TODO - + def test_tts_context(self): - from ovos_plugin_manager.templates.tts import TTSContext + pass # TODO - + def test_tts_validator(self): - from ovos_plugin_manager.templates.tts import TTSValidator + pass # TODO - + def test_concat_tts(self): - from ovos_plugin_manager.templates.tts import ConcatTTS + pass # TODO - + def test_remote_tt(self): - from ovos_plugin_manager.templates.tts import RemoteTTS + pass # TODO @@ -187,15 +193,15 @@ def test_get_tts_config(self, get_config): self.CONFIG_SECTION, None) def test_get_voice_id(self): - from ovos_plugin_manager.tts import get_voice_id + pass # TODO def test_scan_voices(self): - from ovos_plugin_manager.tts import scan_voices + pass # TODO def test_get_voices(self): - from ovos_plugin_manager.tts import get_voices + pass # TODO @@ -246,13 +252,13 @@ def test_create(self, get_class): "config": True, "lang": "en-ca"} get_class.assert_called_once_with(expected_config) - plugin_class.assert_called_once_with(lang=None, config=expected_config) + plugin_class.assert_called_once_with(config=expected_config) self.assertEqual(plugin, plugin_class()) # Test create with TTS config and no module config plugin = OVOSTTSFactory.create(tts_config) get_class.assert_called_with(tts_config) - plugin_class.assert_called_with(lang=None, config=tts_config) + plugin_class.assert_called_with(config=tts_config) self.assertEqual(plugin, plugin_class()) # Test create with TTS config with module-specific config @@ -260,5 +266,110 @@ def test_create(self, get_class): expected_config = {"module": "test-tts-plugin-test", "config": True, "lang": "es-mx"} get_class.assert_called_with(expected_config) - plugin_class.assert_called_with(lang=None, config=expected_config) + plugin_class.assert_called_with(config=expected_config) self.assertEqual(plugin, plugin_class()) + + +class TestTTSContext(unittest.TestCase): + + @patch("ovos_plugin_manager.templates.tts.TextToSpeechCache", autospec=True) + def test_tts_context_get_cache(self, cache_mock): + tts_context = TTSContext("plug", "voice", "lang") + + result = tts_context.get_cache() + + self.assertEqual(result, cache_mock.return_value) + self.assertEqual(result, tts_context._caches[tts_context.tts_id]) + + +class TestTTSCache(unittest.TestCase): + def setUp(self): + self.tts_mock = TTS(config={"some_config_key": "some_config_value"}) + self.tts_mock.stopwatch = MagicMock() + self.tts_mock.queue = MagicMock() + self.tts_mock.playback = MagicMock() + + @patch("ovos_plugin_manager.templates.tts.hash_sentence", return_value="fake_hash") + @patch("ovos_plugin_manager.templates.tts.TTSContext") + def test_tts_synth(self, tts_context_mock, hash_sentence_mock): + tts_context_mock.get_cache.return_value = MagicMock() + tts_context_mock.get_cache.return_value.define_audio_file.return_value.path = "fake_audio_path" + + sentence = "Hello world!" + result = self.tts_mock.synth(sentence, tts_context_mock) + + tts_context_mock.get_cache.assert_called_once_with("wav", self.tts_mock.config) + tts_context_mock.get_cache.return_value.define_audio_file.assert_called_once_with("fake_hash") + self.assertEqual(result, (tts_context_mock.get_cache.return_value.define_audio_file.return_value, None)) + + @patch("ovos_plugin_manager.templates.tts.hash_sentence", return_value="fake_hash") + def test_tts_synth_cache_enabled(self, hash_sentence_mock): + tts_context_mock = MagicMock() + tts_context_mock.tts_id = "fake_tts_id" + tts_context_mock.get_cache.return_value = MagicMock() + tts_context_mock.get_cache.return_value.cached_sentences = {} + tts_context_mock.get_cache.return_value.define_audio_file.return_value.path = "fake_audio_path" + tts_context_mock._caches = {tts_context_mock.tts_id: tts_context_mock.get_cache.return_value} + + sentence = "Hello world!" + self.tts_mock.enable_cache = True + result = self.tts_mock.synth(sentence, tts_context_mock) + + tts_context_mock.get_cache.assert_called_once_with("wav", self.tts_mock.config) + tts_context_mock.get_cache.return_value.define_audio_file.assert_called_once_with("fake_hash") + self.assertEqual(result, (tts_context_mock.get_cache.return_value.define_audio_file.return_value, None)) + self.assertIn("fake_hash", tts_context_mock.get_cache.return_value.cached_sentences) + + @patch("ovos_plugin_manager.templates.tts.hash_sentence", return_value="fake_hash") + def test_tts_synth_cache_disabled(self, hash_sentence_mock): + tts_context_mock = MagicMock() + tts_context_mock.tts_id = "fake_tts_id" + tts_context_mock.get_cache.return_value = MagicMock() + tts_context_mock.get_cache.return_value.cached_sentences = {} + tts_context_mock.get_cache.return_value.define_audio_file.return_value.path = "fake_audio_path" + tts_context_mock._caches = {tts_context_mock.tts_id: tts_context_mock.get_cache.return_value} + + sentence = "Hello world!" + self.tts_mock.enable_cache = False + result = self.tts_mock.synth(sentence, tts_context_mock) + + tts_context_mock.get_cache.assert_called_once_with("wav", self.tts_mock.config) + tts_context_mock.get_cache.return_value.define_audio_file.assert_called_once_with("fake_hash") + self.assertEqual(result, (tts_context_mock.get_cache.return_value.define_audio_file.return_value, None)) + self.assertNotIn("fake_hash", tts_context_mock.get_cache.return_value.cached_sentences) + + +class TestSession(unittest.TestCase): + def test_tts_session(self): + sess = Session(session_id="123", lang="en-us") + m = Message("speak", + context={"session": sess.serialize()}) + + tts = TTS() + self.assertEqual(tts.plugin_id, "ovos-tts-plugin-dummy") + self.assertEqual(tts.voice, "default") # no voice set + self.assertEqual(tts.lang, "en-us") # from config + + # test that session makes it all the way to the TTS.queue + kwargs = {"message": m} + tts.execute("test sentence", **kwargs) + path, visemes, listen, tts_id, message = tts.queue.get() + self.assertEqual(message, m) + self.assertEqual(message.context["session"]["session_id"], sess.session_id) + + # test that lang from Session is used + ctxt = tts._get_ctxt(kwargs) + self.assertEqual(ctxt.plugin_id, tts.plugin_id) + self.assertEqual(ctxt.lang, sess.lang) + self.assertEqual(ctxt.tts_id, f"{tts.plugin_id}/default/en-us") + self.assertEqual(ctxt.synth_kwargs, {'lang': 'en-us'}) + + sess = Session(session_id="123", + lang="klingon") + m = Message("speak", + context={"session": sess.serialize()}) + kwargs = {"message": m} + ctxt = tts._get_ctxt(kwargs) + self.assertEqual(ctxt.lang, sess.lang) + self.assertEqual(ctxt.tts_id, f"{tts.plugin_id}/default/klingon") + self.assertEqual(ctxt.synth_kwargs, {'lang': 'klingon'}) From 53b73a221ccb021a54705acd91a415b722df6517 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 29 Apr 2024 04:44:27 +0000 Subject: [PATCH 046/129] Increment Version to --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 401f6ae7..8c1fcb45 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 15 +VERSION_ALPHA = 16 # END_VERSION_BLOCK From 2d9423521f230fc9f269eacf0f78637cf08936f8 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 29 Apr 2024 04:44:59 +0000 Subject: [PATCH 047/129] Update Changelog --- CHANGELOG.md | 845 +-------------------------------------------------- 1 file changed, 1 insertion(+), 844 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8840d7b..22902c23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ **Implemented enhancements:** - feat/lang\_detection\_plugin [\#220](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/220) ([JarbasAl](https://github.com/JarbasAl)) +- feat/restore phonetic spellings [\#195](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/195) ([JarbasAl](https://github.com/JarbasAl)) **Fixed bugs:** @@ -109,850 +110,6 @@ - packaging/update imports [\#203](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/203) ([JarbasAl](https://github.com/JarbasAl)) - Update requirements.txt [\#201](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/201) ([JarbasAl](https://github.com/JarbasAl)) -## [V0.0.25](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25) (2023-12-29) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a4...V0.0.25) - -## [V0.0.25a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a4) (2023-12-29) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a3...V0.0.25a4) - -**Merged pull requests:** - -- update imports [\#198](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/198) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a3) (2023-12-29) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a2...V0.0.25a3) - -**Merged pull requests:** - -- Update requirements.txt [\#197](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/197) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a2) (2023-12-28) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.25a1...V0.0.25a2) - -**Closed issues:** - -- module 'inspect' has no attribute 'formatargspec' [\#189](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/189) - -**Merged pull requests:** - -- refactor/move\_from\_utils [\#196](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/196) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.25a1) (2023-12-09) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24...V0.0.25a1) - -**Implemented enhancements:** - -- feat/disable\_cache [\#194](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/194) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.24](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24) (2023-10-26) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a18...V0.0.24) - -## [V0.0.24a18](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a18) (2023-10-25) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a17...V0.0.24a18) - -**Fixed bugs:** - -- STT class init with wrong config [\#132](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/132) -- Fix STT and TTS configuration handling [\#187](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/187) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.24a17](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a17) (2023-10-25) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a16...V0.0.24a17) - -**Merged pull requests:** - -- Update test dependencies [\#190](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/190) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.24a16](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a16) (2023-10-24) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a15...V0.0.24a16) - -**Fixed bugs:** - -- remove log spam [\#188](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/188) ([JarbasAl](https://github.com/JarbasAl)) - -**Closed issues:** - -- support ovos-translate-server [\#182](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/182) - -## [V0.0.24a15](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a15) (2023-10-12) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a14...V0.0.24a15) - -**Fixed bugs:** - -- Language Module Factory Tests/Fixes [\#184](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/184) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.24a14](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a14) (2023-10-11) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a13...V0.0.24a14) - -**Fixed bugs:** - -- No module named 'mycroft\_bus\_client' [\#146](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/146) -- Circular import while importing `StreamHandler` [\#130](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/130) - -**Merged pull requests:** - -- Add license notice and link to plugins from neon\_solvers package [\#185](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/185) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.24a13](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a13) (2023-10-10) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a12...V0.0.24a13) - -**Implemented enhancements:** - -- feat/translate\_plug\_as\_arg [\#183](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/183) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.24a12](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a12) (2023-10-08) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a11...V0.0.24a12) - -**Fixed bugs:** - -- fix/playback\_thread startup [\#181](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/181) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.24a11](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a11) (2023-10-08) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a10...V0.0.24a11) - -**Fixed bugs:** - -- fix/playback\_thread startup [\#180](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/180) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.24a10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a10) (2023-10-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a9...V0.0.24a10) - -**Implemented enhancements:** - -- feat/ovos\_dialog\_tts\_transformers [\#179](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/179) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.24a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a9) (2023-09-22) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a8...V0.0.24a9) - -**Fixed bugs:** - -- fix/deprecation\_warnings [\#178](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/178) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.24a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a8) (2023-09-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a6...V0.0.24a8) - -**Merged pull requests:** - -- Don't swallow plugin load errors [\#176](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/176) ([strugee](https://github.com/strugee)) - -## [V0.0.24a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a6) (2023-09-08) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a5...V0.0.24a6) - -**Implemented enhancements:** - -- feat/extract\_speech [\#139](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/139) ([JarbasAl](https://github.com/JarbasAl)) - -**Fixed bugs:** - -- fix/audio\_config [\#174](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/174) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.24a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a5) (2023-07-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a4...V0.0.24a5) - -**Merged pull requests:** - -- Updates logging around language plugin errors [\#171](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/171) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.24a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a4) (2023-07-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a3...V0.0.24a4) - -**Merged pull requests:** - -- Replace `sleep` with `Event.wait` [\#170](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/170) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.24a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a3) (2023-07-04) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a2...V0.0.24a3) - -**Fixed bugs:** - -- feat/optional\_g2p [\#169](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/169) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.24a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a2) (2023-07-04) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.24a1...V0.0.24a2) - -**Fixed bugs:** - -- fix/on\_mouth\_viseme\_list [\#168](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/168) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.24a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.24a1) (2023-06-21) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23...V0.0.24a1) - -**Implemented enhancements:** - -- feat/tts\_session\_context [\#167](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/167) ([JarbasAl](https://github.com/JarbasAl)) - -**Fixed bugs:** - -- Automation fixes [\#164](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/164) ([NeonDaniel](https://github.com/NeonDaniel)) - -**Closed issues:** - -- ImportError: Wake Word marvin with module ovos-ww-plugin-precise-lite failed to load [\#166](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/166) - -## [V0.0.23](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23) (2023-06-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a27...V0.0.23) - -## [V0.0.23a27](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a27) (2023-06-02) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a26...V0.0.23a27) - -**Fixed bugs:** - -- Update `get_plugin_config` and tests for GUI support [\#163](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/163) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a26](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a26) (2023-06-02) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a25...V0.0.23a26) - -**Fixed bugs:** - -- Logged exception when cache directory doesn't exist [\#133](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/133) -- Add error handling and tests to cache curation [\#162](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/162) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a25](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a25) (2023-06-02) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a24...V0.0.23a25) - -**Merged pull requests:** - -- Troubleshoot default STT config handling [\#161](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/161) ([NeonDaniel](https://github.com/NeonDaniel)) -- Add trivial test case to `test_ocp` [\#160](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/160) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a24](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a24) (2023-05-31) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a23...V0.0.23a24) - -**Implemented enhancements:** - -- Add persona plugins [\#159](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/159) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a23](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a23) (2023-05-31) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a22...V0.0.23a23) - -**Merged pull requests:** - -- Release Automation and Stable Dependencies [\#142](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/142) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a22](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a22) (2023-05-26) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a21...V0.0.23a22) - -**Implemented enhancements:** - -- Outline Unit Tests [\#158](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/158) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a21](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a21) (2023-05-26) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a20...V0.0.23a21) - -**Implemented enhancements:** - -- Refactor to consolidate common logic [\#157](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/157) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a20](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a20) (2023-05-24) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a19...V0.0.23a20) - -**Merged pull requests:** - -- Add VAD module tests with bugfixes and doc updates [\#156](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/156) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a19](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a19) (2023-05-22) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a18...V0.0.23a19) - -**Merged pull requests:** - -- Fix MicrophoneFactory config handling [\#155](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/155) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a18](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a18) (2023-05-20) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a17...V0.0.23a18) - -**Implemented enhancements:** - -- Dinkum listener compat fixes with tests [\#154](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/154) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a17](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a17) (2023-05-18) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a16...V0.0.23a17) - -**Fixed bugs:** - -- Fix Hotword Plugin Load Compat [\#153](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/153) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a16](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a16) (2023-05-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a15...V0.0.23a16) - -**Implemented enhancements:** - -- feat/microphone\_plugs [\#152](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/152) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a15](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a15) (2023-05-16) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a14...V0.0.23a15) - -**Implemented enhancements:** - -- :tada: - GUI plugin [\#151](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/151) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a14](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a14) (2023-05-12) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a13...V0.0.23a14) - -**Fixed bugs:** - -- fix/ww\_json [\#150](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/150) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a13](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a13) (2023-05-12) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a12...V0.0.23a13) - -**Merged pull requests:** - -- refactor/solvers\_init [\#149](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/149) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a12](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a12) (2023-05-12) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a11...V0.0.23a12) - -**Implemented enhancements:** - -- feat/more\_solvers [\#148](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/148) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a11](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a11) (2023-05-06) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a10...V0.0.23a11) - -**Implemented enhancements:** - -- feat/audio2ipa [\#147](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/147) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a10) (2023-04-29) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a9...V0.0.23a10) - -**Fixed bugs:** - -- better translate fallback module handling [\#145](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/145) ([emphasize](https://github.com/emphasize)) - -**Closed issues:** - -- translator: api key from configuration [\#143](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/143) - -## [V0.0.23a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a9) (2023-04-13) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a8...V0.0.23a9) - -**Implemented enhancements:** - -- feat/neon\_transformers [\#141](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/141) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a8) (2023-04-13) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a7...V0.0.23a8) - -**Implemented enhancements:** - -- feat/question\_solvers [\#140](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/140) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a7) (2023-04-09) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a6...V0.0.23a7) - -**Fixed bugs:** - -- fix plugin requirements conflicts [\#138](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/138) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a6) (2023-04-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a5...V0.0.23a6) - -**Merged pull requests:** - -- migrate to ovos-bus-client [\#136](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/136) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a5) (2023-04-05) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a4...V0.0.23a5) - -## [V0.0.23a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a4) (2023-03-31) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a3...V0.0.23a4) - -**Fixed bugs:** - -- more missing imports [\#135](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/135) ([builderjer](https://github.com/builderjer)) - -## [V0.0.23a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a3) (2023-03-31) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a2...V0.0.23a3) - -**Fixed bugs:** - -- fix/missing\_imports md5 [\#134](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/134) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a2) (2023-03-31) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.23a1...V0.0.23a2) - -**Implemented enhancements:** - -- feat/voice\_configs [\#131](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/131) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.23a1) (2023-03-10) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22...V0.0.23a1) - -**Fixed bugs:** - -- fix/dummy\_stt\_plugin [\#129](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/129) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.22](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22) (2023-03-02) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a9...V0.0.22) - -## [V0.0.22a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a9) (2023-03-01) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a8...V0.0.22a9) - -**Merged pull requests:** - -- Update load exception logging [\#128](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/128) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.22a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a8) (2023-02-25) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a7...V0.0.22a8) - -**Merged pull requests:** - -- Update ovos\_utils dependency to stable release [\#127](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/127) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.22a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a7) (2023-02-23) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a6...V0.0.22a7) - -**Fixed bugs:** - -- fix/chained\_sei\_extract [\#124](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/124) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.22a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a6) (2023-02-14) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a5...V0.0.22a6) - -## [V0.0.22a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a5) (2023-02-14) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a4...V0.0.22a5) - -**Fixed bugs:** - -- fix/logspam [\#123](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/123) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.22a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a4) (2023-02-14) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a3...V0.0.22a4) - -**Fixed bugs:** - -- feat/improve\_ocp\_plugin\_error\_handling [\#122](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/122) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.22a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a3) (2023-02-09) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a2...V0.0.22a3) - -**Implemented enhancements:** - -- feat/generalize runtime requirements [\#118](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/118) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.22a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a2) (2023-02-02) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.22a1...V0.0.22a2) - -**Fixed bugs:** - -- fix/entrypoint\_loading [\#117](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/117) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.22a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.22a1) (2023-01-28) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21...V0.0.22a1) - -**Merged pull requests:** - -- help out with misconfigured TTS modules [\#101](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/101) ([osheroff](https://github.com/osheroff)) - -## [V0.0.21](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21) (2023-01-24) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a8...V0.0.21) - -## [V0.0.21a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a8) (2023-01-22) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a7...V0.0.21a8) - -**Fixed bugs:** - -- fixed bug with 'emit' [\#103](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/103) ([builderjer](https://github.com/builderjer)) - -## [V0.0.21a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a7) (2023-01-20) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a6...V0.0.21a7) - -**Implemented enhancements:** - -- feat/ww fallback [\#99](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/99) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.21a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a6) (2022-12-28) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a5...V0.0.21a6) - -**Implemented enhancements:** - -- Feat/ocp stream extractors [\#100](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/100) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.21a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a5) (2022-12-10) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a4...V0.0.21a5) - -**Merged pull requests:** - -- Add alternating LED animation [\#98](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/98) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.21a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a4) (2022-12-05) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a3...V0.0.21a4) - -**Fixed bugs:** - -- fix - phal plugins should be threads [\#96](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/96) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.21a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a3) (2022-11-22) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a2...V0.0.21a3) - -**Merged pull requests:** - -- Fix typo in `get_tts_config` to get tts section from passed config [\#95](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/95) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.21a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a2) (2022-11-19) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.21a1...V0.0.21a2) - -**Merged pull requests:** - -- One-shot LED animations [\#93](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/93) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.21a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.21a1) (2022-11-18) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20...V0.0.21a1) - -**Fixed bugs:** - -- Fix typo in `RefillLedAnimation` [\#92](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/92) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.20](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20) (2022-11-15) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a6...V0.0.20) - -## [V0.0.20a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a6) (2022-11-10) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a5...V0.0.20a6) - -## [V0.0.20a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a5) (2022-11-09) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a4...V0.0.20a5) - -## [V0.0.20a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a4) (2022-11-08) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a3...V0.0.20a4) - -## [V0.0.20a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a3) (2022-11-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a2...V0.0.20a3) - -## [V0.0.20a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a2) (2022-11-01) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.20a1...V0.0.20a2) - -## [V0.0.20a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.20a1) (2022-10-28) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19...V0.0.20a1) - -## [V0.0.19](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19) (2022-10-25) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a12...V0.0.19) - -## [V0.0.19a12](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a12) (2022-10-24) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a11...V0.0.19a12) - -## [V0.0.19a11](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a11) (2022-10-21) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a10...V0.0.19a11) - -## [V0.0.19a10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a10) (2022-10-21) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a9...V0.0.19a10) - -## [V0.0.19a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a9) (2022-10-20) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a8...V0.0.19a9) - -## [V0.0.19a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a8) (2022-10-18) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a7...V0.0.19a8) - -## [V0.0.19a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a7) (2022-10-18) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a6...V0.0.19a7) - -## [V0.0.19a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a6) (2022-10-18) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a5...V0.0.19a6) - -## [V0.0.19a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a5) (2022-10-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a4...V0.0.19a5) - -## [V0.0.19a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a4) (2022-09-18) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a3...V0.0.19a4) - -## [V0.0.19a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a3) (2022-09-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a2...V0.0.19a3) - -## [V0.0.19a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a2) (2022-09-16) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.19a1...V0.0.19a2) - -## [V0.0.19a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.19a1) (2022-08-26) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18...V0.0.19a1) - -## [V0.0.18](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18) (2022-08-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a13...V0.0.18) - -## [V0.0.18a13](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a13) (2022-08-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a12...V0.0.18a13) - -## [V0.0.18a12](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a12) (2022-08-02) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a11...V0.0.18a12) - -## [V0.0.18a11](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a11) (2022-07-29) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a10...V0.0.18a11) - -## [V0.0.18a10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a10) (2022-07-22) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a9...V0.0.18a10) - -## [V0.0.18a9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a9) (2022-07-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a8...V0.0.18a9) - -## [V0.0.18a8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a8) (2022-06-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a7...V0.0.18a8) - -## [V0.0.18a7](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a7) (2022-06-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a6...V0.0.18a7) - -## [V0.0.18a6](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a6) (2022-06-15) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a5...V0.0.18a6) - -## [V0.0.18a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a5) (2022-06-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a4...V0.0.18a5) - -## [V0.0.18a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a4) (2022-06-01) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a3...V0.0.18a4) - -## [V0.0.18a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a3) (2022-05-22) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a2...V0.0.18a3) - -## [V0.0.18a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a2) (2022-05-22) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.18a1...V0.0.18a2) - -## [V0.0.18a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.18a1) (2022-05-22) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.17...V0.0.18a1) - -## [V0.0.17](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.17) (2022-04-26) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.17a2...V0.0.17) - -## [V0.0.17a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.17a2) (2022-04-26) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.17a1...V0.0.17a2) - -## [V0.0.17a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.17a1) (2022-04-20) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.16...V0.0.17a1) - -## [V0.0.16](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.16) (2022-03-30) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.16a3...V0.0.16) - -## [V0.0.16a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.16a3) (2022-03-30) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.16a2...V0.0.16a3) - -## [V0.0.16a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.16a2) (2022-03-30) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.16a1...V0.0.16a2) - -## [V0.0.16a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.16a1) (2022-03-30) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.15...V0.0.16a1) - -## [V0.0.15](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.15) (2022-03-26) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.15a2...V0.0.15) - -## [V0.0.15a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.15a2) (2022-03-26) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.15a1...V0.0.15a2) - -## [V0.0.15a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.15a1) (2022-03-26) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14...V0.0.15a1) - -## [V0.0.14](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14) (2022-03-26) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14a5...V0.0.14) - -## [V0.0.14a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14a5) (2022-03-26) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14a4...V0.0.14a5) - -## [V0.0.14a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14a4) (2022-03-18) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14a3...V0.0.14a4) - -## [V0.0.14a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14a3) (2022-03-14) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14a2...V0.0.14a3) - -## [V0.0.14a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14a2) (2022-03-14) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.14a1...V0.0.14a2) - -## [V0.0.14a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.14a1) (2022-03-14) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.13...V0.0.14a1) - -## [V0.0.13](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.13) (2022-03-12) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.13a1...V0.0.13) - -## [V0.0.13a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.13a1) (2022-03-12) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.12...V0.0.13a1) - -## [V0.0.12](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.12) (2022-03-05) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.12a1...V0.0.12) - -## [V0.0.12a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.12a1) (2022-03-05) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11...V0.0.12a1) - -## [V0.0.11](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11) (2022-03-05) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11a5...V0.0.11) - -## [V0.0.11a5](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11a5) (2022-03-05) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11a4...V0.0.11a5) - -## [V0.0.11a4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11a4) (2022-03-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11a3...V0.0.11a4) - -## [V0.0.11a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11a3) (2022-03-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11a2...V0.0.11a3) - -## [V0.0.11a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11a2) (2022-03-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.11a1...V0.0.11a2) - -## [V0.0.11a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.11a1) (2022-03-01) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.10...V0.0.11a1) - -## [V0.0.10](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.10) (2022-03-01) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.10a1...V0.0.10) - -## [V0.0.10a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.10a1) (2022-03-01) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.9...V0.0.10a1) - -## [V0.0.9](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.9) (2022-03-01) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.8a3...V0.0.9) - -## [V0.0.8a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.8a3) (2022-02-28) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.8a2...V0.0.8a3) - -## [V0.0.8a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.8a2) (2022-02-25) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.8a1...V0.0.8a2) - -## [V0.0.8a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.8a1) (2022-02-24) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.8...V0.0.8a1) - -## [V0.0.8](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.8) (2022-02-24) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.7a1...V0.0.8) - -## [V0.0.7a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V0.0.7a1) (2022-02-24) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/0.0.4...V0.0.7a1) - -## [0.0.4](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.4) (2022-02-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/0.0.2...0.0.4) - -## [0.0.2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.2) (2021-11-05) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/c73ba5973781e871f9434113b84bd5d2845ae1d6...0.0.2) - \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* From da0bbeba637f1dfb872ff8c8cf5a98a6033d8cec Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Tue, 30 Apr 2024 03:30:16 +0100 Subject: [PATCH 048/129] hotfix/clean_shutdown (#222) missed in #195 refactor --- ovos_plugin_manager/templates/tts.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ovos_plugin_manager/templates/tts.py b/ovos_plugin_manager/templates/tts.py index fe24bf86..f24b2fff 100644 --- a/ovos_plugin_manager/templates/tts.py +++ b/ovos_plugin_manager/templates/tts.py @@ -717,8 +717,6 @@ def stop(self): def shutdown(self): """Shuts down the TTS engine.""" self.stop() - if TTS.playback: - TTS.playback.detach_tts(self) def __del__(self): """Destructor for the TTS object.""" From 31e2865afc8436c756cd05d5e08f19f6e11843ee Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Tue, 30 Apr 2024 02:30:31 +0000 Subject: [PATCH 049/129] Increment Version to --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 8c1fcb45..5ce58c10 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 16 +VERSION_ALPHA = 17 # END_VERSION_BLOCK From a95cd2333903ce2643d8e4d06816ede92dbbba3f Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Tue, 30 Apr 2024 02:30:59 +0000 Subject: [PATCH 050/129] Update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22902c23..d776b49c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ **Fixed bugs:** +- hotfix/clean\_shutdown [\#222](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/222) ([JarbasAl](https://github.com/JarbasAl)) - fix/tts\_reload [\#219](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/219) ([JarbasAl](https://github.com/JarbasAl)) **Closed issues:** From 74636b6b801b0aab5f078246009a6bc43d63d54d Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Wed, 1 May 2024 19:06:27 +0100 Subject: [PATCH 051/129] simplify readme, point to docs --- README.md | 45 +++------------------------------------------ 1 file changed, 3 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 625aa5f1..9456e061 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,7 @@ # OVOS plugin manager -OPM can be used to search, install, load and create plugins for the OpenVoiceOS ecosystem! +OPM can be used to load and create plugins for the OpenVoiceOS ecosystem! -![logo](https://raw.githubusercontent.com/OpenVoiceOS/ovos_assets/921b41891ed18c9e16d24d1894266200ee3bd104/Logo/Raw/opm-logo.svg) +![image](https://github.com/OpenVoiceOS/ovos-plugin-manager/assets/33701864/8c939267-42fc-4377-bcdb-f7df65e73252) -## Install - -```bash -pip install ovos-plugin-manager -``` - -## Usage - -see [./examples](examples) folder for basic usage - - -## Plugins - -OPM provides templates to create OVOS plugins - -#### List of plugins - -This list is non exhaustive - -##### Wake Words -- [ovos-ww-plugin-hotkeys](https://github.com/OpenVoiceOS/ovos_ww_plugin_hotkeys) -- [ovos-ww-plugin-pocketsphinx](https://github.com/OpenVoiceOS/ovos-wakeword-plugin-pocketsphinx) -- [ovos-ww-plugin-precise](https://github.com/OpenVoiceOS/ovos-wake-word-plugin-precise) -- [chatterbox-ww-plugin-dummy](https://github.com/HelloChatterbox/dummy_wakeword_plugin) - -##### TTS -- [ovos-tts-plugin-mimic](https://github.com/OpenVoiceOS/ovos-tts-plugin-mimic) -- [ovos-tts-plugin-mimic2](https://github.com/OpenVoiceOS/ovos-tts-plugin-mimic2) -- [ovos-tts-plugin-google](https://github.com/OpenVoiceOS/ovos-tts-plugin-google) -- [ovos-tts-plugin-responsivevoice](https://github.com/OpenVoiceOS/ovos-tts-plugin-responsivevoice) -- [ovos-tts-plugin-pico](https://github.com/OpenVoiceOS/ovos-tts-plugin-pico) -- [ovos-tts-plugin-espeakNG](https://github.com/OpenVoiceOS/ovos-tts-plugin-espeakNG) -- [chatterbox-polly-tts-plugin](https://github.com/HelloChatterbox/chatterbox-polly-tts-plugin) - -##### STT -- [ovos-stt-plugin-vosk](https://github.com/OpenVoiceOS/ovos-stt-plugin-vosk) - -##### Audio Service -- [ovos-guiplayer-plugin](https://github.com/OpenVoiceOS/ovos-guiplayer-plugin) -- [ovos-vlc-plugin](https://github.com/OpenVoiceOS/ovos-vlc-plugin) +Documentation can be found in the [ovos-technical-manual](https://openvoiceos.github.io/ovos-technical-manual/OPM) From c27916d0d320a33ac19d74a1b33badb7e281d464 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Wed, 1 May 2024 21:33:13 +0100 Subject: [PATCH 052/129] hotfix/voice_kwarg (#223) * hotfix/voice_kwarg was not being passed to kwargs in TTS template * get rid of deprecation warnings * test --- ovos_plugin_manager/templates/tts.py | 8 +++++--- test/unittests/test_tts.py | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ovos_plugin_manager/templates/tts.py b/ovos_plugin_manager/templates/tts.py index f24b2fff..d911f784 100644 --- a/ovos_plugin_manager/templates/tts.py +++ b/ovos_plugin_manager/templates/tts.py @@ -547,6 +547,10 @@ def _get_ctxt(self, kwargs=None) -> TTSContext: sess = SessionManager.get(message) kwargs["lang"] = sess.lang + # voice from config + if "voice" not in kwargs: + kwargs["voice"] = self.voice + # filter kwargs accepted by this specific plugin kwargs = {k: v for k, v in kwargs.items() if k in inspect.signature(self.get_tts).parameters @@ -555,7 +559,7 @@ def _get_ctxt(self, kwargs=None) -> TTSContext: LOG.debug(f"TTS kwargs: {kwargs}") return TTSContext(plugin_id=self.plugin_id, lang=kwargs.get("lang") or Configuration().get("lang", "en-us"), - voice=kwargs.get("voice") or self.voice, + voice=kwargs.get("voice", "default"), synth_kwargs=kwargs) def _execute(self, sentence, ident, listen, preprocess=True, **kwargs): @@ -885,8 +889,6 @@ def get_from_cache(self, sentence): return self._get_ctxt().get_from_cache(sentence, self.audio_ext, self.config) @property - @deprecated("language is defined per request in get_tts, self.lang is not used", - "0.1.0") def lang(self): message = dig_for_message() if message: diff --git a/test/unittests/test_tts.py b/test/unittests/test_tts.py index 88b7a29f..ec3baf16 100644 --- a/test/unittests/test_tts.py +++ b/test/unittests/test_tts.py @@ -362,14 +362,14 @@ def test_tts_session(self): self.assertEqual(ctxt.plugin_id, tts.plugin_id) self.assertEqual(ctxt.lang, sess.lang) self.assertEqual(ctxt.tts_id, f"{tts.plugin_id}/default/en-us") - self.assertEqual(ctxt.synth_kwargs, {'lang': 'en-us'}) + self.assertEqual(ctxt.synth_kwargs, {'lang': 'en-us', "voice": "default"}) sess = Session(session_id="123", lang="klingon") m = Message("speak", context={"session": sess.serialize()}) - kwargs = {"message": m} + kwargs = {"message": m, "voice": "Daghor"} ctxt = tts._get_ctxt(kwargs) self.assertEqual(ctxt.lang, sess.lang) - self.assertEqual(ctxt.tts_id, f"{tts.plugin_id}/default/klingon") - self.assertEqual(ctxt.synth_kwargs, {'lang': 'klingon'}) + self.assertEqual(ctxt.tts_id, f"{tts.plugin_id}/Daghor/klingon") + self.assertEqual(ctxt.synth_kwargs, {'lang': 'klingon', 'voice': 'Daghor'}) From cf0934c98a0f96c50f5b792040d4cc7a4c5482da Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Wed, 1 May 2024 20:33:27 +0000 Subject: [PATCH 053/129] Increment Version to --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 5ce58c10..0ba69e1a 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 17 +VERSION_ALPHA = 18 # END_VERSION_BLOCK From b0895b586d0b4919c6d4b3d2b1e322d9f49d564c Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Wed, 1 May 2024 20:33:51 +0000 Subject: [PATCH 054/129] Update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d776b49c..62a967ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ **Fixed bugs:** +- hotfix/voice\_kwarg [\#223](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/223) ([JarbasAl](https://github.com/JarbasAl)) - hotfix/clean\_shutdown [\#222](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/222) ([JarbasAl](https://github.com/JarbasAl)) - fix/tts\_reload [\#219](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/219) ([JarbasAl](https://github.com/JarbasAl)) From eff6f90630235a83105145a915dd342a11d15996 Mon Sep 17 00:00:00 2001 From: Daniel McKnight <34697904+NeonDaniel@users.noreply.github.com> Date: Thu, 9 May 2024 11:17:17 -0700 Subject: [PATCH 055/129] Update for ovos-utils 0.0.X compat. (#224) * Update for ovos-utils 0.0.X compat. Related to https://github.com/OpenVoiceOS/OVOS-workshop/pull/201 * Add ovos-utils alpha dependency to test.txt * More import refactoring to allow ovos-utils<0.1 * Refactor to include backwards-compat imports * Refactor for ovos-utils 0.0.X compat * Wrap unused imports for compat. --------- Co-authored-by: Daniel McKnight --- ovos_plugin_manager/ocp.py | 7 +- ovos_plugin_manager/templates/audio.py | 205 ++++++++++++++++++++++++- ovos_plugin_manager/templates/media.py | 3 +- requirements/requirements.txt | 2 +- requirements/test.txt | 3 +- 5 files changed, 208 insertions(+), 12 deletions(-) diff --git a/ovos_plugin_manager/ocp.py b/ovos_plugin_manager/ocp.py index 5749cae5..9dcf9c15 100644 --- a/ovos_plugin_manager/ocp.py +++ b/ovos_plugin_manager/ocp.py @@ -1,11 +1,16 @@ from ovos_plugin_manager.utils import PluginTypes from ovos_plugin_manager.templates.ocp import OCPStreamExtractor -from ovos_plugin_manager.templates.media import AudioPlayerBackend, VideoPlayerBackend, WebPlayerBackend from ovos_utils.log import LOG from functools import lru_cache from ovos_plugin_manager.utils import find_plugins +try: + from ovos_plugin_manager.templates.media import AudioPlayerBackend, VideoPlayerBackend, WebPlayerBackend +except ImportError: + LOG.warning("Please install ovos-utils~=0.1 for `AudioPlayerBackend`, " + "`VideoPlayerBackend`, and `WebPlayerBackend` imports.") + def find_ocp_plugins() -> dict: """ diff --git a/ovos_plugin_manager/templates/audio.py b/ovos_plugin_manager/templates/audio.py index fbb1e330..083f049a 100644 --- a/ovos_plugin_manager/templates/audio.py +++ b/ovos_plugin_manager/templates/audio.py @@ -3,19 +3,53 @@ These classes can be used to create an Audioservice plugin extending OpenVoiceOS's media playback options. """ -from ovos_bus_client.message import Message +from abc import ABCMeta, abstractmethod -from ovos_plugin_manager.templates.media import AudioPlayerBackend as _AB +from ovos_bus_client import Message +from ovos_bus_client.message import dig_for_message from ovos_utils import classproperty -from ovos_utils.log import log_deprecation -from ovos_utils.ocp import PlaybackType, TrackState +from ovos_utils.log import log_deprecation, LOG +from ovos_utils.fakebus import FakeBus from ovos_utils.process_utils import RuntimeRequirements +try: + from ovos_utils.ocp import PlaybackType, TrackState +except ImportError: + LOG.warning("Please update to ovos-utils~=0.1.") + from enum import IntEnum + + class PlaybackType(IntEnum): + SKILL = 0 # skills handle playback whatever way they see fit, + # eg spotify / mycroft common play + VIDEO = 1 # Video results + AUDIO = 2 # Results should be played audio only + AUDIO_SERVICE = 3 ## DEPRECATED - used in ovos 0.0.7 + MPRIS = 4 # External MPRIS compliant player + WEBVIEW = 5 # webview, render a url instead of media player + UNDEFINED = 100 # data not available, hopefully status will be updated soon.. + + + class TrackState(IntEnum): + DISAMBIGUATION = 1 # media result, not queued for playback + PLAYING_SKILL = 20 # Skill is handling playback internally + PLAYING_AUDIOSERVICE = 21 ## DEPRECATED - used in ovos 0.0.7 + PLAYING_VIDEO = 22 # Skill forwarded playback to video service + PLAYING_AUDIO = 23 # Skill forwarded playback to audio service + PLAYING_MPRIS = 24 # External media player is handling playback + PLAYING_WEBVIEW = 25 # Media playback handled in browser (eg. javascript) + + QUEUED_SKILL = 30 # Waiting playback to be handled inside skill + QUEUED_AUDIOSERVICE = 31 ## DEPRECATED - used in ovos 0.0.7 + QUEUED_VIDEO = 32 # Waiting playback in video service + QUEUED_AUDIO = 33 # Waiting playback in audio service + QUEUED_WEBVIEW = 34 # Waiting playback in browser service + + log_deprecation("ovos_plugin_manager.templates.audio has been deprecated on ovos-audio, " "move to ovos_plugin_manager.templates.media", "0.1.0") -class AudioBackend(_AB): +class AudioBackend(metaclass=ABCMeta): """Base class for all audio backend implementations. Arguments: @@ -23,6 +57,12 @@ class AudioBackend(_AB): bus (MessageBusClient): OpenVoiceOS messagebus emitter """ + def __init__(self, config=None, bus=None): + self._track_start_callback = None + self.supports_mime_hints = False + self.config = config or {} + self.bus = bus or FakeBus() + @classproperty def runtime_requirements(self): """ skill developers should override this if they do not require connectivity @@ -58,14 +98,23 @@ def runtime_requirements(self): no_internet_fallback=True, no_network_fallback=True) - # methods below deprecated and handled by OCP directly - # playlists are no longer managed plugin side - # this is just a compat layer forwarding commands to OCP + @property + def playback_time(self): + return 0 + + def supported_uris(self): + """List of supported uri types. + + Returns: + list: Supported uri's + """ + def clear_list(self): """Clear playlist.""" msg = Message('ovos.common_play.playlist.clear') self.bus.emit(msg) + @abstractmethod def add_list(self, tracks): """Add tracks to backend's playlist. @@ -97,6 +146,49 @@ def _uri2meta(uri): } return meta + @abstractmethod + def play(self, repeat=False): + """Start playback. + + Starts playing the first track in the playlist and will contiune + until all tracks have been played. + + Arguments: + repeat (bool): Repeat playlist, defaults to False + """ + + def stop(self): + """Stop playback. + + Stops the current playback. + + Returns: + bool: True if playback was stopped, otherwise False + """ + + def set_track_start_callback(self, callback_func): + """Register callback on track start. + + This method should be called as each track in a playlist is started. + """ + self._track_start_callback = callback_func + + def pause(self): + """Pause playback. + + Stops playback but may be resumed at the exact position the pause + occured. + """ + msg = Message('ovos.common_play.pause') + self.bus.emit(msg) + + def resume(self): + """Resume paused playback. + + Resumes playback after being paused. + """ + msg = Message('ovos.common_play.resume') + def next(self): """Skip to next track in playlist.""" self.bus.emit(Message("ovos.common_play.next")) @@ -105,6 +197,103 @@ def previous(self): """Skip to previous track in playlist.""" self.bus.emit(Message("ovos.common_play.previous")) + def lower_volume(self): + """Lower volume. + + This method is used to implement audio ducking. It will be called when + OpenVoiceOS is listening or speaking to make sure the media playing isn't + interfering. + """ + + def restore_volume(self): + """Restore normal volume. + + Called when to restore the playback volume to previous level after + OpenVoiceOS has lowered it using lower_volume(). + """ + + def get_track_length(self): + """ + getting the duration of the audio in miliseconds + """ + length = 0 + msg = self._format_msg('ovos.common_play.get_track_length') + info = self.bus.wait_for_response(msg, timeout=1) + if info: + length = info.data.get("length", 0) + return length + + def get_track_position(self): + """ + get current position in miliseconds + """ + pos = 0 + msg = self._format_msg('ovos.common_play.get_track_position') + info = self.bus.wait_for_response(msg, timeout=1) + if info: + pos = info.data.get("position", 0) + return pos + + def set_track_position(self, milliseconds): + """Go to X position. + Arguments: + milliseconds (int): position to go to in milliseconds + """ + msg = self._format_msg('ovos.common_play.set_track_position', + {"position": milliseconds}) + self.bus.emit(msg) + + def seek_forward(self, seconds=1): + """Skip X seconds. + + Arguments: + seconds (int): number of seconds to seek, if negative rewind + """ + msg = self._format_msg('ovos.common_play.seek', + {"seconds": seconds}) + self.bus.emit(msg) + + def seek_backward(self, seconds=1): + """Rewind X seconds. + + Arguments: + seconds (int): number of seconds to seek, if negative jump forward. + """ + msg = self._format_msg('ovos.common_play.seek', + {"seconds": seconds * -1}) + self.bus.emit(msg) + + def track_info(self): + """Request information of current playing track. + Returns: + Dict with track info. + """ + msg = self._format_msg('ovos.common_play.track_info') + response = self.bus.wait_for_response(msg) + return response.data if response else {} + + def shutdown(self): + """Perform clean shutdown. + + Implements any audio backend specific shutdown procedures. + """ + self.stop() + + def _format_msg(self, msg_type, msg_data=None): + # this method ensures all skills are .forward from the utterance + # that triggered the skill, this ensures proper routing and metadata + msg_data = msg_data or {} + msg = dig_for_message() + if msg: + msg = msg.forward(msg_type, msg_data) + else: + msg = Message(msg_type, msg_data) + # at this stage source == skills, lets indicate audio service took over + sauce = msg.context.get("source") + if sauce == "skills": + msg.context["source"] = "audio_service" + return msg + class RemoteAudioBackend(AudioBackend): """Base class for remote audio backends. diff --git a/ovos_plugin_manager/templates/media.py b/ovos_plugin_manager/templates/media.py index 8d94132a..85e25381 100644 --- a/ovos_plugin_manager/templates/media.py +++ b/ovos_plugin_manager/templates/media.py @@ -3,7 +3,6 @@ from ovos_bus_client.message import Message from ovos_utils.log import LOG from ovos_utils.messagebus import FakeBus - from ovos_utils.ocp import MediaState, PlayerState, TrackState @@ -18,6 +17,8 @@ class MediaBackend(metaclass=ABCMeta): """ def __init__(self, config=None, bus=None): + if MediaState is None: + raise RuntimeError("Please update to ovos-utils~=0.1.") self._now_playing = None # single uri self._track_start_callback = None self.supports_mime_hints = False diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 3f1f5ef2..2ffff61e 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,4 +1,4 @@ -ovos-utils < 0.2.0, >=0.1.0a8 +ovos-utils < 0.2.0, >=0.0.38 ovos-bus-client < 0.2.0, >=0.0.9a3 ovos-config < 0.2.0, >=0.0.12 combo_lock~=0.2 diff --git a/requirements/test.txt b/requirements/test.txt index 48548151..7694cc16 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,4 +1,5 @@ pytest pytest-timeout pytest-cov -ovos-translate-server-plugin \ No newline at end of file +ovos-translate-server-plugin +ovos-utils>=0.1.0a8 \ No newline at end of file From 7e0c1a118f714aac577cc33ff09eef55b58443ec Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 9 May 2024 18:17:44 +0000 Subject: [PATCH 056/129] Increment Version to --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 0ba69e1a..30d7d874 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 18 +VERSION_ALPHA = 19 # END_VERSION_BLOCK From cc4f1ec0b2177df0ba54a72e1285957eda4daa82 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 9 May 2024 18:18:23 +0000 Subject: [PATCH 057/129] Update Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62a967ca..59b46df0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ - Error about Azure TTS plugin [\#193](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/193) +**Merged pull requests:** + +- Update for ovos-utils 0.0.X compat. [\#224](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/224) ([NeonDaniel](https://github.com/NeonDaniel)) + ## [V](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V) (2024-03-10) [Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V0.0.26a10...V) From d0eed3edad17f90cfc6cb38cbddd68f7b0a15249 Mon Sep 17 00:00:00 2001 From: Daniel McKnight <34697904+NeonDaniel@users.noreply.github.com> Date: Thu, 9 May 2024 12:20:44 -0700 Subject: [PATCH 058/129] Fix file path handling in setup.py (#225) Co-authored-by: Daniel McKnight --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f73a96d9..2567aee9 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ def required(requirements_file): STT_PLUGIN_ENTRY_POINT = 'ovos-stt-plugin-dummy=ovos_plugin_manager.templates.stt:STT' WW_PLUGIN_ENTRY_POINT = 'ovos-ww-plugin-dummy=ovos_plugin_manager.templates.hotwords:HotWordEngine' -with open("README.md", "r") as f: +with open(os.path.join(BASEDIR, "README.md"), "r") as f: long_description = f.read() setup( From cb855969521d93a642e3ae561132b79c3cbab974 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 9 May 2024 19:21:01 +0000 Subject: [PATCH 059/129] Increment Version to 0.0.26a20 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 30d7d874..3ecacee5 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 19 +VERSION_ALPHA = 20 # END_VERSION_BLOCK From ed69e14338dfd7bd96980e41b607f3ce27a7f0e5 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 9 May 2024 19:21:25 +0000 Subject: [PATCH 060/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59b46df0..59093106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [Unreleased](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/HEAD) +## [0.0.26a20](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a20) (2024-05-09) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...HEAD) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a20) **Implemented enhancements:** @@ -21,6 +21,7 @@ **Merged pull requests:** +- Fix file path handling in setup.py [\#225](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/225) ([NeonDaniel](https://github.com/NeonDaniel)) - Update for ovos-utils 0.0.X compat. [\#224](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/224) ([NeonDaniel](https://github.com/NeonDaniel)) ## [V](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/V) (2024-03-10) From 77c66a34312abb8d8dd3382423a2fc4da1837113 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 11 May 2024 04:32:14 +0100 Subject: [PATCH 061/129] refactor/legacy_audio (#226) * refactor/legacy_audio use abstractmethods to force implementation of key pieces in plugins drop dependency on common_play base class helps keeping the legacy plugins alive independently of OCP * error logs * idx --- ovos_plugin_manager/templates/audio.py | 325 ++++++++++++++++--------- 1 file changed, 210 insertions(+), 115 deletions(-) diff --git a/ovos_plugin_manager/templates/audio.py b/ovos_plugin_manager/templates/audio.py index 083f049a..c8d1e398 100644 --- a/ovos_plugin_manager/templates/audio.py +++ b/ovos_plugin_manager/templates/audio.py @@ -4,20 +4,55 @@ OpenVoiceOS's media playback options. """ from abc import ABCMeta, abstractmethod +from typing import List from ovos_bus_client import Message from ovos_bus_client.message import dig_for_message from ovos_utils import classproperty -from ovos_utils.log import log_deprecation, LOG from ovos_utils.fakebus import FakeBus +from ovos_utils.log import LOG from ovos_utils.process_utils import RuntimeRequirements try: - from ovos_utils.ocp import PlaybackType, TrackState + from ovos_utils.ocp import PlaybackType, TrackState, PlayerState, MediaState except ImportError: LOG.warning("Please update to ovos-utils~=0.1.") from enum import IntEnum + + class MediaState(IntEnum): + # https://doc.qt.io/qt-5/qmediaplayer.html#MediaStatus-enum + # The status of the media cannot be determined. + UNKNOWN = 0 + # There is no current media. PlayerState == STOPPED + NO_MEDIA = 1 + # The current media is being loaded. The player may be in any state. + LOADING_MEDIA = 2 + # The current media has been loaded. PlayerState== STOPPED + LOADED_MEDIA = 3 + # Playback of the current media has stalled due to + # insufficient buffering or some other temporary interruption. + # PlayerState != STOPPED + STALLED_MEDIA = 4 + # The player is buffering data but has enough data buffered + # for playback to continue for the immediate future. + # PlayerState != STOPPED + BUFFERING_MEDIA = 5 + # The player has fully buffered the current media. PlayerState != STOPPED + BUFFERED_MEDIA = 6 + # Playback has reached the end of the current media. PlayerState == STOPPED + END_OF_MEDIA = 7 + # The current media cannot be played. PlayerState == STOPPED + INVALID_MEDIA = 8 + + + class PlayerState(IntEnum): + # https://doc.qt.io/qt-5/qmediaplayer.html#State-enum + STOPPED = 0 + PLAYING = 1 + PAUSED = 2 + + class PlaybackType(IntEnum): SKILL = 0 # skills handle playback whatever way they see fit, # eg spotify / mycroft common play @@ -45,10 +80,6 @@ class TrackState(IntEnum): QUEUED_WEBVIEW = 34 # Waiting playback in browser service -log_deprecation("ovos_plugin_manager.templates.audio has been deprecated on ovos-audio, " - "move to ovos_plugin_manager.templates.media", "0.1.0") - - class AudioBackend(metaclass=ABCMeta): """Base class for all audio backend implementations. @@ -58,6 +89,9 @@ class AudioBackend(metaclass=ABCMeta): """ def __init__(self, config=None, bus=None): + self._now_playing = None # single uri + self._tracks = [] # list of dicts for OCP entries + self._idx = 0 self._track_start_callback = None self.supports_mime_hints = False self.config = config or {} @@ -99,53 +133,18 @@ def runtime_requirements(self): no_network_fallback=True) @property - def playback_time(self): - return 0 + @abstractmethod + def playback_time(self) -> int: + """ in milliseconds """ - def supported_uris(self): + @abstractmethod + def supported_uris(self) -> List[str]: """List of supported uri types. Returns: list: Supported uri's """ - def clear_list(self): - """Clear playlist.""" - msg = Message('ovos.common_play.playlist.clear') - self.bus.emit(msg) - - @abstractmethod - def add_list(self, tracks): - """Add tracks to backend's playlist. - - Arguments: - tracks (list): list of tracks. - """ - tracks = tracks or [] - if isinstance(tracks, (str, tuple)): - tracks = [tracks] - elif not isinstance(tracks, list): - raise ValueError - tracks = [self._uri2meta(t) for t in tracks] - msg = Message('ovos.common_play.playlist.queue', - {'tracks': tracks}) - self.bus.emit(msg) - - @staticmethod - def _uri2meta(uri): - if isinstance(uri, list): - uri = uri[0] - try: - from ovos_ocp_files_plugin.plugin import OCPFilesMetadataExtractor - return OCPFilesMetadataExtractor.extract_metadata(uri) - except: - meta = {"uri": uri, - "skill_id": "mycroft.audio_interface", - "playback": PlaybackType.AUDIO, # TODO mime type check - "status": TrackState.QUEUED_AUDIO, - } - return meta - @abstractmethod def play(self, repeat=False): """Start playback. @@ -157,91 +156,116 @@ def play(self, repeat=False): repeat (bool): Repeat playlist, defaults to False """ - def stop(self): - """Stop playback. + @abstractmethod + def lower_volume(self): + """Lower volume. - Stops the current playback. + This method is used to implement audio ducking. It will be called when + OpenVoiceOS is listening or speaking to make sure the media playing isn't + interfering. + """ - Returns: - bool: True if playback was stopped, otherwise False + @abstractmethod + def restore_volume(self): + """Restore normal volume. + + Called when to restore the playback volume to previous level after + OpenVoiceOS has lowered it using lower_volume(). """ - def set_track_start_callback(self, callback_func): - """Register callback on track start. + @abstractmethod + def get_track_length(self) -> int: + """ + getting the duration of the audio in miliseconds + """ - This method should be called as each track in a playlist is started. + @abstractmethod + def get_track_position(self) -> int: + """ + get current position in miliseconds """ - self._track_start_callback = callback_func + @abstractmethod + def set_track_position(self, milliseconds): + """Go to X position. + Arguments: + milliseconds (int): position to go to in milliseconds + """ + + @abstractmethod def pause(self): """Pause playback. Stops playback but may be resumed at the exact position the pause occured. """ - msg = Message('ovos.common_play.pause') - self.bus.emit(msg) + @abstractmethod def resume(self): """Resume paused playback. Resumes playback after being paused. """ - msg = Message('ovos.common_play.resume') - - def next(self): - """Skip to next track in playlist.""" - self.bus.emit(Message("ovos.common_play.next")) - def previous(self): - """Skip to previous track in playlist.""" - self.bus.emit(Message("ovos.common_play.previous")) + @abstractmethod + def stop(self): + """Stop playback. - def lower_volume(self): - """Lower volume. + Stops the current playback. - This method is used to implement audio ducking. It will be called when - OpenVoiceOS is listening or speaking to make sure the media playing isn't - interfering. + Returns: + bool: True if playback was stopped, otherwise False """ - def restore_volume(self): - """Restore normal volume. - - Called when to restore the playback volume to previous level after - OpenVoiceOS has lowered it using lower_volume(). + ##################### + # internals and default implementations + def track_info(self) -> dict: + """Request information of current playing track. + Returns: + Dict with track info. """ + return self._uri2meta(self._now_playing) - def get_track_length(self): - """ - getting the duration of the audio in miliseconds - """ - length = 0 - msg = self._format_msg('ovos.common_play.get_track_length') - info = self.bus.wait_for_response(msg, timeout=1) - if info: - length = info.data.get("length", 0) - return length - - def get_track_position(self): - """ - get current position in miliseconds - """ - pos = 0 - msg = self._format_msg('ovos.common_play.get_track_position') - info = self.bus.wait_for_response(msg, timeout=1) - if info: - pos = info.data.get("position", 0) - return pos + def clear_list(self): + """Clear playlist.""" + self._tracks = [] + self._idx = 0 + + def add_list(self, tracks): + """Add tracks to backend's playlist. - def set_track_position(self, milliseconds): - """Go to X position. Arguments: - milliseconds (int): position to go to in milliseconds + tracks (list): list of tracks. """ - msg = self._format_msg('ovos.common_play.set_track_position', - {"position": milliseconds}) - self.bus.emit(msg) + tracks = tracks or [] + if isinstance(tracks, str): + tracks = [tracks] + elif not isinstance(tracks, list): + raise ValueError + if tracks: + self.load_track(tracks[0]) + self._idx = 0 + else: + LOG.error("called add_list without tracks!") + self._tracks = tracks + + def next(self): + """Skip to next track in playlist.""" + self._idx += 1 + if self._idx < len(self._tracks): + self.load_track(self._tracks[self._idx]) + self.play() + else: + LOG.error("no more tracks!") + + def previous(self): + """Skip to previous track in playlist.""" + self._idx = max(self._idx - 1, 0) + if self._idx < len(self._tracks): + self.load_track(self._tracks[self._idx]) + self.play() + else: + LOG.error("already in first track!") def seek_forward(self, seconds=1): """Skip X seconds. @@ -249,9 +273,9 @@ def seek_forward(self, seconds=1): Arguments: seconds (int): number of seconds to seek, if negative rewind """ - msg = self._format_msg('ovos.common_play.seek', - {"seconds": seconds}) - self.bus.emit(msg) + miliseconds = seconds * 1000 + new_pos = self.get_track_position() + miliseconds + self.set_track_position(new_pos) def seek_backward(self, seconds=1): """Rewind X seconds. @@ -259,18 +283,16 @@ def seek_backward(self, seconds=1): Arguments: seconds (int): number of seconds to seek, if negative jump forward. """ - msg = self._format_msg('ovos.common_play.seek', - {"seconds": seconds * -1}) - self.bus.emit(msg) + miliseconds = seconds * 1000 + new_pos = self.get_track_position() - miliseconds + self.set_track_position(new_pos) - def track_info(self): - """Request information of current playing track. - Returns: - Dict with track info. + def set_track_start_callback(self, callback_func): + """Register callback on track start. + + This method should be called as each track in a playlist is started. """ - msg = self._format_msg('ovos.common_play.track_info') - response = self.bus.wait_for_response(msg) - return response.data if response else {} + self._track_start_callback = callback_func def shutdown(self): """Perform clean shutdown. @@ -294,6 +316,79 @@ def _format_msg(self, msg_type, msg_data=None): msg.context["source"] = "audio_service" return msg + ############################ + # OCP extensions - new methods to improve compat with OCP + @staticmethod + def _uri2meta(uri): + if isinstance(uri, list): + uri = uri[0] + try: + from ovos_ocp_files_plugin.plugin import OCPFilesMetadataExtractor + return OCPFilesMetadataExtractor.extract_metadata(uri) + except: + meta = {"uri": uri, + "skill_id": "mycroft.audio_interface", + "playback": PlaybackType.AUDIO, # TODO mime type check + "status": TrackState.QUEUED_AUDIO, + } + return meta + + def load_track(self, uri): + """ This method is only used by ovos-core + In ovos audio backends are single-track, playlists are handled by OCP + """ + self._now_playing = uri + LOG.debug(f"queuing for {self.__class__.__name__} playback: {uri}") + self.bus.emit(Message("ovos.common_play.media.state", + {"state": MediaState.LOADED_MEDIA})) + self.bus.emit(Message("ovos.common_play.track.state", { + "state": TrackState.QUEUED_AUDIOSERVICE + })) + + def ocp_sync_playback(self, playback_time): + self.bus.emit(Message("ovos.common_play.playback_time", + {"position": playback_time, + "length": self.get_track_length()})) + + def ocp_start(self): + """Emit OCP status events for play""" + self.bus.emit(Message("ovos.common_play.player.state", + {"state": PlayerState.PLAYING})) + self.bus.emit(Message("ovos.common_play.media.state", + {"state": MediaState.LOADED_MEDIA})) + self.bus.emit(Message("ovos.common_play.track.state", + {"state": TrackState.PLAYING_AUDIOSERVICE})) + + def ocp_error(self): + """Emit OCP status events for playback error""" + if self._now_playing: + self.bus.emit(Message("ovos.common_play.media.state", + {"state": MediaState.INVALID_MEDIA})) + self._now_playing = None + + def ocp_stop(self): + """Emit OCP status events for stop""" + if self._now_playing: + self._now_playing = None + self.bus.emit(Message("ovos.common_play.player.state", + {"state": PlayerState.STOPPED})) + self.bus.emit(Message("ovos.common_play.media.state", + {"state": MediaState.END_OF_MEDIA})) + + def ocp_pause(self): + """Emit OCP status events for pause""" + if self._now_playing: + self.bus.emit(Message("ovos.common_play.player.state", + {"state": PlayerState.PAUSED})) + + def ocp_resume(self): + """Emit OCP status events for resume""" + if self._now_playing: + self.bus.emit(Message("ovos.common_play.player.state", + {"state": PlayerState.PLAYING})) + self.bus.emit(Message("ovos.common_play.track.state", + {"state": TrackState.PLAYING_AUDIOSERVICE})) + class RemoteAudioBackend(AudioBackend): """Base class for remote audio backends. From ee6b6a11ceed7b076fcee9fd9745f23e35d42190 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 11 May 2024 03:32:27 +0000 Subject: [PATCH 062/129] Increment Version to 0.0.26a21 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 3ecacee5..42fd89f0 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 20 +VERSION_ALPHA = 21 # END_VERSION_BLOCK From 801d26b4981db8e6f22fab57db0f0e5897eebfcd Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 11 May 2024 03:32:50 +0000 Subject: [PATCH 063/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59093106..d26cb7f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a20](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a20) (2024-05-09) +## [0.0.26a21](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a21) (2024-05-11) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a20) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a21) **Implemented enhancements:** @@ -21,6 +21,7 @@ **Merged pull requests:** +- refactor/legacy\_audio [\#226](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/226) ([JarbasAl](https://github.com/JarbasAl)) - Fix file path handling in setup.py [\#225](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/225) ([NeonDaniel](https://github.com/NeonDaniel)) - Update for ovos-utils 0.0.X compat. [\#224](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/224) ([NeonDaniel](https://github.com/NeonDaniel)) From 2c3e7696aa41905261cd1ddd48ae14ba7ae28f75 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 11 May 2024 05:44:54 +0100 Subject: [PATCH 064/129] fix/legacy_playlist_queue (#227) bug: queueing new tracks always replaced old playlist detected in end2end tests from https://github.com/OpenVoiceOS/ovos-audio/pull/64/ --- ovos_plugin_manager/templates/audio.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ovos_plugin_manager/templates/audio.py b/ovos_plugin_manager/templates/audio.py index c8d1e398..9f012d04 100644 --- a/ovos_plugin_manager/templates/audio.py +++ b/ovos_plugin_manager/templates/audio.py @@ -242,12 +242,10 @@ def add_list(self, tracks): tracks = [tracks] elif not isinstance(tracks, list): raise ValueError - if tracks: + if tracks and not self._tracks: self.load_track(tracks[0]) self._idx = 0 - else: - LOG.error("called add_list without tracks!") - self._tracks = tracks + self._tracks += tracks def next(self): """Skip to next track in playlist.""" @@ -340,7 +338,7 @@ def load_track(self, uri): self._now_playing = uri LOG.debug(f"queuing for {self.__class__.__name__} playback: {uri}") self.bus.emit(Message("ovos.common_play.media.state", - {"state": MediaState.LOADED_MEDIA})) + {"state": MediaState.LOADING_MEDIA})) self.bus.emit(Message("ovos.common_play.track.state", { "state": TrackState.QUEUED_AUDIOSERVICE })) From b6a5f0b51d73071de50221ded3458acd08ff7cc3 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 11 May 2024 04:45:11 +0000 Subject: [PATCH 065/129] Increment Version to 0.0.26a22 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 42fd89f0..b044a35b 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 21 +VERSION_ALPHA = 22 # END_VERSION_BLOCK From 4072d0d8e7885ed952d77a6b4bb10baec3717e6c Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 11 May 2024 04:45:41 +0000 Subject: [PATCH 066/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d26cb7f7..4058e54e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a21](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a21) (2024-05-11) +## [0.0.26a22](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a22) (2024-05-11) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a21) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a22) **Implemented enhancements:** @@ -11,6 +11,7 @@ **Fixed bugs:** +- fix/legacy\_playlist\_queue [\#227](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/227) ([JarbasAl](https://github.com/JarbasAl)) - hotfix/voice\_kwarg [\#223](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/223) ([JarbasAl](https://github.com/JarbasAl)) - hotfix/clean\_shutdown [\#222](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/222) ([JarbasAl](https://github.com/JarbasAl)) - fix/tts\_reload [\#219](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/219) ([JarbasAl](https://github.com/JarbasAl)) From f25552dbb25a738ad7d122bf7905ed5e5ce95acc Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 18 May 2024 19:56:47 +0100 Subject: [PATCH 067/129] fix/playback_time_not_abstract (#230) the playback_time property does not need to be an abstract method, and it causes OCP to fail to load --- ovos_plugin_manager/templates/audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/templates/audio.py b/ovos_plugin_manager/templates/audio.py index 9f012d04..471294be 100644 --- a/ovos_plugin_manager/templates/audio.py +++ b/ovos_plugin_manager/templates/audio.py @@ -133,9 +133,9 @@ def runtime_requirements(self): no_network_fallback=True) @property - @abstractmethod def playback_time(self) -> int: """ in milliseconds """ + return 0 @abstractmethod def supported_uris(self) -> List[str]: From 0fc60aa38972fe46e33a4759701aeaa0dafe6c53 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 18 May 2024 18:57:04 +0000 Subject: [PATCH 068/129] Increment Version to 0.0.26a23 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index b044a35b..c746ac78 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 22 +VERSION_ALPHA = 23 # END_VERSION_BLOCK From dd64776527b5d40ab0a6fb7ef7889ae37fb78409 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 18 May 2024 18:57:29 +0000 Subject: [PATCH 069/129] Update Changelog --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4058e54e..2d333736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a22](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a22) (2024-05-11) +## [0.0.26a23](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a23) (2024-05-18) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a22) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a23) **Implemented enhancements:** @@ -11,6 +11,8 @@ **Fixed bugs:** +- abstractmethod decorator breaks OCP 0.0.6 compat. [\#229](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/229) +- fix/playback\_time\_not\_abstract [\#230](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/230) ([JarbasAl](https://github.com/JarbasAl)) - fix/legacy\_playlist\_queue [\#227](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/227) ([JarbasAl](https://github.com/JarbasAl)) - hotfix/voice\_kwarg [\#223](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/223) ([JarbasAl](https://github.com/JarbasAl)) - hotfix/clean\_shutdown [\#222](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/222) ([JarbasAl](https://github.com/JarbasAl)) From 7ea03f8991689a5d1fcd42fcd9c94f1fd5fcdbd3 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sun, 19 May 2024 22:43:29 +0100 Subject: [PATCH 070/129] fix/py3.12 (#231) setuptools needs to be explicitly installed now, or OPM fails to scan plugins --- requirements/requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 2ffff61e..0f6b4437 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -8,3 +8,6 @@ langcodes~=3.3.0 # see https://github.com/pypa/setuptools/issues/1471 importlib_metadata + +# needed explicitly since python 3.12 +setuptools From 304b19ac530e77b4b9b0044fe72bf51777608c1f Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 19 May 2024 21:43:44 +0000 Subject: [PATCH 071/129] Increment Version to 0.0.26a24 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index c746ac78..eb9910f2 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 23 +VERSION_ALPHA = 24 # END_VERSION_BLOCK From 09482f93729b1452279b6ea15bb9a188b0edad5d Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 19 May 2024 21:44:07 +0000 Subject: [PATCH 072/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d333736..ead30625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a23](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a23) (2024-05-18) +## [0.0.26a24](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a24) (2024-05-19) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a23) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a24) **Implemented enhancements:** @@ -24,6 +24,7 @@ **Merged pull requests:** +- fix/py3.12 [\#231](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/231) ([JarbasAl](https://github.com/JarbasAl)) - refactor/legacy\_audio [\#226](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/226) ([JarbasAl](https://github.com/JarbasAl)) - Fix file path handling in setup.py [\#225](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/225) ([NeonDaniel](https://github.com/NeonDaniel)) - Update for ovos-utils 0.0.X compat. [\#224](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/224) ([NeonDaniel](https://github.com/NeonDaniel)) From fc7c86cb7db18f9e91f475df639ac32e6803997a Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:46:00 +0100 Subject: [PATCH 073/129] ensure cache dir exists (#232) * ensure cache dir exists fixes https://github.com/OpenVoiceOS/ovos-audio/issues/68 * handle empty string --- ovos_plugin_manager/templates/tts.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ovos_plugin_manager/templates/tts.py b/ovos_plugin_manager/templates/tts.py index d911f784..caccae2a 100644 --- a/ovos_plugin_manager/templates/tts.py +++ b/ovos_plugin_manager/templates/tts.py @@ -629,6 +629,10 @@ def synth(self, sentence, ctxt: TTSContext = None, **kwargs): # synth + cache audio = cache.define_audio_file(sentence_hash) + # ensure cache dir exists + base_dir = os.path.dirname(str(audio)) + if base_dir: # handle empty string + os.makedirs(base_dir, exist_ok=True) audio.path, phonemes = self.get_tts(sentence, str(audio), **ctxt.synth_kwargs) self.add_metric({"metric_type": "tts.synth.finished"}) From d2d556f8c786095d22444607dd1c5426489f6d1c Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 2 Jun 2024 22:46:16 +0000 Subject: [PATCH 074/129] Increment Version to 0.0.26a25 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index eb9910f2..af54f659 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 24 +VERSION_ALPHA = 25 # END_VERSION_BLOCK From 3ed9c5629d5fcc7665b8a0a5c4fad8034ab89544 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 2 Jun 2024 22:46:40 +0000 Subject: [PATCH 075/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ead30625..36b051cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a24](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a24) (2024-05-19) +## [0.0.26a25](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a25) (2024-06-02) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a24) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a25) **Implemented enhancements:** @@ -12,6 +12,7 @@ **Fixed bugs:** - abstractmethod decorator breaks OCP 0.0.6 compat. [\#229](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/229) +- ensure cache dir exists [\#232](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/232) ([JarbasAl](https://github.com/JarbasAl)) - fix/playback\_time\_not\_abstract [\#230](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/230) ([JarbasAl](https://github.com/JarbasAl)) - fix/legacy\_playlist\_queue [\#227](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/227) ([JarbasAl](https://github.com/JarbasAl)) - hotfix/voice\_kwarg [\#223](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/223) ([JarbasAl](https://github.com/JarbasAl)) From ff342fe6d85678bf83382d63b7f599c7f0338c71 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Mon, 3 Jun 2024 00:22:14 +0100 Subject: [PATCH 076/129] refactor/deprecation_warnings (#233) add deprecation warnings for the old STT helper classes from classic mycroft --- ovos_plugin_manager/templates/stt.py | 33 +++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/ovos_plugin_manager/templates/stt.py b/ovos_plugin_manager/templates/stt.py index ef593fa6..9d108d87 100644 --- a/ovos_plugin_manager/templates/stt.py +++ b/ovos_plugin_manager/templates/stt.py @@ -11,6 +11,7 @@ from ovos_config import Configuration from ovos_utils import classproperty +from ovos_utils.log import deprecated from ovos_utils.process_utils import RuntimeRequirements from ovos_plugin_manager.utils.config import get_plugin_config @@ -20,8 +21,6 @@ class STT(metaclass=ABCMeta): """ STT Base class, all STT backends derives from this one. """ def __init__(self, config=None): - # only imported here to not drag dependency - from speech_recognition import Recognizer self.config_core = Configuration() self._lang = None self._credential = None @@ -30,7 +29,7 @@ def __init__(self, config=None): self.config = get_plugin_config(config, "stt") self.can_stream = False - self.recognizer = Recognizer() + self._recognizer = None @classproperty def runtime_requirements(self): @@ -62,11 +61,25 @@ def runtime_requirements(self): """ return RuntimeRequirements() + @property + @deprecated("self.recognizer has been deprecated! " + "if you need it 'from speech_recognition import Recognizer' directly", "0.1.0") + def recognizer(self): + # only imported here to not drag dependency + from speech_recognition import Recognizer + if not self._recognizer: + self._recognizer = Recognizer() + return self._recognizer + + @recognizer.setter + def recognizer(self, val): + self._recognizer = val + @property def lang(self): return self._lang or \ self.config.get("lang") or \ - self.init_language(self.config_core) + Configuration().get("lang", "en-us") @lang.setter def lang(self, val): @@ -74,6 +87,8 @@ def lang(self, val): self._lang = val @property + @deprecated("self.keys has been deprecated! " + "implement config handling directly instead", "0.1.0") def keys(self): return self._keys or self.config_core.get("keys", {}) @@ -83,6 +98,8 @@ def keys(self, val): self._keys = val @property + @deprecated("self.credential has been deprecated! " + "implement config handling directly instead", "0.1.0") def credential(self): return self._credential or self.config.get("credential", {}) @@ -92,6 +109,8 @@ def credential(self, val): self._credential = val @staticmethod + @deprecated("self.init_language has been deprecated! " + "implement config handling directly instead", "0.1.0") def init_language(config_core): lang = config_core.get("lang", "en-US") langs = lang.split("-") @@ -99,6 +118,7 @@ def init_language(config_core): return langs[0].lower() + "-" + langs[1].upper() return lang + @abstractmethod def execute(self, audio, language=None): pass @@ -114,12 +134,14 @@ def available_languages(self) -> set: class TokenSTT(STT, metaclass=ABCMeta): + @deprecated("TokenSTT is deprecated, please subclass from STT directly", "0.1.0") def __init__(self, config=None): super().__init__(config) self.token = self.credential.get("token") class GoogleJsonSTT(STT, metaclass=ABCMeta): + @deprecated("GoogleJsonSTT is deprecated, please subclass from STT directly", "0.1.0") def __init__(self, config=None): super().__init__(config) if not self.credential.get("json") or self.keys.get("google_cloud"): @@ -128,7 +150,7 @@ def __init__(self, config=None): class BasicSTT(STT, metaclass=ABCMeta): - + @deprecated("BasicSTT is deprecated, please subclass from STT directly", "0.1.0") def __init__(self, config=None): super().__init__(config) self.username = str(self.credential.get("username")) @@ -137,6 +159,7 @@ def __init__(self, config=None): class KeySTT(STT, metaclass=ABCMeta): + @deprecated("KeySTT is deprecated, please subclass from STT directly", "0.1.0") def __init__(self, config=None): super().__init__(config) self.id = str(self.credential.get("client_id")) From 1ac5d2cfeae5c632501dd062da79ea7071656415 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 2 Jun 2024 23:22:29 +0000 Subject: [PATCH 077/129] Increment Version to 0.0.26a26 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index af54f659..cf716567 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 25 +VERSION_ALPHA = 26 # END_VERSION_BLOCK From efac63838e4dc97bb90c8466aa3af80f990fc108 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 2 Jun 2024 23:23:00 +0000 Subject: [PATCH 078/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36b051cb..1f93c94c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a25](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a25) (2024-06-02) +## [0.0.26a26](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a26) (2024-06-02) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a25) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a26) **Implemented enhancements:** @@ -25,6 +25,7 @@ **Merged pull requests:** +- refactor/deprecation\_warnings [\#233](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/233) ([JarbasAl](https://github.com/JarbasAl)) - fix/py3.12 [\#231](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/231) ([JarbasAl](https://github.com/JarbasAl)) - refactor/legacy\_audio [\#226](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/226) ([JarbasAl](https://github.com/JarbasAl)) - Fix file path handling in setup.py [\#225](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/225) ([NeonDaniel](https://github.com/NeonDaniel)) From 85763c299ffbcefc1917a4de4d6f76635dbc8919 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:53:08 +0100 Subject: [PATCH 079/129] refactor/improve_readwritestream (#234) * refactor/improve_readwritestream improve the ReadWriteStream class used by audio transformers make it threadsafe and more performant by using a deque add a max size to ensure it doesnt grow forever maybe fixes undiagnosed issue https://github.com/OpenVoiceOS/ovos-dinkum-listener/issues/98 * default max sizes --- ovos_plugin_manager/templates/transformers.py | 8 ++- ovos_plugin_manager/utils/__init__.py | 58 +++++++++++-------- test/unittests/test_audio_transformers.py | 52 ++++++++++++++++- 3 files changed, 90 insertions(+), 28 deletions(-) diff --git a/ovos_plugin_manager/templates/transformers.py b/ovos_plugin_manager/templates/transformers.py index ead4282a..9a58fad4 100644 --- a/ovos_plugin_manager/templates/transformers.py +++ b/ovos_plugin_manager/templates/transformers.py @@ -95,9 +95,11 @@ def __init__(self, name, priority=50, config=None): # buffers with audio chunks to be used in predictions # always cleared before STT stage - self.noise_feed = ReadWriteStream() - self.hotword_feed = ReadWriteStream() - self.speech_feed = ReadWriteStream() + # 16000 samples/second * 2 bytes/sample * 3 seconds = 96000 bytes. + self.noise_feed = ReadWriteStream(max_size=96000) # 3 second buffer + self.hotword_feed = ReadWriteStream(max_size=96000) # 3 seconds buffer + # 16000 samples/second * 2 bytes/sample * 10 seconds = 320000 bytes. + self.speech_feed = ReadWriteStream(max_size=320000) # 10 seconds buffer def _read_mycroft_conf(self): config_core = dict(Configuration()) diff --git a/ovos_plugin_manager/utils/__init__.py b/ovos_plugin_manager/utils/__init__.py index 441390bf..203fd3ec 100644 --- a/ovos_plugin_manager/utils/__init__.py +++ b/ovos_plugin_manager/utils/__init__.py @@ -12,12 +12,12 @@ # """Common functions for loading plugins.""" import time +from collections import deque from enum import Enum -from threading import Event +from threading import Event, Lock from typing import Optional import pkg_resources - from ovos_utils.log import LOG @@ -185,36 +185,47 @@ def normalize_lang(lang): class ReadWriteStream: """ Class used to support writing binary audio data at any pace, - optionally chopping when the buffer gets too large + with an optional maximum buffer size """ - def __init__(self, s=b'', chop_samples=-1): - self.buffer = s + def __init__(self, s=b'', max_size=None): + self.buffer = deque(s) self.write_event = Event() - self.chop_samples = chop_samples + self.lock = Lock() + self.max_size = max_size # Introduce max size def __len__(self): - return len(self.buffer) + with self.lock: + return len(self.buffer) def read(self, n=-1, timeout=None): - if n == -1: - n = len(self.buffer) - if 0 < self.chop_samples < len(self.buffer): - samples_left = len(self.buffer) % self.chop_samples - self.buffer = self.buffer[-samples_left:] - return_time = 1e10 if timeout is None else ( - timeout + time.time() - ) - while len(self.buffer) < n: + with self.lock: + if n == -1 or n > len(self.buffer): + n = len(self.buffer) + + end_time = time.time() + timeout if timeout is not None else float('inf') + + while True: + with self.lock: + if len(self.buffer) >= n: + chunk = bytes([self.buffer.popleft() for _ in range(n)]) + return chunk + + remaining_time = None + if timeout is not None: + remaining_time = end_time - time.time() + if remaining_time <= 0: + return b'' + self.write_event.clear() - if not self.write_event.wait(return_time - time.time()): - return b'' - chunk = self.buffer[:n] - self.buffer = self.buffer[n:] - return chunk + self.write_event.wait(remaining_time) def write(self, s): - self.buffer += s + with self.lock: + self.buffer.extend(s) + if self.max_size is not None: + while len(self.buffer) > self.max_size: + self.buffer.popleft() # Discard oldest data to maintain max size self.write_event.set() def flush(self): @@ -222,4 +233,5 @@ def flush(self): pass def clear(self): - self.buffer = b'' + with self.lock: + self.buffer.clear() diff --git a/test/unittests/test_audio_transformers.py b/test/unittests/test_audio_transformers.py index 70d8ba69..2e44a72c 100644 --- a/test/unittests/test_audio_transformers.py +++ b/test/unittests/test_audio_transformers.py @@ -1,12 +1,60 @@ import unittest +import time from unittest.mock import patch -from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes +from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes, ReadWriteStream + + +class TestReadWriteStream(unittest.TestCase): + def test_write_and_read(self): + # Initialize the stream + stream = ReadWriteStream() + + # Write some data to the stream + stream.write(b'1234567890abcdefghijklmnopqrstuvwxyz') + + # Read some data from the stream + self.assertEqual(stream.read(10), b'1234567890') + + # Read more data with a timeout + self.assertEqual(stream.read(5, timeout=1), b'abcde') + + def test_clear_buffer(self): + # Initialize the stream + stream = ReadWriteStream() + + # Write some data to the stream + stream.write(b'1234567890abcdefghijklmnopqrstuvwxyz') + + # Clear the buffer + stream.clear() + self.assertEqual(len(stream), 0) + + def test_write_with_max_size(self): + # Initialize the stream with a max size of 20 bytes + stream = ReadWriteStream(max_size=20) + + # Write some data to the stream + stream.write(b'1234567890abcdefghijklmnopqrstuvwxyz') + + # The buffer should have been trimmed to the last 20 bytes + self.assertEqual(stream.read(20), b'ghijklmnopqrstuvwxyz') + + def test_clear_buffer_with_max_size(self): + # Initialize the stream with a max size of 20 bytes + stream = ReadWriteStream(max_size=20) + + # Write some data to the stream + stream.write(b'1234567890abcdefghijklmnopqrstuvwxyz') + + # Clear the buffer + stream.clear() + self.assertEqual(len(stream), 0) class TestAudioTransformersTemplate(unittest.TestCase): def test_audio_transformer(self): - from ovos_plugin_manager.templates.transformers import AudioTransformer + pass # TODO From e4bf646a0e44585042602140a70968489109c529 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 10 Jun 2024 12:53:24 +0000 Subject: [PATCH 080/129] Increment Version to 0.0.26a27 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index cf716567..f8ef9f4b 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 26 +VERSION_ALPHA = 27 # END_VERSION_BLOCK From 83818540443b683b0b74e2d0f75beedba3870ad5 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 10 Jun 2024 12:53:50 +0000 Subject: [PATCH 081/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f93c94c..44166d0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a26](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a26) (2024-06-02) +## [0.0.26a27](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a27) (2024-06-10) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a26) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a27) **Implemented enhancements:** @@ -25,6 +25,7 @@ **Merged pull requests:** +- refactor/improve\_readwritestream [\#234](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/234) ([JarbasAl](https://github.com/JarbasAl)) - refactor/deprecation\_warnings [\#233](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/233) ([JarbasAl](https://github.com/JarbasAl)) - fix/py3.12 [\#231](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/231) ([JarbasAl](https://github.com/JarbasAl)) - refactor/legacy\_audio [\#226](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/226) ([JarbasAl](https://github.com/JarbasAl)) From b8c763138802b801cee86ebc003222c92b1cac46 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 15 Jun 2024 15:55:36 +0100 Subject: [PATCH 082/129] feat/alternative_transcripts (#236) * feat/alternative_transcripts closes https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/46 * optional in streaming STT * leave TODO comment --- ovos_plugin_manager/templates/stt.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/ovos_plugin_manager/templates/stt.py b/ovos_plugin_manager/templates/stt.py index 9d108d87..70f072b2 100644 --- a/ovos_plugin_manager/templates/stt.py +++ b/ovos_plugin_manager/templates/stt.py @@ -8,6 +8,7 @@ from abc import ABCMeta, abstractmethod from queue import Queue from threading import Thread, Event +from typing import List, Tuple, Optional from ovos_config import Configuration from ovos_utils import classproperty @@ -78,8 +79,8 @@ def recognizer(self, val): @property def lang(self): return self._lang or \ - self.config.get("lang") or \ - Configuration().get("lang", "en-us") + self.config.get("lang") or \ + Configuration().get("lang", "en-us") @lang.setter def lang(self, val): @@ -119,9 +120,15 @@ def init_language(config_core): return lang @abstractmethod - def execute(self, audio, language=None): + def execute(self, audio, language: Optional[str] = None) -> str: + # TODO - eventually deprecate this and make transcribe the @abstractmethod pass + def transcribe(self, audio, lang: Optional[str] = None) -> List[Tuple[str, float]]: + """transcribe audio data to a list of + possible transcriptions and respective confidences""" + return [(self.execute(audio, lang), 1.0)] + @property def available_languages(self) -> set: """Return languages supported by this STT implementation in this state @@ -230,9 +237,16 @@ def stream_stop(self): return text return None - def execute(self, audio, language=None): + def execute(self, audio: Optional = None, + language: Optional[str] = None): return self.stream_stop() + def transcribe(self, audio: Optional = None, + lang: Optional[str] = None) -> List[Tuple[str, float]]: + """transcribe audio data to a list of + possible transcriptions and respective confidences""" + return [(self.execute(audio, lang), 1.0)] + @abstractmethod def create_streaming_thread(self): pass From d999da80e04c14d3b232447e10c6865481a754a3 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 15 Jun 2024 14:55:55 +0000 Subject: [PATCH 083/129] Increment Version to 0.0.26a28 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index f8ef9f4b..c29e3453 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 27 +VERSION_ALPHA = 28 # END_VERSION_BLOCK From 9857a3ef5d2b348b2276fbf5821bea71b6678cfa Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 15 Jun 2024 14:56:21 +0000 Subject: [PATCH 084/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44166d0d..da32b45c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -## [0.0.26a27](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a27) (2024-06-10) +## [0.0.26a28](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a28) (2024-06-15) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a27) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a28) **Implemented enhancements:** +- feat/alternative\_transcripts [\#236](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/236) ([JarbasAl](https://github.com/JarbasAl)) - feat/lang\_detection\_plugin [\#220](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/220) ([JarbasAl](https://github.com/JarbasAl)) - feat/restore phonetic spellings [\#195](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/195) ([JarbasAl](https://github.com/JarbasAl)) From baa1b1c690fbc4f6eafe1289e06d9297ec0a77c5 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Fri, 5 Jul 2024 18:31:58 +0100 Subject: [PATCH 085/129] fix/missing_property (#239) at some point self.name was dropped --- ovos_plugin_manager/templates/audio.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ovos_plugin_manager/templates/audio.py b/ovos_plugin_manager/templates/audio.py index 471294be..b5dca2be 100644 --- a/ovos_plugin_manager/templates/audio.py +++ b/ovos_plugin_manager/templates/audio.py @@ -88,7 +88,8 @@ class AudioBackend(metaclass=ABCMeta): bus (MessageBusClient): OpenVoiceOS messagebus emitter """ - def __init__(self, config=None, bus=None): + def __init__(self, config=None, bus=None, name=None): + self.name = name or self.__class__.__name__ self._now_playing = None # single uri self._tracks = [] # list of dicts for OCP entries self._idx = 0 From fee448315412dc69e3356cb87cf12c339294bfd6 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Fri, 5 Jul 2024 17:32:13 +0000 Subject: [PATCH 086/129] Increment Version to 0.0.26a29 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index c29e3453..fecabf19 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 28 +VERSION_ALPHA = 29 # END_VERSION_BLOCK From fbab3817cc07e9964b9871399f88f5246a01afcb Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Fri, 5 Jul 2024 17:32:42 +0000 Subject: [PATCH 087/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da32b45c..6e917dee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a28](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a28) (2024-06-15) +## [0.0.26a29](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a29) (2024-07-05) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a28) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a29) **Implemented enhancements:** @@ -13,6 +13,7 @@ **Fixed bugs:** - abstractmethod decorator breaks OCP 0.0.6 compat. [\#229](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/229) +- fix/missing\_property [\#239](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/239) ([JarbasAl](https://github.com/JarbasAl)) - ensure cache dir exists [\#232](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/232) ([JarbasAl](https://github.com/JarbasAl)) - fix/playback\_time\_not\_abstract [\#230](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/230) ([JarbasAl](https://github.com/JarbasAl)) - fix/legacy\_playlist\_queue [\#227](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/227) ([JarbasAl](https://github.com/JarbasAl)) From 95766b2c682b423b0a68309472ba921e0a34b433 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:33:41 +0100 Subject: [PATCH 088/129] feat/embeddings plugins (#240) * feat/embeddings plugins example usage: https://github.com/TigreGotico/ovos-face-embeddings-plugin https://github.com/TigreGotico/ovos-voice-embeddings-plugin https://github.com/TigreGotico/ovos-chromadb-embeddings-plugin https://github.com/TigreGotico/ovos-gguf-embeddings-plugin * distance metrics galore * distance metrics galore --- ovos_plugin_manager/embeddings.py | 78 +++ ovos_plugin_manager/templates/embeddings.py | 579 ++++++++++++++++++++ ovos_plugin_manager/utils/__init__.py | 8 + 3 files changed, 665 insertions(+) create mode 100644 ovos_plugin_manager/embeddings.py create mode 100644 ovos_plugin_manager/templates/embeddings.py diff --git a/ovos_plugin_manager/embeddings.py b/ovos_plugin_manager/embeddings.py new file mode 100644 index 00000000..123c3b11 --- /dev/null +++ b/ovos_plugin_manager/embeddings.py @@ -0,0 +1,78 @@ +from ovos_plugin_manager.templates.embeddings import EmbeddingsDB, TextEmbeddingsStore, FaceEmbeddingsStore, VoiceEmbeddingsStore +from ovos_plugin_manager.utils import PluginTypes + + +def find_embeddings_plugins() -> dict: + """ + Find all installed plugins + @return: dict plugin names to entrypoints + """ + from ovos_plugin_manager.utils import find_plugins + return find_plugins(PluginTypes.EMBEDDINGS) + + +def load_embeddings_plugin(module_name: str) -> type(EmbeddingsDB): + """ + Get an uninstantiated class for the requested module_name + @param module_name: Plugin entrypoint name to load + @return: Uninstantiated class + """ + from ovos_plugin_manager.utils import load_plugin + return load_plugin(module_name, PluginTypes.EMBEDDINGS) + + +def find_voice_embeddings_plugins() -> dict: + """ + Find all installed plugins + @return: dict plugin names to entrypoints + """ + from ovos_plugin_manager.utils import find_plugins + return find_plugins(PluginTypes.VOICE_EMBEDDINGS) + + +def load_voice_embeddings_plugin(module_name: str) -> type(VoiceEmbeddingsStore): + """ + Get an uninstantiated class for the requested module_name + @param module_name: Plugin entrypoint name to load + @return: Uninstantiated class + """ + from ovos_plugin_manager.utils import load_plugin + return load_plugin(module_name, PluginTypes.VOICE_EMBEDDINGS) + + +def find_face_embeddings_plugins() -> dict: + """ + Find all installed plugins + @return: dict plugin names to entrypoints + """ + from ovos_plugin_manager.utils import find_plugins + return find_plugins(PluginTypes.FACE_EMBEDDINGS) + + +def load_face_embeddings_plugin(module_name: str) -> type(FaceEmbeddingsStore): + """ + Get an uninstantiated class for the requested module_name + @param module_name: Plugin entrypoint name to load + @return: Uninstantiated class + """ + from ovos_plugin_manager.utils import load_plugin + return load_plugin(module_name, PluginTypes.FACE_EMBEDDINGS) + + +def find_text_embeddings_plugins() -> dict: + """ + Find all installed plugins + @return: dict plugin names to entrypoints + """ + from ovos_plugin_manager.utils import find_plugins + return find_plugins(PluginTypes.TEXT_EMBEDDINGS) + + +def load_text_embeddings_plugin(module_name: str) -> type(TextEmbeddingsStore): + """ + Get an uninstantiated class for the requested module_name + @param module_name: Plugin entrypoint name to load + @return: Uninstantiated class + """ + from ovos_plugin_manager.utils import load_plugin + return load_plugin(module_name, PluginTypes.TEXT_EMBEDDINGS) diff --git a/ovos_plugin_manager/templates/embeddings.py b/ovos_plugin_manager/templates/embeddings.py new file mode 100644 index 00000000..d941f081 --- /dev/null +++ b/ovos_plugin_manager/templates/embeddings.py @@ -0,0 +1,579 @@ +import abc +from typing import List, Optional, Tuple + +import numpy as np + + +class EmbeddingsDB: + """Base plugin for embeddings database""" + + @abc.abstractmethod + def add_embeddings(self, key: str, embedding: np.ndarray) -> np.ndarray: + """Store 'embedding' under 'key' with associated metadata. + + Args: + key (str): The unique key for the embedding. + embedding (np.ndarray): The embedding vector to store. + + Returns: + np.ndarray: The stored embedding. + """ + return NotImplemented + + @abc.abstractmethod + def get_embeddings(self, key: str) -> np.ndarray: + """Retrieve embeddings stored under 'key'. + + Args: + key (str): The unique key for the embedding. + + Returns: + np.ndarray: The retrieved embedding. + """ + return NotImplemented + + @abc.abstractmethod + def delete_embeddings(self, key: str) -> np.ndarray: + """Delete embeddings stored under 'key'. + + Args: + key (str): The unique key for the embedding. + + Returns: + np.ndarray: The deleted embedding. + """ + return NotImplemented + + @abc.abstractmethod + def query(self, embeddings: np.ndarray, top_k: int = 5) -> List[Tuple[str, float]]: + """Return top_k embeddings closest to the given 'embeddings'. + + Args: + embeddings (np.ndarray): The embedding vector to query. + top_k (int, optional): The number of top results to return. Defaults to 5. + + Returns: + List[Tuple[str, float]]: List of tuples containing the key and distance. + """ + return NotImplemented + + def distance(self, embeddings_a: np.ndarray, embeddings_b: np.ndarray, metric: str = "cosine", + alpha: float = 0.5, # for alpha_divergence and tversky metrics + beta: float = 0.5, # for tversky metric + p: float = 3, # for minkowski and weighted_minkowski metrics + euclidean_weights: Optional[np.ndarray] = None, # required for weighted_euclidean and weighted_minkowski metrics + covariance_matrix: Optional[np.ndarray] = None # required for mahalanobis distance with user-defined covariance + ) -> float: + """ + Calculate the distance between two embeddings vectors using the specified distance metric. + + Args: + embeddings_a (np.ndarray): The first embedding vector. + embeddings_b (np.ndarray): The second embedding vector. + metric (str, optional): The distance metric to use. Defaults to "cosine". + Supported metrics include: + - "cosine": Cosine distance, 1 - cosine similarity. Useful for text similarity and high-dimensional data. + - "euclidean": Euclidean distance, L2 norm of the difference. Commonly used in clustering and geometric distance. + - "manhattan": Manhattan distance, L1 norm of the difference. Suitable for grid-based maps and robotics. + - "chebyshev": Chebyshev distance, maximum absolute difference. Used for chessboard distance and pathfinding. + - "minkowski": Minkowski distance, generalization of Euclidean and Manhattan distances. Parameterized by p, flexible use case. + - "weighted_minkowski": Weighted Minkowski distance, a generalization of Minkowski with weights. Parameterized by `p`, uses `euclidean_weights`. + - "hamming": Hamming distance, proportion of differing elements. Ideal for error detection and binary data. + - "jaccard": Jaccard distance, 1 - Jaccard similarity (intersection over union). Used for set similarity and binary attributes. + - "canberra": Canberra distance, weighted version of Manhattan distance. Sensitive to small changes, used in environmental data. + - "braycurtis": Bray-Curtis distance, dissimilarity between non-negative vectors. Common in ecology and species abundance studies. + - "mahalanobis": Mahalanobis distance, considering correlations (requires covariance matrix). Useful for multivariate outlier detection. + - "pearson_correlation": Pearson correlation distance, 1 - Pearson correlation coefficient. Used in time series analysis and signal processing. + - "spearman_rank": Spearman rank correlation distance, 1 - Spearman rank correlation coefficient. Measures rank correlation for non-linear monotonic relationships. + - "wasserstein": Earth Mover's Distance (Wasserstein distance). Compares probability distributions or histograms. + - "cosine_squared": Cosine squared distance, 1 - cosine similarity squared. For squared similarity in high-dimensional data. + - "kl_divergence": Kullback-Leibler divergence, asymmetric measure of difference between distributions. Applied in information theory and probability distributions. + - "bhattacharyya": Bhattacharyya distance, measure of overlap between statistical samples. Useful in classification and image processing. + - "hellinger": Hellinger distance, measure of similarity between two probability distributions. Applied in statistical inference. + - "ruzicka": Ruzicka distance, similarity measure for non-negative vectors. Used in ecology and species abundance. + - "kulczynski": Kulczynski distance, used in ecology to compare similarity. Suitable for ecological studies and species distribution. + - "sorensen": Sørensen distance, another name for Dice distance. Applied in binary data comparison and text similarity. + - "chi_squared": Chi-squared distance, used for comparing categorical data distributions. Suitable for categorical data analysis and distribution comparison. + - "jensen_shannon": Jensen-Shannon divergence, symmetrized and smoothed version of KL divergence. Used in information theory and probability distributions. + - "squared_euclidean": Squared Euclidean distance, square of the Euclidean distance. Useful for clustering algorithms and geometric distance. + - "weighted_euclidean": Weighted Euclidean distance, L2 norm with weights. Applied when features have different scales or importance. + - "log_cosh": Log-Cosh distance, log of the hyperbolic cosine of the difference. Robust to outliers. + - "tanimoto": Tanimoto coefficient, similarity measure for binary vectors. Used for binary data comparison. + - "rao": Rao's Quadratic Entropy, measure of divergence between distributions. Useful for comparing probability distributions. + - "gower": Gower distance, handles mixed types of data. Applied in cases with numerical and categorical data. + - "tversky": Tversky index, generalization of Jaccard and Dice for asymmetrical comparison. Parameterized by alpha and beta. + - "alpha_divergence": Alpha divergence, generalized divergence measure. Parameterized by alpha, used for comparing distributions. + - "kendall_tau": Kendall's Tau distance: 1 - Kendall Tau correlation coefficient. Use case: Rank correlation for ordinal data + - "renyi_divergence": Generalized divergence measure. Use case: Comparing probability distributions + - "total_variation": Measure of divergence between distributions. Use case: Probability distributions, statistical inference + + alpha (float, optional): Parameter for `tversky` and `alpha_divergence` metrics. Default is 0.5. + beta (float, optional): Parameter for `tversky` metric. Default is 0.5. + p (float, optional): Parameter for `minkowski` and `weighted_minkowski` metrics. Default is 3. + euclidean_weights (Optional[np.ndarray], optional): Weights for `weighted_euclidean` and `weighted_minkowski` metrics. Must be provided if using these metrics. Default is None. + covariance_matrix (Optional[np.ndarray], optional): Covariance matrix for `mahalanobis` distance. Must be provided if using this metric. Default is None. + + Returns: + float: The calculated distance between the two embedding vectors. + + Raises: + ValueError: If the specified metric is unsupported or requires parameters not provided. + """ + if metric == "cosine": + # Cosine distance: 1 - cosine similarity + # Use case: Text similarity, high-dimensional data + dot = np.dot(embeddings_a, embeddings_b) + norma = np.linalg.norm(embeddings_a) + normb = np.linalg.norm(embeddings_b) + cos = dot / (norma * normb) + return 1 - cos + elif metric == "euclidean": + # Euclidean distance: L2 norm of the difference + # Use case: Geometric distance, clustering + return np.linalg.norm(embeddings_a - embeddings_b) + elif metric == "manhattan": + # Manhattan distance: L1 norm of the difference + # Use case: Grid-based maps, robotics + return np.sum(np.abs(embeddings_a - embeddings_b)) + elif metric == "chebyshev": + # Chebyshev distance: Maximum absolute difference + # Use case: Chessboard distance, pathfinding + return np.max(np.abs(embeddings_a - embeddings_b)) + elif metric == "minkowski": + # Minkowski distance: Generalization of Euclidean and Manhattan distances + # Use case: Flexible distance metric, parameterized by p + return np.sum(np.abs(embeddings_a - embeddings_b) ** p) ** (1 / p) + elif metric == "weighted_minkowski": + # Weighted Minkowski distance: Generalization of Minkowski distance with weights + # Use case: Flexible distance metric with weighted dimensions + if euclidean_weights is None: + raise ValueError("euclidean_weights must be provided for weighted_minkowski metric") + return np.sum(euclidean_weights * np.abs(embeddings_a - embeddings_b) ** p) ** (1 / p) + elif metric == "hamming": + # Hamming distance: Proportion of differing elements + # Use case: Error detection, binary data + return np.mean(embeddings_a != embeddings_b) + elif metric == "jaccard": + # Jaccard distance: 1 - Jaccard similarity (intersection over union) + # Use case: Set similarity, binary attributes + intersection = np.sum(np.minimum(embeddings_a, embeddings_b)) + union = np.sum(np.maximum(embeddings_a, embeddings_b)) + return 1 - intersection / union + elif metric == "canberra": + # Canberra distance: Weighted version of Manhattan distance + # Use case: Environmental data, sensitive to small changes + return np.sum(np.abs(embeddings_a - embeddings_b) / (np.abs(embeddings_a) + np.abs(embeddings_b))) + elif metric == "braycurtis": + # Bray-Curtis distance: Dissimilarity between non-negative vectors + # Use case: Ecology, species abundance + return np.sum(np.abs(embeddings_a - embeddings_b)) / np.sum(np.abs(embeddings_a + embeddings_b)) + elif metric == "mahalanobis": + # Mahalanobis distance: Distance considering correlations (requires covariance matrix) + # Use case: Multivariate outlier detection + if covariance_matrix is None: + covariance_matrix = np.cov(embeddings_a, embeddings_b, rowvar=False) + inv_cov_matrix = np.linalg.inv(covariance_matrix) + delta = embeddings_a - embeddings_b + return np.sqrt(np.dot(np.dot(delta.T, inv_cov_matrix), delta)) + elif metric == "pearson_correlation": + # Correlation distance: 1 - Pearson correlation coefficient + # Use case: Time series analysis, signal processing + mean_a = np.mean(embeddings_a) + mean_b = np.mean(embeddings_b) + centered_a = embeddings_a - mean_a + centered_b = embeddings_b - mean_b + norm_a = np.linalg.norm(centered_a) + norm_b = np.linalg.norm(centered_b) + correlation = np.dot(centered_a, centered_b) / (norm_a * norm_b) + return 1 - correlation + elif metric == "spearman_rank": + # Spearman rank correlation distance: 1 - Spearman rank correlation coefficient. + # Use case: Measures the rank correlation between two vectors. Useful for non-linear monotonic relationships. + rank_a = np.argsort(np.argsort(embeddings_a)) + rank_b = np.argsort(np.argsort(embeddings_b)) + return 1 - np.corrcoef(rank_a, rank_b)[0, 1] + elif metric == "wasserstein": + # Earth Mover's Distance (Wasserstein distance) + # Use case: Comparing probability distributions or histograms + arr1_sorted = np.sort(embeddings_a) + arr2_sorted = np.sort(embeddings_b) + cdf1 = np.cumsum(arr1_sorted) / np.sum(arr1_sorted) + cdf2 = np.cumsum(arr2_sorted) / np.sum(arr2_sorted) + return np.sum(np.abs(cdf1 - cdf2)) + elif metric == "cosine_squared": + # Cosine squared distance: 1 - cosine similarity squared + # Use case: Squared similarity, high-dimensional data + dot = np.dot(embeddings_a, embeddings_b) + norma = np.linalg.norm(embeddings_a) + normb = np.linalg.norm(embeddings_b) + cos = dot / (norma * normb) + return 1 - cos ** 2 + elif metric == "kl_divergence": + # Kullback-Leibler divergence: Asymmetric measure of difference between distributions + # Use case: Information theory, probability distributions + return np.sum(embeddings_a * np.log(embeddings_a / embeddings_b)) + elif metric == "bhattacharyya": + # Bhattacharyya distance: Measure of overlap between statistical samples + # Use case: Classification, image processing + bc = np.sum(np.sqrt(embeddings_a * embeddings_b)) + return -np.log(bc) + elif metric == "hellinger": + # Hellinger distance: Measure of similarity between two probability distributions + # Use case: Probability distributions, statistical inference + return np.sqrt(0.5 * np.sum((np.sqrt(embeddings_a) - np.sqrt(embeddings_b)) ** 2)) + elif metric == "ruzicka": + # Ruzicka distance: Similarity measure for non-negative vectors + # Use case: Ecology, species abundance + return 1 - np.sum(np.minimum(embeddings_a, embeddings_b)) / np.sum(np.maximum(embeddings_a, embeddings_b)) + elif metric == "kulczynski": + # Kulczynski distance: Measure used in ecology to compare similarity + # Use case: Ecological studies, species distribution + return np.sum(np.abs(embeddings_a - embeddings_b)) / np.sum(np.minimum(embeddings_a, embeddings_b)) + elif metric == "sorensen": + # Sørensen distance: Another name for Dice distance + # Use case: Binary data comparison, text similarity + intersection = np.sum(embeddings_a * embeddings_b) + return 1 - (2 * intersection) / (np.sum(embeddings_a) + np.sum(embeddings_b)) + elif metric == "chi_squared": + # Chi-squared distance: Used for comparing categorical data distributions + # Use case: Categorical data analysis, distribution comparison + return np.sum((embeddings_a - embeddings_b) ** 2 / (embeddings_a + embeddings_b)) + elif metric == "jensen_shannon": + # Jensen-Shannon divergence: Symmetrized and smoothed version of KL divergence + # Use case: Information theory, probability distributions + m = 0.5 * (embeddings_a + embeddings_b) + return 0.5 * (np.sum(embeddings_a * np.log(embeddings_a / m)) + np.sum(embeddings_b * np.log(embeddings_b / m))) + elif metric == "squared_euclidean": + # Squared Euclidean distance: Square of the Euclidean distance + # Use case: Clustering algorithms, geometric distance + return np.sum((embeddings_a - embeddings_b) ** 2) + elif metric == "weighted_euclidean": + # Weighted Euclidean distance: L2 norm with weights + # Use case: Features with different scales or importance + if euclidean_weights is None: + raise ValueError("euclidean_weights must be provided for weighted_euclidean metric") + return np.sqrt(np.sum(euclidean_weights * (embeddings_a - embeddings_b) ** 2)) + elif metric == "log_cosh": + # Log-Cosh distance: Log of the hyperbolic cosine of the difference + # Use case: Robustness to outliers + return np.sum(np.log(np.cosh(embeddings_a - embeddings_b))) + elif metric == "tanimoto": + # Tanimoto coefficient: Similarity measure for binary vectors + # Use case: Binary data comparison + intersection = np.sum(embeddings_a * embeddings_b) + return 1 - intersection / (np.sum(embeddings_a) + np.sum(embeddings_b) - intersection) + elif metric == "rao": + # Rao's Quadratic Entropy: Measure of divergence between distributions + # Use case: Comparing probability distributions + p = embeddings_a / np.sum(embeddings_a) + q = embeddings_b / np.sum(embeddings_b) + return np.sum((p - q) ** 2 / (p + q)) + elif metric == "gower": + # Gower distance: Handles mixed types of data + # Use case: Mixed data types (numerical and categorical) + numerical_part = np.sum(np.abs(embeddings_a - embeddings_b)) / len(embeddings_a) + categorical_part = np.mean(embeddings_a != embeddings_b) + return numerical_part + categorical_part + elif metric == "tversky": + # Tversky index: Generalization of Jaccard and Dice for asymmetrical comparison + intersection = np.sum(np.minimum(embeddings_a, embeddings_b)) + return 1 - intersection / (intersection + alpha * np.sum(embeddings_a - embeddings_b) + beta * np.sum(embeddings_b - embeddings_a)) + elif metric == "alpha_divergence": + # Alpha divergence: Generalized divergence measure + p = embeddings_a / np.sum(embeddings_a) + q = embeddings_b / np.sum(embeddings_b) + return np.sum((p ** alpha - q ** alpha) / (alpha * (p + q) ** alpha)) + elif metric == "kendall_tau": + # Kendall's Tau distance: 1 - Kendall Tau correlation coefficient + # Use case: Rank correlation for ordinal data + concordant = np.sum((embeddings_a > embeddings_b) == (embeddings_b > embeddings_a)) + discordant = np.sum((embeddings_a > embeddings_b) != (embeddings_b > embeddings_a)) + return 1 - (concordant - discordant) / (concordant + discordant) + elif metric == "renyi_divergence": + # Renyi Divergence: Generalized divergence measure + # Use case: Comparing probability distributions + p = embeddings_a / np.sum(embeddings_a) + q = embeddings_b / np.sum(embeddings_b) + return 1 / (1 - alpha) * np.log(np.sum((p ** alpha + q ** alpha) / 2)) + elif metric == "total_variation": + # Total Variation distance: Measure of divergence between distributions + # Use case: Probability distributions, statistical inference + p = embeddings_a / np.sum(embeddings_a) + q = embeddings_b / np.sum(embeddings_b) + return 0.5 * np.sum(np.abs(p - q)) + else: + raise ValueError("Unsupported metric") + + +class TextEmbeddingsStore: + """A store for text embeddings interfacing with the embeddings database""" + + def __init__(self, db: EmbeddingsDB): + """Initialize the text embeddings store. + + Args: + db (EmbeddingsDB): The embeddings database instance. + """ + self.db = db + + @abc.abstractmethod + def get_text_embeddings(self, text: str) -> np.ndarray: + """Convert text to its corresponding embeddings. + + Args: + text (str): The input text to be converted. + + Returns: + np.ndarray: The resulting embeddings. + """ + return NotImplemented + + def add_document(self, document: str) -> None: + """Add a document and its embeddings to the database. + + Args: + document (str): The document to add. + """ + embeddings = self.get_text_embeddings(document) + self.db.add_embeddings(document, embeddings) + + def delete_document(self, document: str) -> None: + """Delete a document and its embeddings from the database. + + Args: + document (str): The document to delete. + """ + self.db.delete_embeddings(document) + + def query(self, document: str, top_k: int = 5) -> List[Tuple[str, float]]: + """Query the database for the top_k closest embeddings to the document. + + Args: + document (str): The document to query. + top_k (int, optional): The number of top results to return. Defaults to 5. + + Returns: + List[Tuple[str, float]]: List of tuples containing the document and distance. + """ + embeddings = self.get_text_embeddings(document) + return self.db.query(embeddings, top_k) + + def distance(self, text_a: str, text_b: str, metric: str = "cosine") -> float: + """Calculate the distance between embeddings of two texts. + + Args: + text_a (str): The first text. + text_b (str): The second text. + metric (str, optional): The distance metric to use. Defaults to "cosine". + + Returns: + float: The calculated distance. + """ + emb: np.ndarray = self.get_text_embeddings(text_a) + emb2: np.ndarray = self.get_text_embeddings(text_b) + return self.db.distance(emb, emb2, metric) + + +class FaceEmbeddingsStore: + """A store for face embeddings interfacing with the embeddings database""" + + def __init__(self, db: EmbeddingsDB): + """Initialize the face embeddings store. + + Args: + db (EmbeddingsDB): The embeddings database instance. + """ + self.db = db + + @abc.abstractmethod + def get_face_embeddings(self, frame: np.ndarray) -> np.ndarray: + """Convert an image frame to its corresponding face embeddings. + + Args: + frame (np.ndarray): The input image frame containing a face. + + Returns: + np.ndarray: The resulting face embeddings. + """ + return NotImplemented + + def add_face(self, user_id: str, frame: np.ndarray): + """Add a face and its embeddings to the database. + + Args: + user_id (str): The unique user ID. + frame (np.ndarray): The image frame containing the face. + + Returns: + np.ndarray: The stored face embeddings. + """ + emb: np.ndarray = self.get_face_embeddings(frame) + return self.db.add_embeddings(user_id, emb) + + def delete_face(self, user_id: str): + """Delete a face and its embeddings from the database. + + Args: + user_id (str): The unique user ID. + + Returns: + np.ndarray: The deleted face embeddings. + """ + return self.db.delete_embeddings(user_id) + + def predict(self, frame: np.ndarray, top_k: int = 3, thresh: float = 0.15) -> Optional[str]: + """Return the top predicted face closest to the given frame. + + Args: + frame (np.ndarray): The input image frame containing a face. + top_k (int, optional): The number of top results to return. Defaults to 3. + thresh (float, optional): The threshold for prediction. Defaults to 0.15. + + Returns: + Optional[str]: The predicted user ID or None if the best match exceeds the threshold. + """ + matches = self.query(frame, top_k) + if not matches: + return None + best = min(matches, key=lambda k: k[1]) + if best[1] > thresh: + return None + return best[0] + + def query(self, frame: np.ndarray, top_k: int = 5) -> List[Tuple[str, float]]: + """Query the database for the top_k closest face embeddings to the frame. + + Args: + frame (np.ndarray): The input image frame containing a face. + top_k (int, optional): The number of top results to return. Defaults to 5. + + Returns: + List[Tuple[str, float]]: List of tuples containing the user ID and distance. + """ + emb = self.get_face_embeddings(frame) + return self.db.query(emb, top_k) + + def distance(self, face_a: np.ndarray, face_b: np.ndarray, metric: str = "cosine") -> float: + """Calculate the distance between embeddings of two faces. + + Args: + face_a (np.ndarray): The first face embedding. + face_b (np.ndarray): The second face embedding. + metric (str, optional): The distance metric to use. Defaults to "cosine". + + Returns: + float: The calculated distance. + """ + emb: np.ndarray = self.get_face_embeddings(face_a) + emb2: np.ndarray = self.get_face_embeddings(face_b) + return self.db.distance(emb, emb2, metric) + + +class VoiceEmbeddingsStore: + """A store for voice embeddings interfacing with the embeddings database""" + + def __init__(self, db: EmbeddingsDB): + """Initialize the voice embeddings store. + + Args: + db (EmbeddingsDB): The embeddings database instance. + """ + self.db = db + + @staticmethod + def audiochunk2array(audio_bytes: bytes) -> np.ndarray: + """Convert audio buffer to a normalized float32 NumPy array. + + Args: + audio_bytes (bytes): The audio data buffer. + + Returns: + np.ndarray: The normalized float32 audio array. + """ + audio_as_np_int16 = np.frombuffer(audio_bytes, dtype=np.int16) + audio_as_np_float32 = audio_as_np_int16.astype(np.float32) + # Normalise float32 array so that values are between -1.0 and +1.0 + max_int16 = 2 ** 15 + data = audio_as_np_float32 / max_int16 + return data + + @abc.abstractmethod + def get_voice_embeddings(self, audio_data: np.ndarray) -> np.ndarray: + """Convert audio data to its corresponding voice embeddings. + + Args: + audio_data (np.ndarray): The input audio data. + + Returns: + np.ndarray: The resulting voice embeddings. + """ + return NotImplemented + + def add_voice(self, user_id: str, audio_data: np.ndarray): + """Add a voice and its embeddings to the database. + + Args: + user_id (str): The unique user ID. + audio_data (np.ndarray): The input audio data. + + Returns: + np.ndarray: The stored voice embeddings. + """ + emb: np.ndarray = self.get_voice_embeddings(audio_data) + return self.db.add_embeddings(user_id, emb) + + def delete_voice(self, user_id: str): + """Delete a voice and its embeddings from the database. + + Args: + user_id (str): The unique user ID. + + Returns: + np.ndarray: The deleted voice embeddings. + """ + return self.db.delete_embeddings(user_id) + + def predict(self, audio_data: np.ndarray, top_k: int = 3, thresh: float = 0.75) -> Optional[str]: + """Return the top predicted voice closest to the given audio_data. + + Args: + audio_data (np.ndarray): The input audio data. + top_k (int, optional): The number of top results to return. Defaults to 3. + thresh (float, optional): The threshold for prediction. Defaults to 0.75. + + Returns: + Optional[str]: The predicted user ID or None if the best match exceeds the threshold. + """ + matches = self.query(audio_data, top_k) + best = min(matches, key=lambda k: k[1]) + if best[1] > thresh: + return None + return best[0] + + def query(self, audio_data: np.ndarray, top_k: int = 5) -> List[Tuple[str, float]]: + """Query the database for the top_k closest voice embeddings to the audio_data. + + Args: + audio_data (np.ndarray): The input audio data. + top_k (int, optional): The number of top results to return. Defaults to 5. + + Returns: + List[Tuple[str, float]]: List of tuples containing the user ID and distance. + """ + emb = self.get_voice_embeddings(audio_data) + return self.db.query(emb, top_k) + + def distance(self, voice_a: np.ndarray, voice_b: np.ndarray, metric: str = "cosine") -> float: + """Calculate the distance between embeddings of two voices. + + Args: + voice_a (np.ndarray): The first voice embedding. + voice_b (np.ndarray): The second voice embedding. + metric (str, optional): The distance metric to use. Defaults to "cosine". + + Returns: + float: The calculated distance. + """ + emb = self.get_voice_embeddings(voice_a) + emb2 = self.get_voice_embeddings(voice_b) + return self.db.distance(emb, emb2, metric) diff --git a/ovos_plugin_manager/utils/__init__.py b/ovos_plugin_manager/utils/__init__.py index 203fd3ec..3ef6f5f3 100644 --- a/ovos_plugin_manager/utils/__init__.py +++ b/ovos_plugin_manager/utils/__init__.py @@ -22,6 +22,10 @@ class PluginTypes(str, Enum): + EMBEDDINGS = "opm.embeddings" + FACE_EMBEDDINGS = "opm.embeddings.face" + VOICE_EMBEDDINGS = "opm.embeddings.voice" + TEXT_EMBEDDINGS = "opm.embeddings.text" GUI = "ovos.plugin.gui" PHAL = "ovos.plugin.phal" ADMIN = "ovos.plugin.phal.admin" @@ -59,6 +63,10 @@ class PluginTypes(str, Enum): class PluginConfigTypes(str, Enum): + EMBEDDINGS = "opm.embeddings.config" + FACE_EMBEDDINGS = "opm.embeddings.face.config" + VOICE_EMBEDDINGS = "opm.embeddings.voice.config" + TEXT_EMBEDDINGS = "opm.embeddings.text.config" GUI = "ovos.plugin.gui.config" PHAL = "ovos.plugin.phal.config" ADMIN = "ovos.plugin.phal.admin.config" From 9f550adfc80def04fce6fcdea7ac937e1f53ef78 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Jul 2024 14:33:56 +0000 Subject: [PATCH 089/129] Increment Version to 0.0.26a30 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index fecabf19..14797b3e 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 29 +VERSION_ALPHA = 30 # END_VERSION_BLOCK From 1dcb5bc783936687f4fd2cab5aa01b835203f7eb Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Jul 2024 14:34:24 +0000 Subject: [PATCH 090/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e917dee..bd700f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -## [0.0.26a29](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a29) (2024-07-05) +## [0.0.26a30](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a30) (2024-07-20) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a29) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a30) **Implemented enhancements:** +- feat/embeddings plugins [\#240](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/240) ([JarbasAl](https://github.com/JarbasAl)) - feat/alternative\_transcripts [\#236](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/236) ([JarbasAl](https://github.com/JarbasAl)) - feat/lang\_detection\_plugin [\#220](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/220) ([JarbasAl](https://github.com/JarbasAl)) - feat/restore phonetic spellings [\#195](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/195) ([JarbasAl](https://github.com/JarbasAl)) From 42f2ae60b216e6ba6385dcf3fa08d31b769ad0af Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Jul 2024 16:17:42 +0000 Subject: [PATCH 091/129] Increment Version to 0.0.26a31 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 14797b3e..1be35365 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 30 +VERSION_ALPHA = 31 # END_VERSION_BLOCK From 85469df4ee519bb41db80aea768ce9a2b02fae39 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Jul 2024 16:18:08 +0000 Subject: [PATCH 092/129] Update Changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd700f98..9ff41d2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a30](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a30) (2024-07-20) +## [0.0.26a31](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a31) (2024-07-20) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a30) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a31) **Implemented enhancements:** From 2dbd3168ca6bff9b44eb6ac29ad8dd28dfcfa756 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 20 Jul 2024 17:39:11 +0100 Subject: [PATCH 093/129] feat/pipeline_plugin_placeholder (#241) * feat/pipeline_plugin_placeholder in preparation for the future pipeline plugins, just reserving the namespace * fix typing --- ovos_plugin_manager/pipeline.py | 21 +++++++++++++++++++++ ovos_plugin_manager/templates/pipeline.py | 4 ++++ ovos_plugin_manager/utils/__init__.py | 2 ++ 3 files changed, 27 insertions(+) create mode 100644 ovos_plugin_manager/pipeline.py create mode 100644 ovos_plugin_manager/templates/pipeline.py diff --git a/ovos_plugin_manager/pipeline.py b/ovos_plugin_manager/pipeline.py new file mode 100644 index 00000000..56bcc01c --- /dev/null +++ b/ovos_plugin_manager/pipeline.py @@ -0,0 +1,21 @@ +from ovos_plugin_manager.templates.pipeline import PipelinePlugin +from ovos_plugin_manager.utils import PluginTypes + + +def find_pipeline_plugins() -> dict: + """ + Find all installed plugins + @return: dict plugin names to entrypoints + """ + from ovos_plugin_manager.utils import find_plugins + return find_plugins(PluginTypes.PIPELINE) + + +def load_pipeline_plugin(module_name: str) -> type(PipelinePlugin): + """ + Get an uninstantiated class for the requested module_name + @param module_name: Plugin entrypoint name to load + @return: Uninstantiated class + """ + from ovos_plugin_manager.utils import load_plugin + return load_plugin(module_name, PluginTypes.PIPELINE) diff --git a/ovos_plugin_manager/templates/pipeline.py b/ovos_plugin_manager/templates/pipeline.py new file mode 100644 index 00000000..244cee87 --- /dev/null +++ b/ovos_plugin_manager/templates/pipeline.py @@ -0,0 +1,4 @@ + + +class PipelinePlugin: + """This class is a placeholder, this API will be defined in ovos-core release 0.1.0""" diff --git a/ovos_plugin_manager/utils/__init__.py b/ovos_plugin_manager/utils/__init__.py index 3ef6f5f3..c1c4ee21 100644 --- a/ovos_plugin_manager/utils/__init__.py +++ b/ovos_plugin_manager/utils/__init__.py @@ -22,6 +22,7 @@ class PluginTypes(str, Enum): + PIPELINE = "opm.pipeline" EMBEDDINGS = "opm.embeddings" FACE_EMBEDDINGS = "opm.embeddings.face" VOICE_EMBEDDINGS = "opm.embeddings.voice" @@ -63,6 +64,7 @@ class PluginTypes(str, Enum): class PluginConfigTypes(str, Enum): + PIPELINE = "opm.pipeline.config" EMBEDDINGS = "opm.embeddings.config" FACE_EMBEDDINGS = "opm.embeddings.face.config" VOICE_EMBEDDINGS = "opm.embeddings.voice.config" From 98585347ffc04b6706e6ddbb06cb1411d9e18e0e Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Jul 2024 16:39:25 +0000 Subject: [PATCH 094/129] Increment Version to 0.0.26a32 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 1be35365..d9e7e243 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 31 +VERSION_ALPHA = 32 # END_VERSION_BLOCK From f70a952f611e18d93ebbb525bdc0d30762ac0953 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Jul 2024 16:39:54 +0000 Subject: [PATCH 095/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ff41d2f..11d84370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -## [0.0.26a31](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a31) (2024-07-20) +## [0.0.26a32](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a32) (2024-07-20) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a31) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a32) **Implemented enhancements:** +- feat/pipeline\_plugin\_placeholder [\#241](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/241) ([JarbasAl](https://github.com/JarbasAl)) - feat/embeddings plugins [\#240](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/240) ([JarbasAl](https://github.com/JarbasAl)) - feat/alternative\_transcripts [\#236](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/236) ([JarbasAl](https://github.com/JarbasAl)) - feat/lang\_detection\_plugin [\#220](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/220) ([JarbasAl](https://github.com/JarbasAl)) From 1480ca6a2bdfabb118e810fb643f59a5e6b2d643 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 20 Jul 2024 18:18:10 +0100 Subject: [PATCH 096/129] feat/pipeline_intent_match (#242) move the expected IntentMatch class to OPM allows proper typing --- ovos_plugin_manager/templates/pipeline.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ovos_plugin_manager/templates/pipeline.py b/ovos_plugin_manager/templates/pipeline.py index 244cee87..ce85dda7 100644 --- a/ovos_plugin_manager/templates/pipeline.py +++ b/ovos_plugin_manager/templates/pipeline.py @@ -1,4 +1,18 @@ +from collections import namedtuple +from typing import Optional, Dict + +# Intent match response tuple, ovos-core expects PipelinePlugin to return this data structure +# intent_service: Name of the service that matched the intent +# intent_type: intent name (used to call intent handler over the message bus) +# intent_data: data provided by the intent match +# skill_id: the skill this handler belongs to +IntentMatch = namedtuple('IntentMatch', + ['intent_service', 'intent_type', + 'intent_data', 'skill_id', 'utterance'] + ) class PipelinePlugin: """This class is a placeholder, this API will be defined in ovos-core release 0.1.0""" + def __init__(self, config: Optional[Dict] = None): + self.config = config or {} From 91c4e36c8f00d2d76e3e1016cae2b96b7f91de79 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Jul 2024 17:18:25 +0000 Subject: [PATCH 097/129] Increment Version to 0.0.26a33 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index d9e7e243..c824963b 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 32 +VERSION_ALPHA = 33 # END_VERSION_BLOCK From 252f71de9f539e0b672a896e649087dc9c954a4e Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sat, 20 Jul 2024 17:18:52 +0000 Subject: [PATCH 098/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d84370..c22e54af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -## [0.0.26a32](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a32) (2024-07-20) +## [0.0.26a33](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a33) (2024-07-20) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a32) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a33) **Implemented enhancements:** +- feat/pipeline\_intent\_match [\#242](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/242) ([JarbasAl](https://github.com/JarbasAl)) - feat/pipeline\_plugin\_placeholder [\#241](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/241) ([JarbasAl](https://github.com/JarbasAl)) - feat/embeddings plugins [\#240](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/240) ([JarbasAl](https://github.com/JarbasAl)) - feat/alternative\_transcripts [\#236](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/236) ([JarbasAl](https://github.com/JarbasAl)) From 45a6e24a541099c0eee63a5f0fe0fad176197a52 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:16:30 +0100 Subject: [PATCH 099/129] feat/add_rerank_method (#243) fix entailment solver (copy paste error/incomplete implementation) remove hack around libretranslate plugin bugs (does not belong in OPM) shorten license headers to reduce confusion around OPM package license --- ovos_plugin_manager/templates/solvers.py | 61 ++++++++---------------- 1 file changed, 19 insertions(+), 42 deletions(-) diff --git a/ovos_plugin_manager/templates/solvers.py b/ovos_plugin_manager/templates/solvers.py index f08dd084..92522b9d 100644 --- a/ovos_plugin_manager/templates/solvers.py +++ b/ovos_plugin_manager/templates/solvers.py @@ -1,34 +1,8 @@ -# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework -# All trademark and other rights reserved by their respective owners -# Copyright 2008-2022 Neongecko.com Inc. -# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, -# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo -# BSD-3 License -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# Solver service can be found at: https://github.com/Neongeckocom/neon_solvers +# Original AbstractSolver class taken from: https://github.com/Neongeckocom/neon_solvers, licensed under BSD-3 +# QuestionSolver Improvements and other solver classes are OVOS originals licensed under Apache 2.0 + import abc -from typing import Optional, List, Iterable +from typing import Optional, List, Iterable, Tuple from json_database import JsonStorageXDG from ovos_plugin_manager.language import OVOSLangTranslationFactory @@ -90,12 +64,6 @@ def _tx_query(self, query: str, context["lang"] = lang - # HACK - cleanup some common translation mess ups - # this is properly solving by using a good translate plugin - # only common mistakes in default libretranslate plugin are handled - if lang.startswith("en"): - query = query.replace("who is is ", "who is ") - return query, context, lang def shutdown(self): @@ -344,14 +312,24 @@ class MultipleChoiceSolver(AbstractSolver): # plugin methods to override - @abc.abstractmethod + # TODO - make abstract in the future, + # just giving some time buffer to update existing + # plugins in the wild missing this method + #@abc.abstractmethod + def rerank(self, query: str, options: List[str], + context: Optional[dict] = None) -> List[Tuple[float, str]]: + """ + rank options list, returning a list of tuples (score, text) + """ + raise NotImplementedError + def select_answer(self, query: str, options: List[str], context: Optional[dict] = None) -> str: """ query and options assured to be in self.default_lang return best answer from options list """ - raise NotImplementedError + return self.rerank(query, options, context)[0][1] # user facing methods def solve(self, query: str, options: List[str], @@ -394,8 +372,7 @@ def entails(self, premise: str, hypothesis: str, cache and auto translate premise and hypothesis if needed return Bool, True if premise entails the hypothesis False otherwise """ - user_lang = self._get_user_lang(context, lang) - query, context, lang = self._tx_query(query, context, lang) - - # summarize + premise, context, lang = self._tx_query(premise, context, lang) + hypothesis, context, lang = self._tx_query(hypothesis, context, lang) + # check for entailment return self.check_entailment(premise, hypothesis) From 8a40148d20c6cf51588c6de8ac0cfeaa07f409b7 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 21 Jul 2024 19:16:41 +0000 Subject: [PATCH 100/129] Increment Version to 0.0.26a34 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index c824963b..1a17113d 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 33 +VERSION_ALPHA = 34 # END_VERSION_BLOCK From d3bbb47424c27914cf22d666879389f312ec1ec3 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 21 Jul 2024 19:17:06 +0000 Subject: [PATCH 101/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c22e54af..0543d02b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -## [0.0.26a33](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a33) (2024-07-20) +## [0.0.26a34](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a34) (2024-07-21) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a33) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a34) **Implemented enhancements:** +- feat/add\_rerank\_method [\#243](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/243) ([JarbasAl](https://github.com/JarbasAl)) - feat/pipeline\_intent\_match [\#242](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/242) ([JarbasAl](https://github.com/JarbasAl)) - feat/pipeline\_plugin\_placeholder [\#241](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/241) ([JarbasAl](https://github.com/JarbasAl)) - feat/embeddings plugins [\#240](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/240) ([JarbasAl](https://github.com/JarbasAl)) From 16a6f7ba63c660bf944e30540cee473726ccb71e Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Thu, 25 Jul 2024 01:00:57 +0100 Subject: [PATCH 102/129] feat/embeddings_metadata_support (#245) * feat/embeddings_metadata_support typing cleanup * docstrs --- ovos_plugin_manager/templates/embeddings.py | 140 +++++++++++--------- 1 file changed, 77 insertions(+), 63 deletions(-) diff --git a/ovos_plugin_manager/templates/embeddings.py b/ovos_plugin_manager/templates/embeddings.py index d941f081..4014f32b 100644 --- a/ovos_plugin_manager/templates/embeddings.py +++ b/ovos_plugin_manager/templates/embeddings.py @@ -1,27 +1,35 @@ import abc -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Dict, Union, Iterable -import numpy as np +# Typing helpers for readability +try: + import numpy as np + EmbeddingsArray = np.ndarray +except ImportError: + EmbeddingsArray = Iterable[Union[int, float]] +EmbeddingsTuple = Union[Tuple[str, float], Tuple[str, float, Dict]] class EmbeddingsDB: - """Base plugin for embeddings database""" + """Base class for an embeddings database that supports storage, retrieval, and querying of embeddings.""" @abc.abstractmethod - def add_embeddings(self, key: str, embedding: np.ndarray) -> np.ndarray: + def add_embeddings(self, key: str, embedding: EmbeddingsArray, + metadata: Optional[Dict[str, any]] = None) -> EmbeddingsArray: """Store 'embedding' under 'key' with associated metadata. Args: key (str): The unique key for the embedding. embedding (np.ndarray): The embedding vector to store. + metadata (Optional[Dict[str, any]]): Optional metadata associated with the embedding. Returns: np.ndarray: The stored embedding. """ - return NotImplemented + raise NotImplementedError @abc.abstractmethod - def get_embeddings(self, key: str) -> np.ndarray: + def get_embeddings(self, key: str) -> EmbeddingsArray: """Retrieve embeddings stored under 'key'. Args: @@ -30,10 +38,10 @@ def get_embeddings(self, key: str) -> np.ndarray: Returns: np.ndarray: The retrieved embedding. """ - return NotImplemented + raise NotImplementedError @abc.abstractmethod - def delete_embeddings(self, key: str) -> np.ndarray: + def delete_embeddings(self, key: str) -> EmbeddingsArray: """Delete embeddings stored under 'key'. Args: @@ -42,27 +50,29 @@ def delete_embeddings(self, key: str) -> np.ndarray: Returns: np.ndarray: The deleted embedding. """ - return NotImplemented + raise NotImplementedError @abc.abstractmethod - def query(self, embeddings: np.ndarray, top_k: int = 5) -> List[Tuple[str, float]]: - """Return top_k embeddings closest to the given 'embeddings'. + def query(self, embeddings: EmbeddingsArray, top_k: int = 5, + return_metadata: bool = False) -> List[EmbeddingsTuple]: + """Return the top_k embeddings closest to the given 'embeddings'. Args: embeddings (np.ndarray): The embedding vector to query. top_k (int, optional): The number of top results to return. Defaults to 5. + return_metadata (bool, optional): Whether to include metadata in the results. Defaults to False. Returns: - List[Tuple[str, float]]: List of tuples containing the key and distance. + List[EmbeddingsTuple]: List of tuples containing the key and distance, and optionally metadata. """ - return NotImplemented + raise NotImplementedError - def distance(self, embeddings_a: np.ndarray, embeddings_b: np.ndarray, metric: str = "cosine", + def distance(self, embeddings_a: EmbeddingsArray, embeddings_b: EmbeddingsArray, metric: str = "cosine", alpha: float = 0.5, # for alpha_divergence and tversky metrics beta: float = 0.5, # for tversky metric p: float = 3, # for minkowski and weighted_minkowski metrics - euclidean_weights: Optional[np.ndarray] = None, # required for weighted_euclidean and weighted_minkowski metrics - covariance_matrix: Optional[np.ndarray] = None # required for mahalanobis distance with user-defined covariance + euclidean_weights: Optional[EmbeddingsArray] = None, # required for weighted_euclidean and weighted_minkowski metrics + covariance_matrix: Optional[EmbeddingsArray] = None # required for mahalanobis distance with user-defined covariance ) -> float: """ Calculate the distance between two embeddings vectors using the specified distance metric. @@ -306,7 +316,7 @@ def distance(self, embeddings_a: np.ndarray, embeddings_b: np.ndarray, metric: s class TextEmbeddingsStore: - """A store for text embeddings interfacing with the embeddings database""" + """A store for text embeddings interfacing with the embeddings database.""" def __init__(self, db: EmbeddingsDB): """Initialize the text embeddings store. @@ -317,7 +327,7 @@ def __init__(self, db: EmbeddingsDB): self.db = db @abc.abstractmethod - def get_text_embeddings(self, text: str) -> np.ndarray: + def get_text_embeddings(self, text: str) -> EmbeddingsArray: """Convert text to its corresponding embeddings. Args: @@ -326,16 +336,17 @@ def get_text_embeddings(self, text: str) -> np.ndarray: Returns: np.ndarray: The resulting embeddings. """ - return NotImplemented + raise NotImplementedError - def add_document(self, document: str) -> None: + def add_document(self, document: str, metadata: Optional[Dict[str, any]] = None) -> None: """Add a document and its embeddings to the database. Args: document (str): The document to add. + metadata (Optional[Dict[str, any]]): Optional metadata associated with the document. """ embeddings = self.get_text_embeddings(document) - self.db.add_embeddings(document, embeddings) + self.db.add_embeddings(document, embeddings, metadata) def delete_document(self, document: str) -> None: """Delete a document and its embeddings from the database. @@ -369,13 +380,13 @@ def distance(self, text_a: str, text_b: str, metric: str = "cosine") -> float: Returns: float: The calculated distance. """ - emb: np.ndarray = self.get_text_embeddings(text_a) - emb2: np.ndarray = self.get_text_embeddings(text_b) - return self.db.distance(emb, emb2, metric) + emb_a = self.get_text_embeddings(text_a) + emb_b = self.get_text_embeddings(text_b) + return self.db.distance(emb_a, emb_b, metric) class FaceEmbeddingsStore: - """A store for face embeddings interfacing with the embeddings database""" + """A store for face embeddings interfacing with the embeddings database.""" def __init__(self, db: EmbeddingsDB): """Initialize the face embeddings store. @@ -386,7 +397,7 @@ def __init__(self, db: EmbeddingsDB): self.db = db @abc.abstractmethod - def get_face_embeddings(self, frame: np.ndarray) -> np.ndarray: + def get_face_embeddings(self, frame: EmbeddingsArray) -> EmbeddingsArray: """Convert an image frame to its corresponding face embeddings. Args: @@ -395,22 +406,23 @@ def get_face_embeddings(self, frame: np.ndarray) -> np.ndarray: Returns: np.ndarray: The resulting face embeddings. """ - return NotImplemented + raise NotImplementedError - def add_face(self, user_id: str, frame: np.ndarray): + def add_face(self, user_id: str, frame: EmbeddingsArray, metadata: Optional[Dict[str, any]] = None) -> EmbeddingsArray: """Add a face and its embeddings to the database. Args: user_id (str): The unique user ID. frame (np.ndarray): The image frame containing the face. + metadata (Optional[Dict[str, any]]): Optional metadata associated with the face. Returns: np.ndarray: The stored face embeddings. """ - emb: np.ndarray = self.get_face_embeddings(frame) - return self.db.add_embeddings(user_id, emb) + embeddings = self.get_face_embeddings(frame) + return self.db.add_embeddings(user_id, embeddings, metadata) - def delete_face(self, user_id: str): + def delete_face(self, user_id: str) -> EmbeddingsArray: """Delete a face and its embeddings from the database. Args: @@ -421,7 +433,7 @@ def delete_face(self, user_id: str): """ return self.db.delete_embeddings(user_id) - def predict(self, frame: np.ndarray, top_k: int = 3, thresh: float = 0.15) -> Optional[str]: + def predict(self, frame: EmbeddingsArray, top_k: int = 3, thresh: float = 0.15) -> Optional[str]: """Return the top predicted face closest to the given frame. Args: @@ -435,12 +447,12 @@ def predict(self, frame: np.ndarray, top_k: int = 3, thresh: float = 0.15) -> Op matches = self.query(frame, top_k) if not matches: return None - best = min(matches, key=lambda k: k[1]) - if best[1] > thresh: + best_match = min(matches, key=lambda k: k[1]) + if best_match[1] > thresh: return None - return best[0] + return best_match[0] - def query(self, frame: np.ndarray, top_k: int = 5) -> List[Tuple[str, float]]: + def query(self, frame: EmbeddingsArray, top_k: int = 5) -> List[Tuple[str, float]]: """Query the database for the top_k closest face embeddings to the frame. Args: @@ -450,10 +462,10 @@ def query(self, frame: np.ndarray, top_k: int = 5) -> List[Tuple[str, float]]: Returns: List[Tuple[str, float]]: List of tuples containing the user ID and distance. """ - emb = self.get_face_embeddings(frame) - return self.db.query(emb, top_k) + embeddings = self.get_face_embeddings(frame) + return self.db.query(embeddings, top_k) - def distance(self, face_a: np.ndarray, face_b: np.ndarray, metric: str = "cosine") -> float: + def distance(self, face_a: EmbeddingsArray, face_b: EmbeddingsArray, metric: str = "cosine") -> float: """Calculate the distance between embeddings of two faces. Args: @@ -464,13 +476,13 @@ def distance(self, face_a: np.ndarray, face_b: np.ndarray, metric: str = "cosine Returns: float: The calculated distance. """ - emb: np.ndarray = self.get_face_embeddings(face_a) - emb2: np.ndarray = self.get_face_embeddings(face_b) - return self.db.distance(emb, emb2, metric) + emb_a = self.get_face_embeddings(face_a) + emb_b = self.get_face_embeddings(face_b) + return self.db.distance(emb_a, emb_b, metric) class VoiceEmbeddingsStore: - """A store for voice embeddings interfacing with the embeddings database""" + """A store for voice embeddings interfacing with the embeddings database.""" def __init__(self, db: EmbeddingsDB): """Initialize the voice embeddings store. @@ -481,7 +493,7 @@ def __init__(self, db: EmbeddingsDB): self.db = db @staticmethod - def audiochunk2array(audio_bytes: bytes) -> np.ndarray: + def audiochunk2array(audio_bytes: bytes) -> EmbeddingsArray: """Convert audio buffer to a normalized float32 NumPy array. Args: @@ -494,11 +506,10 @@ def audiochunk2array(audio_bytes: bytes) -> np.ndarray: audio_as_np_float32 = audio_as_np_int16.astype(np.float32) # Normalise float32 array so that values are between -1.0 and +1.0 max_int16 = 2 ** 15 - data = audio_as_np_float32 / max_int16 - return data + return audio_as_np_float32 / max_int16 @abc.abstractmethod - def get_voice_embeddings(self, audio_data: np.ndarray) -> np.ndarray: + def get_voice_embeddings(self, audio_data: EmbeddingsArray) -> EmbeddingsArray: """Convert audio data to its corresponding voice embeddings. Args: @@ -507,22 +518,23 @@ def get_voice_embeddings(self, audio_data: np.ndarray) -> np.ndarray: Returns: np.ndarray: The resulting voice embeddings. """ - return NotImplemented + raise NotImplementedError - def add_voice(self, user_id: str, audio_data: np.ndarray): + def add_voice(self, user_id: str, audio_data: EmbeddingsArray, metadata: Optional[Dict[str, any]] = None) -> EmbeddingsArray: """Add a voice and its embeddings to the database. Args: user_id (str): The unique user ID. audio_data (np.ndarray): The input audio data. + metadata (Optional[Dict[str, any]]): Optional metadata associated with the voice. Returns: np.ndarray: The stored voice embeddings. """ - emb: np.ndarray = self.get_voice_embeddings(audio_data) - return self.db.add_embeddings(user_id, emb) + embeddings = self.get_voice_embeddings(audio_data) + return self.db.add_embeddings(user_id, embeddings, metadata) - def delete_voice(self, user_id: str): + def delete_voice(self, user_id: str) -> EmbeddingsArray: """Delete a voice and its embeddings from the database. Args: @@ -533,7 +545,7 @@ def delete_voice(self, user_id: str): """ return self.db.delete_embeddings(user_id) - def predict(self, audio_data: np.ndarray, top_k: int = 3, thresh: float = 0.75) -> Optional[str]: + def predict(self, audio_data: EmbeddingsArray, top_k: int = 3, thresh: float = 0.75) -> Optional[str]: """Return the top predicted voice closest to the given audio_data. Args: @@ -545,12 +557,14 @@ def predict(self, audio_data: np.ndarray, top_k: int = 3, thresh: float = 0.75) Optional[str]: The predicted user ID or None if the best match exceeds the threshold. """ matches = self.query(audio_data, top_k) - best = min(matches, key=lambda k: k[1]) - if best[1] > thresh: + if not matches: return None - return best[0] + best_match = min(matches, key=lambda k: k[1]) + if best_match[1] > thresh: + return None + return best_match[0] - def query(self, audio_data: np.ndarray, top_k: int = 5) -> List[Tuple[str, float]]: + def query(self, audio_data: EmbeddingsArray, top_k: int = 5) -> List[Tuple[str, float]]: """Query the database for the top_k closest voice embeddings to the audio_data. Args: @@ -560,10 +574,10 @@ def query(self, audio_data: np.ndarray, top_k: int = 5) -> List[Tuple[str, float Returns: List[Tuple[str, float]]: List of tuples containing the user ID and distance. """ - emb = self.get_voice_embeddings(audio_data) - return self.db.query(emb, top_k) + embeddings = self.get_voice_embeddings(audio_data) + return self.db.query(embeddings, top_k) - def distance(self, voice_a: np.ndarray, voice_b: np.ndarray, metric: str = "cosine") -> float: + def distance(self, voice_a: EmbeddingsArray, voice_b: EmbeddingsArray, metric: str = "cosine") -> float: """Calculate the distance between embeddings of two voices. Args: @@ -574,6 +588,6 @@ def distance(self, voice_a: np.ndarray, voice_b: np.ndarray, metric: str = "cosi Returns: float: The calculated distance. """ - emb = self.get_voice_embeddings(voice_a) - emb2 = self.get_voice_embeddings(voice_b) - return self.db.distance(emb, emb2, metric) + emb_a = self.get_voice_embeddings(voice_a) + emb_b = self.get_voice_embeddings(voice_b) + return self.db.distance(emb_a, emb_b, metric) From a82ffaca273dd0bc5acd979e80bfe1d32040285c Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 25 Jul 2024 00:01:10 +0000 Subject: [PATCH 103/129] Increment Version to 0.0.26a35 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 1a17113d..d09bfcba 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 34 +VERSION_ALPHA = 35 # END_VERSION_BLOCK From 9fa22003c6d0a2ae0a9ddc306742be8054e34a05 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 25 Jul 2024 00:01:38 +0000 Subject: [PATCH 104/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0543d02b..fe4b9deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -## [0.0.26a34](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a34) (2024-07-21) +## [0.0.26a35](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a35) (2024-07-25) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a34) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a35) **Implemented enhancements:** +- feat/embeddings\_metadata\_support [\#245](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/245) ([JarbasAl](https://github.com/JarbasAl)) - feat/add\_rerank\_method [\#243](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/243) ([JarbasAl](https://github.com/JarbasAl)) - feat/pipeline\_intent\_match [\#242](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/242) ([JarbasAl](https://github.com/JarbasAl)) - feat/pipeline\_plugin\_placeholder [\#241](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/241) ([JarbasAl](https://github.com/JarbasAl)) From c682e4b682c8a6915ff36279e979a429ec67527f Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Thu, 25 Jul 2024 01:39:29 +0100 Subject: [PATCH 105/129] feat/metadata (#246) allow to request metadata in queries --- ovos_plugin_manager/templates/embeddings.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ovos_plugin_manager/templates/embeddings.py b/ovos_plugin_manager/templates/embeddings.py index 4014f32b..943772b9 100644 --- a/ovos_plugin_manager/templates/embeddings.py +++ b/ovos_plugin_manager/templates/embeddings.py @@ -356,18 +356,21 @@ def delete_document(self, document: str) -> None: """ self.db.delete_embeddings(document) - def query(self, document: str, top_k: int = 5) -> List[Tuple[str, float]]: + def query(self, document: str, top_k: int = 5, + return_metadata: bool = False) -> List[Tuple[str, float]]: """Query the database for the top_k closest embeddings to the document. Args: document (str): The document to query. top_k (int, optional): The number of top results to return. Defaults to 5. + return_metadata (bool, optional): Whether to include metadata in the results. Defaults to False. Returns: List[Tuple[str, float]]: List of tuples containing the document and distance. """ embeddings = self.get_text_embeddings(document) - return self.db.query(embeddings, top_k) + return self.db.query(embeddings, top_k, + return_metadata=return_metadata) def distance(self, text_a: str, text_b: str, metric: str = "cosine") -> float: """Calculate the distance between embeddings of two texts. @@ -452,18 +455,21 @@ def predict(self, frame: EmbeddingsArray, top_k: int = 3, thresh: float = 0.15) return None return best_match[0] - def query(self, frame: EmbeddingsArray, top_k: int = 5) -> List[Tuple[str, float]]: + def query(self, frame: EmbeddingsArray, top_k: int = 5, + return_metadata: bool = False) -> List[Tuple[str, float]]: """Query the database for the top_k closest face embeddings to the frame. Args: frame (np.ndarray): The input image frame containing a face. top_k (int, optional): The number of top results to return. Defaults to 5. + return_metadata (bool, optional): Whether to include metadata in the results. Defaults to False. Returns: List[Tuple[str, float]]: List of tuples containing the user ID and distance. """ embeddings = self.get_face_embeddings(frame) - return self.db.query(embeddings, top_k) + return self.db.query(embeddings, top_k, + return_metadata=return_metadata) def distance(self, face_a: EmbeddingsArray, face_b: EmbeddingsArray, metric: str = "cosine") -> float: """Calculate the distance between embeddings of two faces. @@ -564,18 +570,21 @@ def predict(self, audio_data: EmbeddingsArray, top_k: int = 3, thresh: float = 0 return None return best_match[0] - def query(self, audio_data: EmbeddingsArray, top_k: int = 5) -> List[Tuple[str, float]]: + def query(self, audio_data: EmbeddingsArray, top_k: int = 5, + return_metadata: bool = False) -> List[Tuple[str, float]]: """Query the database for the top_k closest voice embeddings to the audio_data. Args: audio_data (np.ndarray): The input audio data. top_k (int, optional): The number of top results to return. Defaults to 5. + return_metadata (bool, optional): Whether to include metadata in the results. Defaults to False. Returns: List[Tuple[str, float]]: List of tuples containing the user ID and distance. """ embeddings = self.get_voice_embeddings(audio_data) - return self.db.query(embeddings, top_k) + return self.db.query(embeddings, top_k, + return_metadata=return_metadata) def distance(self, voice_a: EmbeddingsArray, voice_b: EmbeddingsArray, metric: str = "cosine") -> float: """Calculate the distance between embeddings of two voices. From 19353c392399089eb4fd41a56b739a602757be0e Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 25 Jul 2024 00:39:44 +0000 Subject: [PATCH 106/129] Increment Version to 0.0.26a36 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index d09bfcba..51ff75c9 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 35 +VERSION_ALPHA = 36 # END_VERSION_BLOCK From 1b361514dfb4a6def32a583338ed54b3e4c3bb79 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 25 Jul 2024 00:40:14 +0000 Subject: [PATCH 107/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe4b9deb..a574b86e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -## [0.0.26a35](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a35) (2024-07-25) +## [0.0.26a36](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a36) (2024-07-25) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a35) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a36) **Implemented enhancements:** +- feat/metadata [\#246](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/246) ([JarbasAl](https://github.com/JarbasAl)) - feat/embeddings\_metadata\_support [\#245](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/245) ([JarbasAl](https://github.com/JarbasAl)) - feat/add\_rerank\_method [\#243](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/243) ([JarbasAl](https://github.com/JarbasAl)) - feat/pipeline\_intent\_match [\#242](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/242) ([JarbasAl](https://github.com/JarbasAl)) From dfaf818921b6447e2dcfa274c74e3344bde5e076 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Thu, 25 Jul 2024 02:43:53 +0100 Subject: [PATCH 108/129] refactor/solver_decorators (#244) * refactor/decorators make code more portable and easier to understand and maintain by using decorators for the autotranslation steps move class properties to init method add configurable language detector plugin and new util methods for translation and detection * improve decorators * lazy load language plugins as needed * tests * add tests --- ovos_plugin_manager/templates/language.py | 226 +++--- ovos_plugin_manager/templates/solvers.py | 809 ++++++++++++++++------ requirements/test.txt | 1 + test/unittests/test_solver.py | 83 ++- 4 files changed, 797 insertions(+), 322 deletions(-) diff --git a/ovos_plugin_manager/templates/language.py b/ovos_plugin_manager/templates/language.py index 34efdc51..743674ff 100644 --- a/ovos_plugin_manager/templates/language.py +++ b/ovos_plugin_manager/templates/language.py @@ -1,75 +1,88 @@ +import abc + from ovos_config.config import Configuration from ovos_utils import classproperty from ovos_utils.process_utils import RuntimeRequirements +from typing import Optional, Dict, Union, List, Set class LanguageDetector: - def __init__(self, config=None): + def __init__(self, config: Optional[Dict[str, Union[str, int]]] = None): + """ + Initialize the LanguageDetector with configuration settings. + + Args: + config (Optional[Dict[str, Union[str, int]]]): Configuration dictionary. + Can contain "lang" for default language, "hint_lang" for a hint language, and "boost" for language boost score. + """ self.config = config or {} - self.default_language = self.config.get("lang") or "en-us" - # hint_language: str E.g., 'it' boosts Italian - self.hint_language = self.config.get("hint_lang") or \ - self.config.get('user') or self.default_language - # boost score for this language + self.default_language = self.config.get("lang", "en-us") + self.hint_language = self.config.get("hint_lang") or self.config.get('user') or self.default_language self.boost = self.config.get("boost") @classproperty - def runtime_requirements(self): - """ skill developers should override this if they do not require connectivity - some examples: - IOT plugin that controls devices via LAN could return: - scans_on_init = True - RuntimeRequirements(internet_before_load=False, - network_before_load=scans_on_init, - requires_internet=False, - requires_network=True, - no_internet_fallback=True, - no_network_fallback=False) - online search plugin with a local cache: - has_cache = False - RuntimeRequirements(internet_before_load=not has_cache, - network_before_load=not has_cache, - requires_internet=True, - requires_network=True, - no_internet_fallback=True, - no_network_fallback=True) - a fully offline plugin: - RuntimeRequirements(internet_before_load=False, - network_before_load=False, - requires_internet=False, - requires_network=False, - no_internet_fallback=True, - no_network_fallback=True) - """ - return RuntimeRequirements(internet_before_load=False, - network_before_load=False, - requires_internet=False, - requires_network=False, - no_internet_fallback=True, - no_network_fallback=True) - - def detect(self, text): - # assume default language - return self.default_language - - def detect_probs(self, text): - return {self.detect(text): 1} - - @property - def available_languages(self) -> set: + def runtime_requirements(self) -> RuntimeRequirements: + """ + Define the runtime requirements for this language detector. + + Returns: + RuntimeRequirements: Object indicating the runtime needs, including internet and network requirements. + """ + return RuntimeRequirements( + internet_before_load=False, + network_before_load=False, + requires_internet=False, + requires_network=False, + no_internet_fallback=True, + no_network_fallback=True + ) + + @abc.abstractmethod + def detect(self, text: str) -> str: + """ + Detect the language of the given text. + + Args: + text (str): The text to detect the language of. + + Returns: + str: The detected language code (e.g., 'en-us'). + """ + + @abc.abstractmethod + def detect_probs(self, text: str) -> Dict[str, float]: + """ + Detect the language of the text and return probabilities. + + Args: + text (str): The text to detect the language of. + + Returns: + Dict[str, float]: A dictionary with the detected language as the key and its probability as the value. + """ + + @property # TODO - make abstract method in future releases (mandatory for plugins to implement) + def available_languages(self) -> Set[str]: """ Return languages supported by this detector implementation in this state. This should be a set of languages this detector is capable of recognizing. This property should be overridden by the derived class to advertise what languages that engine supports. Returns: - set: supported languages + Set[str]: A set of language codes supported by this detector. """ return set() class LanguageTranslator: - def __init__(self, config=None): + def __init__(self, config: Optional[Dict[str, str]] = None): + """ + Initialize the LanguageTranslator with configuration settings. + + Args: + config (Optional[Dict[str, str]]): Configuration dictionary. + Can contain "lang" for the default language and "internal" for the internal language. + """ self.config = config or {} # translate from, unless specified/detected otherwise self.default_language = self.config.get("lang") or "en-us" @@ -79,44 +92,48 @@ def __init__(self, config=None): self.default_language @classproperty - def runtime_requirements(self): - """ skill developers should override this if they do not require connectivity - some examples: - IOT plugin that controls devices via LAN could return: - scans_on_init = True - RuntimeRequirements(internet_before_load=False, - network_before_load=scans_on_init, - requires_internet=False, - requires_network=True, - no_internet_fallback=True, - no_network_fallback=False) - online search plugin with a local cache: - has_cache = False - RuntimeRequirements(internet_before_load=not has_cache, - network_before_load=not has_cache, - requires_internet=True, - requires_network=True, - no_internet_fallback=True, - no_network_fallback=True) - a fully offline plugin: - RuntimeRequirements(internet_before_load=False, - network_before_load=False, - requires_internet=False, - requires_network=False, - no_internet_fallback=True, - no_network_fallback=True) - """ - return RuntimeRequirements(internet_before_load=False, - network_before_load=False, - requires_internet=False, - requires_network=False, - no_internet_fallback=True, - no_network_fallback=True) - - def translate(self, text, target=None, source=None): - return text - - def translate_dict(self, data, lang_tgt, lang_src="en"): + def runtime_requirements(self) -> RuntimeRequirements: + """ + Define the runtime requirements for this language translator. + + Returns: + RuntimeRequirements: Object indicating the runtime needs, including internet and network requirements. + """ + return RuntimeRequirements( + internet_before_load=False, + network_before_load=False, + requires_internet=False, + requires_network=False, + no_internet_fallback=True, + no_network_fallback=True + ) + + @abc.abstractmethod + def translate(self, text: str, target: Optional[str] = None, source: Optional[str] = None) -> str: + """ + Translate the given text from the source language to the target language. + + Args: + text (str): The text to translate. + target (Optional[str]): The target language code. If None, the internal language is used. + source (Optional[str]): The source language code. If None, the default language is used. + + Returns: + str: The translated text. + """ + + def translate_dict(self, data: Dict[str, Union[str, Dict, List]], lang_tgt: str, lang_src: str = "en") -> Dict[str, Union[str, Dict, List]]: + """ + Translate the values in a dictionary from one language to another. + + Args: + data (Dict[str, Union[str, Dict, List]]): The dictionary containing text to translate. + lang_tgt (str): The target language code. + lang_src (str): The source language code. + + Returns: + Dict[str, Union[str, Dict, List]]: The dictionary with translated values. + """ for k, v in data.items(): if isinstance(v, dict): data[k] = self.translate_dict(v, lang_tgt, lang_src) @@ -126,7 +143,18 @@ def translate_dict(self, data, lang_tgt, lang_src="en"): data[k] = self.translate_list(v, lang_tgt, lang_src) return data - def translate_list(self, data, lang_tgt, lang_src="en"): + def translate_list(self, data: List[Union[str, Dict, List]], lang_tgt: str, lang_src: str = "en") -> List[Union[str, Dict, List]]: + """ + Translate the values in a list from one language to another. + + Args: + data (List[Union[str, Dict, List]]): The list containing text to translate. + lang_tgt (str): The target language code. + lang_src (str): The source language code. + + Returns: + List[Union[str, Dict, List]]: The list with translated values. + """ for idx, v in enumerate(data): if isinstance(v, dict): data[idx] = self.translate_dict(v, lang_tgt, lang_src) @@ -136,25 +164,27 @@ def translate_list(self, data, lang_tgt, lang_src="en"): data[idx] = self.translate_list(v, lang_tgt, lang_src) return data - @property - def available_languages(self) -> set: + @property # TODO - make abstract method in future releases (mandatory for plugins to implement) + def available_languages(self) -> Set[str]: """ Return languages supported by this translator implementation in this state. Any language in this set should be translatable to any other language in the set. This property should be overridden by the derived class to advertise what languages that engine supports. Returns: - set: supported languages + Set[str]: A set of language codes supported by this translator. """ return set() - def supported_translations(self, source_lang: str = None) -> set: + # TODO - make abstract method in future releases (mandatory for plugins to implement) + def supported_translations(self, source_lang: Optional[str] = None) -> Set[str]: """ - Return valid target languages we can translate `source_lang` to. - This method should be overridden by the derived class. + Get the set of target languages to which the source language can be translated. + Args: - source_lang: ISO 639-1 source language code + source_lang (Optional[str]): The source language code. + Returns: - set of ISO 639-1 languages the source language can be translated to + Set[str]: A set of language codes that the source language can be translated to. """ return self.available_languages diff --git a/ovos_plugin_manager/templates/solvers.py b/ovos_plugin_manager/templates/solvers.py index 92522b9d..dfa8f481 100644 --- a/ovos_plugin_manager/templates/solvers.py +++ b/ovos_plugin_manager/templates/solvers.py @@ -2,161 +2,393 @@ # QuestionSolver Improvements and other solver classes are OVOS originals licensed under Apache 2.0 import abc -from typing import Optional, List, Iterable, Tuple +import inspect +from functools import wraps, lru_cache +from typing import Optional, List, Iterable, Tuple, Dict, Union from json_database import JsonStorageXDG -from ovos_plugin_manager.language import OVOSLangTranslationFactory -from ovos_utils.log import LOG +from ovos_utils import flatten_list +from ovos_utils.log import LOG, log_deprecation from ovos_utils.xdg_utils import xdg_cache_home from quebra_frases import sentence_tokenize +from ovos_plugin_manager.language import OVOSLangTranslationFactory, OVOSLangDetectionFactory +from ovos_plugin_manager.templates.language import LanguageTranslator, LanguageDetector + + +def auto_translate(translate_keys: List[str], translate_str_args=True): + """ Decorator to ensure all kwargs in 'translate_keys' are translated to self.default_lang. + data returned by the decorated function will be translated back to original language + NOTE: not meant to be used outside solver plugins""" + + def func_decorator(func): + + @wraps(func) + def func_wrapper(*args, **kwargs): + solver: AbstractSolver = args[0] + # check if translation is enabled + if not solver.enable_tx: + return func(*args, **kwargs) + + lang = kwargs.get("lang") + # check if translation can be skipped + if any([lang is None, + lang == solver.default_lang, + lang in solver.supported_langs]): + LOG.debug(f"skipping translation, 'lang': {lang} is supported by {func}") + return func(*args, **kwargs) + + # translate string arguments + if translate_str_args: + args = list(args) + for idx, arg in enumerate(args): + if isinstance(arg, str): + LOG.debug( + f"translating string argument with index: '{idx}' from {lang} to {solver.default_lang} for func: {func}") + args[idx] = _do_tx(solver, arg, + source_lang=lang, + target_lang=solver.default_lang) + + # translate input keys + for k in translate_keys: + v = kwargs.get(k) + if not v: + continue + kwargs[k] = _do_tx(solver, v, + source_lang=lang, + target_lang=solver.default_lang) + + out = func(*args, **kwargs) + + # reverse translate + return _do_tx(solver, out, + source_lang=solver.default_lang, + target_lang=lang) + + return func_wrapper + + return func_decorator + + +def auto_detect_lang(text_keys: List[str]): + """ Decorator to auto detect language if needed + NOTE: requires "lang" argument, not meant to be used outside solver plugins""" + + def func_decorator(func): + + @wraps(func) + def func_wrapper(*args, **kwargs): + solver: AbstractSolver = args[0] + + # detect language if needed + lang = kwargs.get("lang") + if lang is None: + LOG.debug(f"'lang' missing in kwargs for func: {func}") + for k in text_keys: + v = kwargs.get(k) + if isinstance(v, str): + lang = solver.detect_language(v) + LOG.debug(f"detected 'lang': {lang} in key: '{k}' for func: {func}") + break + else: + for idx, v in enumerate(args): + if isinstance(v, str) and len(v.split(" ")) > 1: + lang = solver.detect_language(v) + LOG.debug(f"detected 'lang': {lang} in argument '{idx}' for func: {func}") + + kwargs["lang"] = lang + return func(*args, **kwargs) + + return func_wrapper + + return func_decorator + + +def _deprecate_context2lang(): + """Decorator to deprecate the 'context' kwarg and replace it with 'lang'. + NOTE: can only be used in methods that accept "lang" as argument""" + + def func_decorator(func): + + @wraps(func) + def func_wrapper(*args, **kwargs): + + # Inspect the function signature to ensure it has both 'lang' and 'context' parameters + signature = inspect.signature(func) + params = signature.parameters + + if "context" in kwargs: + # NOTE: deprecate this at same time we + # standardize plugin namespaces to opm.XXX + log_deprecation("'context' kwarg has been deprecated, " + "please pass 'lang' as it's own kwarg instead", "0.1.0") + if "lang" in kwargs["context"] and "lang" not in kwargs: + kwargs["lang"] = kwargs["context"]["lang"] + + # ensure valid kwargs + if "lang" not in params and "lang" in kwargs: + kwargs.pop("lang") + if "context" not in params and "context" in kwargs: + kwargs.pop("context") + return func(*args, **kwargs) + + return func_wrapper + + return func_decorator + class AbstractSolver: - # these are defined by the plugin developer - priority = 50 - enable_tx = False - enable_cache = False - - def __init__(self, config=None, translator=None, *args, **kwargs): - if args or kwargs: - LOG.warning("solver plugins init signature changed, please update to accept config=None, translator=None. " - "an exception will be raised in next stable release") - for arg in args: - if isinstance(arg, str): - kwargs["name"] = arg - if isinstance(arg, int): - kwargs["priority"] = arg - if "priority" in kwargs: - self.priority = kwargs["priority"] - if "enable_tx" in kwargs: - self.enable_tx = kwargs["enable_tx"] - if "enable_cache" in kwargs: - self.enable_cache = kwargs["enable_cache"] + """Base class for solvers that perform various NLP tasks.""" + + def __init__(self, config=None, + translator: Optional[LanguageTranslator] = None, + detector: Optional[LanguageDetector] = None, + priority=50, + enable_tx=False, + enable_cache=False, + internal_lang: Optional[str] = None, + *args, **kwargs): + self.priority = priority + self.enable_tx = enable_tx + self.enable_cache = enable_cache self.config = config or {} self.supported_langs = self.config.get("supported_langs") or [] - self.default_lang = self.config.get("lang", "en") + self.default_lang = internal_lang or self.config.get("lang", "en") if self.default_lang not in self.supported_langs: self.supported_langs.insert(0, self.default_lang) - self.translator = translator or OVOSLangTranslationFactory.create() + self._translator = translator or OVOSLangTranslationFactory.create() if self.enable_tx else None + self._detector = detector or OVOSLangDetectionFactory.create() if self.enable_tx else None + LOG.debug(f"{self.__class__.__name__} default language: {self.default_lang}") + + @property + def detector(self): + """ language detector, lazy init on first access""" + if not self._detector: + # if it's being used, there is no recovery, do not try: except: + self._detector = OVOSLangDetectionFactory.create() + return self._detector + + @detector.setter + def detector(self, val): + self._detector = val + + @property + def translator(self): + """ language translator, lazy init on first access""" + if not self._translator: + # if it's being used, there is no recovery, do not try: except: + self._translator = OVOSLangTranslationFactory.create() + return self._translator + + @translator.setter + def translator(self, val): + self._translator = val @staticmethod - def sentence_split(text: str, max_sentences: int=25) -> List[str]: - return sentence_tokenize(text)[:max_sentences] + def sentence_split(text: str, max_sentences: int = 25) -> List[str]: + """ + Split text into sentences. - def _get_user_lang(self, context: Optional[dict] = None, - lang: Optional[str] = None) -> str: - context = context or {} - lang = lang or context.get("lang") or self.default_lang - lang = lang.split("-")[0] - return lang + :param text: Input text. + :param max_sentences: Maximum number of sentences to return. + :return: List of sentences. + """ + try: + # sentence_tokenize occasionally has issues with \n for some reason + return flatten_list([sentence_tokenize(t) + for t in text.split("\n")])[:max_sentences] + except Exception as e: + LOG.exception(f"Error in sentence_split: {e}") + return [text] + + @lru_cache(maxsize=128) + def detect_language(self, text: str) -> str: + """ + Detect the language of the input text. + + :param text: Input text. + :return: Detected language code. + """ + return self.detector.detect(text) - def _tx_query(self, query: str, - context: Optional[dict] = None, lang: Optional[str] = None): - if not self.enable_tx: - return query, context, lang - context = context or {} - lang = user_lang = self._get_user_lang(context, lang) + @lru_cache(maxsize=128) + def translate(self, text: str, + target_lang: Optional[str] = None, + source_lang: Optional[str] = None) -> str: + """ + Translate text from source_lang to target_lang. - # translate input to default lang - if user_lang not in self.supported_langs: - lang = self.default_lang - query = self.translator.translate(query, lang, user_lang) + :param text: Input text. + :param target_lang: Target language code. + :param source_lang: Source language code. + :return: Translated text. + """ + source_lang = source_lang or self.detect_language(text) + target_lang = target_lang or self.default_lang + if source_lang.split("-")[0] == target_lang.split("-")[0]: + return text # skip translation + return self.translator.translate(text, + target=target_lang, + source=source_lang) - context["lang"] = lang + def translate_list(self, data: List[str], + target_lang: Optional[str] = None, + source_lang: Optional[str] = None) -> List[str]: + """ + Translate a list of strings from source_lang to target_lang. - return query, context, lang + :param data: List of strings. + :param target_lang: Target language code. + :param source_lang: Source language code. + :return: List of translated strings. + """ + return self.translator.translate_list(data, + lang_tgt=target_lang, + lang_src=source_lang) + + def translate_dict(self, data: Dict[str, str], + target_lang: Optional[str] = None, + source_lang: Optional[str] = None) -> Dict[str, str]: + """ + Translate a dictionary of strings from source_lang to target_lang. + + :param data: Dictionary of strings. + :param target_lang: Target language code. + :param source_lang: Source language code. + :return: Dictionary of translated strings. + """ + return self.translator.translate_dict(data, + lang_tgt=target_lang, + lang_src=source_lang) def shutdown(self): - """ module specific shutdown method """ + """Module specific shutdown method.""" pass class QuestionSolver(AbstractSolver): - """free form unscontrained spoken question solver - handling automatic translation back and forth as needed""" - - def __init__(self, config=None, translator=None, *args, **kwargs): - super().__init__(config, translator, *args, **kwargs) + """ + A solver for free-form, unconstrained spoken questions that handles automatic translation as needed. + """ + + def __init__(self, config: Optional[Dict] = None, + translator: Optional[LanguageTranslator] = None, + detector: Optional[LanguageDetector] = None, + priority: int = 50, + enable_tx: bool = False, + enable_cache: bool = False, + internal_lang: Optional[str] = None, + *args, **kwargs): + """ + Initialize the QuestionSolver. + + :param config: Optional configuration dictionary. + :param translator: Optional language translator. + :param detector: Optional language detector. + :param priority: Priority of the solver. + :param enable_tx: Flag to enable translation. + :param enable_cache: Flag to enable caching. + """ + super().__init__(config, translator, detector, priority, + enable_tx, enable_cache, internal_lang, + *args, **kwargs) name = kwargs.get("name") or self.__class__.__name__ if self.enable_cache: # cache contains raw data self.cache = JsonStorageXDG(name + "_data", xdg_folder=xdg_cache_home(), - subfolder="neon_solvers") + subfolder="ovos_solvers") # spoken cache contains dialogs self.spoken_cache = JsonStorageXDG(name, xdg_folder=xdg_cache_home(), - subfolder="neon_solvers") + subfolder="ovos_solvers") else: self.cache = self.spoken_cache = {} # plugin methods to override @abc.abstractmethod - def get_spoken_answer(self, query: str, - context: Optional[dict] = None) -> str: + def get_spoken_answer(self, query: str, lang: Optional[str] = None) -> str: """ - query assured to be in self.default_lang - return a single sentence text response + Obtain the spoken answer for a given query. + + :param query: The query text. + :param lang: Optional language code. + :return: The spoken answer as a text response. """ raise NotImplementedError - def stream_utterances(self, query: str, - context: Optional[dict] = None) -> Iterable[str]: - """streaming api, yields utterances as they become available - each utterance can be sent to TTS before we have a full answer - this is particularly helpful with LLMs""" - ans = self.get_spoken_answer(query, context) + @_deprecate_context2lang() + def stream_utterances(self, query: str, lang: Optional[str] = None) -> Iterable[str]: + """ + Stream utterances for the given query as they become available. + + :param query: The query text. + :param lang: Optional language code. + :return: An iterable of utterances. + """ + ans = _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang) for utt in self.sentence_split(ans): yield utt - def get_data(self, query: str, - context: Optional[dict] = None) -> dict: + @_deprecate_context2lang() + def get_data(self, query: str, lang: Optional[str] = None) -> Optional[dict]: """ - query assured to be in self.default_lang - return a dict response + Retrieve data for the given query. + + :param query: The query text. + :param lang: Optional language code. + :return: A dictionary containing the answer. """ - return {"answer": self.get_spoken_answer(query, context)} + return {"answer": _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang)} - def get_image(self, query: str, - context: Optional[dict] = None) -> str: + @_deprecate_context2lang() + def get_image(self, query: str, lang: Optional[str] = None) -> Optional[str]: """ - query assured to be in self.default_lang - return path/url to a single image to acompany spoken_answer + Get the path or URL to an image associated with the query. + + :param query: The query text + :param lang: Optional language code. + :return: The path or URL to a single image. """ return None - def get_expanded_answer(self, query: str, - context: Optional[dict] = None) -> List[dict]: + @_deprecate_context2lang() + def get_expanded_answer(self, query: str, lang: Optional[str] = None) -> List[dict]: """ - query assured to be in self.default_lang - return a list of ordered steps to expand the answer, eg, "tell me more" - { - "title": "optional", - "summary": "speak this", - "img": "optional/path/or/url - } - :return: + Get an expanded list of steps to elaborate on the answer. + + :param query: The query text + :param lang: Optional language code. + :return: A list of dictionaries with each step containing a title, summary, and optional image. """ return [{"title": query, - "summary": self.get_spoken_answer(query, context), - "img": self.get_image(query, context)}] + "summary": _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang), + "img": _call_with_sanitized_kwargs(self.get_image, query, lang=lang)}] # user facing methods - def search(self, query: str, - context: Optional[dict] = None, lang: Optional[str] = None) -> dict: + @_deprecate_context2lang() + @auto_detect_lang(text_keys=["query"]) + @auto_translate(translate_keys=["query"]) + def search(self, query: str, lang: Optional[str] = None) -> dict: """ - cache and auto translate query if needed - returns translated response from self.get_data + Perform a search with automatic translation and caching. + + NOTE: "lang" assured to be in self.supported_langs, + otherwise "query" automatically translated to self.default_lang. + If translations happens, the returned value of this method will also + be automatically translated back + + :param query: The query text. + :param lang: Optional language code. + :return: The data dictionary retrieved from the cache or computed anew. """ - user_lang = self._get_user_lang(context, lang) - query, context, lang = self._tx_query(query, context, lang) # read from cache if self.enable_cache and query in self.cache: data = self.cache[query] else: # search data try: - data = self.get_data(query, context) + data = _call_with_sanitized_kwargs(self.get_data, query, lang=lang) except: return {} @@ -164,215 +396,352 @@ def search(self, query: str, if self.enable_cache: self.cache[query] = data self.cache.store() - - # translate english output to user lang - if self.enable_tx and user_lang not in self.supported_langs: - return self.translator.translate_dict(data, user_lang, lang) return data - def visual_answer(self, query: str, - context: Optional[dict] = None, lang: Optional[str] = None) -> str: + @_deprecate_context2lang() + @auto_detect_lang(text_keys=["query"]) + @auto_translate(translate_keys=["query"]) + def visual_answer(self, query: str, lang: Optional[str] = None) -> str: """ - cache and auto translate query if needed - returns image that answers query - """ - query, context, lang = self._tx_query(query, context, lang) - return self.get_image(query, context) + Retrieve the image associated with the query with automatic translation and caching. + + NOTE: "lang" assured to be in self.supported_langs, + otherwise "query" automatically translated to self.default_lang. + If translations happens, the returned value of this method will also + be automatically translated back - def spoken_answer(self, query: str, - context: Optional[dict] = None, lang: Optional[str] = None) -> str: + :param query: The query text. + :param lang: Optional language code. + :return: The path or URL to the image. """ - cache and auto translate query if needed - returns chunked and translated response from self.get_spoken_answer + return _call_with_sanitized_kwargs(self.get_image, query, lang=lang) + + @_deprecate_context2lang() + @auto_detect_lang(text_keys=["query"]) + @auto_translate(translate_keys=["query"]) + def spoken_answer(self, query: str, lang: Optional[str] = None) -> str: """ - user_lang = self._get_user_lang(context, lang) - query, context, lang = self._tx_query(query, context, lang) + Retrieve the spoken answer for the query with automatic translation and caching. + NOTE: "lang" assured to be in self.supported_langs, + otherwise "query" automatically translated to self.default_lang. + If translations happens, the returned value of this method will also + be automatically translated back + + :param query: The query text. + :param lang: Optional language code. + :return: The spoken answer as a text response. + """ # get answer if self.enable_cache and query in self.spoken_cache: # read from cache summary = self.spoken_cache[query] else: - summary = self.get_spoken_answer(query, context) + + summary = _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang) # save to cache if self.enable_cache: self.spoken_cache[query] = summary self.spoken_cache.store() + return summary - # summarize - if summary: - # translate english output to user lang - if self.enable_tx and user_lang not in self.supported_langs: - return self.translator.translate(summary, user_lang, lang) - else: - return summary - - def long_answer(self, query: str, - context: Optional[dict] = None, lang: Optional[str] = None) -> List[dict]: - """ - return a list of ordered steps to expand the answer, eg, "tell me more" - step0 is always self.spoken_answer and self.get_image - { - "title": "optional", - "summary": "speak this", - "img": "optional/path/or/url - } - :return: + @_deprecate_context2lang() + @auto_detect_lang(text_keys=["query"]) + @auto_translate(translate_keys=["query"]) + def long_answer(self, query: str, lang: Optional[str] = None) -> List[dict]: """ - user_lang = self._get_user_lang(context, lang) - query, context, lang = self._tx_query(query, context, lang) - steps = self.get_expanded_answer(query, context) + Retrieve a detailed list of steps to expand the answer. + NOTE: "lang" assured to be in self.supported_langs, + otherwise "query" automatically translated to self.default_lang. + If translations happens, the returned value of this method will also + be automatically translated back + + :param query: The query text. + :param lang: Optional language code. + :return: A list of steps to elaborate on the answer, with each step containing a title, summary, and optional image. + """ + steps = _call_with_sanitized_kwargs(self.get_expanded_answer, query, lang=lang) # use spoken_answer as last resort if not steps: - summary = self.get_spoken_answer(query, context) + summary = _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang) if summary: - img = self.get_image(query, context) + img = _call_with_sanitized_kwargs(self.get_image, query, lang=lang) steps = [{"title": query, "summary": step0, "img": img} for step0 in self.sentence_split(summary, -1)] - - # translate english output to user lang - if self.enable_tx and user_lang not in self.supported_langs: - return self.translator.translate_list(steps, user_lang, lang) return steps -class TldrSolver(AbstractSolver): - """perform NLP summarization task, - handling automatic translation back and forth as needed""" +class CorpusSolver(QuestionSolver): + """Retrieval based question solver""" - # plugin methods to override + def __init__(self, config=None, + translator: Optional[LanguageTranslator] = None, + detector: Optional[LanguageDetector] = None, + priority: int = 50, + enable_tx: bool = False, + enable_cache: bool = False, + *args, **kwargs): + super().__init__(config, translator, detector, + priority, enable_tx, enable_cache, + *args, **kwargs) + LOG.debug(f"corpus presumed to be in language: {self.default_lang}") + + @abc.abstractmethod + def load_corpus(self, corpus: List[str]): + """index the provided list of sentences""" + + @abc.abstractmethod + def query(self, query: str, lang: Optional[str], k: int = 3) -> Iterable[Tuple[str, float]]: + """return top_k matches from indexed corpus""" + + @auto_detect_lang(text_keys=["query"]) + @auto_translate(translate_keys=["query"]) + def retrieve_from_corpus(self, query: str, k: int = 3, lang: Optional[str] = None) -> List[Tuple[float, str]]: + """return top_k matches from indexed corpus""" + res = [] + for doc, score in self.query(query, lang, k=k): + LOG.debug(f"Rank {len(res) + 1} (score: {score}): {doc}") + if self.config.get("min_conf"): + if score >= self.config["min_conf"]: + res.append((score, doc)) + else: + res.append((score, doc)) + return res + + @auto_detect_lang(text_keys=["query"]) + @auto_translate(translate_keys=["query"]) + def get_spoken_answer(self, query: str, lang: Optional[str] = None) -> str: + # Query the corpus + answers = [a[1] for a in self.retrieve_from_corpus(query, lang=lang, + k=self.config.get("n_answer", 1))] + if answers: + return ". ".join(answers[:self.config.get("n_answer", 1)]) + + +class QACorpusSolver(CorpusSolver): + def __init__(self, config=None, + translator: Optional[LanguageTranslator] = None, + detector: Optional[LanguageDetector] = None, + priority: int = 50, + enable_tx: bool = False, + enable_cache: bool = False, + *args, **kwargs): + self.answers = {} + super().__init__(config, translator, detector, + priority, enable_tx, enable_cache, + *args, **kwargs) + + def load_corpus(self, corpus: Dict): + self.answers = corpus + super().load_corpus(list(self.answers.keys())) + + @auto_detect_lang(text_keys=["query"]) + @auto_translate(translate_keys=["query"]) + def retrieve_from_corpus(self, query: str, k: int = 1, lang: Optional[str] = None) -> List[Tuple[float, str]]: + res = [] + for doc, score in super().retrieve_from_corpus(query, k, lang): + LOG.debug(f"Answer {len(res) + 1} (score: {score}): {self.answers[doc]}") + res.append((score, self.answers[doc])) + return res + + +class TldrSolver(AbstractSolver): + """ + Solver for performing NLP summarization tasks, + handling automatic translation as needed. + """ @abc.abstractmethod def get_tldr(self, document: str, - context: Optional[dict] = None) -> str: + lang: Optional[str] = None) -> str: """ - document assured to be in self.default_lang - returns summary of provided document + Summarize the provided document. + + :param document: The text of the document to summarize, assured to be in the default language. + :param lang: Optional language code. + :return: A summary of the provided document. """ raise NotImplementedError # user facing methods - def tldr(self, document: str, - context: Optional[dict] = None, lang: Optional[str] = None) -> str: - """ - cache and auto translate query if needed - returns summary of provided document + + @_deprecate_context2lang() + @auto_detect_lang(text_keys=["document"]) + @auto_translate(translate_keys=["document"]) + def tldr(self, document: str, lang: Optional[str] = None) -> str: """ - user_lang = self._get_user_lang(context, lang) - document, context, lang = self._tx_query(document, context, lang) + Summarize the provided document with automatic translation and caching if needed. - # summarize - tldr = self.get_tldr(document, context) + NOTE: "lang" assured to be in self.supported_langs, + otherwise "document" automatically translated to self.default_lang. + If translations happens, the returned value of this method will also + be automatically translated back - # translate output to user lang - if self.enable_tx and user_lang not in self.supported_langs: - return self.translator.translate(tldr, user_lang, lang) - return tldr + :param document: The text of the document to summarize. + :param lang: Optional language code. + :return: A summary of the provided document. + """ + # summarize + return _call_with_sanitized_kwargs(self.get_tldr, document, lang=lang) class EvidenceSolver(AbstractSolver): - """perform NLP reading comprehension task, - handling automatic translation back and forth as needed""" - - # plugin methods to override + """ + Solver for NLP reading comprehension tasks, + handling automatic translation as needed. + """ @abc.abstractmethod def get_best_passage(self, evidence: str, question: str, - context: Optional[dict] = None) -> str: + lang: Optional[str] = None) -> str: """ - evidence and question assured to be in self.default_lang - returns summary of provided document + Extract the best passage from evidence that answers the given question. + + :param evidence: The text containing the evidence, assured to be in the default language. + :param question: The question to answer, assured to be in the default language. + :param lang: Optional language code. + :return: The passage from the evidence that best answers the question. """ raise NotImplementedError # user facing methods + @_deprecate_context2lang() + @auto_detect_lang(text_keys=["evidence", "question"]) + @auto_translate(translate_keys=["evidence", "question"]) def extract_answer(self, evidence: str, question: str, - context: Optional[dict] = None, lang: Optional[str] = None) -> str: - """ - cache and auto translate evidence and question if needed - returns passage from evidence that answers question + lang: Optional[str] = None) -> str: """ - user_lang = self._get_user_lang(context, lang) - evidence, context, lang = self._tx_query(evidence, context, lang) - question, context, lang = self._tx_query(question, context, lang) + Extract the best passage from evidence that answers the question with automatic translation and caching if needed. - # extract answer from doc - ans = self.get_best_passage(evidence, question, context) + NOTE: "lang" assured to be in self.supported_langs, + otherwise "evidence" and "question" are automatically translated to self.default_lang. + If translations happens, the returned value of this method will also + be automatically translated back - # translate output to user lang - if self.enable_tx and user_lang not in self.supported_langs: - return self.translator.translate(ans, user_lang, lang) - return ans + :param evidence: The text containing the evidence. + :param question: The question to answer. + :param lang: Optional language code. + :return: The passage from the evidence that answers the question. + """ + # extract answer from doc + return self.get_best_passage(evidence, question, lang=lang) class MultipleChoiceSolver(AbstractSolver): - """ select best answer from question + multiple choice - handling automatic translation back and forth as needed""" - - # plugin methods to override + """ + Solver for selecting the best answer from a question with multiple choices, + handling automatic translation as needed. + """ - # TODO - make abstract in the future, - # just giving some time buffer to update existing - # plugins in the wild missing this method - #@abc.abstractmethod + @abc.abstractmethod def rerank(self, query: str, options: List[str], - context: Optional[dict] = None) -> List[Tuple[float, str]]: + lang: Optional[str] = None, + return_index: bool = False) -> List[Tuple[float, Union[str, int]]]: """ - rank options list, returning a list of tuples (score, text) + Rank the provided options based on the query. + + :param query: The query text, assured to be in the default language. + :param options: A list of answer options, each assured to be in the default language. + :param lang: Optional language code. + :param return_index: If True, return the index of the best option; otherwise, return the best option text. + :return: A list of tuples where each tuple contains a score and the corresponding option text, sorted by score. """ raise NotImplementedError + @_deprecate_context2lang() + @auto_detect_lang(text_keys=["query", "options"]) + @auto_translate(translate_keys=["query", "options"]) def select_answer(self, query: str, options: List[str], - context: Optional[dict] = None) -> str: + lang: Optional[str] = None, + return_index: bool = False) -> Union[str, int]: """ - query and options assured to be in self.default_lang - return best answer from options list - """ - return self.rerank(query, options, context)[0][1] + Select the best answer from the provided options based on the query with automatic translation and caching if needed. - # user facing methods - def solve(self, query: str, options: List[str], - context: Optional[dict] = None, lang: Optional[str] = None) -> str: - """ - cache and auto translate query and options if needed - returns best answer from provided options - """ - user_lang = self._get_user_lang(context, lang) - query, context, lang = self._tx_query(query, context, lang) - opts = [self.translator.translate(opt, lang, user_lang) - for opt in options] + NOTE: "lang" assured to be in self.supported_langs, + otherwise "query" and "options" are automatically translated to self.default_lang. + If translations happens, the returned value of this method will also + be automatically translated back - # select best answer - ans = self.select_answer(query, opts, context) - - idx = opts.index(ans) - return options[idx] + :param query: The query text. + :param options: A list of answer options. + :param lang: Optional language code. + :param return_index: If True, return the index of the best option; otherwise, return the best option text. + :return: The best answer from the options list, or the index of the best option if `return_index` is True. + """ + return self.rerank(query, options, lang=lang, return_index=return_index)[0][1] class EntailmentSolver(AbstractSolver): """ select best answer from question + multiple choice handling automatic translation back and forth as needed""" - # plugin methods to override - @abc.abstractmethod def check_entailment(self, premise: str, hypothesis: str, - context: Optional[dict] = None) -> bool: + lang: Optional[str] = None) -> bool: """ - premise and hyopithesis assured to be in self.default_lang - return Bool, True if premise entails the hypothesis False otherwise + Check if the premise entails the hypothesis. + + :param premise: The premise text, assured to be in the default language. + :param hypothesis: The hypothesis text, assured to be in the default language. + :param lang: Optional language code. + :return: True if the premise entails the hypothesis; False otherwise. """ raise NotImplementedError # user facing methods - def entails(self, premise: str, hypothesis: str, - context: Optional[dict] = None, lang: Optional[str] = None) -> bool: + @_deprecate_context2lang() + @auto_detect_lang(text_keys=["premise", "hypothesis"]) + @auto_translate(translate_keys=["premise", "hypothesis"]) + def entails(self, premise: str, hypothesis: str, lang: Optional[str] = None) -> bool: """ - cache and auto translate premise and hypothesis if needed - return Bool, True if premise entails the hypothesis False otherwise + Determine if the premise entails the hypothesis with automatic translation and caching if needed. + + NOTE: "lang" assured to be in self.supported_langs, + otherwise "premise" and "hypothesis" are automatically translated to self.default_lang. + If translations happens, the returned value of this method will also + be automatically translated back + + :param premise: The premise text. + :param hypothesis: The hypothesis text. + :param lang: Optional language code. + :return: True if the premise entails the hypothesis; False otherwise. """ - premise, context, lang = self._tx_query(premise, context, lang) - hypothesis, context, lang = self._tx_query(hypothesis, context, lang) # check for entailment - return self.check_entailment(premise, hypothesis) + return self.check_entailment(premise, hypothesis, lang=lang) + + +def _do_tx(solver, data, source_lang, target_lang): + if isinstance(data, str): + return solver.translate(data, + source_lang=source_lang, target_lang=target_lang) + elif isinstance(data, list): + for idx, e in enumerate(data): + data[idx] = _do_tx(solver, e, source_lang=source_lang, target_lang=target_lang) + elif isinstance(data, dict): + for k, v in data.items(): + data[k] = _do_tx(solver, v, source_lang=source_lang, target_lang=target_lang) + elif isinstance(data, tuple) and len(data) == 2: + if isinstance(data[0], str): + a = _do_tx(solver, data[0], source_lang=source_lang, target_lang=target_lang) + else: + a = data[0] + if isinstance(data[1], str): + b = _do_tx(solver, data[1], source_lang=source_lang, target_lang=target_lang) + else: + b = data[1] + return (a, b) + return data + + +def _call_with_sanitized_kwargs(func, *args, lang: Optional[str] = None): + # Inspect the function signature to ensure it has both 'lang' and 'context' parameters + params = inspect.signature(func).parameters + kwargs = {} + if "lang" in params: + # new style - only lang is passed + kwargs["lang"] = lang + elif "context" in kwargs: + # old style - when plugins received context only + kwargs["context"]["lang"] = lang + return func(*args, **kwargs) diff --git a/requirements/test.txt b/requirements/test.txt index 7694cc16..4373cf13 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -2,4 +2,5 @@ pytest pytest-timeout pytest-cov ovos-translate-server-plugin +ovos-classifiers ovos-utils>=0.1.0a8 \ No newline at end of file diff --git a/test/unittests/test_solver.py b/test/unittests/test_solver.py index c460f267..354b18af 100644 --- a/test/unittests/test_solver.py +++ b/test/unittests/test_solver.py @@ -1,8 +1,10 @@ import unittest -from unittest.mock import Mock, patch +from unittest.mock import Mock, patch, MagicMock +from ovos_plugin_manager.templates.solvers import QuestionSolver, auto_detect_lang, auto_translate, _deprecate_context2lang, AbstractSolver from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes -from ovos_plugin_manager.templates.solvers import QuestionSolver + + # TODO: Test Tldr, Evidence, MultipleChoice, Entailment @@ -141,11 +143,11 @@ def test_translation(self): solver.translator.translate.return_value = "a wild translation appears" # no translation - ans = solver.spoken_answer("some query") + ans = solver.spoken_answer("some query", lang="en") solver.translator.translate.assert_not_called() # translation - ans = solver.spoken_answer("not english", context={"lang": "unk"}) + ans = solver.spoken_answer("not english", lang="unk") solver.translator.translate.assert_called() @@ -398,3 +400,76 @@ def test_get_supported_langs(self, get_supported_languages): get_reading_comprehension_solver_supported_langs get_reading_comprehension_solver_supported_langs() get_supported_languages.assert_called_once_with(self.PLUGIN_TYPE) + + +class TestAutoTranslate(unittest.TestCase): + def setUp(self): + self.solver = AbstractSolver(enable_tx=True, default_lang='en') + self.solver.translate = MagicMock(side_effect=lambda text, source_lang=None, target_lang=None: text[ + ::-1] if source_lang and target_lang else text) + + def test_auto_translate_decorator(self): + @auto_translate(translate_keys=['text']) + def test_func(solver, text, lang=None): + return text[::-1] + + result = test_func(self.solver, 'hello', lang='es') + self.assertEqual(result, 'olleh') # 'hello' reversed due to mock translation + + def test_auto_translate_no_translation(self): + @auto_translate(translate_keys=['text']) + def test_func(solver, text, lang=None): + return text + + result = test_func(self.solver, 'hello') + self.assertEqual(result, 'hello') + + +class TestAutoDetectLang(unittest.TestCase): + def setUp(self): + self.solver = AbstractSolver() + self.solver.detect_language = MagicMock(return_value='en') + + def test_auto_detect_lang_decorator(self): + self.solver.detector = Mock() + self.solver.detector.detect.return_value = "en" + + @auto_detect_lang(text_keys=['text']) + def test_func(solver, text, lang=None): + return lang + + result = test_func(self.solver, 'hello world') + self.assertEqual(result, 'en') + + def test_auto_detect_lang_with_lang(self): + @auto_detect_lang(text_keys=['text']) + def test_func(solver, text, lang=None): + return lang + + result = test_func(self.solver, 'hello', lang='es') + self.assertEqual(result, 'es') + + +class TestDeprecateContext2Lang(unittest.TestCase): + def setUp(self): + self.solver = AbstractSolver() + + def test_deprecate_context2lang(self): + @_deprecate_context2lang() + def test_func(solver, lang=None): + return lang + + result = test_func(self.solver, context={'lang': 'en'}) + self.assertEqual(result, 'en') + + def test_no_context(self): + @_deprecate_context2lang() + def test_func(solver, lang=None): + return lang + + result = test_func(self.solver, lang='fr') + self.assertEqual(result, 'fr') + + +if __name__ == '__main__': + unittest.main() From 2f5dc4a251a8f50ed0d5564cbe2cb992078a21b4 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 25 Jul 2024 01:44:05 +0000 Subject: [PATCH 109/129] Increment Version to 0.0.26a37 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 51ff75c9..3a70be95 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 36 +VERSION_ALPHA = 37 # END_VERSION_BLOCK From ac788cacec1af1e8bbed37455bccfb32d9dcd6d4 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 25 Jul 2024 01:44:29 +0000 Subject: [PATCH 110/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a574b86e..1763f712 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a36](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a36) (2024-07-25) +## [0.0.26a37](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a37) (2024-07-25) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a36) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a37) **Implemented enhancements:** @@ -19,6 +19,7 @@ **Fixed bugs:** - abstractmethod decorator breaks OCP 0.0.6 compat. [\#229](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/229) +- refactor/solver\_decorators [\#244](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/244) ([JarbasAl](https://github.com/JarbasAl)) - fix/missing\_property [\#239](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/239) ([JarbasAl](https://github.com/JarbasAl)) - ensure cache dir exists [\#232](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/232) ([JarbasAl](https://github.com/JarbasAl)) - fix/playback\_time\_not\_abstract [\#230](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/230) ([JarbasAl](https://github.com/JarbasAl)) From 91f8b7d24c72e022bfe52f77f582aa984229690e Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sun, 4 Aug 2024 03:07:32 +0100 Subject: [PATCH 111/129] feat/units_kwarg_solvers (#247) explicitly support units in solver plugin kwargs --- ovos_plugin_manager/templates/solvers.py | 207 ++++++++++++++++------- 1 file changed, 148 insertions(+), 59 deletions(-) diff --git a/ovos_plugin_manager/templates/solvers.py b/ovos_plugin_manager/templates/solvers.py index dfa8f481..0d880133 100644 --- a/ovos_plugin_manager/templates/solvers.py +++ b/ovos_plugin_manager/templates/solvers.py @@ -4,7 +4,7 @@ import abc import inspect from functools import wraps, lru_cache -from typing import Optional, List, Iterable, Tuple, Dict, Union +from typing import Optional, List, Iterable, Tuple, Dict, Union, Any from json_database import JsonStorageXDG from ovos_utils import flatten_list @@ -282,12 +282,14 @@ def __init__(self, config: Optional[Dict] = None, """ Initialize the QuestionSolver. - :param config: Optional configuration dictionary. - :param translator: Optional language translator. - :param detector: Optional language detector. - :param priority: Priority of the solver. - :param enable_tx: Flag to enable translation. - :param enable_cache: Flag to enable caching. + Args: + config (Optional[Dict]): Optional configuration dictionary. + translator (Optional[LanguageTranslator]): Optional language translator. + detector (Optional[LanguageDetector]): Optional language detector. + priority (int): Priority of the solver. + enable_tx (bool): Flag to enable translation. + enable_cache (bool): Flag to enable caching. + internal_lang (Optional[str]): Internal language code. Defaults to None. """ super().__init__(config, translator, detector, priority, enable_tx, enable_cache, internal_lang, @@ -307,69 +309,101 @@ def __init__(self, config: Optional[Dict] = None, # plugin methods to override @abc.abstractmethod - def get_spoken_answer(self, query: str, lang: Optional[str] = None) -> str: + def get_spoken_answer(self, query: str, + lang: Optional[str] = None, + units: Optional[str] = None) -> str: """ Obtain the spoken answer for a given query. - :param query: The query text. - :param lang: Optional language code. - :return: The spoken answer as a text response. + Args: + query (str): The query text. + lang (Optional[str]): Optional language code. Defaults to None. + units (Optional[str]): Optional units for the query. Defaults to None. + + Returns: + str: The spoken answer as a text response. """ raise NotImplementedError @_deprecate_context2lang() - def stream_utterances(self, query: str, lang: Optional[str] = None) -> Iterable[str]: + def stream_utterances(self, query: str, + lang: Optional[str] = None, + units: Optional[str] = None) -> Iterable[str]: """ Stream utterances for the given query as they become available. - :param query: The query text. - :param lang: Optional language code. - :return: An iterable of utterances. + Args: + query (str): The query text. + lang (Optional[str]): Optional language code. Defaults to None. + units (Optional[str]): Optional units for the query. Defaults to None. + + Returns: + Iterable[str]: An iterable of utterances. """ - ans = _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang) + ans = _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang, units=units) for utt in self.sentence_split(ans): yield utt @_deprecate_context2lang() - def get_data(self, query: str, lang: Optional[str] = None) -> Optional[dict]: + def get_data(self, query: str, + lang: Optional[str] = None, + units: Optional[str] = None) -> Optional[Dict]: """ Retrieve data for the given query. - :param query: The query text. - :param lang: Optional language code. - :return: A dictionary containing the answer. + Args: + query (str): The query text. + lang (Optional[str]): Optional language code. Defaults to None. + units (Optional[str]): Optional units for the query. Defaults to None. + + Returns: + Optional[Dict]: A dictionary containing the answer. """ - return {"answer": _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang)} + return {"answer": _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang, units=units)} @_deprecate_context2lang() - def get_image(self, query: str, lang: Optional[str] = None) -> Optional[str]: + def get_image(self, query: str, + lang: Optional[str] = None, + units: Optional[str] = None) -> Optional[str]: """ Get the path or URL to an image associated with the query. - :param query: The query text - :param lang: Optional language code. - :return: The path or URL to a single image. + Args: + query (str): The query text. + lang (Optional[str]): Optional language code. Defaults to None. + units (Optional[str]): Optional units for the query. Defaults to None. + + Returns: + Optional[str]: The path or URL to a single image. """ return None @_deprecate_context2lang() - def get_expanded_answer(self, query: str, lang: Optional[str] = None) -> List[dict]: + def get_expanded_answer(self, query: str, + lang: Optional[str] = None, + units: Optional[str] = None) -> List[dict]: """ Get an expanded list of steps to elaborate on the answer. - :param query: The query text - :param lang: Optional language code. - :return: A list of dictionaries with each step containing a title, summary, and optional image. + Args: + query (str): The query text. + lang (Optional[str]): Optional language code. Defaults to None. + units (Optional[str]): Optional units for the query. Defaults to None. + + Returns: + List[Dict]: A list of dictionaries with each step containing a title, summary, and optional image. """ return [{"title": query, - "summary": _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang), - "img": _call_with_sanitized_kwargs(self.get_image, query, lang=lang)}] + "summary": _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang, units=units), + "img": _call_with_sanitized_kwargs(self.get_image, query, lang=lang, units=units)}] # user facing methods @_deprecate_context2lang() @auto_detect_lang(text_keys=["query"]) @auto_translate(translate_keys=["query"]) - def search(self, query: str, lang: Optional[str] = None) -> dict: + def search(self, query: str, + lang: Optional[str] = None, + units: Optional[str] = None) -> dict: """ Perform a search with automatic translation and caching. @@ -378,9 +412,13 @@ def search(self, query: str, lang: Optional[str] = None) -> dict: If translations happens, the returned value of this method will also be automatically translated back - :param query: The query text. - :param lang: Optional language code. - :return: The data dictionary retrieved from the cache or computed anew. + Args: + query (str): The query text. + lang (Optional[str]): Optional language code. Defaults to None. + units (Optional[str]): Optional units for the query. Defaults to None. + + Returns: + Dict: The data dictionary retrieved from the cache or computed anew. """ # read from cache if self.enable_cache and query in self.cache: @@ -388,7 +426,7 @@ def search(self, query: str, lang: Optional[str] = None) -> dict: else: # search data try: - data = _call_with_sanitized_kwargs(self.get_data, query, lang=lang) + data = _call_with_sanitized_kwargs(self.get_data, query, lang=lang, units=units) except: return {} @@ -401,7 +439,9 @@ def search(self, query: str, lang: Optional[str] = None) -> dict: @_deprecate_context2lang() @auto_detect_lang(text_keys=["query"]) @auto_translate(translate_keys=["query"]) - def visual_answer(self, query: str, lang: Optional[str] = None) -> str: + def visual_answer(self, query: str, + lang: Optional[str] = None, + units: Optional[str] = None) -> str: """ Retrieve the image associated with the query with automatic translation and caching. @@ -410,16 +450,22 @@ def visual_answer(self, query: str, lang: Optional[str] = None) -> str: If translations happens, the returned value of this method will also be automatically translated back - :param query: The query text. - :param lang: Optional language code. - :return: The path or URL to the image. + Args: + query (str): The query text. + lang (Optional[str]): Optional language code. Defaults to None. + units (Optional[str]): Optional units for the query. Defaults to None. + + Returns: + str: The path or URL to the image. """ - return _call_with_sanitized_kwargs(self.get_image, query, lang=lang) + return _call_with_sanitized_kwargs(self.get_image, query, lang=lang, units=units) @_deprecate_context2lang() @auto_detect_lang(text_keys=["query"]) @auto_translate(translate_keys=["query"]) - def spoken_answer(self, query: str, lang: Optional[str] = None) -> str: + def spoken_answer(self, query: str, + lang: Optional[str] = None, + units: Optional[str] = None) -> str: """ Retrieve the spoken answer for the query with automatic translation and caching. @@ -428,9 +474,13 @@ def spoken_answer(self, query: str, lang: Optional[str] = None) -> str: If translations happens, the returned value of this method will also be automatically translated back - :param query: The query text. - :param lang: Optional language code. - :return: The spoken answer as a text response. + Args: + query (str): The query text. + lang (Optional[str]): Optional language code. Defaults to None. + units (Optional[str]): Optional units for the query. Defaults to None. + + Returns: + str: The spoken answer as a text response. """ # get answer if self.enable_cache and query in self.spoken_cache: @@ -438,7 +488,7 @@ def spoken_answer(self, query: str, lang: Optional[str] = None) -> str: summary = self.spoken_cache[query] else: - summary = _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang) + summary = _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang, units=units) # save to cache if self.enable_cache: self.spoken_cache[query] = summary @@ -448,7 +498,9 @@ def spoken_answer(self, query: str, lang: Optional[str] = None) -> str: @_deprecate_context2lang() @auto_detect_lang(text_keys=["query"]) @auto_translate(translate_keys=["query"]) - def long_answer(self, query: str, lang: Optional[str] = None) -> List[dict]: + def long_answer(self, query: str, + lang: Optional[str] = None, + units: Optional[str] = None) -> List[Dict]: """ Retrieve a detailed list of steps to expand the answer. @@ -457,18 +509,21 @@ def long_answer(self, query: str, lang: Optional[str] = None) -> List[dict]: If translations happens, the returned value of this method will also be automatically translated back - :param query: The query text. - :param lang: Optional language code. - :return: A list of steps to elaborate on the answer, with each step containing a title, summary, and optional image. + Args: + query (str): The query text. + lang (Optional[str]): Optional language code. Defaults to None. + units (Optional[str]): Optional units for the query. Defaults to None. + + Returns: + List[Dict]: A list of steps to elaborate on the answer, with each step containing a title, summary, and optional image. """ - steps = _call_with_sanitized_kwargs(self.get_expanded_answer, query, lang=lang) + steps = _call_with_sanitized_kwargs(self.get_expanded_answer, query, lang=lang, units=units) # use spoken_answer as last resort if not steps: - summary = _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang) + summary = _call_with_sanitized_kwargs(self.get_spoken_answer, query, lang=lang, units=units) if summary: - img = _call_with_sanitized_kwargs(self.get_image, query, lang=lang) - steps = [{"title": query, "summary": step0, "img": img} - for step0 in self.sentence_split(summary, -1)] + img = _call_with_sanitized_kwargs(self.get_image, query, lang=lang, units=units) + steps = [{"title": query, "summary": step, "img": img} for step in self.sentence_split(summary, -1)] return steps @@ -711,7 +766,19 @@ def entails(self, premise: str, hypothesis: str, lang: Optional[str] = None) -> return self.check_entailment(premise, hypothesis, lang=lang) -def _do_tx(solver, data, source_lang, target_lang): +def _do_tx(solver, data: Any, source_lang: str, target_lang: str) -> Any: + """ + Translate the given data from source language to target language using the provided solver. + + Args: + solver: The translation solver. + data (Any): The data to translate. Can be a string, list, dictionary, or tuple. + source_lang (str): The source language code. + target_lang (str): The target language code. + + Returns: + Any: The translated data in the same structure as the input data. + """ if isinstance(data, str): return solver.translate(data, source_lang=source_lang, target_lang=target_lang) @@ -734,14 +801,36 @@ def _do_tx(solver, data, source_lang, target_lang): return data -def _call_with_sanitized_kwargs(func, *args, lang: Optional[str] = None): - # Inspect the function signature to ensure it has both 'lang' and 'context' parameters +def _call_with_sanitized_kwargs(func, *args: Any, + lang: Optional[str] = None, + units: Optional[str] = None) -> Any: + """ + Call a function with sanitized keyword arguments for language and units. + + Args: + func: The function to call. + args (Any): Positional arguments to pass to the function. + lang (Optional[str]): Optional language code. Defaults to None. + units (Optional[str]): Optional units for the query. Defaults to None. + + Returns: + Any: The result of the function call. + """ params = inspect.signature(func).parameters kwargs = {} + if "lang" in params: - # new style - only lang is passed + # new style - only lang/units is passed kwargs["lang"] = lang elif "context" in kwargs: # old style - when plugins received context only kwargs["context"]["lang"] = lang + + if "units" in params: + # new style - only lang/units is passed + kwargs["units"] = units + elif "context" in kwargs: + # old style - when plugins received context only + kwargs["context"]["units"] = units + return func(*args, **kwargs) From 1708fcba6c139f9b52e435c994901797595578ce Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 4 Aug 2024 02:07:46 +0000 Subject: [PATCH 112/129] Increment Version to 0.0.26a38 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 3a70be95..911307b5 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 37 +VERSION_ALPHA = 38 # END_VERSION_BLOCK From 1bb93ec2cb0daac24c131cc01cc6ade568de5724 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Sun, 4 Aug 2024 02:08:10 +0000 Subject: [PATCH 113/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1763f712..3109c4d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -## [0.0.26a37](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a37) (2024-07-25) +## [0.0.26a38](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a38) (2024-08-04) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a37) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a38) **Implemented enhancements:** +- feat/units\_kwarg\_solvers [\#247](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/247) ([JarbasAl](https://github.com/JarbasAl)) - feat/metadata [\#246](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/246) ([JarbasAl](https://github.com/JarbasAl)) - feat/embeddings\_metadata\_support [\#245](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/245) ([JarbasAl](https://github.com/JarbasAl)) - feat/add\_rerank\_method [\#243](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/243) ([JarbasAl](https://github.com/JarbasAl)) From d825c0bd7b6332040553a690234c21901c9916d9 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Mon, 5 Aug 2024 03:18:33 +0100 Subject: [PATCH 114/129] fix/context_kwarg_backwards_compat (#248) improve typing --- ovos_plugin_manager/templates/solvers.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ovos_plugin_manager/templates/solvers.py b/ovos_plugin_manager/templates/solvers.py index 0d880133..85b8ce25 100644 --- a/ovos_plugin_manager/templates/solvers.py +++ b/ovos_plugin_manager/templates/solvers.py @@ -311,7 +311,7 @@ def __init__(self, config: Optional[Dict] = None, @abc.abstractmethod def get_spoken_answer(self, query: str, lang: Optional[str] = None, - units: Optional[str] = None) -> str: + units: Optional[str] = None) -> Optional[str]: """ Obtain the spoken answer for a given query. @@ -347,7 +347,7 @@ def stream_utterances(self, query: str, @_deprecate_context2lang() def get_data(self, query: str, lang: Optional[str] = None, - units: Optional[str] = None) -> Optional[Dict]: + units: Optional[str] = None) -> Optional[Dict[str, str]]: """ Retrieve data for the given query. @@ -381,7 +381,7 @@ def get_image(self, query: str, @_deprecate_context2lang() def get_expanded_answer(self, query: str, lang: Optional[str] = None, - units: Optional[str] = None) -> List[dict]: + units: Optional[str] = None) -> List[Dict[str, str]]: """ Get an expanded list of steps to elaborate on the answer. @@ -403,7 +403,7 @@ def get_expanded_answer(self, query: str, @auto_translate(translate_keys=["query"]) def search(self, query: str, lang: Optional[str] = None, - units: Optional[str] = None) -> dict: + units: Optional[str] = None) -> Optional[Dict]: """ Perform a search with automatic translation and caching. @@ -441,7 +441,7 @@ def search(self, query: str, @auto_translate(translate_keys=["query"]) def visual_answer(self, query: str, lang: Optional[str] = None, - units: Optional[str] = None) -> str: + units: Optional[str] = None) -> Optional[str]: """ Retrieve the image associated with the query with automatic translation and caching. @@ -465,7 +465,7 @@ def visual_answer(self, query: str, @auto_translate(translate_keys=["query"]) def spoken_answer(self, query: str, lang: Optional[str] = None, - units: Optional[str] = None) -> str: + units: Optional[str] = None) -> Optional[str]: """ Retrieve the spoken answer for the query with automatic translation and caching. @@ -500,7 +500,7 @@ def spoken_answer(self, query: str, @auto_translate(translate_keys=["query"]) def long_answer(self, query: str, lang: Optional[str] = None, - units: Optional[str] = None) -> List[Dict]: + units: Optional[str] = None) -> List[Dict[str, str]]: """ Retrieve a detailed list of steps to expand the answer. @@ -566,7 +566,7 @@ def retrieve_from_corpus(self, query: str, k: int = 3, lang: Optional[str] = Non @auto_detect_lang(text_keys=["query"]) @auto_translate(translate_keys=["query"]) - def get_spoken_answer(self, query: str, lang: Optional[str] = None) -> str: + def get_spoken_answer(self, query: str, lang: Optional[str] = None) -> Optional[str]: # Query the corpus answers = [a[1] for a in self.retrieve_from_corpus(query, lang=lang, k=self.config.get("n_answer", 1))] @@ -819,6 +819,10 @@ def _call_with_sanitized_kwargs(func, *args: Any, params = inspect.signature(func).parameters kwargs = {} + # ensure context is passed, it didn't used to be optional + if "context" in params and "context" not in kwargs: + kwargs["context"] = {} + if "lang" in params: # new style - only lang/units is passed kwargs["lang"] = lang From 307de4acddaa403164b9e62be89ea18e1a7179da Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 5 Aug 2024 02:18:50 +0000 Subject: [PATCH 115/129] Increment Version to 0.0.26a39 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 911307b5..fd0d9fe3 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 VERSION_BUILD = 26 -VERSION_ALPHA = 38 +VERSION_ALPHA = 39 # END_VERSION_BLOCK From 7b011f3dafda3b4a74bf693870e66bea50ac943c Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 5 Aug 2024 02:19:17 +0000 Subject: [PATCH 116/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3109c4d3..0fb1a1f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a38](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a38) (2024-08-04) +## [0.0.26a39](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a39) (2024-08-05) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a38) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a39) **Implemented enhancements:** @@ -20,6 +20,7 @@ **Fixed bugs:** - abstractmethod decorator breaks OCP 0.0.6 compat. [\#229](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/229) +- fix/context\_kwarg\_backwards\_compat [\#248](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/248) ([JarbasAl](https://github.com/JarbasAl)) - refactor/solver\_decorators [\#244](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/244) ([JarbasAl](https://github.com/JarbasAl)) - fix/missing\_property [\#239](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/239) ([JarbasAl](https://github.com/JarbasAl)) - ensure cache dir exists [\#232](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/232) ([JarbasAl](https://github.com/JarbasAl)) From b62cc29d987f529dfc8747b3b4c0423b978fbc0f Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 2 Sep 2024 16:24:34 +0000 Subject: [PATCH 117/129] Increment Version to 0.1.0 --- ovos_plugin_manager/version.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index fd0d9fe3..316a5fd0 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -1,7 +1,7 @@ # The following lines are replaced during the release process. # START_VERSION_BLOCK VERSION_MAJOR = 0 -VERSION_MINOR = 0 -VERSION_BUILD = 26 -VERSION_ALPHA = 39 +VERSION_MINOR = 1 +VERSION_BUILD = 0 +VERSION_ALPHA = 0 # END_VERSION_BLOCK From 7b0d673f47b51894d398d8992a03137c74cd32c7 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 2 Sep 2024 16:24:58 +0000 Subject: [PATCH 118/129] Update Changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb1a1f0..c000d821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.0.26a39](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.0.26a39) (2024-08-05) +## [0.1.0](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.1.0) (2024-09-02) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.0.26a39) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.1.0) **Implemented enhancements:** From 5ca16a9d5684298e4c1ad109d62b803f2123872a Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:53:20 +0100 Subject: [PATCH 119/129] planned_deprecations (#254) * planned_deprecations * tests --- ovos_plugin_manager/__init__.py | 17 ------- ovos_plugin_manager/audio.py | 12 ++--- ovos_plugin_manager/audio2ipa.py | 16 ------ ovos_plugin_manager/audio_transformers.py | 18 ------- ovos_plugin_manager/coreference.py | 16 ------ ovos_plugin_manager/g2p.py | 17 ------- ovos_plugin_manager/gui.py | 16 ------ ovos_plugin_manager/keywords.py | 16 ------ ovos_plugin_manager/language.py | 16 ------ ovos_plugin_manager/metadata_transformers.py | 17 ------- ovos_plugin_manager/microphone.py | 16 ------ ovos_plugin_manager/phal.py | 16 ------ ovos_plugin_manager/postag.py | 16 ------ ovos_plugin_manager/segmentation.py | 15 ------ ovos_plugin_manager/skills.py | 2 +- ovos_plugin_manager/solvers.py | 15 ------ ovos_plugin_manager/stt.py | 42 ---------------- ovos_plugin_manager/templates/solvers.py | 2 +- ovos_plugin_manager/templates/stt.py | 16 +++--- ovos_plugin_manager/templates/tts.py | 30 +++++------ ovos_plugin_manager/text_transformers.py | 39 --------------- ovos_plugin_manager/tokenization.py | 16 ------ ovos_plugin_manager/tts.py | 52 +++----------------- ovos_plugin_manager/vad.py | 16 ------ ovos_plugin_manager/wakewords.py | 16 ------ test/unittests/test_stt.py | 11 +---- test/unittests/test_tts.py | 11 +---- 27 files changed, 39 insertions(+), 453 deletions(-) diff --git a/ovos_plugin_manager/__init__.py b/ovos_plugin_manager/__init__.py index 0d0fd980..64282511 100644 --- a/ovos_plugin_manager/__init__.py +++ b/ovos_plugin_manager/__init__.py @@ -1,19 +1,2 @@ from ovos_plugin_manager.utils import PluginTypes from ovos_plugin_manager.plugin_entry import OpenVoiceOSPlugin -from ovos_utils.log import LOG - - -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) diff --git a/ovos_plugin_manager/audio.py b/ovos_plugin_manager/audio.py index a60ff0db..370952f5 100644 --- a/ovos_plugin_manager/audio.py +++ b/ovos_plugin_manager/audio.py @@ -4,16 +4,10 @@ from ovos_config import Configuration from ovos_utils.log import log_deprecation -log_deprecation("ovos_plugin_manager.audio has been deprecated on ovos-audio, " - "move to ovos_plugin_manager.media", "0.1.0") - -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) +# TODO - restore this log in next release with updated version string +#log_deprecation("ovos_plugin_manager.audio has been deprecated on ovos-audio, " +# "move to ovos_plugin_manager.media", "1.0.0") def find_audio_service_plugins() -> dict: diff --git a/ovos_plugin_manager/audio2ipa.py b/ovos_plugin_manager/audio2ipa.py index 39738a42..9c1dcbbd 100644 --- a/ovos_plugin_manager/audio2ipa.py +++ b/ovos_plugin_manager/audio2ipa.py @@ -4,22 +4,6 @@ from ovos_utils.log import LOG -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_audio2ipa_plugins() -> dict: """ Find all installed plugins diff --git a/ovos_plugin_manager/audio_transformers.py b/ovos_plugin_manager/audio_transformers.py index 3f4e3b8c..f2e0f004 100644 --- a/ovos_plugin_manager/audio_transformers.py +++ b/ovos_plugin_manager/audio_transformers.py @@ -1,25 +1,7 @@ -from ovos_utils.log import LOG - from ovos_plugin_manager.templates.transformers import AudioTransformer, AudioLanguageDetector from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_audio_transformer_plugins() -> dict: """ Find all installed plugins diff --git a/ovos_plugin_manager/coreference.py b/ovos_plugin_manager/coreference.py index 6d58d546..0f94274c 100644 --- a/ovos_plugin_manager/coreference.py +++ b/ovos_plugin_manager/coreference.py @@ -8,22 +8,6 @@ replace_coreferences -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_coref_plugins() -> dict: """ Find all installed plugins diff --git a/ovos_plugin_manager/g2p.py b/ovos_plugin_manager/g2p.py index 5146ea5c..64497a15 100644 --- a/ovos_plugin_manager/g2p.py +++ b/ovos_plugin_manager/g2p.py @@ -3,23 +3,6 @@ from ovos_plugin_manager.utils import normalize_lang, PluginTypes, PluginConfigTypes from ovos_plugin_manager.templates.g2p import Grapheme2PhonemePlugin, PhonemeAlphabet from ovos_utils.log import LOG -from ovos_config import Configuration - - -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) def find_g2p_plugins() -> dict: diff --git a/ovos_plugin_manager/gui.py b/ovos_plugin_manager/gui.py index 5bd1fc73..6759a9a9 100644 --- a/ovos_plugin_manager/gui.py +++ b/ovos_plugin_manager/gui.py @@ -5,22 +5,6 @@ from ovos_utils.log import LOG -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_gui_plugins() -> dict: """ Find all installed plugins diff --git a/ovos_plugin_manager/keywords.py b/ovos_plugin_manager/keywords.py index 9ad3152e..1de2d34b 100644 --- a/ovos_plugin_manager/keywords.py +++ b/ovos_plugin_manager/keywords.py @@ -4,22 +4,6 @@ from ovos_plugin_manager.templates.keywords import KeywordExtractor -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_keyword_extract_plugins() -> dict: """ Find all installed plugins diff --git a/ovos_plugin_manager/language.py b/ovos_plugin_manager/language.py index ed9f8fec..ebf2a25c 100644 --- a/ovos_plugin_manager/language.py +++ b/ovos_plugin_manager/language.py @@ -7,22 +7,6 @@ from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_tx_plugins() -> dict: """ Find all installed plugins diff --git a/ovos_plugin_manager/metadata_transformers.py b/ovos_plugin_manager/metadata_transformers.py index 4872404e..c70fbc8e 100644 --- a/ovos_plugin_manager/metadata_transformers.py +++ b/ovos_plugin_manager/metadata_transformers.py @@ -1,23 +1,6 @@ from ovos_plugin_manager.utils import normalize_lang, PluginTypes, \ PluginConfigTypes from ovos_plugin_manager.templates.transformers import MetadataTransformer -from ovos_utils.log import LOG - - -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) def find_metadata_transformer_plugins() -> dict: diff --git a/ovos_plugin_manager/microphone.py b/ovos_plugin_manager/microphone.py index 5a7de18a..2e8f0c84 100644 --- a/ovos_plugin_manager/microphone.py +++ b/ovos_plugin_manager/microphone.py @@ -3,22 +3,6 @@ from ovos_utils.log import LOG -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_microphone_plugins() -> dict: """ Find all installed plugins diff --git a/ovos_plugin_manager/phal.py b/ovos_plugin_manager/phal.py index 935f9bb9..4a8228f4 100644 --- a/ovos_plugin_manager/phal.py +++ b/ovos_plugin_manager/phal.py @@ -4,22 +4,6 @@ from ovos_utils.log import LOG -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_phal_plugins() -> dict: """ Find all installed plugins diff --git a/ovos_plugin_manager/postag.py b/ovos_plugin_manager/postag.py index 726b7207..4a05e09e 100644 --- a/ovos_plugin_manager/postag.py +++ b/ovos_plugin_manager/postag.py @@ -5,22 +5,6 @@ from ovos_plugin_manager.templates.postag import PosTagger -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_postag_plugins() -> dict: """ Find all installed plugins diff --git a/ovos_plugin_manager/segmentation.py b/ovos_plugin_manager/segmentation.py index 1b56456b..b3807574 100644 --- a/ovos_plugin_manager/segmentation.py +++ b/ovos_plugin_manager/segmentation.py @@ -5,21 +5,6 @@ from ovos_plugin_manager.templates.segmentation import Segmenter -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - def find_segmentation_plugins() -> dict: """ diff --git a/ovos_plugin_manager/skills.py b/ovos_plugin_manager/skills.py index a38093dc..b48ee3f6 100644 --- a/ovos_plugin_manager/skills.py +++ b/ovos_plugin_manager/skills.py @@ -114,7 +114,7 @@ def get_default_skills_directory(conf: Optional[dict] = None) -> str: if path_override: log_deprecation("'directory_override' is deprecated!" "add the new path to 'extra_directories' instead", - "0.1.0") + "1.0.0") skills_folder = expanduser(path_override) elif conf["skills"].get("extra_directories") and \ len(conf["skills"].get("extra_directories")) > 0: diff --git a/ovos_plugin_manager/solvers.py b/ovos_plugin_manager/solvers.py index 23c0a7a7..352d2319 100644 --- a/ovos_plugin_manager/solvers.py +++ b/ovos_plugin_manager/solvers.py @@ -5,21 +5,6 @@ from ovos_utils.log import LOG -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - def find_question_solver_plugins() -> dict: """ diff --git a/ovos_plugin_manager/stt.py b/ovos_plugin_manager/stt.py index dd92ff0c..4cd8592c 100644 --- a/ovos_plugin_manager/stt.py +++ b/ovos_plugin_manager/stt.py @@ -7,22 +7,6 @@ from ovos_plugin_manager.templates.stt import STT, StreamingSTT, StreamThread -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_stt_plugins() -> dict: """ Find all installed plugins @@ -103,25 +87,6 @@ def get_stt_config(config: dict = None, module: str = None) -> dict: class OVOSSTTFactory: """ replicates the base mycroft class, but uses only OPM enabled plugins""" - MAPPINGS = { - "mycroft": "ovos-stt-plugin-selene", - "dummy": "ovos-stt-plugin-dummy", - "google": "ovos-stt-plugin-chromium", - # "google_cloud": GoogleCloudSTT, - # "google_cloud_streaming": GoogleCloudStreamingSTT, - # "wit": WITSTT, - # "ibm": IBMSTT, - # "kaldi": KaldiSTT, - # "bing": BingSTT, - # "govivace": GoVivaceSTT, - # "houndify": HoundifySTT, - # "deepspeech_server": DeepSpeechServerSTT, - # "deepspeech_stream_server": DeepSpeechStreamServerSTT, - # "mycroft_deepspeech": MycroftDeepSpeechSTT, - # "yandex": YandexSTT - "vosk": "ovos-stt-plugin-vosk", - "vosk_streaming": "ovos-stt-plugin-vosk-streaming" - } @staticmethod def get_class(config=None): @@ -136,8 +101,6 @@ def get_class(config=None): """ config = get_stt_config(config) stt_module = config["module"] - if stt_module in OVOSSTTFactory.MAPPINGS: - stt_module = OVOSSTTFactory.MAPPINGS[stt_module] return load_stt_plugin(stt_module) @staticmethod @@ -152,11 +115,6 @@ def create(config=None): } """ stt_config = get_stt_config(config) - plugin = stt_config.get("module", "dummy") - if plugin in OVOSSTTFactory.MAPPINGS: - log_deprecation("Module mappings will be deprecated", "0.1.0") - plugin = OVOSSTTFactory.MAPPINGS[plugin] - stt_config = get_stt_config(config, plugin) try: clazz = OVOSSTTFactory.get_class(stt_config) return clazz(stt_config) diff --git a/ovos_plugin_manager/templates/solvers.py b/ovos_plugin_manager/templates/solvers.py index 85b8ce25..c40ea4cf 100644 --- a/ovos_plugin_manager/templates/solvers.py +++ b/ovos_plugin_manager/templates/solvers.py @@ -121,7 +121,7 @@ def func_wrapper(*args, **kwargs): # NOTE: deprecate this at same time we # standardize plugin namespaces to opm.XXX log_deprecation("'context' kwarg has been deprecated, " - "please pass 'lang' as it's own kwarg instead", "0.1.0") + "please pass 'lang' as it's own kwarg instead", "1.0.0") if "lang" in kwargs["context"] and "lang" not in kwargs: kwargs["lang"] = kwargs["context"]["lang"] diff --git a/ovos_plugin_manager/templates/stt.py b/ovos_plugin_manager/templates/stt.py index 70f072b2..e028cad0 100644 --- a/ovos_plugin_manager/templates/stt.py +++ b/ovos_plugin_manager/templates/stt.py @@ -64,7 +64,7 @@ def runtime_requirements(self): @property @deprecated("self.recognizer has been deprecated! " - "if you need it 'from speech_recognition import Recognizer' directly", "0.1.0") + "if you need it 'from speech_recognition import Recognizer' directly", "1.0.0") def recognizer(self): # only imported here to not drag dependency from speech_recognition import Recognizer @@ -89,7 +89,7 @@ def lang(self, val): @property @deprecated("self.keys has been deprecated! " - "implement config handling directly instead", "0.1.0") + "implement config handling directly instead", "1.0.0") def keys(self): return self._keys or self.config_core.get("keys", {}) @@ -100,7 +100,7 @@ def keys(self, val): @property @deprecated("self.credential has been deprecated! " - "implement config handling directly instead", "0.1.0") + "implement config handling directly instead", "1.0.0") def credential(self): return self._credential or self.config.get("credential", {}) @@ -111,7 +111,7 @@ def credential(self, val): @staticmethod @deprecated("self.init_language has been deprecated! " - "implement config handling directly instead", "0.1.0") + "implement config handling directly instead", "1.0.0") def init_language(config_core): lang = config_core.get("lang", "en-US") langs = lang.split("-") @@ -141,14 +141,14 @@ def available_languages(self) -> set: class TokenSTT(STT, metaclass=ABCMeta): - @deprecated("TokenSTT is deprecated, please subclass from STT directly", "0.1.0") + @deprecated("TokenSTT is deprecated, please subclass from STT directly", "1.0.0") def __init__(self, config=None): super().__init__(config) self.token = self.credential.get("token") class GoogleJsonSTT(STT, metaclass=ABCMeta): - @deprecated("GoogleJsonSTT is deprecated, please subclass from STT directly", "0.1.0") + @deprecated("GoogleJsonSTT is deprecated, please subclass from STT directly", "1.0.0") def __init__(self, config=None): super().__init__(config) if not self.credential.get("json") or self.keys.get("google_cloud"): @@ -157,7 +157,7 @@ def __init__(self, config=None): class BasicSTT(STT, metaclass=ABCMeta): - @deprecated("BasicSTT is deprecated, please subclass from STT directly", "0.1.0") + @deprecated("BasicSTT is deprecated, please subclass from STT directly", "1.0.0") def __init__(self, config=None): super().__init__(config) self.username = str(self.credential.get("username")) @@ -166,7 +166,7 @@ def __init__(self, config=None): class KeySTT(STT, metaclass=ABCMeta): - @deprecated("KeySTT is deprecated, please subclass from STT directly", "0.1.0") + @deprecated("KeySTT is deprecated, please subclass from STT directly", "1.0.0") def __init__(self, config=None): super().__init__(config) self.id = str(self.credential.get("client_id")) diff --git a/ovos_plugin_manager/templates/tts.py b/ovos_plugin_manager/templates/tts.py index caccae2a..c4fc9f96 100644 --- a/ovos_plugin_manager/templates/tts.py +++ b/ovos_plugin_manager/templates/tts.py @@ -733,7 +733,7 @@ def __del__(self): # below code is all deprecated and marked for removal in next stable release @property @deprecated("self.enclosure has been deprecated, use EnclosureAPI directly decoupled from the plugin code", - "0.1.0") + "1.0.0") def enclosure(self): """Deprecated. Accessor for the enclosure property. @@ -747,7 +747,7 @@ def enclosure(self): @enclosure.setter @deprecated("self.enclosure has been deprecated, use EnclosureAPI directly decoupled from the plugin code", - "0.1.0") + "1.0.0") def enclosure(self, val): """Deprecated. Setter for the enclosure property. @@ -758,7 +758,7 @@ def enclosure(self, val): @property @deprecated("self.filename has been deprecated, unused for a long time now", - "0.1.0") + "1.0.0") def filename(self): """Deprecated. Accessor for the filename property. @@ -770,7 +770,7 @@ def filename(self): @filename.setter @deprecated("self.filename has been deprecated, unused for a long time now", - "0.1.0") + "1.0.0") def filename(self, val): """Deprecated. Setter for the filename property. @@ -780,7 +780,7 @@ def filename(self, val): @property @deprecated("self.tts_id has been deprecated, use TTSContext().tts_id", - "0.1.0") + "1.0.0") def tts_id(self): """Deprecated. Accessor for the tts_id property. @@ -791,7 +791,7 @@ def tts_id(self): @property @deprecated("self.cache has been deprecated, use TTSContext().get_cache", - "0.1.0") + "1.0.0") def cache(self): """Deprecated. Accessor for the cache property. @@ -803,7 +803,7 @@ def cache(self): @cache.setter @deprecated("self.cache has been deprecated, use TTSContext().get_cache", - "0.1.0") + "1.0.0") def cache(self, val): """Deprecated. Setter for the cache property. @@ -813,7 +813,7 @@ def cache(self, val): TTSContext._caches[self.tts_id] = val @deprecated("get_voice was never formally adopted and is unused, it will be removed", - "0.1.0") + "1.0.0") def get_voice(self, gender, lang=None): """Deprecated. Get a valid voice for the TTS engine. @@ -827,7 +827,7 @@ def get_voice(self, gender, lang=None): return gender @deprecated("get_cache has been deprecated, use TTSContext().get_cache directly", - "0.1.0") + "1.0.0") def get_cache(self, voice=None, lang=None): """Deprecated. Get the cache associated with the TTS context. @@ -841,14 +841,14 @@ def get_cache(self, voice=None, lang=None): return self._get_ctxt().get_cache(self.audio_ext, self.config) @deprecated("clear_cache has been deprecated, use TTSContext().get_cache directly", - "0.1.0") + "1.0.0") def clear_cache(self): """Deprecated. Clear all cached files.""" cache = self._get_ctxt().get_cache(self.audio_ext, self.config) cache.clear() @deprecated("save_phonemes has been deprecated, use TTSContext().get_cache directly", - "0.1.0") + "1.0.0") def save_phonemes(self, key, phonemes): """Deprecated. Cache phonemes. @@ -865,7 +865,7 @@ def save_phonemes(self, key, phonemes): return phoneme_file @deprecated("load_phonemes has been deprecated, use TTSContext().get_cache directly", - "0.1.0") + "1.0.0") def load_phonemes(self, key): """Deprecated. Load phonemes from cache file. @@ -880,7 +880,7 @@ def load_phonemes(self, key): return phoneme_file.load() @deprecated("get_from_cache has been deprecated, use TTSContext().get_from_cache directly", - "0.1.0") + "1.0.0") def get_from_cache(self, sentence): """Deprecated. Get data from the cache. @@ -902,7 +902,7 @@ def lang(self): @lang.setter @deprecated("language is defined per request in get_tts, self.lang is not used", - "0.1.0") + "1.0.0") def lang(self, val): LOG.warning("self.lang can not be set! it comes from the bus message") @@ -1186,7 +1186,7 @@ class RemoteTTS(TTS): """ @deprecated("RemoteTTS has been deprecated, please use the regular TTS class", - "0.1.0") + "1.0.0") def __init__(self, lang, config, url, api_path, validator): super(RemoteTTS, self).__init__(lang, config, validator) self.api_path = api_path diff --git a/ovos_plugin_manager/text_transformers.py b/ovos_plugin_manager/text_transformers.py index efc277dd..4f118092 100644 --- a/ovos_plugin_manager/text_transformers.py +++ b/ovos_plugin_manager/text_transformers.py @@ -1,23 +1,6 @@ from ovos_plugin_manager.utils import normalize_lang, \ PluginTypes, PluginConfigTypes from ovos_plugin_manager.templates.transformers import UtteranceTransformer -from ovos_utils import LOG - - -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) def find_utterance_transformer_plugins() -> dict: @@ -84,25 +67,3 @@ def get_utterance_transformer_supported_langs() -> dict: from ovos_plugin_manager.utils.config import get_plugin_supported_languages return get_plugin_supported_languages(PluginTypes.UTTERANCE_TRANSFORMER) - -def find_text_transformer_plugins() -> dict: - """ - Find all installed plugins - @return: dict plugin names to entrypoints - """ - # TODO: Deprecate in 0.1.0 - LOG.warning(f"This reference is deprecated. " - f"Use `find_utterance_transformer_plugins") - return find_utterance_transformer_plugins() - - -def load_text_transformer_plugin(module_name: str) -> type(UtteranceTransformer): - """ - Get an uninstantiated class for the requested module_name - @param module_name: Plugin entrypoint name to load - @return: Uninstantiated class - """ - # TODO: Deprecate in 0.1.0 - LOG.warning(f"This reference is deprecated. " - f"Use `find_utterance_transformer_plugins") - return load_utterance_transformer_plugin(module_name) diff --git a/ovos_plugin_manager/tokenization.py b/ovos_plugin_manager/tokenization.py index 598331fa..2ce8f1ff 100644 --- a/ovos_plugin_manager/tokenization.py +++ b/ovos_plugin_manager/tokenization.py @@ -5,22 +5,6 @@ from ovos_plugin_manager.templates.tokenization import Tokenizer -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_tokenization_plugins() -> dict: """ Find all installed plugins diff --git a/ovos_plugin_manager/tts.py b/ovos_plugin_manager/tts.py index fe453423..b529ad3a 100644 --- a/ovos_plugin_manager/tts.py +++ b/ovos_plugin_manager/tts.py @@ -11,22 +11,6 @@ from hashlib import md5 -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_tts_plugins() -> dict: """ Find all installed plugins @@ -143,25 +127,6 @@ def get_voices(scan=False): class OVOSTTSFactory: """ replicates the base mycroft class, but uses only OPM enabled plugins""" - MAPPINGS = { - "dummy": "ovos-tts-plugin-dummy", - "mimic": "ovos-tts-plugin-mimic", - "mimic2": "ovos-tts-plugin-mimic2", - "mimic3": "ovos-tts-plugin-mimic3", - "google": "ovos-tts-plugin-google-tx", - "marytts": "ovos-tts-plugin-marytts", - # "fatts": FATTS, - # "festival": Festival, - "espeak": "ovos_tts_plugin_espeakng", - # "spdsay": SpdSay, - # "watson": WatsonTTS, - # "bing": BingTTS, - "responsive_voice": "ovos-tts-plugin-responsivevoice", - # "yandex": YandexTTS, - "polly": "ovos-tts-plugin-polly", - # "mozilla": MozillaTTS, - "pico": "ovos-tts-plugin-pico" - } @staticmethod def get_class(config=None): @@ -175,9 +140,7 @@ def get_class(config=None): } """ config = config or get_tts_config() - tts_module = config.get("module") or "dummy" - if tts_module in OVOSTTSFactory.MAPPINGS: - tts_module = OVOSTTSFactory.MAPPINGS[tts_module] + tts_module = config.get("module") or "ovos-tts-plugin-dummy" return load_tts_plugin(tts_module) @staticmethod @@ -192,13 +155,7 @@ def create(config=None): } """ tts_config = get_tts_config(config) - tts_module = tts_config.get('module', 'dummy') - if tts_module in OVOSTTSFactory.MAPPINGS: - # The configured module maps to a valid plugin; get configuration - # again to make sure any module-specific config/overrides are loaded - log_deprecation("Module mappings will be deprecated", "0.1.0") - tts_module = OVOSTTSFactory.MAPPINGS[tts_module] - tts_config = get_tts_config(config, tts_module) + tts_module = tts_config.get('module', 'ovos-tts-plugin-dummy') try: clazz = OVOSTTSFactory.get_class(tts_config) if clazz: @@ -216,3 +173,8 @@ def create(config=None): f'\nAvailable modules: {modules}') raise return tts + + +if __name__ == "__main__": + lang = "en-us" + print(find_tts_plugins()) \ No newline at end of file diff --git a/ovos_plugin_manager/vad.py b/ovos_plugin_manager/vad.py index 1715d53d..966c3edf 100644 --- a/ovos_plugin_manager/vad.py +++ b/ovos_plugin_manager/vad.py @@ -4,22 +4,6 @@ from ovos_plugin_manager.templates.vad import VADEngine -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_vad_plugins() -> dict: """ Find all installed plugins diff --git a/ovos_plugin_manager/wakewords.py b/ovos_plugin_manager/wakewords.py index f667f997..f098b64a 100644 --- a/ovos_plugin_manager/wakewords.py +++ b/ovos_plugin_manager/wakewords.py @@ -10,22 +10,6 @@ PluginTypes, PluginConfigTypes -def find_plugins(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import find_plugins - return find_plugins(*args, **kwargs) - - -def load_plugin(*args, **kwargs): - # TODO: Deprecate in 0.1.0 - LOG.warning("This reference is deprecated. " - "Import from ovos_plugin_manager.utils directly") - from ovos_plugin_manager.utils import load_plugin - return load_plugin(*args, **kwargs) - - def find_wake_word_plugins() -> dict: """ Find all installed plugins diff --git a/test/unittests/test_stt.py b/test/unittests/test_stt.py index a1705dd3..de866fe5 100644 --- a/test/unittests/test_stt.py +++ b/test/unittests/test_stt.py @@ -91,18 +91,11 @@ def test_get_stt_config(self, get_config): class TestSTTFactory(unittest.TestCase): - def test_mappings(self): - from ovos_plugin_manager.stt import OVOSSTTFactory - self.assertIsInstance(OVOSSTTFactory.MAPPINGS, dict) - for key in OVOSSTTFactory.MAPPINGS: - self.assertIsInstance(key, str) - self.assertIsInstance(OVOSSTTFactory.MAPPINGS[key], str) - self.assertNotEqual(key, OVOSSTTFactory.MAPPINGS[key]) @patch("ovos_plugin_manager.stt.load_stt_plugin") def test_get_class(self, load_plugin): from ovos_plugin_manager.stt import OVOSSTTFactory - global_config = {"stt": {"module": "dummy"}} + global_config = {"stt": {"module": "ovos-stt-plugin-dummy"}} tts_config = {"module": "test-stt-plugin-test"} # Test load plugin mapped global config @@ -120,7 +113,7 @@ def test_create(self, get_class): get_class.return_value = plugin_class global_config = {"lang": "en-gb", - "stt": {"module": "dummy", + "stt": {"module": "ovos-stt-plugin-dummy", "ovos-stt-plugin-dummy": {"config": True, "lang": "en-ca"}}} stt_config = {"lang": "es-es", diff --git a/test/unittests/test_tts.py b/test/unittests/test_tts.py index ec3baf16..b5602921 100644 --- a/test/unittests/test_tts.py +++ b/test/unittests/test_tts.py @@ -206,18 +206,11 @@ def test_get_voices(self): class TestTTSFactory(unittest.TestCase): - def test_mappings(self): - from ovos_plugin_manager.tts import OVOSTTSFactory - self.assertIsInstance(OVOSTTSFactory.MAPPINGS, dict) - for key in OVOSTTSFactory.MAPPINGS: - self.assertIsInstance(key, str) - self.assertIsInstance(OVOSTTSFactory.MAPPINGS[key], str) - self.assertNotEqual(key, OVOSTTSFactory.MAPPINGS[key]) @patch("ovos_plugin_manager.tts.load_tts_plugin") def test_get_class(self, load_plugin): from ovos_plugin_manager.tts import OVOSTTSFactory - global_config = {"tts": {"module": "dummy"}} + global_config = {"tts": {"module": "ovos-tts-plugin-dummy"}} tts_config = {"module": "test-tts-plugin-test"} # Test load plugin mapped global config @@ -235,7 +228,7 @@ def test_create(self, get_class): get_class.return_value = plugin_class global_config = {"lang": "en-gb", - "tts": {"module": "dummy", + "tts": {"module": "ovos-tts-plugin-dummy", "ovos-tts-plugin-dummy": {"config": True, "lang": "en-ca"}}} tts_config = {"lang": "es-es", From 4bbafb37ccc0a0d99cda85852cf38ba95ff5b1f5 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 5 Sep 2024 09:53:34 +0000 Subject: [PATCH 120/129] Increment Version to 0.1.1a1 --- ovos_plugin_manager/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 316a5fd0..4f64505b 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -2,6 +2,6 @@ # START_VERSION_BLOCK VERSION_MAJOR = 0 VERSION_MINOR = 1 -VERSION_BUILD = 0 -VERSION_ALPHA = 0 +VERSION_BUILD = 1 +VERSION_ALPHA = 1 # END_VERSION_BLOCK From b16b7ce33af44bed4d4f130f8fe8be61b79afcb0 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 5 Sep 2024 09:54:04 +0000 Subject: [PATCH 121/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c000d821..7ed860ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.1.0](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.1.0) (2024-09-02) +## [0.1.1a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.1.1a1) (2024-09-05) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.1.0) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.1.1a1) **Implemented enhancements:** @@ -36,6 +36,7 @@ **Merged pull requests:** +- planned\_deprecations [\#254](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/254) ([JarbasAl](https://github.com/JarbasAl)) - refactor/improve\_readwritestream [\#234](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/234) ([JarbasAl](https://github.com/JarbasAl)) - refactor/deprecation\_warnings [\#233](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/233) ([JarbasAl](https://github.com/JarbasAl)) - fix/py3.12 [\#231](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/231) ([JarbasAl](https://github.com/JarbasAl)) From 004ad0655210bc1f594145df04b91cde8b05b048 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:12:35 +0100 Subject: [PATCH 122/129] license compliance (#255) * refactor/license compliance keep the header notices * refactor/license compliance keep the header notices --- ovos_plugin_manager/templates/solvers.py | 138 +---------------- ovos_plugin_manager/thirdparty/__init__.py | 1 + ovos_plugin_manager/thirdparty/solvers.py | 166 +++++++++++++++++++++ setup.py | 1 + 4 files changed, 170 insertions(+), 136 deletions(-) create mode 100644 ovos_plugin_manager/thirdparty/__init__.py create mode 100644 ovos_plugin_manager/thirdparty/solvers.py diff --git a/ovos_plugin_manager/templates/solvers.py b/ovos_plugin_manager/templates/solvers.py index c40ea4cf..aea580d0 100644 --- a/ovos_plugin_manager/templates/solvers.py +++ b/ovos_plugin_manager/templates/solvers.py @@ -1,19 +1,14 @@ -# Original AbstractSolver class taken from: https://github.com/Neongeckocom/neon_solvers, licensed under BSD-3 -# QuestionSolver Improvements and other solver classes are OVOS originals licensed under Apache 2.0 - import abc import inspect -from functools import wraps, lru_cache +from functools import wraps from typing import Optional, List, Iterable, Tuple, Dict, Union, Any from json_database import JsonStorageXDG -from ovos_utils import flatten_list from ovos_utils.log import LOG, log_deprecation from ovos_utils.xdg_utils import xdg_cache_home -from quebra_frases import sentence_tokenize -from ovos_plugin_manager.language import OVOSLangTranslationFactory, OVOSLangDetectionFactory from ovos_plugin_manager.templates.language import LanguageTranslator, LanguageDetector +from ovos_plugin_manager.thirdparty.solvers import AbstractSolver def auto_translate(translate_keys: List[str], translate_str_args=True): @@ -137,135 +132,6 @@ def func_wrapper(*args, **kwargs): return func_decorator -class AbstractSolver: - """Base class for solvers that perform various NLP tasks.""" - - def __init__(self, config=None, - translator: Optional[LanguageTranslator] = None, - detector: Optional[LanguageDetector] = None, - priority=50, - enable_tx=False, - enable_cache=False, - internal_lang: Optional[str] = None, - *args, **kwargs): - self.priority = priority - self.enable_tx = enable_tx - self.enable_cache = enable_cache - self.config = config or {} - self.supported_langs = self.config.get("supported_langs") or [] - self.default_lang = internal_lang or self.config.get("lang", "en") - if self.default_lang not in self.supported_langs: - self.supported_langs.insert(0, self.default_lang) - self._translator = translator or OVOSLangTranslationFactory.create() if self.enable_tx else None - self._detector = detector or OVOSLangDetectionFactory.create() if self.enable_tx else None - LOG.debug(f"{self.__class__.__name__} default language: {self.default_lang}") - - @property - def detector(self): - """ language detector, lazy init on first access""" - if not self._detector: - # if it's being used, there is no recovery, do not try: except: - self._detector = OVOSLangDetectionFactory.create() - return self._detector - - @detector.setter - def detector(self, val): - self._detector = val - - @property - def translator(self): - """ language translator, lazy init on first access""" - if not self._translator: - # if it's being used, there is no recovery, do not try: except: - self._translator = OVOSLangTranslationFactory.create() - return self._translator - - @translator.setter - def translator(self, val): - self._translator = val - - @staticmethod - def sentence_split(text: str, max_sentences: int = 25) -> List[str]: - """ - Split text into sentences. - - :param text: Input text. - :param max_sentences: Maximum number of sentences to return. - :return: List of sentences. - """ - try: - # sentence_tokenize occasionally has issues with \n for some reason - return flatten_list([sentence_tokenize(t) - for t in text.split("\n")])[:max_sentences] - except Exception as e: - LOG.exception(f"Error in sentence_split: {e}") - return [text] - - @lru_cache(maxsize=128) - def detect_language(self, text: str) -> str: - """ - Detect the language of the input text. - - :param text: Input text. - :return: Detected language code. - """ - return self.detector.detect(text) - - @lru_cache(maxsize=128) - def translate(self, text: str, - target_lang: Optional[str] = None, - source_lang: Optional[str] = None) -> str: - """ - Translate text from source_lang to target_lang. - - :param text: Input text. - :param target_lang: Target language code. - :param source_lang: Source language code. - :return: Translated text. - """ - source_lang = source_lang or self.detect_language(text) - target_lang = target_lang or self.default_lang - if source_lang.split("-")[0] == target_lang.split("-")[0]: - return text # skip translation - return self.translator.translate(text, - target=target_lang, - source=source_lang) - - def translate_list(self, data: List[str], - target_lang: Optional[str] = None, - source_lang: Optional[str] = None) -> List[str]: - """ - Translate a list of strings from source_lang to target_lang. - - :param data: List of strings. - :param target_lang: Target language code. - :param source_lang: Source language code. - :return: List of translated strings. - """ - return self.translator.translate_list(data, - lang_tgt=target_lang, - lang_src=source_lang) - - def translate_dict(self, data: Dict[str, str], - target_lang: Optional[str] = None, - source_lang: Optional[str] = None) -> Dict[str, str]: - """ - Translate a dictionary of strings from source_lang to target_lang. - - :param data: Dictionary of strings. - :param target_lang: Target language code. - :param source_lang: Source language code. - :return: Dictionary of translated strings. - """ - return self.translator.translate_dict(data, - lang_tgt=target_lang, - lang_src=source_lang) - - def shutdown(self): - """Module specific shutdown method.""" - pass - - class QuestionSolver(AbstractSolver): """ A solver for free-form, unconstrained spoken questions that handles automatic translation as needed. diff --git a/ovos_plugin_manager/thirdparty/__init__.py b/ovos_plugin_manager/thirdparty/__init__.py new file mode 100644 index 00000000..ebc428b9 --- /dev/null +++ b/ovos_plugin_manager/thirdparty/__init__.py @@ -0,0 +1 @@ +# any code that isnt OVOS original should live in this submodule to ensure proper attribution diff --git a/ovos_plugin_manager/thirdparty/solvers.py b/ovos_plugin_manager/thirdparty/solvers.py new file mode 100644 index 00000000..91bdef55 --- /dev/null +++ b/ovos_plugin_manager/thirdparty/solvers.py @@ -0,0 +1,166 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, +# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo +# BSD-3 License +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from functools import lru_cache +from typing import Optional, List, Dict + +from ovos_utils import flatten_list +from ovos_utils.log import LOG +from quebra_frases import sentence_tokenize + +from ovos_plugin_manager.language import OVOSLangTranslationFactory, OVOSLangDetectionFactory +from ovos_plugin_manager.templates.language import LanguageTranslator, LanguageDetector + + +class AbstractSolver: + """Base class for solvers that perform various NLP tasks.""" + + def __init__(self, config=None, + translator: Optional[LanguageTranslator] = None, + detector: Optional[LanguageDetector] = None, + priority=50, + enable_tx=False, + enable_cache=False, + internal_lang: Optional[str] = None, + *args, **kwargs): + self.priority = priority + self.enable_tx = enable_tx + self.enable_cache = enable_cache + self.config = config or {} + self.supported_langs = self.config.get("supported_langs") or [] + self.default_lang = internal_lang or self.config.get("lang", "en") + if self.default_lang not in self.supported_langs: + self.supported_langs.insert(0, self.default_lang) + self._translator = translator or OVOSLangTranslationFactory.create() if self.enable_tx else None + self._detector = detector or OVOSLangDetectionFactory.create() if self.enable_tx else None + LOG.debug(f"{self.__class__.__name__} default language: {self.default_lang}") + + @property + def detector(self): + """ language detector, lazy init on first access""" + if not self._detector: + # if it's being used, there is no recovery, do not try: except: + self._detector = OVOSLangDetectionFactory.create() + return self._detector + + @detector.setter + def detector(self, val): + self._detector = val + + @property + def translator(self): + """ language translator, lazy init on first access""" + if not self._translator: + # if it's being used, there is no recovery, do not try: except: + self._translator = OVOSLangTranslationFactory.create() + return self._translator + + @translator.setter + def translator(self, val): + self._translator = val + + @staticmethod + def sentence_split(text: str, max_sentences: int = 25) -> List[str]: + """ + Split text into sentences. + + :param text: Input text. + :param max_sentences: Maximum number of sentences to return. + :return: List of sentences. + """ + try: + # sentence_tokenize occasionally has issues with \n for some reason + return flatten_list([sentence_tokenize(t) + for t in text.split("\n")])[:max_sentences] + except Exception as e: + LOG.exception(f"Error in sentence_split: {e}") + return [text] + + @lru_cache(maxsize=128) + def detect_language(self, text: str) -> str: + """ + Detect the language of the input text. + + :param text: Input text. + :return: Detected language code. + """ + return self.detector.detect(text) + + @lru_cache(maxsize=128) + def translate(self, text: str, + target_lang: Optional[str] = None, + source_lang: Optional[str] = None) -> str: + """ + Translate text from source_lang to target_lang. + + :param text: Input text. + :param target_lang: Target language code. + :param source_lang: Source language code. + :return: Translated text. + """ + source_lang = source_lang or self.detect_language(text) + target_lang = target_lang or self.default_lang + if source_lang.split("-")[0] == target_lang.split("-")[0]: + return text # skip translation + return self.translator.translate(text, + target=target_lang, + source=source_lang) + + def translate_list(self, data: List[str], + target_lang: Optional[str] = None, + source_lang: Optional[str] = None) -> List[str]: + """ + Translate a list of strings from source_lang to target_lang. + + :param data: List of strings. + :param target_lang: Target language code. + :param source_lang: Source language code. + :return: List of translated strings. + """ + return self.translator.translate_list(data, + lang_tgt=target_lang, + lang_src=source_lang) + + def translate_dict(self, data: Dict[str, str], + target_lang: Optional[str] = None, + source_lang: Optional[str] = None) -> Dict[str, str]: + """ + Translate a dictionary of strings from source_lang to target_lang. + + :param data: Dictionary of strings. + :param target_lang: Target language code. + :param source_lang: Source language code. + :return: Dictionary of translated strings. + """ + return self.translator.translate_dict(data, + lang_tgt=target_lang, + lang_src=source_lang) + + def shutdown(self): + """Module specific shutdown method.""" + pass diff --git a/setup.py b/setup.py index 2567aee9..b1fe1515 100644 --- a/setup.py +++ b/setup.py @@ -65,6 +65,7 @@ def required(requirements_file): packages=['ovos_plugin_manager', 'ovos_plugin_manager.templates', 'ovos_plugin_manager.utils', + 'ovos_plugin_manager.thirdparty', 'ovos_plugin_manager.hardware', 'ovos_plugin_manager.hardware.led'], url='https://github.com/OpenVoiceOS/OVOS-plugin-manager', From 764749fd3132c76f3438e5ae08a4d542a79f9151 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 5 Sep 2024 10:12:50 +0000 Subject: [PATCH 123/129] Increment Version to 0.1.1a2 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 4f64505b..2414d9e8 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 1 VERSION_BUILD = 1 -VERSION_ALPHA = 1 +VERSION_ALPHA = 2 # END_VERSION_BLOCK From 2428205121c29c3b782b8e87bd7b7db63d27a855 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 5 Sep 2024 10:13:16 +0000 Subject: [PATCH 124/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ed860ae..10b23e12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.1.1a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.1.1a1) (2024-09-05) +## [0.1.1a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.1.1a2) (2024-09-05) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.1.1a1) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.1.1a2) **Implemented enhancements:** @@ -36,6 +36,7 @@ **Merged pull requests:** +- license compliance [\#255](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/255) ([JarbasAl](https://github.com/JarbasAl)) - planned\_deprecations [\#254](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/254) ([JarbasAl](https://github.com/JarbasAl)) - refactor/improve\_readwritestream [\#234](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/234) ([JarbasAl](https://github.com/JarbasAl)) - refactor/deprecation\_warnings [\#233](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/233) ([JarbasAl](https://github.com/JarbasAl)) From b3aad673f94ac198f07472df51c5f41e8cc9c26b Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:35:03 +0100 Subject: [PATCH 125/129] fix/restore_dead_code (#256) with deprecation warnings --- ovos_plugin_manager/templates/tts.py | 39 +++++++++++++++++++++++++-- ovos_plugin_manager/utils/__init__.py | 6 +++-- test/unittests/test_tts.py | 25 +++-------------- 3 files changed, 45 insertions(+), 25 deletions(-) diff --git a/ovos_plugin_manager/templates/tts.py b/ovos_plugin_manager/templates/tts.py index c4fc9f96..b05ecaf5 100644 --- a/ovos_plugin_manager/templates/tts.py +++ b/ovos_plugin_manager/templates/tts.py @@ -9,7 +9,7 @@ from pathlib import Path from queue import Queue from threading import Thread -from typing import AsyncIterable, List, Dict +from typing import AsyncIterable, List, Dict, Tuple, Optional import quebra_frases import requests @@ -21,7 +21,6 @@ from ovos_utils import classproperty from ovos_utils.fakebus import FakeBus from ovos_utils.file_utils import get_cache_directory -from ovos_utils.file_utils import resolve_resource_file from ovos_utils.lang.visimes import VISIMES from ovos_utils.log import LOG, deprecated, log_deprecation from ovos_utils.metrics import Stopwatch @@ -132,6 +131,42 @@ def curate_caches(cls): for cache in TTSContext._caches.values(): cache.curate() + ########### + # deprecated methods + @deprecated("'get_message' has been deprecated without replacement", "1.0.0") + def get_message(self, kwargs) -> Optional[Message]: + msg = kwargs.get("message") or dig_for_message() + if msg and isinstance(msg, Message): + return msg + + @deprecated("'self.get_lang' has been deprecated, access self.lang directly", "1.0.0") + def get_lang(self, kwargs) -> str: + return kwargs.get("lang") or self.lang + + @deprecated("'self.get_gender' has been deprecated, access self.voice and self.lang directly", "1.0.0") + def get_gender(self, kwargs) -> Optional[str]: + gender = kwargs.get("gender") + message = self.get_message(kwargs) + if not gender and message: + gender = message.data.get("gender") or \ + message.context.get("gender") + return gender + + @deprecated("'self.get_voice' has been deprecated, access self.voice directly", "1.0.0") + def get_voice(self, kwargs): + voice = kwargs.get("voice") + message = self.get_message(kwargs) + if not voice and message: + # get voice from message object if possible + voice = message.data.get("voice") or \ + message.context.get("voice") + return voice or self.voice + + @deprecated("'self.get' has been deprecated, access self.voice and self.lang directly", "1.0.0") + def get(self, kwargs=None) -> Tuple[str, Optional[str]]: + kwargs = kwargs or {} + return self.get_lang(kwargs), self.get_voice(kwargs) + class TTS: """TTS abstract class to be implemented by all TTS engines. diff --git a/ovos_plugin_manager/utils/__init__.py b/ovos_plugin_manager/utils/__init__.py index c1c4ee21..8972c4bf 100644 --- a/ovos_plugin_manager/utils/__init__.py +++ b/ovos_plugin_manager/utils/__init__.py @@ -18,7 +18,7 @@ from typing import Optional import pkg_resources -from ovos_utils.log import LOG +from ovos_utils.log import LOG, log_deprecation class PluginTypes(str, Enum): @@ -198,7 +198,9 @@ class ReadWriteStream: with an optional maximum buffer size """ - def __init__(self, s=b'', max_size=None): + def __init__(self, s=b'', chop_samples=-1, max_size=None): + if chop_samples != -1: + log_deprecation("'chop_samples' kwarg has been deprecated and will be ignored", "1.0.0") self.buffer = deque(s) self.write_event = Event() self.lock = Lock() diff --git a/test/unittests/test_tts.py b/test/unittests/test_tts.py index b5602921..6ad28ba9 100644 --- a/test/unittests/test_tts.py +++ b/test/unittests/test_tts.py @@ -3,7 +3,6 @@ from unittest.mock import patch, Mock from ovos_bus_client.session import Session -from ovos_config import Configuration from ovos_utils.fakebus import FakeBus, Message from ovos_plugin_manager.templates.tts import TTS, TTSContext @@ -119,24 +118,8 @@ def test_format_speak_tags_with_speech_no_tags(self): tagged_with_exclusion = TTS.format_speak_tags("Don'tSpeak This.But Not this.", False) self.assertEqual(tagged_with_exclusion, valid_output) - def test_playback_thread(self): - pass - # TODO - - def test_tts_context(self): - pass - # TODO - def test_tts_validator(self): - pass - # TODO - - def test_concat_tts(self): - pass - # TODO - - def test_remote_tt(self): - pass + from ovos_plugin_manager.templates.tts import TTSValidator # TODO @@ -193,15 +176,15 @@ def test_get_tts_config(self, get_config): self.CONFIG_SECTION, None) def test_get_voice_id(self): - pass + from ovos_plugin_manager.tts import get_voice_id # TODO def test_scan_voices(self): - pass + from ovos_plugin_manager.tts import scan_voices # TODO def test_get_voices(self): - pass + from ovos_plugin_manager.tts import get_voices # TODO From 5b548e6db40440b4583e0ab0365e31b719bffebb Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 5 Sep 2024 10:35:19 +0000 Subject: [PATCH 126/129] Increment Version to 0.1.1a3 --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index 2414d9e8..d21ca0c4 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 1 VERSION_BUILD = 1 -VERSION_ALPHA = 2 +VERSION_ALPHA = 3 # END_VERSION_BLOCK From d89d0a05ea0a38f71ede654f471c28c84ba643e8 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 5 Sep 2024 10:35:45 +0000 Subject: [PATCH 127/129] Update Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10b23e12..e8b0ada1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [0.1.1a2](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.1.1a2) (2024-09-05) +## [0.1.1a3](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/0.1.1a3) (2024-09-05) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.1.1a2) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/V...0.1.1a3) **Implemented enhancements:** @@ -20,6 +20,7 @@ **Fixed bugs:** - abstractmethod decorator breaks OCP 0.0.6 compat. [\#229](https://github.com/OpenVoiceOS/ovos-plugin-manager/issues/229) +- fix/restore\_dead\_code [\#256](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/256) ([JarbasAl](https://github.com/JarbasAl)) - fix/context\_kwarg\_backwards\_compat [\#248](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/248) ([JarbasAl](https://github.com/JarbasAl)) - refactor/solver\_decorators [\#244](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/244) ([JarbasAl](https://github.com/JarbasAl)) - fix/missing\_property [\#239](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/239) ([JarbasAl](https://github.com/JarbasAl)) From 910a0111e4cd710a1075dfe0fee70a8972471cac Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:47:30 +0100 Subject: [PATCH 128/129] Update requirements.txt --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 0f6b4437..9ae8badd 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,5 +1,5 @@ ovos-utils < 0.2.0, >=0.0.38 -ovos-bus-client < 0.2.0, >=0.0.9a3 +ovos-bus-client < 0.2.0, >=0.0.9 ovos-config < 0.2.0, >=0.0.12 combo_lock~=0.2 requests~=2.26 From f208c246ec14ff2fd1424e67d1bdacea539cf951 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:52:54 +0100 Subject: [PATCH 129/129] Update version.py --- ovos_plugin_manager/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ovos_plugin_manager/version.py b/ovos_plugin_manager/version.py index d21ca0c4..c79fb1ef 100644 --- a/ovos_plugin_manager/version.py +++ b/ovos_plugin_manager/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 1 VERSION_BUILD = 1 -VERSION_ALPHA = 3 +VERSION_ALPHA = 0 # END_VERSION_BLOCK