From 01043fae7dbf87675be9f8507dd27821a130d46b Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sat, 12 Feb 2022 11:47:26 -0600 Subject: [PATCH 01/12] Updated install script, added support for multiple TTS and STT implementations --- .gitignore | 6 +- requirements.txt | 3 +- run_jarvis.sh | 7 +- setup.sh | 1 + src/jarvis/jarvis/__init__.py | 4 +- src/jarvis/jarvis/engines/__init__.py | 3 +- src/jarvis/jarvis/engines/stt.py | 54 +-------- src/jarvis/jarvis/engines/stt_google.py | 96 +++++++++++++++ src/jarvis/jarvis/engines/stt_vosk.py | 142 +++++++++++++++++++++++ src/jarvis/jarvis/engines/tts.py | 4 +- src/jarvis/jarvis/engines/tts_pyttsx3.py | 133 +++++++++++++++++++++ 11 files changed, 390 insertions(+), 63 deletions(-) mode change 100644 => 100755 run_jarvis.sh create mode 100644 src/jarvis/jarvis/engines/stt_google.py create mode 100644 src/jarvis/jarvis/engines/stt_vosk.py create mode 100644 src/jarvis/jarvis/engines/tts_pyttsx3.py diff --git a/.gitignore b/.gitignore index 68c24744..0851fff6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,8 @@ __pycache__ # Exclude pycharm metadata - .idea/ \ No newline at end of file + .idea/ + +model/ +jarvis_virtualenv/ + diff --git a/requirements.txt b/requirements.txt index a9327dd4..247aefcc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,6 @@ lxml==4.6.3 mock==3.0.5 more-itertools==7.0.0 nltk==3.4.5 -numpy==1.16.4 patch==1.16 platformdirs==2.4.0 playsound==1.2.2 @@ -44,8 +43,10 @@ textblob==0.15.3 tzlocal==1.5.1 urllib3==1.24.3 virtualenv==20.8.1 +vosk==0.3.32 web-cache==1.1.0 wikipedia==1.4.0 wolframalpha==3.0.1 word2number==1.1 xmltodict==0.12.0 +sounddevice==0.4.4 diff --git a/run_jarvis.sh b/run_jarvis.sh old mode 100644 new mode 100755 index 67b46182..01c47b2e --- a/run_jarvis.sh +++ b/run_jarvis.sh @@ -2,14 +2,15 @@ # -------------------------------- # Start MongoDB service # -------------------------------- -sudo systemctl start mongodb +#sudo systemctl start mongod # -------------------------------- # Start Jarvis service with virtualenv # -------------------------------- -./jarvis_virtualenv/bin/python ./src/jarvis/start.py +#./jarvis_virtualenv/bin/python ./src/jarvis/start.py +python3 ./src/jarvis/start.py # -------------------------------- # Stop MongoDB service # -------------------------------- -sudo systemctl stop mongodb \ No newline at end of file +#sudo systemctl stop mongod diff --git a/setup.sh b/setup.sh index 4cf114e8..01a3f82e 100755 --- a/setup.sh +++ b/setup.sh @@ -32,6 +32,7 @@ sudo apt-get install python3-setuptools && / sudo apt-get install python3-pip && / sudo apt-get install python3-venv && / sudo apt-get install portaudio19-dev python3-pyaudio python3-pyaudio && / +sudo apt-get install python3-numpy && / sudo apt-get install libasound2-plugins libsox-fmt-all libsox-dev libxml2-dev libxslt-dev sox ffmpeg && / sudo apt-get install espeak && / sudo apt-get install libcairo2-dev libgirepository1.0-dev gir1.2-gtk-3.0 && / diff --git a/src/jarvis/jarvis/__init__.py b/src/jarvis/jarvis/__init__.py index b77fb60c..fdda8adb 100644 --- a/src/jarvis/jarvis/__init__.py +++ b/src/jarvis/jarvis/__init__.py @@ -55,5 +55,5 @@ # ---------------------------------------------------------------------------------------------------------------------- # Create assistant input and output engine instances # ---------------------------------------------------------------------------------------------------------------------- -input_engine = engines.STTEngine() if input_mode == InputMode.VOICE.value else engines.TTTEngine() -output_engine = engines.TTSEngine() if response_in_speech else engines.TTTEngine() +input_engine = engines.STTEngineVosk()# if input_mode == InputMode.VOICE.value else engines.TTTEngine() +output_engine = engines.TTSEngine()# if response_in_speech else engines.TTTEngine() diff --git a/src/jarvis/jarvis/engines/__init__.py b/src/jarvis/jarvis/engines/__init__.py index b2fbbe45..caa65ef5 100644 --- a/src/jarvis/jarvis/engines/__init__.py +++ b/src/jarvis/jarvis/engines/__init__.py @@ -1,3 +1,4 @@ -from .stt import * +from .stt_google import * +from .stt_vosk import * from .tts import * from .ttt import * diff --git a/src/jarvis/jarvis/engines/stt.py b/src/jarvis/jarvis/engines/stt.py index 1bb9de88..bb122256 100644 --- a/src/jarvis/jarvis/engines/stt.py +++ b/src/jarvis/jarvis/engines/stt.py @@ -30,67 +30,15 @@ class STTEngine: """ Speech To Text Engine (STT) - - Google API Speech recognition settings - SpeechRecognition API : https://pypi.org/project/SpeechRecognition/2.1.3 """ def __init__(self): super().__init__() self.console_manager = ConsoleManager() - self.console_manager.console_output(info_log="Configuring Mic..") - self.recognizer = sr.Recognizer() - self.recognizer.pause_threshold = 0.5 - self.microphone = sr.Microphone() - self.console_manager.console_output(info_log="Microphone configured successfully!") def recognize_input(self, already_activated=False): """ Recognize input from mic and returns transcript if activation tag (assistant name) exist """ + pass - while True: - transcript = self._recognize_speech_from_mic() - if already_activated or self._activation_name_exist(transcript): - transcript = self._remove_activation_word(transcript) - return transcript - - def _recognize_speech_from_mic(self, ): - """ - Capture the words from the recorded audio (audio stream --> free text). - Transcribe speech from recorded from `microphone`. - """ - - with self.microphone as source: - self.recognizer.adjust_for_ambient_noise(source) - audio = self.recognizer.listen(source) - - try: - transcript = self.recognizer.recognize_google(audio).lower() - self.console_manager.console_output(info_log='User said: {0}'.format(transcript)) - except sr.UnknownValueError: - # speech was unintelligible - transcript = '' - self.console_manager.console_output(info_log='Unable to recognize speech', refresh_console=False) - except sr.RequestError: - # API was unreachable or unresponsive - transcript = '' - self.console_manager.console_output(error_log='Google API was unreachable') - return transcript - - @staticmethod - def _activation_name_exist(transcript): - """ - Identifies the assistant name in the input transcript. - """ - - if transcript: - transcript_words = transcript.split() - return bool(set(transcript_words).intersection([jarvis.assistant_name])) - else: - return False - - @staticmethod - def _remove_activation_word(transcript): - transcript = transcript.replace(jarvis.assistant_name, '') - return transcript diff --git a/src/jarvis/jarvis/engines/stt_google.py b/src/jarvis/jarvis/engines/stt_google.py new file mode 100644 index 00000000..f261636b --- /dev/null +++ b/src/jarvis/jarvis/engines/stt_google.py @@ -0,0 +1,96 @@ +# MIT License + +# Copyright (c) 2019 Georgios Papachristou + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import logging +import speech_recognition as sr + +import jarvis +from jarvis.engines.stt import STTEngine +from jarvis.core.console import ConsoleManager + + +class STTEngineGoogle(STTEngine): + """ + Speech To Text Engine (STT) + + Google API Speech recognition settings + SpeechRecognition API : https://pypi.org/project/SpeechRecognition/2.1.3 + """ + + def __init__(self): + super().__init__() + self.console_manager.console_output(info_log="Configuring Mic..") + self.recognizer = sr.Recognizer() + self.recognizer.pause_threshold = 0.5 + self.microphone = sr.Microphone() + self.console_manager.console_output(info_log="Microphone configured successfully!") + + def recognize_input(self, already_activated=False): + """ + Recognize input from mic and returns transcript if activation tag (assistant name) exist + """ + + while True: + transcript = self._recognize_speech_from_mic() + if already_activated or self._activation_name_exist(transcript): + transcript = self._remove_activation_word(transcript) + return transcript + + def _recognize_speech_from_mic(self, ): + """ + Capture the words from the recorded audio (audio stream --> free text). + Transcribe speech from recorded from `microphone`. + """ + + with self.microphone as source: + self.recognizer.adjust_for_ambient_noise(source) + audio = self.recognizer.listen(source) + + try: + transcript = self.recognizer.recognize_google(audio).lower() + self.console_manager.console_output(info_log='User said: {0}'.format(transcript)) + except sr.UnknownValueError: + # speech was unintelligible + transcript = '' + self.console_manager.console_output(info_log='Unable to recognize speech', refresh_console=False) + except sr.RequestError: + # API was unreachable or unresponsive + transcript = '' + self.console_manager.console_output(error_log='Google API was unreachable') + return transcript + + @staticmethod + def _activation_name_exist(transcript): + """ + Identifies the assistant name in the input transcript. + """ + + if transcript: + transcript_words = transcript.split() + return bool(set(transcript_words).intersection([jarvis.assistant_name])) + else: + return False + + @staticmethod + def _remove_activation_word(transcript): + transcript = transcript.replace(jarvis.assistant_name, '') + return transcript diff --git a/src/jarvis/jarvis/engines/stt_vosk.py b/src/jarvis/jarvis/engines/stt_vosk.py new file mode 100644 index 00000000..f242ea7d --- /dev/null +++ b/src/jarvis/jarvis/engines/stt_vosk.py @@ -0,0 +1,142 @@ +# MIT License + +# Copyright (c) 2019 Georgios Papachristou + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from cmath import inf +import logging + +import jarvis +from jarvis.engines.stt import STTEngine +from jarvis.core.console import ConsoleManager +import sounddevice as sd +import vosk +import json +import sys +from types import SimpleNamespace +import queue +import os + +# work_queue = queue.Queue() + +# def other_callback(indata, frames, time, status): +# print("Callback...") +# if status: +# print("Callback status: " + status) +# work_queue.put(bytes(indata)) + +class STTEngineVosk(STTEngine): + """ + Speech To Text Engine (STT) + + Vosk API Speech recognition settings + SpeechRecognition API : https://pypi.org/project/SpeechRecognition/2.1.3 + """ + + def __init__(self): + super().__init__() + self.console_manager.console_output(info_log="Initializing Vosk STT Engine") + self.console_manager.console_output(info_log="Configuring Mic..") + model_dir = 'model' + device_id = 'default' #None + device_info = sd.query_devices(device_id, 'input') + self.sample_rate = int(device_info['default_samplerate']) + if not os.path.exists(model_dir): + self.console_manager.console_output(error_log="Model directory does not exist: {}".format(model_dir)) + exit(0) + self.vosk_model = vosk.Model(model_dir) # default to a model directory of 'model' + self.work_queue = queue.Queue() + self.recognizer = vosk.KaldiRecognizer(self.vosk_model, self.sample_rate) + + self.console_manager.console_output(info_log="Settings: device={}, samplerate: {}".format(device_id, self.sample_rate)) + self.mic_stream = sd.RawInputStream(samplerate=self.sample_rate, device=device_id, blocksize = 8000, dtype='int16', channels=1, + # callback=self.callback) + # callback=other_callback) + callback=lambda indata, frames, time, status: self.callback(self, indata, frames, time, status)) + self.mic_stream.start() + self.console_manager.console_output(info_log="Mic status: {}".format(self.mic_stream.active)) + + self.console_manager.console_output(info_log="Microphone configured successfully!") + + def __exit__(self): + self.mic_stream.close() + + @staticmethod + + def callback(self, indata, frames, time, status): + """This is called (from a separate thread) for each audio block.""" + # self.console_manager.console_output(info_log="Callback...") + # print("callback...") + if status: + print("Callback status: " + status) + # self.console_manager.console_output(info_log="Callback status: " + status) + self.work_queue.put(bytes(indata)) + + def recognize_input(self, already_activated=False): + """ + Recognize input from mic and returns transcript if activation tag (assistant name) exist + """ + + while True: + transcript = self._recognize_speech_from_mic() + if already_activated or self._activation_name_exist(transcript): + transcript = self._remove_activation_word(transcript) + return transcript + + def _recognize_speech_from_mic(self, ): + """ + Capture the words from the recorded audio (audio stream --> free text). + Transcribe speech from recorded from `microphone`. + """ + + # self.console_manager.console_output(info_log="Get from queue") + try: + transcript = '' + data = self.work_queue.get() + # self.console_manager.console_output(info_log="Got data") + if self.recognizer.AcceptWaveform(data): + res = json.loads(self.recognizer.Result(), object_hook=lambda d: SimpleNamespace(**d)) + transcript = res.text + self.console_manager.console_output(info_log='User said: {0}'.format(transcript)) + return transcript + # else: + # # res = json.loads(self.recognizer.PartialResult(), object_hook=lambda d: SimpleNamespace(**d)) + # # self.console_manager.console_output(info_log='Partial: {0}'.format(res.partial)) + # print("Partial: " + self.recognizer.PartialResult()) + except queue.Empty: + pass + # print("Nothing detected yet") + + @staticmethod + def _activation_name_exist(transcript): + """ + Identifies the assistant name in the input transcript. + """ + + if transcript: + transcript_words = transcript.split() + return bool(set(transcript_words).intersection([jarvis.assistant_name])) + else: + return False + + @staticmethod + def _remove_activation_word(transcript): + transcript = transcript.replace(jarvis.assistant_name, '') + return transcript diff --git a/src/jarvis/jarvis/engines/tts.py b/src/jarvis/jarvis/engines/tts.py index fa0b608d..da62804b 100644 --- a/src/jarvis/jarvis/engines/tts.py +++ b/src/jarvis/jarvis/engines/tts.py @@ -70,8 +70,8 @@ def assistant_response(self, message, refresh_console=True): """ self._insert_into_message_queue(message) try: - speech_tread = threading.Thread(target=self._speech_and_console, args=(refresh_console,)) - speech_tread.start() + speech_thread = threading.Thread(target=self._speech_and_console, args=(refresh_console,)) + speech_thread.start() except RuntimeError as e: self.logger.error('Error in assistant response thread with message {0}'.format(e)) diff --git a/src/jarvis/jarvis/engines/tts_pyttsx3.py b/src/jarvis/jarvis/engines/tts_pyttsx3.py new file mode 100644 index 00000000..fa0b608d --- /dev/null +++ b/src/jarvis/jarvis/engines/tts_pyttsx3.py @@ -0,0 +1,133 @@ +# MIT License + +# Copyright (c) 2019 Georgios Papachristou + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import threading +import logging +import pyttsx3 +import queue + +from jarvis.core.console import ConsoleManager + + +class TTS: + """ + Text To Speech Engine (TTS) + """ + + def __init__(self): + self.tts_engine = self._set_voice_engine() + + def run_engine(self): + try: + self.tts_engine.runAndWait() + except RuntimeError: + pass + + @staticmethod + def _set_voice_engine(): + """ + Setup text to speech engine + :return: gtts engine object + """ + tts_engine = pyttsx3.init() + tts_engine.setProperty('rate', 160) # Setting up new voice rate + tts_engine.setProperty('volume', 1.0) # Setting up volume level between 0 and 1 + return tts_engine + + +class TTSEngine(TTS): + def __init__(self): + super().__init__() + self.logger = logging + self.message_queue = queue.Queue(maxsize=9) # Maxsize is the size of the queue / capacity of messages + self.stop_speaking = False + self.console_manager = ConsoleManager() + + def assistant_response(self, message, refresh_console=True): + """ + Assistant response in voice. + :param refresh_console: boolean + :param message: string + """ + self._insert_into_message_queue(message) + try: + speech_tread = threading.Thread(target=self._speech_and_console, args=(refresh_console,)) + speech_tread.start() + except RuntimeError as e: + self.logger.error('Error in assistant response thread with message {0}'.format(e)) + + def _insert_into_message_queue(self, message): + try: + self.message_queue.put(message) + except Exception as e: + self.logger.error("Unable to insert message to queue with error message: {0}".format(e)) + + def _speech_and_console(self, refresh_console): + """ + Speech method translate text batches to speech and print them in the console. + :param text: string (e.g 'tell me about google') + """ + try: + while not self.message_queue.empty(): + + cumulative_batch = '' + message = self.message_queue.get() + if message: + batches = self._create_text_batches(raw_text=message) + for batch in batches: + self.tts_engine.say(batch) + cumulative_batch += batch + self.console_manager.console_output(cumulative_batch, refresh_console=refresh_console) + self.run_engine() + if self.stop_speaking: + self.logger.debug('Speech interruption triggered') + self.stop_speaking = False + break + except Exception as e: + self.logger.error("Speech and console error message: {0}".format(e)) + + @staticmethod + def _create_text_batches(raw_text, number_of_words_per_batch=8): + """ + Splits the user speech message into batches and return a list with the split batches + :param raw_text: string + :param number_of_words_per_batch: int + :return: list + """ + raw_text = raw_text + ' ' + list_of_batches = [] + total_words = raw_text.count(' ') + letter_id = 0 + + for split in range(0, int(total_words / number_of_words_per_batch)): + batch = '' + words_count = 0 + while words_count < number_of_words_per_batch: + batch += raw_text[letter_id] + if raw_text[letter_id] == ' ': + words_count += 1 + letter_id += 1 + list_of_batches.append(batch) + + if letter_id < len(raw_text): # Add the rest of word in a batch + list_of_batches.append(raw_text[letter_id:]) + return list_of_batches From 89477d00d49f3d39b1dcd748f4eb4fe1d7261f2c Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sat, 12 Feb 2022 12:03:46 -0600 Subject: [PATCH 02/12] Cleaned up apt install commands --- setup.sh | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/setup.sh b/setup.sh index 01a3f82e..d8755725 100755 --- a/setup.sh +++ b/setup.sh @@ -25,19 +25,16 @@ fi #----------------------------------- # System dependencies installation #----------------------------------- -sudo apt-get update && / -sudo apt-get install build-essential && / -sudo apt-get install python3-dev && / -sudo apt-get install python3-setuptools && / -sudo apt-get install python3-pip && / -sudo apt-get install python3-venv && / -sudo apt-get install portaudio19-dev python3-pyaudio python3-pyaudio && / -sudo apt-get install python3-numpy && / -sudo apt-get install libasound2-plugins libsox-fmt-all libsox-dev libxml2-dev libxslt-dev sox ffmpeg && / -sudo apt-get install espeak && / -sudo apt-get install libcairo2-dev libgirepository1.0-dev gir1.2-gtk-3.0 && / -sudo apt install mongodb && / -sudo apt-get install gnupg +sudo apt-get update && \ +sudo apt-get install build-essential \ + python3-dev python3-setuptools python3-pip python3-venv \ + portaudio19-dev python3-pyaudio python3-pyaudio \ + python3-numpy \ + libasound2-plugins libsox-fmt-all libsox-dev libxml2-dev libxslt-dev sox ffmpeg \ + espeak \ + libcairo2-dev libgirepository1.0-dev gir1.2-gtk-3.0 \ + mongodb \ + gnupg # Reload local package database sudo apt-get update @@ -74,7 +71,7 @@ echo "${green} ${activated_python_version} activated!${reset}" # Install python requirements pip3 install --upgrade cython pip3 install wheel -python setup.py bdist_wheel +python3 setup.py bdist_wheel pip3 install -r $JARVIS_DIR/requirements.txt pip3 install -U scikit-learn From 5ce787a0ed683ca483834e3da09a8a380b7e56e2 Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sat, 12 Feb 2022 15:46:51 -0600 Subject: [PATCH 03/12] Storing general settings in sqlite --- run_jarvis.sh | 4 +- src/jarvis/jarvis/core/console.py | 18 ++-- src/jarvis/jarvis/engines/stt_vosk.py | 5 +- .../jarvis/skills/collection/activation.py | 6 +- .../jarvis/skills/collection/configuration.py | 14 +-- src/jarvis/jarvis/utils/settings_database.py | 85 +++++++++++++++++++ 6 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 src/jarvis/jarvis/utils/settings_database.py diff --git a/run_jarvis.sh b/run_jarvis.sh index 01c47b2e..bc4e63d3 100755 --- a/run_jarvis.sh +++ b/run_jarvis.sh @@ -7,8 +7,8 @@ # -------------------------------- # Start Jarvis service with virtualenv # -------------------------------- -#./jarvis_virtualenv/bin/python ./src/jarvis/start.py -python3 ./src/jarvis/start.py +./jarvis_virtualenv/bin/python ./src/jarvis/start.py +#python3 ./src/jarvis/start.py # -------------------------------- # Stop MongoDB service diff --git a/src/jarvis/jarvis/core/console.py b/src/jarvis/jarvis/core/console.py index d138af86..b2ba1b7a 100644 --- a/src/jarvis/jarvis/core/console.py +++ b/src/jarvis/jarvis/core/console.py @@ -26,7 +26,7 @@ import logging from jarvis import settings -from jarvis.utils.mongoDB import db +from jarvis.utils.settings_database import settingsDB from jarvis.utils.console import jarvis_logo, start_text, OutputStyler, headerize from jarvis.enumerations import MongoCollections, InputMode @@ -90,15 +90,13 @@ def console_output(self, text='', debug_log=None, info_log=None, warn_log=None, # ---------------------------------------------------------------------------------------------------------- # General info sector # ---------------------------------------------------------------------------------------------------------- - settings_documents = db.get_documents(collection=MongoCollections.GENERAL_SETTINGS.value) - if settings_documents: - settings_ = settings_documents[0] - print(OutputStyler.HEADER + headerize('GENERAL INFO') + OutputStyler.ENDC) - enabled = OutputStyler.GREEN + 'ENABLED' + OutputStyler.ENDC if settings_['response_in_speech'] else OutputStyler.WARNING + 'NOT ENABLED' + OutputStyler.ENDC - print(OutputStyler.BOLD + 'RESPONSE IN SPEECH: ' + enabled) - print(OutputStyler.BOLD + 'INPUT MODE: ' + OutputStyler.GREEN + '{0}'.format(settings_['input_mode'].upper() + OutputStyler.ENDC) + OutputStyler.ENDC) - if settings_['input_mode'] == InputMode.VOICE.value: - print(OutputStyler.BOLD + 'NOTE: ' + OutputStyler.GREEN + "Include " + "'{0}'".format(settings_['assistant_name'].upper()) + " in you command to enable assistant" + OutputStyler.ENDC + OutputStyler.ENDC) + settings_ = settingsDB.getGeneralSettings() + print(OutputStyler.HEADER + headerize('GENERAL INFO') + OutputStyler.ENDC) + enabled = OutputStyler.GREEN + 'ENABLED' + OutputStyler.ENDC if settings_.response_in_speech else OutputStyler.WARNING + 'NOT ENABLED' + OutputStyler.ENDC + print(OutputStyler.BOLD + 'RESPONSE IN SPEECH: ' + enabled) + print(OutputStyler.BOLD + 'INPUT MODE: ' + OutputStyler.GREEN + '{0}'.format(settings_.input_mode.upper() + OutputStyler.ENDC) + OutputStyler.ENDC) + if settings_.input_mode == InputMode.VOICE.value: + print(OutputStyler.BOLD + 'NOTE: ' + OutputStyler.GREEN + "Include " + "'{0}'".format(settings_.assistant_name.upper()) + " in you command to enable assistant" + OutputStyler.ENDC + OutputStyler.ENDC) # ---------------------------------------------------------------------------------------------------------- # System info sector diff --git a/src/jarvis/jarvis/engines/stt_vosk.py b/src/jarvis/jarvis/engines/stt_vosk.py index f242ea7d..d911b9af 100644 --- a/src/jarvis/jarvis/engines/stt_vosk.py +++ b/src/jarvis/jarvis/engines/stt_vosk.py @@ -67,8 +67,6 @@ def __init__(self): self.console_manager.console_output(info_log="Settings: device={}, samplerate: {}".format(device_id, self.sample_rate)) self.mic_stream = sd.RawInputStream(samplerate=self.sample_rate, device=device_id, blocksize = 8000, dtype='int16', channels=1, - # callback=self.callback) - # callback=other_callback) callback=lambda indata, frames, time, status: self.callback(self, indata, frames, time, status)) self.mic_stream.start() self.console_manager.console_output(info_log="Mic status: {}".format(self.mic_stream.active)) @@ -79,13 +77,12 @@ def __exit__(self): self.mic_stream.close() @staticmethod - def callback(self, indata, frames, time, status): """This is called (from a separate thread) for each audio block.""" # self.console_manager.console_output(info_log="Callback...") # print("callback...") if status: - print("Callback status: " + status) + print("Callback status: %s" % status) # self.console_manager.console_output(info_log="Callback status: " + status) self.work_queue.put(bytes(indata)) diff --git a/src/jarvis/jarvis/skills/collection/activation.py b/src/jarvis/jarvis/skills/collection/activation.py index 27f413dc..e29b7d8c 100644 --- a/src/jarvis/jarvis/skills/collection/activation.py +++ b/src/jarvis/jarvis/skills/collection/activation.py @@ -26,7 +26,7 @@ from jarvis.skills.skill import AssistantSkill from jarvis.utils.startup import play_activation_sound -from jarvis.utils.mongoDB import db +from jarvis.utils.settings_database import settingsDB from jarvis.enumerations import InputMode, MongoCollections @@ -38,8 +38,8 @@ def enable_assistant(cls, **kwargs): Plays activation sound and creates the assistant response according to the day hour. """ - input_mode = db.get_documents(collection=MongoCollections.GENERAL_SETTINGS.value)[0]['input_mode'] - if input_mode == InputMode.VOICE.value: + generalSettings = settingsDB.getGeneralSettings() + if generalSettings.input_mode == InputMode.VOICE.value: play_activation_sound() @classmethod diff --git a/src/jarvis/jarvis/skills/collection/configuration.py b/src/jarvis/jarvis/skills/collection/configuration.py index 211aa0e7..af3b70c6 100644 --- a/src/jarvis/jarvis/skills/collection/configuration.py +++ b/src/jarvis/jarvis/skills/collection/configuration.py @@ -24,14 +24,15 @@ from jarvis.skills.skill import AssistantSkill from jarvis import settings -from jarvis.utils.mongoDB import db +from jarvis.utils.settings_database import settingsDB from jarvis.enumerations import InputMode, MongoCollections from jarvis.utils import console from jarvis.utils import input -input_mode = db.get_documents(collection='general_settings')[0]['input_mode'] -response_in_speech = db.get_documents(collection='general_settings')[0]['response_in_speech'] -assistant_name = db.get_documents(collection='general_settings')[0]['assistant_name'] +generalSettings = settingsDB.getGeneralSettings() +input_mode = generalSettings.input_mode +response_in_speech = generalSettings.response_in_speech +assistant_name = generalSettings.assistant_name class ConfigurationSkills(AssistantSkill): @@ -70,7 +71,10 @@ def configure_assistant(cls, **kwargs): cls.response('Do you want to save new settings? ', refresh_console=False) save = input.check_input_to_continue() if save: - db.update_collection(collection=MongoCollections.GENERAL_SETTINGS.value, documents=[new_settings]) + generalSettings.input_mode = new_input_mode + generalSettings.assistant_name = new_assistant_name + generalSettings.response_in_speech = new_response_in_speech + settingsDB.updateGeneralSettings(generalSettings) import jarvis importlib.reload(jarvis) diff --git a/src/jarvis/jarvis/utils/settings_database.py b/src/jarvis/jarvis/utils/settings_database.py new file mode 100644 index 00000000..1eb0b393 --- /dev/null +++ b/src/jarvis/jarvis/utils/settings_database.py @@ -0,0 +1,85 @@ +import sqlite3 +import json + +class GeneralSettings: + def __init__(self, input_mode='voice', assistant_name='jarvis', response_in_speech='true'): + self.input_mode = input_mode + self.assistant_name = assistant_name + self.response_in_speech = response_in_speech + def __str__(self): + return "input_mode=%s, assistant_name=%s, response_in_speech=%s" % (self.input_mode, self.assistant_name, self.response_in_speech) + +# class ControlSkills: +# def __init__(self): +# self.something = 0 + +# class EnabledBasicSkills: +# def __init__(self): +# self.something = 0 + +# class LearnedSkills: +# def __init__(self): +# self.something = 0 + + +class SettingsDatabase: + def __init__(self, database='settings.db'): + self.database = database + self._ensure_database_exists() + + def _ensure_database_exists(self): + conn = sqlite3.connect(self.database) + conn.execute( + """create table if not exists collections ( + name VARCHAR(255) PRIMARY KEY, + data BLOB + ); """) + conn.commit() + conn.close() + + def getGeneralSettings(self): + conn = sqlite3.connect(self.database) + cursor = conn.cursor() + rows = cursor.execute("select data from collections where name = 'general_settings'").fetchall() + conn.close() + + if len(rows) < 1: + #print("Not found, getting default") + return GeneralSettings() + + settings = json.loads(rows[0][0].decode('utf-8')) + return GeneralSettings( + input_mode = settings['input_mode'], + response_in_text = settings['response_in_speech'], + assistant_name = settings['assistant_name'] + ) + + def updateGeneralSettings(self, settings): + blob = bytes(json.dumps(settings.__dict__), 'utf-8') + conn = sqlite3.connect(self.database) + conn.execute( + ''' + insert into collections(name, data) values ('general_settings', ?) + on conflict(name) do update set data=excluded.data; + ''', (blob,)) + conn.commit() + conn.close() + +class History: + def __init__(self): + self.something = 0 + +class HistoryDatabase: + def __init__(self, database='history.db'): + self.conn = sqlite3.connect(database) + + +#print("Create settings") +settingsDB = SettingsDatabase() +# print("Get settings") +# gen = settingsDB.getGeneralSettings() +# print("Gen = (%s)" % gen) +# gen.assistant_name = 'travis' +# settingsDB.updateGeneralSettings(gen) +# gen = settingsDB.getGeneralSettings() +# print("Gen = (%s)" % gen) From 914513f730dd13d10015b77cdf42ad11768bcafb Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sat, 12 Feb 2022 17:01:01 -0600 Subject: [PATCH 04/12] History and general settings are in sqlite --- src/jarvis/jarvis/__init__.py | 18 ++--- src/jarvis/jarvis/core/processor.py | 15 ++-- .../jarvis/skills/collection/history.py | 17 +++-- src/jarvis/jarvis/utils/history_database.py | 71 +++++++++++++++++++ src/jarvis/jarvis/utils/settings_database.py | 23 +++--- src/jarvis/jarvis/utils/startup.py | 12 ++-- 6 files changed, 114 insertions(+), 42 deletions(-) create mode 100644 src/jarvis/jarvis/utils/history_database.py diff --git a/src/jarvis/jarvis/__init__.py b/src/jarvis/jarvis/__init__.py index fdda8adb..8a4dda7a 100644 --- a/src/jarvis/jarvis/__init__.py +++ b/src/jarvis/jarvis/__init__.py @@ -24,10 +24,10 @@ from jarvis import settings from jarvis.settings import ROOT_LOG_CONF -from jarvis.utils.mongoDB import db -from jarvis.utils.startup import configure_MongoDB +from jarvis.utils.startup import configure_defaults from jarvis.enumerations import InputMode import jarvis.engines as engines +from jarvis.utils.settings_database import settingsDB # ---------------------------------------------------------------------------------------------------------------------- # Create a Console & Rotating file logger @@ -43,17 +43,19 @@ # ---------------------------------------------------------------------------------------------------------------------- # Configuare MongoDB, load skills and settings # ---------------------------------------------------------------------------------------------------------------------- -configure_MongoDB(db, settings) +configure_defaults(settings) # ---------------------------------------------------------------------------------------------------------------------- # Get assistant settings # ---------------------------------------------------------------------------------------------------------------------- -input_mode = db.get_documents(collection='general_settings')[0]['input_mode'] -response_in_speech = db.get_documents(collection='general_settings')[0]['response_in_speech'] -assistant_name = db.get_documents(collection='general_settings')[0]['assistant_name'] +generalSettings = settingsDB.getGeneralSettings() +input_mode = generalSettings.input_mode +response_in_speech = generalSettings.response_in_speech +assistant_name = generalSettings.assistant_name # ---------------------------------------------------------------------------------------------------------------------- # Create assistant input and output engine instances # ---------------------------------------------------------------------------------------------------------------------- -input_engine = engines.STTEngineVosk()# if input_mode == InputMode.VOICE.value else engines.TTTEngine() -output_engine = engines.TTSEngine()# if response_in_speech else engines.TTTEngine() +#input_engine = engines.STTEngineVosk()# if input_mode == InputMode.VOICE.value else engines.TTTEngine() +input_engine = engines.STTEngineGoogle() if input_mode == InputMode.VOICE.value else engines.TTTEngine() +output_engine = engines.TTSEngine() if response_in_speech else engines.TTTEngine() diff --git a/src/jarvis/jarvis/core/processor.py b/src/jarvis/jarvis/core/processor.py index 6d54f13f..7c212c6f 100644 --- a/src/jarvis/jarvis/core/processor.py +++ b/src/jarvis/jarvis/core/processor.py @@ -29,7 +29,7 @@ from jarvis.skills.registry import skill_objects from jarvis.core.nlp import ResponseCreator from jarvis.skills.collection.activation import ActivationSkills -from jarvis.utils.mongoDB import db +from jarvis.utils.history_database import historyDB, History from jarvis.skills.collection.wolframalpha import WolframSkills @@ -53,7 +53,7 @@ def run(self): - STEP 2: Matches the input with a skill - STEP 3: Create a response - STEP 4: Execute matched skill - - STEP 5: Insert user transcript and response in history collection (in MongoDB) + - STEP 5: Insert user transcript and response in history collection """ @@ -100,13 +100,10 @@ def run(self): # -------------------------------------------------------------------------------------------------------------- # Add new record to history # -------------------------------------------------------------------------------------------------------------- - - record = {'user_transcript': transcript, - 'response': response if response else '--', - 'executed_skill': skill_to_execute if skill_to_execute else '--' - } - - db.insert_many_documents('history', [record]) + historyDB.addHistory(History( + user_transcript=transcript, + response=response, + executed_skill='--' if not skill_to_execute else skill_to_execute['skill']['name'])) def _execute_skill(self, skill): if skill: diff --git a/src/jarvis/jarvis/skills/collection/history.py b/src/jarvis/jarvis/skills/collection/history.py index ef4d1a43..7a96d7d8 100644 --- a/src/jarvis/jarvis/skills/collection/history.py +++ b/src/jarvis/jarvis/skills/collection/history.py @@ -23,7 +23,7 @@ import re from jarvis.skills.skill import AssistantSkill -from jarvis.utils.mongoDB import db +from jarvis.utils.history_database import historyDB header = """ ----------------------------------------------------------------------------------------------- @@ -52,19 +52,18 @@ def show_history_log(cls, voice_transcript, skill): limit = cls._extract_history_limit(voice_transcript, skill) limit = limit if limit else cls.default_limit - documents = db.get_documents(collection='history', limit=limit) - response = cls._create_response(documents) + records = historyDB.getHistory(limit=limit) + response = cls._create_response(records) cls.console(response) @classmethod - def _create_response(cls, documents): + def _create_response(cls, records): response = '' try: - for document in documents: - response += response_base.format(document.get('user_transcript', '--'), - document.get('response', '--'), - document.get('executed_skill').get('skill').get('name') if - document.get('executed_skill') else '--' + for record in records: + response += response_base.format(record.user_transcript, + record.response, + record.executed_skill ) except Exception as e: cls.console(error_log=e) diff --git a/src/jarvis/jarvis/utils/history_database.py b/src/jarvis/jarvis/utils/history_database.py new file mode 100644 index 00000000..5713155b --- /dev/null +++ b/src/jarvis/jarvis/utils/history_database.py @@ -0,0 +1,71 @@ +import sqlite3 +from time import time + +class History: + def __init__(self, user_transcript = '--', response = '--', executed_skill = '--', timestamp=0): + self.user_transcript = user_transcript + self.response = response + self.executed_skill = executed_skill + self.timestamp = timestamp if timestamp != 0 else int(time() * 1000) + + def __str__(self): + return "user_transcript=%s, response=%s, executed_skill=%s, timestamp=%s" % ( + self.user_transcript, self.response, self.executed_skill, self.timestamp) + + def __repr__(self): + return self.__str__() + +class HistoryDatabase: + def __init__(self, database='history.db'): + self.database = database + self._ensure_database_exists() + + def _ensure_database_exists(self): + conn = sqlite3.connect(self.database) + conn.execute( + """create table if not exists history ( + timestamp INTEGER PRIMARY KEY, + user_transcript TEXT, + response TEXT, + executed_skill TEXT + ); """) + conn.commit() + conn.close() + + def getHistory(self, limit = 3): + conn = sqlite3.connect(self.database) + cursor = conn.cursor() + rows = cursor.execute("select * from history order by timestamp DESC limit ?", (limit,)).fetchall() + conn.close() + + response = [] + for row in rows: + response.append(History( + timestamp=row[0], + user_transcript=row[1], + response = row[2], + executed_skill = row[3])) + + print("Returned %d history items" % len(response)) + return response + + def addHistory(self, history): + conn = sqlite3.connect(self.database) + conn.execute( + ''' + insert into history + (timestamp, user_transcript, response, executed_skill) values + (?, ?, ?, ?) + ''', (history.timestamp, history.user_transcript, history.response, history.executed_skill,)) + conn.commit() + conn.close() + +historyDB = HistoryDatabase() +# his = historyDB.getHistory() +# print("history = %s" % his) +# historyDB.addHistory(History(user_transcript='test1', response='resp1', executed_skill='skill1')) +# historyDB.addHistory(History(user_transcript='test2', response='resp2', executed_skill='skill2')) +# historyDB.addHistory(History(user_transcript='test3', response='resp3', executed_skill='skill3')) +# his = historyDB.getHistory(limit=3) +# for h in his: +# print (h) diff --git a/src/jarvis/jarvis/utils/settings_database.py b/src/jarvis/jarvis/utils/settings_database.py index 1eb0b393..7bd1daf1 100644 --- a/src/jarvis/jarvis/utils/settings_database.py +++ b/src/jarvis/jarvis/utils/settings_database.py @@ -7,7 +7,8 @@ def __init__(self, input_mode='voice', assistant_name='jarvis', response_in_spee self.assistant_name = assistant_name self.response_in_speech = response_in_speech def __str__(self): - return "input_mode=%s, assistant_name=%s, response_in_speech=%s" % (self.input_mode, self.assistant_name, self.response_in_speech) + return "input_mode=%s, assistant_name=%s, response_in_speech=%s" % ( + self.input_mode, self.assistant_name, self.response_in_speech) # class ControlSkills: # def __init__(self): @@ -37,6 +38,14 @@ def _ensure_database_exists(self): conn.commit() conn.close() + def hasGeneralSettings(self): + conn = sqlite3.connect(self.database) + cursor = conn.cursor() + rows = cursor.execute("select data from collections where name = 'general_settings'").fetchall() + conn.close() + + return len(rows) > 0 + def getGeneralSettings(self): conn = sqlite3.connect(self.database) cursor = conn.cursor() @@ -44,13 +53,12 @@ def getGeneralSettings(self): conn.close() if len(rows) < 1: - #print("Not found, getting default") return GeneralSettings() settings = json.loads(rows[0][0].decode('utf-8')) return GeneralSettings( input_mode = settings['input_mode'], - response_in_text = settings['response_in_speech'], + response_in_speech = settings['response_in_speech'], assistant_name = settings['assistant_name'] ) @@ -65,15 +73,6 @@ def updateGeneralSettings(self, settings): conn.commit() conn.close() -class History: - def __init__(self): - self.something = 0 - -class HistoryDatabase: - def __init__(self, database='history.db'): - self.conn = sqlite3.connect(database) - - #print("Create settings") settingsDB = SettingsDatabase() # print("Get settings") diff --git a/src/jarvis/jarvis/utils/startup.py b/src/jarvis/jarvis/utils/startup.py index 61f9f834..16ec6d0e 100644 --- a/src/jarvis/jarvis/utils/startup.py +++ b/src/jarvis/jarvis/utils/startup.py @@ -29,7 +29,7 @@ from jarvis.utils import console from jarvis.enumerations import MongoCollections from jarvis.core.console import ConsoleManager - +from jarvis.utils.settings_database import settingsDB, GeneralSettings def play_activation_sound(): """ @@ -56,14 +56,14 @@ def internet_connectivity_check(url='http://www.google.com/', timeout=2): return False -def configure_MongoDB(db, settings): +def configure_defaults(settings): # ------------------------------------------------------------------------------------------------------------------ # Load settings # ------------------------------------------------------------------------------------------------------------------ # Only in first time or if 'general_settings' collection is deleted - if db.is_collection_empty(collection=MongoCollections.GENERAL_SETTINGS.value): + if not settingsDB.hasGeneralSettings(): console.print_console_header() print('First time configuration..') console.print_console_header() @@ -80,7 +80,11 @@ def configure_MongoDB(db, settings): } try: - db.update_collection(collection=MongoCollections.GENERAL_SETTINGS.value, documents=[new_settings]) + settingsDB.updateGeneralSettings(GeneralSettings( + input_mode=default_input_mode, + assistant_name=default_assistant_name, + response_in_speech=default_response_in_speech + )) console.print_console_header('Assistant Name') print('Assistant name- {0} configured successfully!'.format(default_assistant_name.lower())) print('Input mode - {0} configured successfully!'.format(default_input_mode)) From 87f9626c427a804d57fd402c347fc84e68bdc97e Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sat, 12 Feb 2022 19:11:45 -0600 Subject: [PATCH 05/12] Everything uses sqlite now --- .gitignore | 3 +- src/jarvis/jarvis/__init__.py | 7 +- src/jarvis/jarvis/core/processor.py | 4 +- src/jarvis/jarvis/skills/analyzer.py | 7 +- src/jarvis/jarvis/skills/collection/info.py | 7 +- .../jarvis/skills/collection/remember.py | 10 +-- src/jarvis/jarvis/skills/registry.py | 1 + src/jarvis/jarvis/utils/mongoDB.py | 80 ------------------- src/jarvis/jarvis/utils/skills_database.py | 59 ++++++++++++++ src/jarvis/jarvis/utils/skills_registry.py | 43 ++++++++++ src/jarvis/jarvis/utils/startup.py | 14 ---- 11 files changed, 124 insertions(+), 111 deletions(-) delete mode 100644 src/jarvis/jarvis/utils/mongoDB.py create mode 100644 src/jarvis/jarvis/utils/skills_database.py create mode 100644 src/jarvis/jarvis/utils/skills_registry.py diff --git a/.gitignore b/.gitignore index 0851fff6..5dbcc1e6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ __pycache__ model/ jarvis_virtualenv/ - +*.db +*.log diff --git a/src/jarvis/jarvis/__init__.py b/src/jarvis/jarvis/__init__.py index 8a4dda7a..eb9863c2 100644 --- a/src/jarvis/jarvis/__init__.py +++ b/src/jarvis/jarvis/__init__.py @@ -28,6 +28,8 @@ from jarvis.enumerations import InputMode import jarvis.engines as engines from jarvis.utils.settings_database import settingsDB +from jarvis.utils.skills_registry import skills_registry +from jarvis.skills.registry import skill_objects, CONTROL_SKILLS, ENABLED_BASIC_SKILLS # ---------------------------------------------------------------------------------------------------------------------- # Create a Console & Rotating file logger @@ -41,9 +43,12 @@ f.close() # ---------------------------------------------------------------------------------------------------------------------- -# Configuare MongoDB, load skills and settings +# Configuare defaults, load skills and settings # ---------------------------------------------------------------------------------------------------------------------- configure_defaults(settings) +skills_registry.skill_objects = skill_objects +skills_registry.basic_skills = ENABLED_BASIC_SKILLS +skills_registry.control_skills = CONTROL_SKILLS # ---------------------------------------------------------------------------------------------------------------------- # Get assistant settings diff --git a/src/jarvis/jarvis/core/processor.py b/src/jarvis/jarvis/core/processor.py index 7c212c6f..aba24568 100644 --- a/src/jarvis/jarvis/core/processor.py +++ b/src/jarvis/jarvis/core/processor.py @@ -26,7 +26,7 @@ from sklearn.metrics.pairwise import cosine_similarity from jarvis.skills.analyzer import SkillAnalyzer -from jarvis.skills.registry import skill_objects +from jarvis.utils.skills_registry import skills_registry from jarvis.core.nlp import ResponseCreator from jarvis.skills.collection.activation import ActivationSkills from jarvis.utils.history_database import historyDB, History @@ -112,7 +112,7 @@ def _execute_skill(self, skill): try: ActivationSkills.enable_assistant() skill_func_name = skill.get('skill').get('func') - skill_func = skill_objects[skill_func_name] + skill_func = skills_registry.skill_objects[skill_func_name] skill_func(**skill) except Exception as e: self.console_manager.console_output(error_log="Failed to execute skill {0} with message: {1}" diff --git a/src/jarvis/jarvis/skills/analyzer.py b/src/jarvis/jarvis/skills/analyzer.py index 943330b3..8b24c21e 100644 --- a/src/jarvis/jarvis/skills/analyzer.py +++ b/src/jarvis/jarvis/skills/analyzer.py @@ -21,8 +21,7 @@ # SOFTWARE. import logging from jarvis.utils.mapping import math_symbols_mapping -from jarvis.utils.mongoDB import db - +from jarvis.utils.skills_registry import skills_registry class SkillAnalyzer: def __init__(self, weight_measure, similarity_measure, args, sensitivity): @@ -35,9 +34,7 @@ def __init__(self, weight_measure, similarity_measure, args, sensitivity): @property def skills(self): - return db.get_documents(collection='control_skills')\ - + db.get_documents(collection='enabled_basic_skills')\ - + db.get_documents(collection='learned_skills') + return skills_registry.basic_skills + skills_registry.control_skills + skills_registry.learned_skills @property def tags(self): diff --git a/src/jarvis/jarvis/skills/collection/info.py b/src/jarvis/jarvis/skills/collection/info.py index 8a208ffd..97aa85c3 100644 --- a/src/jarvis/jarvis/skills/collection/info.py +++ b/src/jarvis/jarvis/skills/collection/info.py @@ -21,7 +21,7 @@ # SOFTWARE. from jarvis.skills.skill import AssistantSkill -from jarvis.utils.mongoDB import db +from jarvis.utils.skills_registry import skills_registry from jarvis.utils.console import headerize basic_skills_format = """ @@ -94,7 +94,8 @@ def _create_skill_response(cls, response): # -------------------------------------------------------------------------------------------------------------- # For existing skills (basic skills) # -------------------------------------------------------------------------------------------------------------- - basic_skills = db.get_documents(collection='enabled_basic_skills') + # basic_skills = db.get_documents(collection='enabled_basic_skills') + basic_skills = skills_registry.basic_skills response = response + basic_skills_format for skill_id, skill in enumerate(basic_skills, start=1): response = response + basic_skills_body_format.format(skill_id, @@ -106,7 +107,7 @@ def _create_skill_response(cls, response): # -------------------------------------------------------------------------------------------------------------- # For learned skills (created from 'learn' skill) # -------------------------------------------------------------------------------------------------------------- - skills = db.get_documents(collection='learned_skills') + skills = skills_registry.learned_skills response = response + learned_skills_format for skill_id, skill in enumerate(skills, start=1): response = response + learned_skills_body_format.format(skill_id, diff --git a/src/jarvis/jarvis/skills/collection/remember.py b/src/jarvis/jarvis/skills/collection/remember.py index a8e9cca9..04a72f7a 100644 --- a/src/jarvis/jarvis/skills/collection/remember.py +++ b/src/jarvis/jarvis/skills/collection/remember.py @@ -22,7 +22,7 @@ from jarvis.skills.skill import AssistantSkill -from jarvis.utils.mongoDB import db +from jarvis.utils.skills_database import skillsDB from jarvis.utils import input header = """ @@ -50,11 +50,11 @@ def remember(cls, **kwargs): 'func': cls.tell_response.__name__, 'response': response, 'tags': tags, - }, + } cls.response('Add more? ', refresh_console=False) continue_add = input.check_input_to_continue() - db.insert_many_documents(collection='learned_skills', documents=new_skill) + skillsDB.addLearnedSkill(new_skill) @classmethod def tell_response(cls, **kwargs): @@ -62,12 +62,12 @@ def tell_response(cls, **kwargs): @classmethod def clear_learned_skills(cls, **kwargs): - if db.is_collection_empty(collection='learned_skills'): + if skillsDB.hasLearnedSkills(): cls.response("I can't find learned skills in my database") else: cls.response('I found learned skills..') cls.response('Are you sure to remove learned skills? ', refresh_console=False) user_answer = input.check_input_to_continue() if user_answer: - db.drop_collection(collection='learned_skills') + skillsDB.clearLearnedSkills() cls.response("Perfect I have deleted them all") diff --git a/src/jarvis/jarvis/skills/registry.py b/src/jarvis/jarvis/skills/registry.py index a96a476d..c8cff7cd 100644 --- a/src/jarvis/jarvis/skills/registry.py +++ b/src/jarvis/jarvis/skills/registry.py @@ -38,6 +38,7 @@ from jarvis.skills.collection.math import MathSkills from jarvis.utils.mapping import math_tags from jarvis.skills.collection.configuration import ConfigurationSkills +from jarvis.utils.skills_registry import skills_registry # All available assistant skills # Keys description: diff --git a/src/jarvis/jarvis/utils/mongoDB.py b/src/jarvis/jarvis/utils/mongoDB.py deleted file mode 100644 index 0f78940a..00000000 --- a/src/jarvis/jarvis/utils/mongoDB.py +++ /dev/null @@ -1,80 +0,0 @@ -# MIT License - -# Copyright (c) 2019 Georgios Papachristou - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import logging -from pymongo import MongoClient, DESCENDING - - -class MongoDB: - """ - This class encapsulates methods related to MongoDB - """ - - def __init__(self, host='localhost', port=27017): - self.client = MongoClient(host, port) - self.database = self.client['jarvis'] - - def get_documents(self, collection, key=None, limit=None): - collection_obj = self.database[collection] - try: - result = collection_obj.find(key).sort('_id', DESCENDING) - return list(result.limit(limit) if limit else result) - except Exception as e: - logging.error(e) - - def insert_many_documents(self, collection, documents): - collection_obj = self.database[collection] - try: - collection_obj.insert_many(documents) - except Exception as e: - logging.error(e) - - def drop_collection(self, collection): - collection_obj = self.database[collection] - try: - collection_obj.drop() - except Exception as e: - logging.error(e) - - def update_collection(self, collection, documents): - self.drop_collection(collection) - self.insert_many_documents(collection, documents) - - def update_document(self, collection, query, new_value, upsert=True): - collection_obj = self.database[collection] - try: - collection_obj.update_one(query, {'$set': new_value}, upsert) - except Exception as e: - logging.error(e) - - def is_collection_empty(self, collection): - collection_obj = self.database[collection] - try: - return collection_obj.estimated_document_count() == 0 - except Exception as e: - logging.error(e) - - -# ---------------------------------------------------------------------------------------------------------------------- -# Create MongoDB connection instance -# ---------------------------------------------------------------------------------------------------------------------- -db = MongoDB() diff --git a/src/jarvis/jarvis/utils/skills_database.py b/src/jarvis/jarvis/utils/skills_database.py new file mode 100644 index 00000000..08927f84 --- /dev/null +++ b/src/jarvis/jarvis/utils/skills_database.py @@ -0,0 +1,59 @@ +from signal import pause +import sqlite3 +import json + +class SkillsDatabase: + def __init__(self, database='skills.db'): + self.database = database + self._ensure_database_exists() + + def _ensure_database_exists(self): + conn = sqlite3.connect(self.database) + conn.execute( + """create table if not exists collections ( + data BLOB + ); """) + conn.commit() + conn.close() + + def hasLearnedSkills(self): + conn = sqlite3.connect(self.database) + cursor = conn.cursor() + rows = cursor.execute("select count(*) from collections").fetchall() + conn.close() + + return rows[0][0] > 0 + + def getLearnedSkills(self): + conn = sqlite3.connect(self.database) + cursor = conn.cursor() + rows = cursor.execute("select data from collections").fetchall() + conn.close() + + response = [] + for row in rows: + skill = json.loads(row[0].decode('utf-8')) + response.append(skill) + return response + + def addLearnedSkill(self, skill): + blob = bytes(json.dumps(skill), 'utf-8') + conn = sqlite3.connect(self.database) + conn.execute( + ''' + insert into collections(data) values (?) + ''', (blob,)) + conn.commit() + conn.close() + + def clearLearnedSkills(self): + conn = sqlite3.connect(self.database) + conn.execute( + ''' + drop from collections(data) + ''') + conn.commit() + conn.close() + + +skillsDB = SkillsDatabase() diff --git a/src/jarvis/jarvis/utils/skills_registry.py b/src/jarvis/jarvis/utils/skills_registry.py new file mode 100644 index 00000000..c0d45606 --- /dev/null +++ b/src/jarvis/jarvis/utils/skills_registry.py @@ -0,0 +1,43 @@ +import logging +from jarvis.utils.skills_database import skillsDB + +class SkillsRegistry: + def __init__(self): + self.skill_objects = [] + self.basic_skills = [] + self.control_skills = [] + + @property + def skill_objects(self): + return self._skill_objects.copy() + + @skill_objects.setter + def skill_objects(self, skill_objects): + # logging.warn("setting skill_objects = %d" %len(skill_objects)) + self._skill_objects = skill_objects.copy() + + @property + def basic_skills(self): + # logging.warn("getting basic_skills %s" % type(self._basic_skills)) + return self._basic_skills.copy() + + @basic_skills.setter + def basic_skills(self, basic_skills): + # logging.warn("setting basic_skills = %d, %s" % (len(basic_skills), type(basic_skills))) + self._basic_skills = basic_skills.copy() + + @property + def control_skills(self): + # logging.warn("getting control_skills %s" % type(self._control_skills)) + return self._control_skills.copy() + + @control_skills.setter + def control_skills(self, control_skills): + # logging.warn("setting control_skills = %d" % len(control_skills)) + self._control_skills = control_skills.copy() + + @property + def learned_skills(self): + return skillsDB.getLearnedSkills() + +skills_registry = SkillsRegistry() diff --git a/src/jarvis/jarvis/utils/startup.py b/src/jarvis/jarvis/utils/startup.py index 16ec6d0e..99ad15b7 100644 --- a/src/jarvis/jarvis/utils/startup.py +++ b/src/jarvis/jarvis/utils/startup.py @@ -27,7 +27,6 @@ from playsound import playsound from jarvis.utils import console -from jarvis.enumerations import MongoCollections from jarvis.core.console import ConsoleManager from jarvis.utils.settings_database import settingsDB, GeneralSettings @@ -93,16 +92,3 @@ def configure_defaults(settings): except Exception as e: logging.error('Failed to configure assistant settings with error message {0}'.format(e)) - - # ------------------------------------------------------------------------------------------------------------------ - # Load skills - # ------------------------------------------------------------------------------------------------------------------ - - from jarvis.skills.registry import CONTROL_SKILLS, ENABLED_BASIC_SKILLS - - all_skills = { - MongoCollections.CONTROL_SKILLS.value: CONTROL_SKILLS, - MongoCollections.ENABLED_BASIC_SKILLS.value: ENABLED_BASIC_SKILLS, - } - for collection, documents in all_skills.items(): - db.update_collection(collection, documents) From 4f20683035daa81d2bcc830b3400811f470b2572 Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sun, 13 Feb 2022 08:04:09 -0600 Subject: [PATCH 06/12] Needed libgstreamer for espeak, and apparently pybind11 --- requirements.txt | 1 + setup.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 247aefcc..e588d7b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,6 +27,7 @@ playsound==1.2.2 psutil==5.6.6 PTable==0.9.2 PyAudio==0.2.11 +pybind11==2.9.1 pycairo==1.20.1 PyGObject==3.42.0 pymongo==3.10.1 diff --git a/setup.sh b/setup.sh index d8755725..3fe8dc66 100755 --- a/setup.sh +++ b/setup.sh @@ -31,6 +31,7 @@ sudo apt-get install build-essential \ portaudio19-dev python3-pyaudio python3-pyaudio \ python3-numpy \ libasound2-plugins libsox-fmt-all libsox-dev libxml2-dev libxslt-dev sox ffmpeg \ + libgstreamer1.0-dev libgstreamer1.0-0 \ espeak \ libcairo2-dev libgirepository1.0-dev gir1.2-gtk-3.0 \ mongodb \ From ce060ae62b4613214f919cbed896e3f8a570be8d Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sun, 13 Feb 2022 08:08:44 -0600 Subject: [PATCH 07/12] Removed last mentions of Mongo from code --- src/jarvis/jarvis/core/console.py | 2 +- src/jarvis/jarvis/enumerations.py | 6 ------ src/jarvis/jarvis/skills/collection/activation.py | 2 +- src/jarvis/jarvis/skills/collection/configuration.py | 2 +- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/jarvis/jarvis/core/console.py b/src/jarvis/jarvis/core/console.py index b2ba1b7a..56b39ab5 100644 --- a/src/jarvis/jarvis/core/console.py +++ b/src/jarvis/jarvis/core/console.py @@ -28,7 +28,7 @@ from jarvis import settings from jarvis.utils.settings_database import settingsDB from jarvis.utils.console import jarvis_logo, start_text, OutputStyler, headerize -from jarvis.enumerations import MongoCollections, InputMode +from jarvis.enumerations import InputMode class ConsoleManager: diff --git a/src/jarvis/jarvis/enumerations.py b/src/jarvis/jarvis/enumerations.py index cca25e8b..f05c09a9 100644 --- a/src/jarvis/jarvis/enumerations.py +++ b/src/jarvis/jarvis/enumerations.py @@ -26,9 +26,3 @@ class InputMode(Enum): VOICE = 'voice' TEXT = 'text' - - -class MongoCollections(Enum): - GENERAL_SETTINGS = 'general_settings' - CONTROL_SKILLS = 'control_skills' - ENABLED_BASIC_SKILLS = 'enabled_basic_skills' diff --git a/src/jarvis/jarvis/skills/collection/activation.py b/src/jarvis/jarvis/skills/collection/activation.py index e29b7d8c..c5d7cb53 100644 --- a/src/jarvis/jarvis/skills/collection/activation.py +++ b/src/jarvis/jarvis/skills/collection/activation.py @@ -27,7 +27,7 @@ from jarvis.skills.skill import AssistantSkill from jarvis.utils.startup import play_activation_sound from jarvis.utils.settings_database import settingsDB -from jarvis.enumerations import InputMode, MongoCollections +from jarvis.enumerations import InputMode class ActivationSkills(AssistantSkill): diff --git a/src/jarvis/jarvis/skills/collection/configuration.py b/src/jarvis/jarvis/skills/collection/configuration.py index af3b70c6..e9662156 100644 --- a/src/jarvis/jarvis/skills/collection/configuration.py +++ b/src/jarvis/jarvis/skills/collection/configuration.py @@ -25,7 +25,7 @@ from jarvis.skills.skill import AssistantSkill from jarvis import settings from jarvis.utils.settings_database import settingsDB -from jarvis.enumerations import InputMode, MongoCollections +from jarvis.enumerations import InputMode from jarvis.utils import console from jarvis.utils import input From e79ec550b2fc6df5908a24888998c02f9cfc9f46 Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sun, 13 Feb 2022 08:47:42 -0600 Subject: [PATCH 08/12] Cleaned up database code and moved dbs to configurable directory --- .gitignore | 1 - data/.gitignore | 1 + run_jarvis.sh | 10 ------- run_tests.sh | 10 ------- setup.sh | 1 - src/jarvis/jarvis/settings.py | 6 ++++ src/jarvis/jarvis/utils/history_database.py | 16 +++++------ src/jarvis/jarvis/utils/settings_database.py | 29 +++++--------------- src/jarvis/jarvis/utils/skills_database.py | 8 +++++- 9 files changed, 28 insertions(+), 54 deletions(-) create mode 100644 data/.gitignore diff --git a/.gitignore b/.gitignore index 5dbcc1e6..ef68ca82 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,4 @@ __pycache__ model/ jarvis_virtualenv/ -*.db *.log diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 00000000..98e6ef67 --- /dev/null +++ b/data/.gitignore @@ -0,0 +1 @@ +*.db diff --git a/run_jarvis.sh b/run_jarvis.sh index bc4e63d3..fb44aad7 100755 --- a/run_jarvis.sh +++ b/run_jarvis.sh @@ -1,16 +1,6 @@ #!/usr/bin/env bash -# -------------------------------- -# Start MongoDB service -# -------------------------------- -#sudo systemctl start mongod # -------------------------------- # Start Jarvis service with virtualenv # -------------------------------- ./jarvis_virtualenv/bin/python ./src/jarvis/start.py -#python3 ./src/jarvis/start.py - -# -------------------------------- -# Stop MongoDB service -# -------------------------------- -#sudo systemctl stop mongod diff --git a/run_tests.sh b/run_tests.sh index 5e3a91f4..e131d45f 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -5,20 +5,10 @@ export PYTHONPATH="${PYTHONPATH}:./src/jarvis" source jarvis_virtualenv/bin/activate -# -------------------------------- -# Start MongoDB service -# -------------------------------- -sudo systemctl start mongodb - # -------------------------------- # Run unittests # -------------------------------- python -m unittest discover -s ./src -p "*tests.py" exit_code=($?) -# -------------------------------- -# Stop MongoDB service -# -------------------------------- -sudo systemctl stop mongodb - exit $exit_code \ No newline at end of file diff --git a/setup.sh b/setup.sh index 3fe8dc66..4cd0611c 100755 --- a/setup.sh +++ b/setup.sh @@ -34,7 +34,6 @@ sudo apt-get install build-essential \ libgstreamer1.0-dev libgstreamer1.0-0 \ espeak \ libcairo2-dev libgirepository1.0-dev gir1.2-gtk-3.0 \ - mongodb \ gnupg # Reload local package database diff --git a/src/jarvis/jarvis/settings.py b/src/jarvis/jarvis/settings.py index 98f5a0c6..0b50326d 100644 --- a/src/jarvis/jarvis/settings.py +++ b/src/jarvis/jarvis/settings.py @@ -65,6 +65,12 @@ """ +DATABASE_SETTINGS = { + 'base_dir': './data/', + 'history_filename': 'history.db', + 'settings_filename': 'settings.db', + 'skills_filename': 'skills.db' +} DEFAULT_GENERAL_SETTINGS = { 'assistant_name': 'Jarvis', diff --git a/src/jarvis/jarvis/utils/history_database.py b/src/jarvis/jarvis/utils/history_database.py index 5713155b..283ca4e0 100644 --- a/src/jarvis/jarvis/utils/history_database.py +++ b/src/jarvis/jarvis/utils/history_database.py @@ -1,5 +1,8 @@ import sqlite3 from time import time +from os.path import isdir, dirname + +from jarvis.settings import DATABASE_SETTINGS class History: def __init__(self, user_transcript = '--', response = '--', executed_skill = '--', timestamp=0): @@ -21,6 +24,9 @@ def __init__(self, database='history.db'): self._ensure_database_exists() def _ensure_database_exists(self): + if not isdir(dirname(self.database)): + raise IOError("Database base dir %s does not exist" % dirname(self.database)) + conn = sqlite3.connect(self.database) conn.execute( """create table if not exists history ( @@ -60,12 +66,4 @@ def addHistory(self, history): conn.commit() conn.close() -historyDB = HistoryDatabase() -# his = historyDB.getHistory() -# print("history = %s" % his) -# historyDB.addHistory(History(user_transcript='test1', response='resp1', executed_skill='skill1')) -# historyDB.addHistory(History(user_transcript='test2', response='resp2', executed_skill='skill2')) -# historyDB.addHistory(History(user_transcript='test3', response='resp3', executed_skill='skill3')) -# his = historyDB.getHistory(limit=3) -# for h in his: -# print (h) +historyDB = HistoryDatabase(DATABASE_SETTINGS['base_dir'] + DATABASE_SETTINGS['history_filename']) diff --git a/src/jarvis/jarvis/utils/settings_database.py b/src/jarvis/jarvis/utils/settings_database.py index 7bd1daf1..c72dcc1b 100644 --- a/src/jarvis/jarvis/utils/settings_database.py +++ b/src/jarvis/jarvis/utils/settings_database.py @@ -1,5 +1,8 @@ import sqlite3 import json +from os.path import isdir, dirname + +from jarvis.settings import DATABASE_SETTINGS class GeneralSettings: def __init__(self, input_mode='voice', assistant_name='jarvis', response_in_speech='true'): @@ -10,25 +13,15 @@ def __str__(self): return "input_mode=%s, assistant_name=%s, response_in_speech=%s" % ( self.input_mode, self.assistant_name, self.response_in_speech) -# class ControlSkills: -# def __init__(self): -# self.something = 0 - -# class EnabledBasicSkills: -# def __init__(self): -# self.something = 0 - -# class LearnedSkills: -# def __init__(self): -# self.something = 0 - - class SettingsDatabase: def __init__(self, database='settings.db'): self.database = database self._ensure_database_exists() def _ensure_database_exists(self): + if not isdir(dirname(self.database)): + raise IOError("Database base dir %s does not exist" % dirname(self.database)) + conn = sqlite3.connect(self.database) conn.execute( """create table if not exists collections ( @@ -73,12 +66,4 @@ def updateGeneralSettings(self, settings): conn.commit() conn.close() -#print("Create settings") -settingsDB = SettingsDatabase() -# print("Get settings") -# gen = settingsDB.getGeneralSettings() -# print("Gen = (%s)" % gen) -# gen.assistant_name = 'travis' -# settingsDB.updateGeneralSettings(gen) -# gen = settingsDB.getGeneralSettings() -# print("Gen = (%s)" % gen) +settingsDB = SettingsDatabase(DATABASE_SETTINGS['base_dir'] + DATABASE_SETTINGS['settings_filename']) diff --git a/src/jarvis/jarvis/utils/skills_database.py b/src/jarvis/jarvis/utils/skills_database.py index 08927f84..c26ef397 100644 --- a/src/jarvis/jarvis/utils/skills_database.py +++ b/src/jarvis/jarvis/utils/skills_database.py @@ -1,6 +1,9 @@ from signal import pause import sqlite3 import json +from os.path import isdir, dirname + +from jarvis.settings import DATABASE_SETTINGS class SkillsDatabase: def __init__(self, database='skills.db'): @@ -8,6 +11,9 @@ def __init__(self, database='skills.db'): self._ensure_database_exists() def _ensure_database_exists(self): + if not isdir(dirname(self.database)): + raise IOError("Database base dir %s does not exist" % dirname(self.database)) + conn = sqlite3.connect(self.database) conn.execute( """create table if not exists collections ( @@ -56,4 +62,4 @@ def clearLearnedSkills(self): conn.close() -skillsDB = SkillsDatabase() +skillsDB = SkillsDatabase(DATABASE_SETTINGS['base_dir'] + DATABASE_SETTINGS['skills_filename']) From 65f7af4b95211b661c58fb03e91a42474e0fef73 Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sun, 13 Feb 2022 08:51:58 -0600 Subject: [PATCH 09/12] Fixed tests --- src/tests/skill_analyzer_tests.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/tests/skill_analyzer_tests.py b/src/tests/skill_analyzer_tests.py index f252590e..c2510dd1 100644 --- a/src/tests/skill_analyzer_tests.py +++ b/src/tests/skill_analyzer_tests.py @@ -25,35 +25,14 @@ from sklearn.metrics.pairwise import cosine_similarity from jarvis import settings -from jarvis.skills.registry import CONTROL_SKILLS, BASIC_SKILLS, ENABLED_BASIC_SKILLS -from jarvis.enumerations import MongoCollections +from jarvis.skills.registry import BASIC_SKILLS from jarvis.skills.analyzer import SkillAnalyzer -from jarvis.utils.mongoDB import db class TestSkillMatching(unittest.TestCase): def setUp(self): - all_skills = { - MongoCollections.CONTROL_SKILLS.value: CONTROL_SKILLS, - MongoCollections.ENABLED_BASIC_SKILLS.value: ENABLED_BASIC_SKILLS, - } - for collection, documents in all_skills.items(): - db.update_collection(collection, documents) - - default_assistant_name = settings.DEFAULT_GENERAL_SETTINGS['assistant_name'] - default_input_mode = settings.DEFAULT_GENERAL_SETTINGS['input_mode'] - default_response_in_speech = settings.DEFAULT_GENERAL_SETTINGS['response_in_speech'] - - default_settings = { - 'assistant_name': default_assistant_name, - 'input_mode': default_input_mode, - 'response_in_speech': default_response_in_speech, - } - - db.update_collection(collection=MongoCollections.GENERAL_SETTINGS.value, documents=[default_settings]) - self.skill_analyzer = SkillAnalyzer( weight_measure=TfidfVectorizer, similarity_measure=cosine_similarity, From 4144ff94adf1d4ae9249d078d20bd1b9539fcb3d Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sun, 13 Feb 2022 08:55:38 -0600 Subject: [PATCH 10/12] Added license to new files --- src/jarvis/jarvis/utils/history_database.py | 22 ++++++++++++++++++++ src/jarvis/jarvis/utils/settings_database.py | 22 ++++++++++++++++++++ src/jarvis/jarvis/utils/skills_database.py | 22 ++++++++++++++++++++ src/jarvis/jarvis/utils/skills_registry.py | 22 ++++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/src/jarvis/jarvis/utils/history_database.py b/src/jarvis/jarvis/utils/history_database.py index 283ca4e0..4253e03a 100644 --- a/src/jarvis/jarvis/utils/history_database.py +++ b/src/jarvis/jarvis/utils/history_database.py @@ -1,3 +1,25 @@ +# MIT License + +# Copyright (c) 2019 Georgios Papachristou + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + import sqlite3 from time import time from os.path import isdir, dirname diff --git a/src/jarvis/jarvis/utils/settings_database.py b/src/jarvis/jarvis/utils/settings_database.py index c72dcc1b..15379919 100644 --- a/src/jarvis/jarvis/utils/settings_database.py +++ b/src/jarvis/jarvis/utils/settings_database.py @@ -1,3 +1,25 @@ +# MIT License + +# Copyright (c) 2019 Georgios Papachristou + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + import sqlite3 import json from os.path import isdir, dirname diff --git a/src/jarvis/jarvis/utils/skills_database.py b/src/jarvis/jarvis/utils/skills_database.py index c26ef397..fa216118 100644 --- a/src/jarvis/jarvis/utils/skills_database.py +++ b/src/jarvis/jarvis/utils/skills_database.py @@ -1,3 +1,25 @@ +# MIT License + +# Copyright (c) 2019 Georgios Papachristou + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + from signal import pause import sqlite3 import json diff --git a/src/jarvis/jarvis/utils/skills_registry.py b/src/jarvis/jarvis/utils/skills_registry.py index c0d45606..78d3662b 100644 --- a/src/jarvis/jarvis/utils/skills_registry.py +++ b/src/jarvis/jarvis/utils/skills_registry.py @@ -1,3 +1,25 @@ +# MIT License + +# Copyright (c) 2019 Georgios Papachristou + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + import logging from jarvis.utils.skills_database import skillsDB From 478b916074812d5cb1c819f91dedb26a163dd18d Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sun, 13 Feb 2022 15:31:48 -0600 Subject: [PATCH 11/12] Removed the TTS/STT changes --- requirements.txt | 1 - src/jarvis/jarvis/__init__.py | 3 +- src/jarvis/jarvis/engines/__init__.py | 3 +- src/jarvis/jarvis/engines/stt.py | 54 ++++++++- src/jarvis/jarvis/engines/stt_google.py | 96 ---------------- src/jarvis/jarvis/engines/stt_vosk.py | 139 ----------------------- src/jarvis/jarvis/engines/tts.py | 4 +- src/jarvis/jarvis/engines/tts_pyttsx3.py | 133 ---------------------- 8 files changed, 57 insertions(+), 376 deletions(-) delete mode 100644 src/jarvis/jarvis/engines/stt_google.py delete mode 100644 src/jarvis/jarvis/engines/stt_vosk.py delete mode 100644 src/jarvis/jarvis/engines/tts_pyttsx3.py diff --git a/requirements.txt b/requirements.txt index e588d7b4..39c4adb8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,7 +44,6 @@ textblob==0.15.3 tzlocal==1.5.1 urllib3==1.24.3 virtualenv==20.8.1 -vosk==0.3.32 web-cache==1.1.0 wikipedia==1.4.0 wolframalpha==3.0.1 diff --git a/src/jarvis/jarvis/__init__.py b/src/jarvis/jarvis/__init__.py index eb9863c2..01270a9b 100644 --- a/src/jarvis/jarvis/__init__.py +++ b/src/jarvis/jarvis/__init__.py @@ -61,6 +61,5 @@ # ---------------------------------------------------------------------------------------------------------------------- # Create assistant input and output engine instances # ---------------------------------------------------------------------------------------------------------------------- -#input_engine = engines.STTEngineVosk()# if input_mode == InputMode.VOICE.value else engines.TTTEngine() -input_engine = engines.STTEngineGoogle() if input_mode == InputMode.VOICE.value else engines.TTTEngine() +input_engine = engines.STTEngine() if input_mode == InputMode.VOICE.value else engines.TTTEngine() output_engine = engines.TTSEngine() if response_in_speech else engines.TTTEngine() diff --git a/src/jarvis/jarvis/engines/__init__.py b/src/jarvis/jarvis/engines/__init__.py index caa65ef5..b2fbbe45 100644 --- a/src/jarvis/jarvis/engines/__init__.py +++ b/src/jarvis/jarvis/engines/__init__.py @@ -1,4 +1,3 @@ -from .stt_google import * -from .stt_vosk import * +from .stt import * from .tts import * from .ttt import * diff --git a/src/jarvis/jarvis/engines/stt.py b/src/jarvis/jarvis/engines/stt.py index bb122256..1bb9de88 100644 --- a/src/jarvis/jarvis/engines/stt.py +++ b/src/jarvis/jarvis/engines/stt.py @@ -30,15 +30,67 @@ class STTEngine: """ Speech To Text Engine (STT) + + Google API Speech recognition settings + SpeechRecognition API : https://pypi.org/project/SpeechRecognition/2.1.3 """ def __init__(self): super().__init__() self.console_manager = ConsoleManager() + self.console_manager.console_output(info_log="Configuring Mic..") + self.recognizer = sr.Recognizer() + self.recognizer.pause_threshold = 0.5 + self.microphone = sr.Microphone() + self.console_manager.console_output(info_log="Microphone configured successfully!") def recognize_input(self, already_activated=False): """ Recognize input from mic and returns transcript if activation tag (assistant name) exist """ - pass + while True: + transcript = self._recognize_speech_from_mic() + if already_activated or self._activation_name_exist(transcript): + transcript = self._remove_activation_word(transcript) + return transcript + + def _recognize_speech_from_mic(self, ): + """ + Capture the words from the recorded audio (audio stream --> free text). + Transcribe speech from recorded from `microphone`. + """ + + with self.microphone as source: + self.recognizer.adjust_for_ambient_noise(source) + audio = self.recognizer.listen(source) + + try: + transcript = self.recognizer.recognize_google(audio).lower() + self.console_manager.console_output(info_log='User said: {0}'.format(transcript)) + except sr.UnknownValueError: + # speech was unintelligible + transcript = '' + self.console_manager.console_output(info_log='Unable to recognize speech', refresh_console=False) + except sr.RequestError: + # API was unreachable or unresponsive + transcript = '' + self.console_manager.console_output(error_log='Google API was unreachable') + return transcript + + @staticmethod + def _activation_name_exist(transcript): + """ + Identifies the assistant name in the input transcript. + """ + + if transcript: + transcript_words = transcript.split() + return bool(set(transcript_words).intersection([jarvis.assistant_name])) + else: + return False + + @staticmethod + def _remove_activation_word(transcript): + transcript = transcript.replace(jarvis.assistant_name, '') + return transcript diff --git a/src/jarvis/jarvis/engines/stt_google.py b/src/jarvis/jarvis/engines/stt_google.py deleted file mode 100644 index f261636b..00000000 --- a/src/jarvis/jarvis/engines/stt_google.py +++ /dev/null @@ -1,96 +0,0 @@ -# MIT License - -# Copyright (c) 2019 Georgios Papachristou - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import logging -import speech_recognition as sr - -import jarvis -from jarvis.engines.stt import STTEngine -from jarvis.core.console import ConsoleManager - - -class STTEngineGoogle(STTEngine): - """ - Speech To Text Engine (STT) - - Google API Speech recognition settings - SpeechRecognition API : https://pypi.org/project/SpeechRecognition/2.1.3 - """ - - def __init__(self): - super().__init__() - self.console_manager.console_output(info_log="Configuring Mic..") - self.recognizer = sr.Recognizer() - self.recognizer.pause_threshold = 0.5 - self.microphone = sr.Microphone() - self.console_manager.console_output(info_log="Microphone configured successfully!") - - def recognize_input(self, already_activated=False): - """ - Recognize input from mic and returns transcript if activation tag (assistant name) exist - """ - - while True: - transcript = self._recognize_speech_from_mic() - if already_activated or self._activation_name_exist(transcript): - transcript = self._remove_activation_word(transcript) - return transcript - - def _recognize_speech_from_mic(self, ): - """ - Capture the words from the recorded audio (audio stream --> free text). - Transcribe speech from recorded from `microphone`. - """ - - with self.microphone as source: - self.recognizer.adjust_for_ambient_noise(source) - audio = self.recognizer.listen(source) - - try: - transcript = self.recognizer.recognize_google(audio).lower() - self.console_manager.console_output(info_log='User said: {0}'.format(transcript)) - except sr.UnknownValueError: - # speech was unintelligible - transcript = '' - self.console_manager.console_output(info_log='Unable to recognize speech', refresh_console=False) - except sr.RequestError: - # API was unreachable or unresponsive - transcript = '' - self.console_manager.console_output(error_log='Google API was unreachable') - return transcript - - @staticmethod - def _activation_name_exist(transcript): - """ - Identifies the assistant name in the input transcript. - """ - - if transcript: - transcript_words = transcript.split() - return bool(set(transcript_words).intersection([jarvis.assistant_name])) - else: - return False - - @staticmethod - def _remove_activation_word(transcript): - transcript = transcript.replace(jarvis.assistant_name, '') - return transcript diff --git a/src/jarvis/jarvis/engines/stt_vosk.py b/src/jarvis/jarvis/engines/stt_vosk.py deleted file mode 100644 index d911b9af..00000000 --- a/src/jarvis/jarvis/engines/stt_vosk.py +++ /dev/null @@ -1,139 +0,0 @@ -# MIT License - -# Copyright (c) 2019 Georgios Papachristou - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from cmath import inf -import logging - -import jarvis -from jarvis.engines.stt import STTEngine -from jarvis.core.console import ConsoleManager -import sounddevice as sd -import vosk -import json -import sys -from types import SimpleNamespace -import queue -import os - -# work_queue = queue.Queue() - -# def other_callback(indata, frames, time, status): -# print("Callback...") -# if status: -# print("Callback status: " + status) -# work_queue.put(bytes(indata)) - -class STTEngineVosk(STTEngine): - """ - Speech To Text Engine (STT) - - Vosk API Speech recognition settings - SpeechRecognition API : https://pypi.org/project/SpeechRecognition/2.1.3 - """ - - def __init__(self): - super().__init__() - self.console_manager.console_output(info_log="Initializing Vosk STT Engine") - self.console_manager.console_output(info_log="Configuring Mic..") - model_dir = 'model' - device_id = 'default' #None - device_info = sd.query_devices(device_id, 'input') - self.sample_rate = int(device_info['default_samplerate']) - if not os.path.exists(model_dir): - self.console_manager.console_output(error_log="Model directory does not exist: {}".format(model_dir)) - exit(0) - self.vosk_model = vosk.Model(model_dir) # default to a model directory of 'model' - self.work_queue = queue.Queue() - self.recognizer = vosk.KaldiRecognizer(self.vosk_model, self.sample_rate) - - self.console_manager.console_output(info_log="Settings: device={}, samplerate: {}".format(device_id, self.sample_rate)) - self.mic_stream = sd.RawInputStream(samplerate=self.sample_rate, device=device_id, blocksize = 8000, dtype='int16', channels=1, - callback=lambda indata, frames, time, status: self.callback(self, indata, frames, time, status)) - self.mic_stream.start() - self.console_manager.console_output(info_log="Mic status: {}".format(self.mic_stream.active)) - - self.console_manager.console_output(info_log="Microphone configured successfully!") - - def __exit__(self): - self.mic_stream.close() - - @staticmethod - def callback(self, indata, frames, time, status): - """This is called (from a separate thread) for each audio block.""" - # self.console_manager.console_output(info_log="Callback...") - # print("callback...") - if status: - print("Callback status: %s" % status) - # self.console_manager.console_output(info_log="Callback status: " + status) - self.work_queue.put(bytes(indata)) - - def recognize_input(self, already_activated=False): - """ - Recognize input from mic and returns transcript if activation tag (assistant name) exist - """ - - while True: - transcript = self._recognize_speech_from_mic() - if already_activated or self._activation_name_exist(transcript): - transcript = self._remove_activation_word(transcript) - return transcript - - def _recognize_speech_from_mic(self, ): - """ - Capture the words from the recorded audio (audio stream --> free text). - Transcribe speech from recorded from `microphone`. - """ - - # self.console_manager.console_output(info_log="Get from queue") - try: - transcript = '' - data = self.work_queue.get() - # self.console_manager.console_output(info_log="Got data") - if self.recognizer.AcceptWaveform(data): - res = json.loads(self.recognizer.Result(), object_hook=lambda d: SimpleNamespace(**d)) - transcript = res.text - self.console_manager.console_output(info_log='User said: {0}'.format(transcript)) - return transcript - # else: - # # res = json.loads(self.recognizer.PartialResult(), object_hook=lambda d: SimpleNamespace(**d)) - # # self.console_manager.console_output(info_log='Partial: {0}'.format(res.partial)) - # print("Partial: " + self.recognizer.PartialResult()) - except queue.Empty: - pass - # print("Nothing detected yet") - - @staticmethod - def _activation_name_exist(transcript): - """ - Identifies the assistant name in the input transcript. - """ - - if transcript: - transcript_words = transcript.split() - return bool(set(transcript_words).intersection([jarvis.assistant_name])) - else: - return False - - @staticmethod - def _remove_activation_word(transcript): - transcript = transcript.replace(jarvis.assistant_name, '') - return transcript diff --git a/src/jarvis/jarvis/engines/tts.py b/src/jarvis/jarvis/engines/tts.py index da62804b..fa0b608d 100644 --- a/src/jarvis/jarvis/engines/tts.py +++ b/src/jarvis/jarvis/engines/tts.py @@ -70,8 +70,8 @@ def assistant_response(self, message, refresh_console=True): """ self._insert_into_message_queue(message) try: - speech_thread = threading.Thread(target=self._speech_and_console, args=(refresh_console,)) - speech_thread.start() + speech_tread = threading.Thread(target=self._speech_and_console, args=(refresh_console,)) + speech_tread.start() except RuntimeError as e: self.logger.error('Error in assistant response thread with message {0}'.format(e)) diff --git a/src/jarvis/jarvis/engines/tts_pyttsx3.py b/src/jarvis/jarvis/engines/tts_pyttsx3.py deleted file mode 100644 index fa0b608d..00000000 --- a/src/jarvis/jarvis/engines/tts_pyttsx3.py +++ /dev/null @@ -1,133 +0,0 @@ -# MIT License - -# Copyright (c) 2019 Georgios Papachristou - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import threading -import logging -import pyttsx3 -import queue - -from jarvis.core.console import ConsoleManager - - -class TTS: - """ - Text To Speech Engine (TTS) - """ - - def __init__(self): - self.tts_engine = self._set_voice_engine() - - def run_engine(self): - try: - self.tts_engine.runAndWait() - except RuntimeError: - pass - - @staticmethod - def _set_voice_engine(): - """ - Setup text to speech engine - :return: gtts engine object - """ - tts_engine = pyttsx3.init() - tts_engine.setProperty('rate', 160) # Setting up new voice rate - tts_engine.setProperty('volume', 1.0) # Setting up volume level between 0 and 1 - return tts_engine - - -class TTSEngine(TTS): - def __init__(self): - super().__init__() - self.logger = logging - self.message_queue = queue.Queue(maxsize=9) # Maxsize is the size of the queue / capacity of messages - self.stop_speaking = False - self.console_manager = ConsoleManager() - - def assistant_response(self, message, refresh_console=True): - """ - Assistant response in voice. - :param refresh_console: boolean - :param message: string - """ - self._insert_into_message_queue(message) - try: - speech_tread = threading.Thread(target=self._speech_and_console, args=(refresh_console,)) - speech_tread.start() - except RuntimeError as e: - self.logger.error('Error in assistant response thread with message {0}'.format(e)) - - def _insert_into_message_queue(self, message): - try: - self.message_queue.put(message) - except Exception as e: - self.logger.error("Unable to insert message to queue with error message: {0}".format(e)) - - def _speech_and_console(self, refresh_console): - """ - Speech method translate text batches to speech and print them in the console. - :param text: string (e.g 'tell me about google') - """ - try: - while not self.message_queue.empty(): - - cumulative_batch = '' - message = self.message_queue.get() - if message: - batches = self._create_text_batches(raw_text=message) - for batch in batches: - self.tts_engine.say(batch) - cumulative_batch += batch - self.console_manager.console_output(cumulative_batch, refresh_console=refresh_console) - self.run_engine() - if self.stop_speaking: - self.logger.debug('Speech interruption triggered') - self.stop_speaking = False - break - except Exception as e: - self.logger.error("Speech and console error message: {0}".format(e)) - - @staticmethod - def _create_text_batches(raw_text, number_of_words_per_batch=8): - """ - Splits the user speech message into batches and return a list with the split batches - :param raw_text: string - :param number_of_words_per_batch: int - :return: list - """ - raw_text = raw_text + ' ' - list_of_batches = [] - total_words = raw_text.count(' ') - letter_id = 0 - - for split in range(0, int(total_words / number_of_words_per_batch)): - batch = '' - words_count = 0 - while words_count < number_of_words_per_batch: - batch += raw_text[letter_id] - if raw_text[letter_id] == ' ': - words_count += 1 - letter_id += 1 - list_of_batches.append(batch) - - if letter_id < len(raw_text): # Add the rest of word in a batch - list_of_batches.append(raw_text[letter_id:]) - return list_of_batches From f85d6ccd554e20338d23a7f7a6e0ee57a0d7d8d2 Mon Sep 17 00:00:00 2001 From: James Wynn Date: Sun, 13 Feb 2022 15:37:23 -0600 Subject: [PATCH 12/12] Removed unnecessary import --- src/jarvis/jarvis/utils/skills_database.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/jarvis/jarvis/utils/skills_database.py b/src/jarvis/jarvis/utils/skills_database.py index fa216118..6a9c496a 100644 --- a/src/jarvis/jarvis/utils/skills_database.py +++ b/src/jarvis/jarvis/utils/skills_database.py @@ -20,7 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from signal import pause import sqlite3 import json from os.path import isdir, dirname