Skip to content

Commit 1e968f6

Browse files
authored
Add files via upload
1 parent 22813e7 commit 1e968f6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+214
-0
lines changed

main.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import argparse
2+
3+
# the script in the directory
4+
import tiktokvoice
5+
6+
def main():
7+
# adding arguments
8+
parser = argparse.ArgumentParser(description='TikTok TTS')
9+
parser.add_argument('-t', help='text input')
10+
parser.add_argument('-v', help='voice selection', choices=tiktokvoice.VOICES)
11+
parser.add_argument('-n', help='output filename', default='output.mp3')
12+
parser.add_argument('-txt', help='text input from a txt file', type=argparse.FileType('r'))
13+
parser.add_argument('-play', help='play sound after generating audio', action='store_true')
14+
15+
args = parser.parse_args()
16+
17+
# checking if given values are valid
18+
if not args.t and not args.txt:
19+
print("Insert a valid text or txt file")
20+
return
21+
22+
if args.t and args.txt:
23+
print("Only one input type is possible")
24+
return
25+
26+
if not args.v:
27+
print("No voice has been selected")
28+
return
29+
30+
# executing script
31+
if args.t:
32+
tiktokvoice.tts(args.t, args.v, args.n, args.play)
33+
elif args.txt:
34+
tiktokvoice.tts(args.txt.read(), args.v, args.n, args.play)
35+
36+
if __name__ == "__main__":
37+
main()

requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests
2+
playsound

samples/br_001.mp3

31.9 KB
Binary file not shown.

samples/br_003.mp3

34.5 KB
Binary file not shown.

samples/br_004.mp3

37.2 KB
Binary file not shown.

samples/br_005.mp3

27.8 KB
Binary file not shown.

samples/de_001.mp3

40.5 KB
Binary file not shown.

samples/de_002.mp3

30 KB
Binary file not shown.

samples/en_au_001.mp3

22.5 KB
Binary file not shown.

samples/en_au_002.mp3

25.2 KB
Binary file not shown.

samples/en_female_emotional.mp3

45.8 KB
Binary file not shown.
116 KB
Binary file not shown.
118 KB
Binary file not shown.

samples/en_male_funny.mp3

31.2 KB
Binary file not shown.

samples/en_male_m03_lobby.mp3

97.5 KB
Binary file not shown.

samples/en_male_m03_sunshine_soon.mp3

118 KB
Binary file not shown.

samples/en_male_narration.mp3

38.3 KB
Binary file not shown.

samples/en_uk_001.mp3

27 KB
Binary file not shown.

samples/en_uk_003.mp3

28.9 KB
Binary file not shown.

samples/en_us_001.mp3

25.5 KB
Binary file not shown.

samples/en_us_002.mp3

34.2 KB
Binary file not shown.

samples/en_us_006.mp3

33.4 KB
Binary file not shown.

samples/en_us_007.mp3

28.5 KB
Binary file not shown.

samples/en_us_009.mp3

25.2 KB
Binary file not shown.

samples/en_us_010.mp3

31.2 KB
Binary file not shown.

samples/en_us_c3po.mp3

46.2 KB
Binary file not shown.

samples/en_us_chewbacca.mp3

25.9 KB
Binary file not shown.

samples/en_us_ghostface.mp3

12.8 KB
Binary file not shown.

samples/en_us_rocket.mp3

19.5 KB
Binary file not shown.

samples/en_us_stitch.mp3

30.4 KB
Binary file not shown.

samples/en_us_stormtrooper.mp3

28.5 KB
Binary file not shown.

samples/es_002.mp3

28.9 KB
Binary file not shown.

samples/es_mx_002.mp3

29.7 KB
Binary file not shown.

samples/fr_001.mp3

18.4 KB
Binary file not shown.

samples/fr_002.mp3

27.4 KB
Binary file not shown.

samples/id_001.mp3

43.2 KB
Binary file not shown.

samples/jp_001.mp3

51 KB
Binary file not shown.

samples/jp_003.mp3

63.8 KB
Binary file not shown.

samples/jp_005.mp3

54 KB
Binary file not shown.

samples/jp_006.mp3

59.7 KB
Binary file not shown.

samples/kr_002.mp3

57.8 KB
Binary file not shown.

samples/kr_003.mp3

41.3 KB
Binary file not shown.

samples/kr_004.mp3

73.2 KB
Binary file not shown.

