Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
elebumm
GitHub Repository: elebumm/RedditVideoMakerBot
Path: blob/master/TTS/TikTok.py
327 views
1
# documentation for tiktok api: https://github.com/oscie57/tiktok-voice/wiki
2
import base64
3
import random
4
import time
5
from typing import Final, Optional
6
7
import requests
8
9
from utils import settings
10
11
__all__ = ["TikTok", "TikTokTTSException"]
12
13
disney_voices: Final[tuple] = (
14
"en_us_ghostface", # Ghost Face
15
"en_us_chewbacca", # Chewbacca
16
"en_us_c3po", # C3PO
17
"en_us_stitch", # Stitch
18
"en_us_stormtrooper", # Stormtrooper
19
"en_us_rocket", # Rocket
20
"en_female_madam_leota", # Madame Leota
21
"en_male_ghosthost", # Ghost Host
22
"en_male_pirate", # pirate
23
)
24
25
eng_voices: Final[tuple] = (
26
"en_au_001", # English AU - Female
27
"en_au_002", # English AU - Male
28
"en_uk_001", # English UK - Male 1
29
"en_uk_003", # English UK - Male 2
30
"en_us_001", # English US - Female (Int. 1)
31
"en_us_002", # English US - Female (Int. 2)
32
"en_us_006", # English US - Male 1
33
"en_us_007", # English US - Male 2
34
"en_us_009", # English US - Male 3
35
"en_us_010", # English US - Male 4
36
"en_male_narration", # Narrator
37
"en_male_funny", # Funny
38
"en_female_emotional", # Peaceful
39
"en_male_cody", # Serious
40
)
41
42
non_eng_voices: Final[tuple] = (
43
# Western European voices
44
"fr_001", # French - Male 1
45
"fr_002", # French - Male 2
46
"de_001", # German - Female
47
"de_002", # German - Male
48
"es_002", # Spanish - Male
49
"it_male_m18", # Italian - Male
50
# South american voices
51
"es_mx_002", # Spanish MX - Male
52
"br_001", # Portuguese BR - Female 1
53
"br_003", # Portuguese BR - Female 2
54
"br_004", # Portuguese BR - Female 3
55
"br_005", # Portuguese BR - Male
56
# asian voices
57
"id_001", # Indonesian - Female
58
"jp_001", # Japanese - Female 1
59
"jp_003", # Japanese - Female 2
60
"jp_005", # Japanese - Female 3
61
"jp_006", # Japanese - Male
62
"kr_002", # Korean - Male 1
63
"kr_003", # Korean - Female
64
"kr_004", # Korean - Male 2
65
)
66
67
vocals: Final[tuple] = (
68
"en_female_f08_salut_damour", # Alto
69
"en_male_m03_lobby", # Tenor
70
"en_male_m03_sunshine_soon", # Sunshine Soon
71
"en_female_f08_warmy_breeze", # Warmy Breeze
72
"en_female_ht_f08_glorious", # Glorious
73
"en_male_sing_funny_it_goes_up", # It Goes Up
74
"en_male_m2_xhxs_m03_silly", # Chipmunk
75
"en_female_ht_f08_wonderful_world", # Dramatic
76
)
77
78
79
class TikTok:
80
"""TikTok Text-to-Speech Wrapper"""
81
82
def __init__(self):
83
headers = {
84
"User-Agent": "com.zhiliaoapp.musically/2022600030 (Linux; U; Android 7.1.2; es_ES; SM-G988N; "
85
"Build/NRD90M;tt-ok/3.12.13.1)",
86
"Cookie": f"sessionid={settings.config['settings']['tts']['tiktok_sessionid']}",
87
}
88
89
self.URI_BASE = "https://api16-normal-c-useast1a.tiktokv.com/media/api/text/speech/invoke/"
90
self.max_chars = 200
91
92
self._session = requests.Session()
93
# set the headers to the session, so we don't have to do it for every request
94
self._session.headers = headers
95
96
def run(self, text: str, filepath: str, random_voice: bool = False):
97
if random_voice:
98
voice = self.random_voice()
99
else:
100
# if tiktok_voice is not set in the config file, then use a random voice
101
voice = settings.config["settings"]["tts"].get("tiktok_voice", None)
102
103
# get the audio from the TikTok API
104
data = self.get_voices(voice=voice, text=text)
105
106
# check if there was an error in the request
107
status_code = data["status_code"]
108
if status_code != 0:
109
raise TikTokTTSException(status_code, data["message"])
110
111
# decode data from base64 to binary
112
try:
113
raw_voices = data["data"]["v_str"]
114
except:
115
print(
116
"The TikTok TTS returned an invalid response. Please try again later, and report this bug."
117
)
118
raise TikTokTTSException(0, "Invalid response")
119
decoded_voices = base64.b64decode(raw_voices)
120
121
# write voices to specified filepath
122
with open(filepath, "wb") as out:
123
out.write(decoded_voices)
124
125
def get_voices(self, text: str, voice: Optional[str] = None) -> dict:
126
"""If voice is not passed, the API will try to use the most fitting voice"""
127
# sanitize text
128
text = text.replace("+", "plus").replace("&", "and").replace("r/", "")
129
130
# prepare url request
131
params = {"req_text": text, "speaker_map_type": 0, "aid": 1233}
132
133
if voice is not None:
134
params["text_speaker"] = voice
135
136
# send request
137
try:
138
response = self._session.post(self.URI_BASE, params=params)
139
except ConnectionError:
140
time.sleep(random.randrange(1, 7))
141
response = self._session.post(self.URI_BASE, params=params)
142
143
return response.json()
144
145
@staticmethod
146
def random_voice() -> str:
147
return random.choice(eng_voices)
148
149
150
class TikTokTTSException(Exception):
151
def __init__(self, code: int, message: str):
152
self._code = code
153
self._message = message
154
155
def __str__(self) -> str:
156
if self._code == 1:
157
return f"Code: {self._code}, reason: probably the aid value isn't correct, message: {self._message}"
158
159
if self._code == 2:
160
return f"Code: {self._code}, reason: the text is too long, message: {self._message}"
161
162
if self._code == 4:
163
return f"Code: {self._code}, reason: the speaker doesn't exist, message: {self._message}"
164
165
return f"Code: {self._message}, reason: unknown, message: {self._message}"
166
167