|
|
|
@ -1,4 +1,6 @@
|
|
|
|
|
import requests, base64, random, os
|
|
|
|
|
import re
|
|
|
|
|
from moviepy.editor import AudioFileClip, concatenate_audioclips, CompositeAudioClip
|
|
|
|
|
|
|
|
|
|
# https://twitter.com/scanlime/status/1512598559769702406
|
|
|
|
|
voices = [ # DISNEY VOICES
|
|
|
|
@ -55,15 +57,29 @@ class TTTTSWrapper: # TikTok Text-to-Speech Wrapper
|
|
|
|
|
|
|
|
|
|
def tts(self, req_text: str = "TikTok Text To Speech", filename: str = 'title.mp3', random_speaker: bool = False):
|
|
|
|
|
req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and")
|
|
|
|
|
|
|
|
|
|
voice = self.randomvoice() if random_speaker else 'en_us_002'
|
|
|
|
|
r = requests.post(f"{self.URI_BASE}{voice}&req_text={req_text}&speaker_map_type=0")
|
|
|
|
|
|
|
|
|
|
vstr = [r.json()["data"]["v_str"]][0]
|
|
|
|
|
chunks = [m.group().strip() for m in re.finditer(r' *((.{0,200})(\.|.$))',req_text)]
|
|
|
|
|
|
|
|
|
|
audio_clips = []
|
|
|
|
|
|
|
|
|
|
chunkId = 0
|
|
|
|
|
for chunk in chunks:
|
|
|
|
|
r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0")
|
|
|
|
|
vstr = [r.json()["data"]["v_str"]][0]
|
|
|
|
|
b64d = base64.b64decode(vstr)
|
|
|
|
|
|
|
|
|
|
with open(f"{filename}-{chunkId}", "wb") as out:
|
|
|
|
|
out.write(b64d)
|
|
|
|
|
|
|
|
|
|
audio_clips.append(AudioFileClip(f"{filename}-{chunkId}"))
|
|
|
|
|
|
|
|
|
|
b64d = base64.b64decode(vstr)
|
|
|
|
|
chunkId = chunkId+1;
|
|
|
|
|
|
|
|
|
|
with open(filename, "wb") as out:
|
|
|
|
|
out.write(b64d)
|
|
|
|
|
audio_concat = concatenate_audioclips(audio_clips)
|
|
|
|
|
audio_composite = CompositeAudioClip([audio_concat])
|
|
|
|
|
audio_composite.write_audiofile(filename, 44100, 2, 2000, None)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def randomvoice():
|
|
|
|
|