tiktokvoice.py

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import threading, requests, base64
2+
from playsound import playsound
3+
4+
VOICES = [
5+
# DISNEY VOICES
6+
'en_us_ghostface', # Ghost Face
7+
'en_us_chewbacca', # Chewbacca
8+
'en_us_c3po', # C3PO
9+
'en_us_stitch', # Stitch
10+
'en_us_stormtrooper', # Stormtrooper
11+
'en_us_rocket', # Rocket
12+
13+
# ENGLISH VOICES
14+
'en_au_001', # English AU - Female
15+
'en_au_002', # English AU - Male
16+
'en_uk_001', # English UK - Male 1
17+
'en_uk_003', # English UK - Male 2
18+
'en_us_001', # English US - Female (Int. 1)
19+
'en_us_002', # English US - Female (Int. 2)
20+
'en_us_006', # English US - Male 1
21+
'en_us_007', # English US - Male 2
22+
'en_us_009', # English US - Male 3
23+
'en_us_010', # English US - Male 4
24+
25+
# EUROPE VOICES
26+
'fr_001', # French - Male 1
27+
'fr_002', # French - Male 2
28+
'de_001', # German - Female
29+
'de_002', # German - Male
30+
'es_002', # Spanish - Male
31+
32+
# AMERICA VOICES
33+
'es_mx_002', # Spanish MX - Male
34+
'br_001', # Portuguese BR - Female 1
35+
'br_003', # Portuguese BR - Female 2
36+
'br_004', # Portuguese BR - Female 3
37+
'br_005', # Portuguese BR - Male
38+
39+
# ASIA VOICES
40+
'id_001', # Indonesian - Female
41+
'jp_001', # Japanese - Female 1
42+
'jp_003', # Japanese - Female 2
43+
'jp_005', # Japanese - Female 3
44+
'jp_006', # Japanese - Male
45+
'kr_002', # Korean - Male 1
46+
'kr_003', # Korean - Female
47+
'kr_004', # Korean - Male 2
48+
49+
# SINGING VOICES
50+
'en_female_f08_salut_damour', # Alto
51+
'en_male_m03_lobby', # Tenor
52+
'en_female_f08_warmy_breeze', # Warmy Breeze
53+
'en_male_m03_sunshine_soon', # Sunshine Soon
54+
55+
# OTHER
56+
'en_male_narration', # narrator
57+
'en_male_funny', # wacky
58+
'en_female_emotional', # peaceful
59+
]
60+
61+
ENDPOINT = 'https://tiktok-tts.weilnet.workers.dev'
62+
63+
# in one conversion, the text can have a maximum length of 300 characters
64+
TEXT_BYTE_LIMIT = 300
65+
66+
# create a list by splitting a string, every element has n chars
67+
def split_string(string: str, chunk_size: int) -> list[str]:
68+
words = string.split()
69+
result = []
70+
current_chunk = ''
71+
for word in words:
72+
if len(current_chunk) + len(word) + 1 <= chunk_size: # Check if adding the word exceeds the chunk size
73+
current_chunk += ' ' + word
74+
else:
75+
if current_chunk: # Append the current chunk if not empty
76+
result.append(current_chunk.strip())
77+
current_chunk = word
78+
if current_chunk: # Append the last chunk if not empty
79+
result.append(current_chunk.strip())
80+
return result
81+
82+
# checking if the website that provides the service is available
83+
def get_api_response() -> requests.Response:
84+
url = f'{ENDPOINT}/api/status'
85+
response = requests.get(url)
86+
return response
87+
88+
# saving the audio file
89+
def save_audio_file(base64_data: str, filename: str = "output.mp3") -> None:
90+
audio_bytes = base64.b64decode(base64_data)
91+
with open(filename, "wb") as file:
92+
file.write(audio_bytes)
93+
94+
# send POST request to get the audio data
95+
def generate_audio(text: str, voice: str) -> bytes:
96+
url = f'{ENDPOINT}/api/generation'
97+
headers = {'Content-Type': 'application/json'}
98+
data = {'text': text, 'voice': voice}
99+
response = requests.post(url, headers=headers, json=data)
100+
return response.content
101+
102+
# creates an text to speech audio file
103+
def tts(text: str, voice: str = "none", filename: str = "output.mp3", play_sound: bool = False) -> None:
104+
# checking if the website is available
105+
api_response = get_api_response()
106+
107+
if api_response.status_code == 200:
108+
print("Service available!")
109+
else:
110+
print("Service not available, try again later or check, if https://tiktok-tts.weilnet.workers.dev is available...")
111+
return
112+
113+
# checking if arguments are valid
114+
if voice == "none":
115+
print("No voice has been selected")
116+
return
117+
118+
if not voice in VOICES:
119+
print("Voice does not exist")
120+
return
121+
122+
if len(text) == 0:
123+
print("Insert a valid text")
124+
return
125+
126+
# creating the audio file
127+
try:
128+
if len(text) < TEXT_BYTE_LIMIT:
129+
if len(text) < TEXT_BYTE_LIMIT:
130+
audio = generate_audio((text), voice)
131+
audio_base64_data = str(audio).split('"')[5]
132+
133+
if audio_base64_data == "error":
134+
print("This voice is unavailable right now")
135+
return
136+
137+
else:
138+
# Split longer text into smaller parts
139+
text_parts = split_string(text, 299)
140+
audio_base64_data = [None] * len(text_parts)
141+
142+
# Define a thread function to generate audio for each text part
143+
def generate_audio_thread(text_part, index):
144+
audio = generate_audio(text_part, voice)
145+
base64_data = str(audio).split('"')[5]
146+
147+
if audio_base64_data == "error":
148+
print("This voice is unavailable right now")
149+
return "error"
150+
151+
audio_base64_data[index] = base64_data
152+
153+
threads = []
154+
for index, text_part in enumerate(text_parts):
155+
# Create and start a new thread for each text part
156+
thread = threading.Thread(target=generate_audio_thread, args=(text_part, index))
157+
thread.start()
158+
threads.append(thread)
159+
160+
# Wait for all threads to complete
161+
for thread in threads:
162+
thread.join()
163+
if (thread.result) == "error":
164+
return
165+
166+
# Concatenate the base64 data in the correct order
167+
audio_base64_data = "".join(audio_base64_data)
168+
169+
save_audio_file(audio_base64_data, filename)
170+
print(f"Audio file saved successfully as '{filename}'")
171+
if play_sound:
172+
playsound(filename)
173+
174+
except Exception as e:
175+
print("Error occurred while generating audio:", str(e))

0 commit comments

Comments
 (0)