|
|
@ -8,7 +8,23 @@ from moviepy.audio.AudioClip import concatenate_audioclips, CompositeAudioClip
|
|
|
|
from moviepy.audio.io.AudioFileClip import AudioFileClip
|
|
|
|
from moviepy.audio.io.AudioFileClip import AudioFileClip
|
|
|
|
from requests.exceptions import JSONDecodeError
|
|
|
|
from requests.exceptions import JSONDecodeError
|
|
|
|
|
|
|
|
|
|
|
|
voices = ['Brian', 'Emma', 'Russell', 'Joey', 'Matthew', 'Joanna', 'Kimberly', 'Amy', 'Geraint', 'Nicole', 'Justin', 'Ivy', 'Kendra', 'Salli', 'Raveena']
|
|
|
|
voices = [
|
|
|
|
|
|
|
|
"Brian",
|
|
|
|
|
|
|
|
"Emma",
|
|
|
|
|
|
|
|
"Russell",
|
|
|
|
|
|
|
|
"Joey",
|
|
|
|
|
|
|
|
"Matthew",
|
|
|
|
|
|
|
|
"Joanna",
|
|
|
|
|
|
|
|
"Kimberly",
|
|
|
|
|
|
|
|
"Amy",
|
|
|
|
|
|
|
|
"Geraint",
|
|
|
|
|
|
|
|
"Nicole",
|
|
|
|
|
|
|
|
"Justin",
|
|
|
|
|
|
|
|
"Ivy",
|
|
|
|
|
|
|
|
"Kendra",
|
|
|
|
|
|
|
|
"Salli",
|
|
|
|
|
|
|
|
"Raveena",
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# valid voices https://lazypy.ro/tts/
|
|
|
|
# valid voices https://lazypy.ro/tts/
|
|
|
@ -16,29 +32,33 @@ voices = ['Brian', 'Emma', 'Russell', 'Joey', 'Matthew', 'Joanna', 'Kimberly', '
|
|
|
|
|
|
|
|
|
|
|
|
class POLLY:
|
|
|
|
class POLLY:
|
|
|
|
def __init__(self):
|
|
|
|
def __init__(self):
|
|
|
|
self.url = 'https://streamlabs.com/polly/speak'
|
|
|
|
self.url = "https://streamlabs.com/polly/speak"
|
|
|
|
|
|
|
|
|
|
|
|
def tts(
|
|
|
|
def tts(
|
|
|
|
self,
|
|
|
|
self,
|
|
|
|
req_text: str = "Amazon Text To Speech",
|
|
|
|
req_text: str = "Amazon Text To Speech",
|
|
|
|
filename: str = "title.mp3",
|
|
|
|
filename: str = "title.mp3",
|
|
|
|
random_speaker=False,
|
|
|
|
random_speaker=False,
|
|
|
|
censer=False,
|
|
|
|
censor=False,
|
|
|
|
):
|
|
|
|
):
|
|
|
|
if random_speaker:
|
|
|
|
if random_speaker:
|
|
|
|
voice = self.randomvoice()
|
|
|
|
voice = self.randomvoice()
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
if not os.getenv('VOICE'):
|
|
|
|
if not os.getenv("VOICE"):
|
|
|
|
return ValueError('Please set the environment variable VOICE to a valid voice. options are: {}'.format(voices))
|
|
|
|
return ValueError(
|
|
|
|
|
|
|
|
"Please set the environment variable VOICE to a valid voice. options are: {}".format(
|
|
|
|
|
|
|
|
voices
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
voice = str(os.getenv("VOICE")).capitalize()
|
|
|
|
voice = str(os.getenv("VOICE")).capitalize()
|
|
|
|
body = {'voice': voice, 'text': req_text, 'service': 'polly'}
|
|
|
|
body = {"voice": voice, "text": req_text, "service": "polly"}
|
|
|
|
response = requests.post(self.url, data=body)
|
|
|
|
response = requests.post(self.url, data=body)
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
voice_data = requests.get(response.json()['speak_url'])
|
|
|
|
voice_data = requests.get(response.json()["speak_url"])
|
|
|
|
with open(filename, 'wb') as f:
|
|
|
|
with open(filename, "wb") as f:
|
|
|
|
f.write(voice_data.content)
|
|
|
|
f.write(voice_data.content)
|
|
|
|
except (KeyError, JSONDecodeError):
|
|
|
|
except (KeyError, JSONDecodeError):
|
|
|
|
if response.json()['error'] == 'Text length is too long!':
|
|
|
|
if response.json()["error"] == "Text length is too long!":
|
|
|
|
chunks = [
|
|
|
|
chunks = [
|
|
|
|
m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text)
|
|
|
|
m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text)
|
|
|
|
]
|
|
|
|
]
|
|
|
@ -48,9 +68,9 @@ class POLLY:
|
|
|
|
|
|
|
|
|
|
|
|
chunkId = 0
|
|
|
|
chunkId = 0
|
|
|
|
for chunk in chunks:
|
|
|
|
for chunk in chunks:
|
|
|
|
body = {'voice': voice, 'text': chunk, 'service': 'polly'}
|
|
|
|
body = {"voice": voice, "text": chunk, "service": "polly"}
|
|
|
|
resp = requests.post(self.url, data=body)
|
|
|
|
resp = requests.post(self.url, data=body)
|
|
|
|
voice_data = requests.get(resp.json()['speak_url'])
|
|
|
|
voice_data = requests.get(resp.json()["speak_url"])
|
|
|
|
with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out:
|
|
|
|
with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out:
|
|
|
|
out.write(voice_data.content)
|
|
|
|
out.write(voice_data.content)
|
|
|
|
|
|
|
|
|
|
|
@ -63,12 +83,14 @@ class POLLY:
|
|
|
|
cbn.build(audio_clips, filename, "concatenate")
|
|
|
|
cbn.build(audio_clips, filename, "concatenate")
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
os.rename(audio_clips[0], filename)
|
|
|
|
os.rename(audio_clips[0], filename)
|
|
|
|
except (sox.core.SoxError,
|
|
|
|
except (
|
|
|
|
FileNotFoundError): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339
|
|
|
|
sox.core.SoxError,
|
|
|
|
|
|
|
|
FileNotFoundError,
|
|
|
|
|
|
|
|
): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339
|
|
|
|
for clip in audio_clips:
|
|
|
|
for clip in audio_clips:
|
|
|
|
i = audio_clips.index(clip) # get the index of the clip
|
|
|
|
i = audio_clips.index(clip) # get the index of the clip
|
|
|
|
audio_clips = (
|
|
|
|
audio_clips = (
|
|
|
|
audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1:]
|
|
|
|
audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :]
|
|
|
|
) # replace the clip with an AudioFileClip
|
|
|
|
) # replace the clip with an AudioFileClip
|
|
|
|
audio_concat = concatenate_audioclips(audio_clips)
|
|
|
|
audio_concat = concatenate_audioclips(audio_clips)
|
|
|
|
audio_composite = CompositeAudioClip([audio_concat])
|
|
|
|
audio_composite = CompositeAudioClip([audio_concat])
|
|
|
@ -79,7 +101,7 @@ class POLLY:
|
|
|
|
Amazon Polly fails to read some symbols properly such as '& (and)'.
|
|
|
|
Amazon Polly fails to read some symbols properly such as '& (and)'.
|
|
|
|
So we normalize input text before passing it to the service
|
|
|
|
So we normalize input text before passing it to the service
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
text = text.replace('&', 'and')
|
|
|
|
text = text.replace("&", "and")
|
|
|
|
return text
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
def randomvoice(self):
|
|
|
|
def randomvoice(self):
|
|
|
|