import base64
import random
import time
from typing import Final, Optional
import requests
from utils import settings
__all__ = ["TikTok", "TikTokTTSException"]
disney_voices: Final[tuple] = (
"en_us_ghostface",
"en_us_chewbacca",
"en_us_c3po",
"en_us_stitch",
"en_us_stormtrooper",
"en_us_rocket",
"en_female_madam_leota",
"en_male_ghosthost",
"en_male_pirate",
)
eng_voices: Final[tuple] = (
"en_au_001",
"en_au_002",
"en_uk_001",
"en_uk_003",
"en_us_001",
"en_us_002",
"en_us_006",
"en_us_007",
"en_us_009",
"en_us_010",
"en_male_narration",
"en_male_funny",
"en_female_emotional",
"en_male_cody",
)
non_eng_voices: Final[tuple] = (
"fr_001",
"fr_002",
"de_001",
"de_002",
"es_002",
"it_male_m18",
"es_mx_002",
"br_001",
"br_003",
"br_004",
"br_005",
"id_001",
"jp_001",
"jp_003",
"jp_005",
"jp_006",
"kr_002",
"kr_003",
"kr_004",
)
vocals: Final[tuple] = (
"en_female_f08_salut_damour",
"en_male_m03_lobby",
"en_male_m03_sunshine_soon",
"en_female_f08_warmy_breeze",
"en_female_ht_f08_glorious",
"en_male_sing_funny_it_goes_up",
"en_male_m2_xhxs_m03_silly",
"en_female_ht_f08_wonderful_world",
)
class TikTok:
"""TikTok Text-to-Speech Wrapper"""
def __init__(self):
headers = {
"User-Agent": "com.zhiliaoapp.musically/2022600030 (Linux; U; Android 7.1.2; es_ES; SM-G988N; "
"Build/NRD90M;tt-ok/3.12.13.1)",
"Cookie": f"sessionid={settings.config['settings']['tts']['tiktok_sessionid']}",
}
self.URI_BASE = "https://api16-normal-c-useast1a.tiktokv.com/media/api/text/speech/invoke/"
self.max_chars = 200
self._session = requests.Session()
self._session.headers = headers
def run(self, text: str, filepath: str, random_voice: bool = False):
if random_voice:
voice = self.random_voice()
else:
voice = settings.config["settings"]["tts"].get("tiktok_voice", None)
data = self.get_voices(voice=voice, text=text)
status_code = data["status_code"]
if status_code != 0:
raise TikTokTTSException(status_code, data["message"])
try:
raw_voices = data["data"]["v_str"]
except:
print(
"The TikTok TTS returned an invalid response. Please try again later, and report this bug."
)
raise TikTokTTSException(0, "Invalid response")
decoded_voices = base64.b64decode(raw_voices)
with open(filepath, "wb") as out:
out.write(decoded_voices)
def get_voices(self, text: str, voice: Optional[str] = None) -> dict:
"""If voice is not passed, the API will try to use the most fitting voice"""
text = text.replace("+", "plus").replace("&", "and").replace("r/", "")
params = {"req_text": text, "speaker_map_type": 0, "aid": 1233}
if voice is not None:
params["text_speaker"] = voice
try:
response = self._session.post(self.URI_BASE, params=params)
except ConnectionError:
time.sleep(random.randrange(1, 7))
response = self._session.post(self.URI_BASE, params=params)
return response.json()
@staticmethod
def random_voice() -> str:
return random.choice(eng_voices)
class TikTokTTSException(Exception):
def __init__(self, code: int, message: str):
self._code = code
self._message = message
def __str__(self) -> str:
if self._code == 1:
return f"Code: {self._code}, reason: probably the aid value isn't correct, message: {self._message}"
if self._code == 2:
return f"Code: {self._code}, reason: the text is too long, message: {self._message}"
if self._code == 4:
return f"Code: {self._code}, reason: the speaker doesn't exist, message: {self._message}"
return f"Code: {self._message}, reason: unknown, message: {self._message}"