From 7fa92c826f508044d20fab5c83d8efb1f98107d1 Mon Sep 17 00:00:00 2001 From: DGrothe-PhD Date: Thu, 4 Jul 2024 17:41:11 +0200 Subject: [PATCH] separate speaker functionality --- jarvis.py | 119 ++++++++---------------------------------------- speakerSetup.py | 91 ++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 99 deletions(-) create mode 100644 speakerSetup.py diff --git a/jarvis.py b/jarvis.py index 4666ca6..22340d4 100644 --- a/jarvis.py +++ b/jarvis.py @@ -2,9 +2,6 @@ import locale import re -import sys -import platform -import traceback import speech_recognition as sr import pyttsx3 @@ -12,8 +9,8 @@ import wikipedia +from speakerSetup import SpeakerStatus from videoText import RbbWeather -#from videoText import RbbText from videoText import ARDText ## We'll set German wikipedia as default. @@ -26,79 +23,17 @@ # pylint: disable=broad-exception-caught # pylint: disable=too-few-public-methods -### disable false positives of not callable -# pylint: disable=E1102 - -# pylint: disable=E0401 -### E0401 as pylint (GitHub/Linux) cannot install Windows package - -def is_windows_platform() -> bool: - """True if system is Windows""" - return platform.system() == 'Windows' - -class SpeakerInitializeError(Exception): - """Represent speaker initialization errors""" - class JarvisStatus: - """initialize speaker and other settings""" - debugSwitchOffSpeaker = False - engine = None + """initialize Jarvis settings""" isRunning = True - engineUsed = "" + searchingEngine = "" countErrors = 0 wikifound = [] - speak = None - @staticmethod - def initializePyTTSSpeaker() -> bool: - """Tries to initialize py3-tts speaker""" - try: - JarvisStatus.engine = pyttsx3.init() - voices = JarvisStatus.engine.getProperty('voices') - JarvisStatus.engine.setProperty('voice', voices[0].id) - except (RuntimeError, Exception): - print("Sorry, pyttsx3 is not working.") - # goal: if debug mode tell me, else keep quiet - traceback.print_exc(limit=2, file=sys.stdout) - JarvisStatus.engine = None - return False - return True - @staticmethod - def initializeSpVoiceSpeaker() -> bool: - """Tries to initialize Windows speaker""" - if not is_windows_platform(): - raise SpeakerInitializeError("Cannot initialize SpVoice. Windows platform required") - from win32com.client import Dispatch - try: - JarvisStatus.speak = Dispatch("SAPI.SpVoice").Speak - except Exception as exc: - traceback.print_exc(limit=2, file=sys.stdout) - raise SpeakerInitializeError("Cannot initialize SpVoice") from exc - return True +speaker = SpeakerStatus() - @staticmethod - def initializeSpeaker() -> bool: - """setup of speaking functionality""" - try: - # second member will be evaluated only if first will fail - return JarvisStatus.initializePyTTSSpeaker() or JarvisStatus.initializeSpVoiceSpeaker() - except SpeakerInitializeError: - JarvisStatus.debugSwitchOffSpeaker = True - return False - - -JarvisStatus.initializeSpeaker() - -def talk(text): - """lets system's voice speak the text""" - if JarvisStatus.engine: - #and not JarvisStatus.debugSwitchOffSpeaker: - JarvisStatus.engine.say(text) - JarvisStatus.engine.runAndWait() - elif JarvisStatus.speak: - JarvisStatus.speak(text) def makeReadable(text): """Replace for better speaker functionality: @@ -131,7 +66,7 @@ def takeCommand(): # JarvisStatus.isRunning = False except Exception: # rarely happening, however needs test before removal - talk("Beim Einlesen des Sprachkommandos ist etwas schiefgelaufen.") + speaker.talk("Beim Einlesen des Sprachkommandos ist etwas schiefgelaufen.") JarvisStatus.countErrors += 1 if JarvisStatus.countErrors >= 3: JarvisStatus.isRunning = False @@ -143,16 +78,16 @@ class utilities: @staticmethod def searchWikipedia(text, show_all = False): """get the first few lines of Wikipedia article""" - JarvisStatus.engineUsed = "wikipedia" + JarvisStatus.searchingEngine = "wikipedia" JarvisStatus.wikifound = wikipedia.search(text, results=3) if len(JarvisStatus.wikifound) > 1 and not show_all: answersFound = " oder ".join(JarvisStatus.wikifound) print(answersFound) - talk(answersFound) + speaker.talk(answersFound) else: info = wikipedia.summary(text, sentences = 2) print(info) - talk(info) + speaker.talk(info) def runJarvis(): """main function""" @@ -161,14 +96,14 @@ def runJarvis(): # if 'spiel' in command: song = command.replace('spiel', '') - JarvisStatus.engineUsed = "YouTube" - talk('Es läuft ' + song) + JarvisStatus.searchingEngine = "YouTube" + speaker.talk('Es läuft ' + song) pywhatkit.playonyt(song) # elif 'zeit' in command: time = datetime.datetime.now().strftime('%H:%M') print(time) - talk(f"Es ist jetzt {time} Uhr.") + speaker.talk(f"Es ist jetzt {time} Uhr.") # elif 'wikipedia' in command: person = command.replace('wikipedia', '') @@ -178,26 +113,15 @@ def runJarvis(): textMdax = ARDText(716) textResult = makeReadable(textMdax.content) print(textResult) - talk(textResult) + speaker.talk(textResult) textMdax.extractAndPreparePage(716, 2) textResult = makeReadable(textMdax.content) print(textResult) - talk(textResult) - #elif 'was' in command: - # person = command.replace('was', '') - # utilities.searchWikipedia(person) - # - #elif 'wann' in command: - # person = command.replace('wann', '') - # utilities.searchWikipedia(person) - # - #elif 'wo' in command: - # person = command.replace('wo', '') - # utilities.searchWikipedia(person) + speaker.talk(textResult) # elif 'zeige alle' in command: if len(JarvisStatus.wikifound) < 1: - talk("Keine Einträge") + speaker.talk("Keine Einträge") return for person in JarvisStatus.wikifound: utilities.searchWikipedia(person, True) @@ -206,19 +130,19 @@ def runJarvis(): elif 'datum' in command: date = datetime.datetime.now().strftime('%W. KW, %A den %d. %B %Y') print(date) - talk(date) + speaker.talk(date) # elif 'wetter' in command: textHeute = RbbWeather() print(textHeute.content) - talk(textHeute.content) + speaker.talk(textHeute.content) # elif 'stop listening' in command or 'stop listing' in command \ or (command.startswith('stop') and len(command)< 50): - talk('Bye, until next time.') + speaker.talk('Bye, until next time.') JarvisStatus.isRunning = False else: - talk('Entschuldigung, ich habe nicht verstanden.') + speaker.talk('Entschuldigung, ich habe nicht verstanden.') while JarvisStatus.isRunning: print("...")# without it, it didn't stop listening @@ -226,15 +150,12 @@ def runJarvis(): runJarvis() except KeyboardInterrupt: print("Tschüs.") - talk("Tschüs, bis zum nächsten Mal.") + speaker.talk("Tschüs, bis zum nächsten Mal.") break except Exception as e: - talk(f"Entschuldigung, {JarvisStatus.engineUsed} konnte es nicht finden.") + speaker.talk(f"Entschuldigung, {JarvisStatus.searchingEngine} konnte es nicht finden.") print(e) # pylint: disable=invalid-name # pylint: enable=broad-exception-caught # pylint: enable=too-few-public-methods -# pylint: enable=E1102 -# pylint: enable=E0401 -### no issue on a real system though diff --git a/speakerSetup.py b/speakerSetup.py new file mode 100644 index 0000000..d0a5ccd --- /dev/null +++ b/speakerSetup.py @@ -0,0 +1,91 @@ +import sys +import platform +import traceback + +import pyttsx3 + + +# pylint: disable=invalid-name +# pylint: disable=broad-exception-caught +# pylint: disable=too-few-public-methods + +### disable false positives of not callable +# pylint: disable=E1102 + +# pylint: disable=E0401 +### E0401 as pylint (GitHub/Linux) cannot install Windows package + + +class SpeakerInitializeError(Exception): + """Represent speaker initialization errors""" + +class SpeakerStatus: + """initialize speaker and other settings.""" + debugSwitchOffSpeaker = False + engine = None + speak = None + + @staticmethod + def is_windows_platform() -> bool: + """True if system is Windows""" + return platform.system() == 'Windows' + + @staticmethod + def initializePyTTSSpeaker() -> bool: + """Tries to initialize py3-tts speaker""" + try: + SpeakerStatus.engine = pyttsx3.init() + voices = SpeakerStatus.engine.getProperty('voices') + SpeakerStatus.engine.setProperty('voice', voices[0].id) + except (RuntimeError, Exception): + print("Sorry, pyttsx3 is not working.") + # goal: if debug mode tell me, else keep quiet + #traceback.print_exc(limit=2, file=sys.stdout) + SpeakerStatus.engine = None + return False + return True + + @staticmethod + def initializeSpVoiceSpeaker() -> bool: + """Tries to initialize Windows speaker""" + if not SpeakerStatus.is_windows_platform(): + raise SpeakerInitializeError("Cannot initialize SpVoice. Windows platform required") + from win32com.client import Dispatch + try: + SpeakerStatus.speak = Dispatch("SAPI.SpVoice").Speak + except Exception as exc: + #traceback.print_exc(limit=2, file=sys.stdout) + raise SpeakerInitializeError("Cannot initialize SpVoice") from exc + return True + + @staticmethod + def initializeSpeaker() -> bool: + """setup of speaking functionality""" + try: + # second member will be evaluated only if first will fail + return SpeakerStatus.initializePyTTSSpeaker() or SpeakerStatus.initializeSpVoiceSpeaker() + except SpeakerInitializeError: + print("No speaker has been configured.") + SpeakerStatus.debugSwitchOffSpeaker = True + return False + + def __init__(self): + SpeakerStatus.initializeSpeaker() + + def talk(self, text): + """lets system's voice speak the text""" + if SpeakerStatus.engine: + #and not SpeakerStatus.debugSwitchOffSpeaker: + SpeakerStatus.engine.say(text) + SpeakerStatus.engine.runAndWait() + elif SpeakerStatus.speak: + SpeakerStatus.speak(text) + + + +# pylint: disable=invalid-name +# pylint: enable=broad-exception-caught +# pylint: enable=too-few-public-methods +# pylint: enable=E1102 +# pylint: enable=E0401 +### no issue on a real system though