|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import base64 |
|
import requests |
|
import threading |
|
|
|
from typing import List |
|
from termcolor import colored |
|
from playsound import playsound |
|
|
|
|
|
VOICES = [ |
|
|
|
"en_us_ghostface", |
|
"en_us_chewbacca", |
|
"en_us_c3po", |
|
"en_us_stitch", |
|
"en_us_stormtrooper", |
|
"en_us_rocket", |
|
|
|
"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", |
|
|
|
"fr_001", |
|
"fr_002", |
|
"de_001", |
|
"de_002", |
|
"es_002", |
|
|
|
"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", |
|
|
|
"en_female_f08_salut_damour", |
|
"en_male_m03_lobby", |
|
"en_female_f08_warmy_breeze", |
|
"en_male_m03_sunshine_soon", |
|
|
|
"en_male_narration", |
|
"en_male_funny", |
|
"en_female_emotional", |
|
] |
|
|
|
ENDPOINTS = [ |
|
"https://tiktok-tts.weilnet.workers.dev/api/generation", |
|
"https://tiktoktts.com/api/tiktok-tts", |
|
] |
|
current_endpoint = 0 |
|
|
|
TEXT_BYTE_LIMIT = 300 |
|
|
|
|
|
|
|
def split_string(string: str, chunk_size: int) -> List[str]: |
|
words = string.split() |
|
result = [] |
|
current_chunk = "" |
|
for word in words: |
|
if ( |
|
len(current_chunk) + len(word) + 1 <= chunk_size |
|
): |
|
current_chunk += f" {word}" |
|
else: |
|
if current_chunk: |
|
result.append(current_chunk.strip()) |
|
current_chunk = word |
|
if current_chunk: |
|
result.append(current_chunk.strip()) |
|
return result |
|
|
|
|
|
|
|
def get_api_response() -> requests.Response: |
|
url = f'{ENDPOINTS[current_endpoint].split("/a")[0]}' |
|
response = requests.get(url) |
|
return response |
|
|
|
|
|
|
|
def save_audio_file(base64_data: str, filename: str = "output.mp3") -> None: |
|
audio_bytes = base64.b64decode(base64_data) |
|
with open(filename, "wb") as file: |
|
file.write(audio_bytes) |
|
|
|
|
|
|
|
def generate_audio(text: str, voice: str) -> bytes: |
|
url = f"{ENDPOINTS[current_endpoint]}" |
|
headers = {"Content-Type": "application/json"} |
|
data = {"text": text, "voice": voice} |
|
response = requests.post(url, headers=headers, json=data) |
|
return response.content |
|
|
|
|
|
|
|
def tts( |
|
text: str, |
|
voice: str = "none", |
|
filename: str = "output.mp3", |
|
play_sound: bool = False, |
|
) -> None: |
|
|
|
global current_endpoint |
|
|
|
if get_api_response().status_code == 200: |
|
print(colored("[+] TikTok TTS Service available!", "green")) |
|
else: |
|
current_endpoint = (current_endpoint + 1) % 2 |
|
if get_api_response().status_code == 200: |
|
print(colored("[+] TTS Service available!", "green")) |
|
else: |
|
print(colored("[-] TTS Service not available and probably temporarily rate limited, try again later..." , "red")) |
|
return |
|
|
|
|
|
if voice == "none": |
|
print(colored("[-] Please specify a voice", "red")) |
|
return |
|
|
|
if voice not in VOICES: |
|
print(colored("[-] Voice not available", "red")) |
|
return |
|
|
|
if not text: |
|
print(colored("[-] Please specify a text", "red")) |
|
return |
|
|
|
|
|
try: |
|
if len(text) < TEXT_BYTE_LIMIT: |
|
audio = generate_audio((text), voice) |
|
if current_endpoint == 0: |
|
audio_base64_data = str(audio).split('"')[5] |
|
else: |
|
audio_base64_data = str(audio).split('"')[3].split(",")[1] |
|
|
|
if audio_base64_data == "error": |
|
print(colored("[-] This voice is unavailable right now", "red")) |
|
return |
|
|
|
else: |
|
|
|
text_parts = split_string(text, 299) |
|
audio_base64_data = [None] * len(text_parts) |
|
|
|
|
|
def generate_audio_thread(text_part, index): |
|
audio = generate_audio(text_part, voice) |
|
if current_endpoint == 0: |
|
base64_data = str(audio).split('"')[5] |
|
else: |
|
base64_data = str(audio).split('"')[3].split(",")[1] |
|
|
|
if audio_base64_data == "error": |
|
print(colored("[-] This voice is unavailable right now", "red")) |
|
return "error" |
|
|
|
audio_base64_data[index] = base64_data |
|
|
|
threads = [] |
|
for index, text_part in enumerate(text_parts): |
|
|
|
thread = threading.Thread( |
|
target=generate_audio_thread, args=(text_part, index) |
|
) |
|
thread.start() |
|
threads.append(thread) |
|
|
|
|
|
for thread in threads: |
|
thread.join() |
|
|
|
|
|
audio_base64_data = "".join(audio_base64_data) |
|
|
|
save_audio_file(audio_base64_data, filename) |
|
print(colored(f"[+] Audio file saved successfully as '{filename}'", "green")) |
|
if play_sound: |
|
playsound(filename) |
|
|
|
except Exception as e: |
|
print(colored(f"[-] An error occurred during TTS: {e}", "red")) |
|
|