diff --git a/config.py b/config.py index 84c2c11..e4899e4 100644 --- a/config.py +++ b/config.py @@ -1,3 +1,83 @@ +from enum import Enum + + +class Speaker(Enum): + GOOGLE_TEXT_TO_SPEECH = 1 + STREAMLABS_API = 2 + + +class FileNameStrategy(Enum): + TIME_BASED = 1 + CONTENT_BASED = 2 + + +class PollyVoices(Enum): + Aditi = "Aditi" + Amy = "Amy" + Astrid = "Astrid" + Bianca = "Bianca" + Brian = "Brian" + Camila = "Camila" + Carla = "Carla" + Carmen = "Carmen" + Celine = "Celine" + Chantal = "Chantal" + Conchita = "Conchita" + Cristiano = "Cristiano" + Dora = "Dora" + Emma = "Emma" + Enrique = "Enrique" + Ewa = "Ewa" + Filiz = "Filiz" + Geraint = "Geraint" + Giorgio = "Giorgio" + Gwyneth = "Gwyneth" + Hans = "Hans" + Ines = "Ines" + Ivy = "Ivy" + Jacek = "Jacek" + Jan = "Jan" + Joanna = "Joanna" + Joey = "Joey" + Justin = "Justin" + Karl = "Karl" + Kendra = "Kendra" + Kimberly = "Kimberly" + Lea = "Lea" + Liv = "Liv" + Lotte = "Lotte" + Lucia = "Lucia" + Lupe = "Lupe" + Mads = "Mads" + Maja = "Maja" + Marlene = "Marlene" + Mathieu = "Mathieu" + Matthew = "Matthew" + Maxim = "Maxim" + Mia = "Mia" + Miguel = "Miguel" + Mizuki = "Mizuki" + Naja = "Naja" + Nicole = "Nicole" + Penelope = "Penelope" + Raveena = "Raveena" + Ricardo = "Ricardo" + Ruben = "Ruben" + Russell = "Russell" + Salli = "Salli" + Seoyeon = "Seoyeon" + Takumi = "Takumi" + Tatyana = "Tatyana" + Vicki = "Vicki" + Vitoria = "Vitoria" + Zeina = "Zeina" + Zhiyu = "Zhiyu" + + botList = ("Nightbot", "StreamElements", "Moobot", "praxis_bot") slurList = ("fag", "faggot", "niga", "nigga", "nigger", "retard", "tard", "rtard", "coon") + +currentSpeaker = Speaker.STREAMLABS_API +fileNameStrategy = FileNameStrategy.CONTENT_BASED +streamlabsVoice = PollyVoices.Justin diff --git a/requirements.txt b/requirements.txt index d052841..0e8c8c3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ SQLAlchemy pandas numpy gTTS -playsound \ No newline at end of file +playsound +pyglet \ No newline at end of file diff --git a/tests/test_tts.py b/tests/test_tts.py new file mode 100644 index 0000000..faf992e --- /dev/null +++ b/tests/test_tts.py @@ -0,0 +1,12 @@ +import unittest + +import tts + +class TTSTest(unittest.TestCase): + + def test_file_name(self): + tts.create_file_name("test", "mp3") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_twitch_script_class.py b/tests/test_twitch_script_class.py index eba485c..63a6f52 100644 --- a/tests/test_twitch_script_class.py +++ b/tests/test_twitch_script_class.py @@ -4,7 +4,7 @@ import twitch testValidUrls = ['https://shady.ru', 'http://stolencards.zn', 'https://i.imgur.com/FL6slHd.jpg'] -testInvalidUrls = ['this is just a sentence. With a period', 'gotta have some other stuff', 'bad punctuation produces false positives'] +testInvalidUrls = ['this is just a sentence. With a period', 'gotta have some other stuff', 'bad punctuation.does not produces false positives'] class TwitchBotTest(unittest.TestCase): diff --git a/tts.py b/tts.py index 7eff716..b4d3bf5 100644 --- a/tts.py +++ b/tts.py @@ -1,31 +1,89 @@ +import datetime +import hashlib import os -import datetime - +import requests +from gtts import gTTS from playsound import playsound +import config + +streamLabsUrl = "https://streamlabs.com/polly/speak" def tts(inputText: str, *args): - destPath = os.getcwd() + "\\tts\\" - time = datetime.datetime.now() - fileName: str = time.strftime("%m-%d-%Y_%H-%M-%S") + "_tts.mp3" + outpath = create_speech_file(inputText) + playsound(outpath) - if len(args) == 1: - fileName = args[0] + "_tts.mp3" - # tts = gTTS(text=inputText, lang='en') - # tts.save(destPath + fileName) +def create_speech_gtts(input_text: str): + path = os.path.join(get_tts_dir(), create_file_name(input_text, "mp3")) + if not os.path.exists(path): + sound_digest = gTTS(text=input_text, lang='en') + sound_digest.save(path) + return path - playsound(destPath + fileName) - # os.system(filename) +def create_speech_streamlabs(text: str): + path = os.path.join(get_tts_dir(), create_file_name(text, "ogg")) + if not os.path.exists(path): + body = {"voice": config.streamlabsVoice.value, "text": text} + resp = requests.post(streamLabsUrl, data=body).json() + sound_file_url = resp["speak_url"] + if sound_file_url is not None: + sound_bytes = requests.get(sound_file_url, stream=True) + f = open(path, "+wb") + f.write(sound_bytes.content) + f.close() + return path + + +speechCreationFunctions = { # this is a mapping of the Speaker enum to function pointers + config.Speaker.STREAMLABS_API: create_speech_streamlabs, + config.Speaker.GOOGLE_TEXT_TO_SPEECH: create_speech_gtts +} + + +def create_speech_file(text: str): + text_creation_function = speechCreationFunctions.get(config.currentSpeaker) + output_path = text_creation_function(text) + return output_path + + +def create_file_name(text: str, ext: str): + """ + :param text: the content of the message. using the CONTENT_BASED FileNameStrategy, this will (ostensibly) produce a + unique file name based on the content of the message. Two messages of equal content will produce the same name + :param ext: the desired file extension i.e. mp3, ogg, wav, etc... + :return: returns the formatted filename i.e. 01-01-20_01-01-01_tts.mp3 + """ + if config.fileNameStrategy == config.FileNameStrategy.CONTENT_BASED: + unique_id = hashlib.md5(bytes(text, 'utf-8')).hexdigest() + return "%s_tts.%s" % (unique_id, ext) + + elif config.fileNameStrategy == config.FileNameStrategy.CONTENT_BASED: + time = datetime.datetime.now() + return "%s_tts.%s" % (time.strftime("%m-%d-%Y_%H-%M-%S"), ext) + + else: + return "unconfigured_tts.%s" % ext def play_speech(fileName): - destPath = os.getcwd() + "\\tts\\" + destPath = get_tts_dir() playsound(destPath + fileName) +def get_tts_dir(): + """ + Checks for the tts directory, and will create it if it does not exist + :return: the relative file path of the tts dir + """ + dir = os.path.join(os.getcwd(), "tts") # this is platform-agnostic + if not os.path.exists(dir): + os.mkdir(dir) + return dir + + if __name__ == "__main__": print("Enter Text: ") textInput = str(input()) diff --git a/twitch_script_class.py b/twitch_script_class.py index 1590311..fe6cd5f 100644 --- a/twitch_script_class.py +++ b/twitch_script_class.py @@ -1,13 +1,13 @@ import random +import re + import twitch import twitch.chat -import twitch_cred as twitch_credentials import config as config -import tts - import db -import re +import tts +import twitch_cred as twitch_credentials class Twitch_Module(): @@ -19,7 +19,8 @@ class Twitch_Module(): self.links_allowed: bool = True self.whitelisted_users: list = ["thecuriousnerd", "theredpoint", "lakotor"] # don't freak out, this is *merely* a regex for matching urls that will hit just about everything - self._urlMatcher = re.compile("(https?:(/{1,3}|[a-z0-9%])|[a-z0-9.-]+[.](com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw))") + self._urlMatcher = re.compile( + "(https?:(/{1,3}|[a-z0-9%])|[a-z0-9.-]+[.](com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw))") def join_channel(self, channel_name): channel_name = "#" + channel_name @@ -81,14 +82,17 @@ class Twitch_Module(): print("{something went wrong}") def tts_message(self, message: twitch.chat.Message): - if self.contains_slur(message) == False: - if self.tts_enabled == True: - if message.text.startswith('!') == False: + if not self.contains_slur(message): + if self.tts_enabled: + if not message.text.startswith('!'): + text_to_say: str = "%s says, %s" % (message.sender, message.text) + channel_text = "%s user msg" % message.channel + if message.sender.lower() == message.channel: - tts.tts(message.sender + " says, " + message.text) + tts.tts(text_to_say) else: # tts.tts(message.sender + " says, " + message.text) - tts.tts(message.sender + " says, " + message.text, message.channel + " user msg") + tts.tts(text_to_say, channel_text) def contains_url(self, message: twitch.chat.Message): containsURL = re.search(self._urlMatcher, message.text.lower()) is not None