├── .gitignore ├── requirements.txt ├── tiktok_voice ├── __init__.py ├── data │ └── config.json ├── LICENSE └── src │ ├── voice.py │ └── text_to_speech.py ├── samples ├── br_001.mp3 ├── br_003.mp3 ├── br_004.mp3 ├── br_005.mp3 ├── de_001.mp3 ├── de_002.mp3 ├── es_002.mp3 ├── fr_001.mp3 ├── fr_002.mp3 ├── id_001.mp3 ├── jp_001.mp3 ├── jp_003.mp3 ├── jp_005.mp3 ├── jp_006.mp3 ├── kr_002.mp3 ├── kr_003.mp3 ├── kr_004.mp3 ├── en_au_001.mp3 ├── en_au_002.mp3 ├── en_uk_001.mp3 ├── en_uk_003.mp3 ├── en_us_001.mp3 ├── en_us_002.mp3 ├── en_us_006.mp3 ├── en_us_007.mp3 ├── en_us_009.mp3 ├── en_us_010.mp3 ├── es_mx_002.mp3 ├── en_us_c3po.mp3 ├── en_male_funny.mp3 ├── en_us_rocket.mp3 ├── en_us_stitch.mp3 ├── en_us_chewbacca.mp3 ├── en_us_ghostface.mp3 ├── en_female_emotional.mp3 ├── en_male_m03_lobby.mp3 ├── en_male_narration.mp3 ├── en_us_stormtrooper.mp3 ├── en_female_f08_salut_damour.mp3 ├── en_female_f08_warmy_breeze.mp3 └── en_male_m03_sunshine_soon.mp3 ├── example_script.py ├── main.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | *.mp3 3 | __pycache__/ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.13.2 2 | playsound==1.2.2 3 | -------------------------------------------------------------------------------- /tiktok_voice/__init__.py: -------------------------------------------------------------------------------- 1 | from .src.text_to_speech import tts 2 | from .src.voice import Voice -------------------------------------------------------------------------------- /samples/br_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/br_001.mp3 -------------------------------------------------------------------------------- /samples/br_003.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/br_003.mp3 -------------------------------------------------------------------------------- /samples/br_004.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/br_004.mp3 -------------------------------------------------------------------------------- /samples/br_005.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/br_005.mp3 -------------------------------------------------------------------------------- /samples/de_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/de_001.mp3 -------------------------------------------------------------------------------- /samples/de_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/de_002.mp3 -------------------------------------------------------------------------------- /samples/es_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/es_002.mp3 -------------------------------------------------------------------------------- /samples/fr_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/fr_001.mp3 -------------------------------------------------------------------------------- /samples/fr_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/fr_002.mp3 -------------------------------------------------------------------------------- /samples/id_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/id_001.mp3 -------------------------------------------------------------------------------- /samples/jp_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/jp_001.mp3 -------------------------------------------------------------------------------- /samples/jp_003.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/jp_003.mp3 -------------------------------------------------------------------------------- /samples/jp_005.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/jp_005.mp3 -------------------------------------------------------------------------------- /samples/jp_006.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/jp_006.mp3 -------------------------------------------------------------------------------- /samples/kr_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/kr_002.mp3 -------------------------------------------------------------------------------- /samples/kr_003.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/kr_003.mp3 -------------------------------------------------------------------------------- /samples/kr_004.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/kr_004.mp3 -------------------------------------------------------------------------------- /samples/en_au_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_au_001.mp3 -------------------------------------------------------------------------------- /samples/en_au_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_au_002.mp3 -------------------------------------------------------------------------------- /samples/en_uk_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_uk_001.mp3 -------------------------------------------------------------------------------- /samples/en_uk_003.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_uk_003.mp3 -------------------------------------------------------------------------------- /samples/en_us_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_001.mp3 -------------------------------------------------------------------------------- /samples/en_us_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_002.mp3 -------------------------------------------------------------------------------- /samples/en_us_006.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_006.mp3 -------------------------------------------------------------------------------- /samples/en_us_007.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_007.mp3 -------------------------------------------------------------------------------- /samples/en_us_009.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_009.mp3 -------------------------------------------------------------------------------- /samples/en_us_010.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_010.mp3 -------------------------------------------------------------------------------- /samples/es_mx_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/es_mx_002.mp3 -------------------------------------------------------------------------------- /samples/en_us_c3po.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_c3po.mp3 -------------------------------------------------------------------------------- /samples/en_male_funny.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_male_funny.mp3 -------------------------------------------------------------------------------- /samples/en_us_rocket.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_rocket.mp3 -------------------------------------------------------------------------------- /samples/en_us_stitch.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_stitch.mp3 -------------------------------------------------------------------------------- /samples/en_us_chewbacca.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_chewbacca.mp3 -------------------------------------------------------------------------------- /samples/en_us_ghostface.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_ghostface.mp3 -------------------------------------------------------------------------------- /samples/en_female_emotional.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_female_emotional.mp3 -------------------------------------------------------------------------------- /samples/en_male_m03_lobby.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_male_m03_lobby.mp3 -------------------------------------------------------------------------------- /samples/en_male_narration.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_male_narration.mp3 -------------------------------------------------------------------------------- /samples/en_us_stormtrooper.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_us_stormtrooper.mp3 -------------------------------------------------------------------------------- /samples/en_female_f08_salut_damour.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_female_f08_salut_damour.mp3 -------------------------------------------------------------------------------- /samples/en_female_f08_warmy_breeze.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_female_f08_warmy_breeze.mp3 -------------------------------------------------------------------------------- /samples/en_male_m03_sunshine_soon.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark-rez/TikTok-Voice-TTS/HEAD/samples/en_male_m03_sunshine_soon.mp3 -------------------------------------------------------------------------------- /tiktok_voice/data/config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url": "https://tiktok-tts.weilnet.workers.dev/api/generation", 4 | "response": "data" 5 | }, 6 | { 7 | "url": "https://gesserit.co/api/tiktok-tts", 8 | "response": "base64" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /example_script.py: -------------------------------------------------------------------------------- 1 | from tiktok_voice import tts, Voice 2 | 3 | text = 'Tangerines are smaller and less rounded than the oranges. The taste is considered less sour, as well as sweeter and stronger, than that of an orange. A ripe tangerine is firm to slightly soft, and pebbly-skinned with no deep grooves, as well as orange in color. The peel is thin, with little bitter white mesocarp. All of these traits are shared by mandarins generally. Peak tangerine season lasts from autumn to spring. Tangerines are most commonly peeled and eaten by hand. The fresh fruit is also used in salads, desserts and main dishes. The peel is used fresh or dried as a spice or zest for baking and drinks. Fresh tangerine juice and frozen juice concentrate are commonly available in the United States.' 4 | 5 | # arguments: 6 | # - input text 7 | # - voice which is used for the audio 8 | # - output file name 9 | # - play sound after generating the audio 10 | tts(text, Voice.US_MALE_1, "output.mp3", play_sound=True) -------------------------------------------------------------------------------- /tiktok_voice/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mark Reznikov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # author: Giorgio 2 | # date: 23.08.2024 3 | # topic: TikTok-Voice-TTS 4 | # version: 1.3 5 | 6 | from codecs import BOM_UTF32 7 | import argparse 8 | # the script in the directory 9 | from tiktok_voice import tts, Voice 10 | 11 | def main(): 12 | # adding arguments 13 | parser = argparse.ArgumentParser(description='TikTok TTS') 14 | parser.add_argument('-t', help='text input') 15 | parser.add_argument('-v', help='voice selection') 16 | parser.add_argument('-o', help='output filename', default='output.mp3') 17 | parser.add_argument('-txt', help='text input from a txt file', type=argparse.FileType('r', encoding="utf-8")) 18 | parser.add_argument('-play', help='play sound after generating audio', action='store_true') 19 | 20 | args = parser.parse_args() 21 | 22 | # checking if given values are valid 23 | if not args.t and not args.txt: 24 | raise ValueError("insert a valid text or txt file") 25 | 26 | if args.t and args.txt: 27 | raise ValueError("only one input type is possible") 28 | 29 | voice: Voice | None = Voice.from_string(args.v) 30 | if voice == None: 31 | raise ValueError("no valid voice has been selected") 32 | 33 | # executing script 34 | if args.t: 35 | tts(args.t, voice, args.o, args.play) 36 | elif args.txt: 37 | tts(args.txt.read(), voice, args.o, args.play) 38 | 39 | if __name__ == "__main__": 40 | main() 41 | -------------------------------------------------------------------------------- /tiktok_voice/src/voice.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | # Enum to define available voices for text-to-speech conversion 4 | class Voice(Enum): 5 | # DISNEY VOICES 6 | GHOSTFACE = 'en_us_ghostface' 7 | CHEWBACCA = 'en_us_chewbacca' 8 | C3PO = 'en_us_c3po' 9 | STITCH = 'en_us_stitch' 10 | STORMTROOPER = 'en_us_stormtrooper' 11 | ROCKET = 'en_us_rocket' 12 | MADAME_LEOTA = 'en_female_madam_leota' 13 | GHOST_HOST = 'en_male_ghosthost' 14 | PIRATE = 'en_male_pirate' 15 | 16 | # ENGLISH VOICES 17 | AU_FEMALE_1 = 'en_au_001' 18 | AU_MALE_1 = 'en_au_002' 19 | UK_MALE_1 = 'en_uk_001' 20 | UK_MALE_2 = 'en_uk_003' 21 | US_FEMALE_1 = 'en_us_001' 22 | US_FEMALE_2 = 'en_us_002' 23 | US_MALE_1 = 'en_us_006' 24 | US_MALE_2 = 'en_us_007' 25 | US_MALE_3 = 'en_us_009' 26 | US_MALE_4 = 'en_us_010' 27 | MALE_JOMBOY = 'en_male_jomboy' 28 | MALE_CODY = 'en_male_cody' 29 | FEMALE_SAMC = 'en_female_samc' 30 | FEMALE_MAKEUP = 'en_female_makeup' 31 | FEMALE_RICHGIRL = 'en_female_richgirl' 32 | MALE_GRINCH = 'en_male_grinch' 33 | MALE_DEADPOOL = 'en_male_deadpool' 34 | MALE_JARVIS = 'en_male_jarvis' 35 | MALE_ASHMAGIC = 'en_male_ashmagic' 36 | MALE_OLANTERKKERS = 'en_male_olantekkers' 37 | MALE_UKNEIGHBOR = 'en_male_ukneighbor' 38 | MALE_UKBUTLER = 'en_male_ukbutler' 39 | FEMALE_SHENNA = 'en_female_shenna' 40 | FEMALE_PANSINO = 'en_female_pansino' 41 | MALE_TREVOR = 'en_male_trevor' 42 | FEMALE_BETTY = 'en_female_betty' 43 | MALE_CUPID = 'en_male_cupid' 44 | FEMALE_GRANDMA = 'en_female_grandma' 45 | MALE_XMXS_CHRISTMAS = 'en_male_m2_xhxs_m03_christmas' 46 | MALE_SANTA_NARRATION = 'en_male_santa_narration' 47 | MALE_SING_DEEP_JINGLE = 'en_male_sing_deep_jingle' 48 | MALE_SANTA_EFFECT = 'en_male_santa_effect' 49 | FEMALE_HT_NEYEAR = 'en_female_ht_f08_newyear' 50 | MALE_WIZARD = 'en_male_wizard' 51 | FEMALE_HT_HALLOWEEN = 'en_female_ht_f08_halloween' 52 | 53 | # EUROPE VOICES 54 | FR_MALE_1 = 'fr_001' 55 | FR_MALE_2 = 'fr_002' 56 | DE_FEMALE = 'de_001' 57 | DE_MALE = 'de_002' 58 | ES_MALE = 'es_002' 59 | 60 | # AMERICA VOICES 61 | ES_MX_MALE = 'es_mx_002' 62 | BR_FEMALE_1 = 'br_001' 63 | BR_FEMALE_2 = 'br_003' 64 | BR_FEMALE_3 = 'br_004' 65 | BR_MALE = 'br_005' 66 | BP_FEMALE_IVETE = 'bp_female_ivete' 67 | BP_FEMALE_LUDMILLA = 'bp_female_ludmilla' 68 | PT_FEMALE_LHAYS = 'pt_female_lhays' 69 | PT_FEMALE_LAIZZA = 'pt_female_laizza' 70 | PT_MALE_BUENO = 'pt_male_bueno' 71 | 72 | # ASIA VOICES 73 | ID_FEMALE = 'id_001' 74 | JP_FEMALE_1 = 'jp_001' 75 | JP_FEMALE_2 = 'jp_003' 76 | JP_FEMALE_3 = 'jp_005' 77 | JP_MALE = 'jp_006' 78 | KR_MALE_1 = 'kr_002' 79 | KR_FEMALE = 'kr_003' 80 | KR_MALE_2 = 'kr_004' 81 | JP_FEMALE_FUJICOCHAN = 'jp_female_fujicochan' 82 | JP_FEMALE_HASEGAWARIONA = 'jp_female_hasegawariona' 83 | JP_MALE_KEIICHINAKANO = 'jp_male_keiichinakano' 84 | JP_FEMALE_OOMAEAIIKA = 'jp_female_oomaeaika' 85 | JP_MALE_YUJINCHIGUSA = 'jp_male_yujinchigusa' 86 | JP_FEMALE_SHIROU = 'jp_female_shirou' 87 | JP_MALE_TAMAWAKAZUKI = 'jp_male_tamawakazuki' 88 | JP_FEMALE_KAORISHOJI = 'jp_female_kaorishoji' 89 | JP_FEMALE_YAGISHAKI = 'jp_female_yagishaki' 90 | JP_MALE_HIKAKIN = 'jp_male_hikakin' 91 | JP_FEMALE_REI = 'jp_female_rei' 92 | JP_MALE_SHUICHIRO = 'jp_male_shuichiro' 93 | JP_MALE_MATSUDAKE = 'jp_male_matsudake' 94 | JP_FEMALE_MACHIKORIIITA = 'jp_female_machikoriiita' 95 | JP_MALE_MATSUO = 'jp_male_matsuo' 96 | JP_MALE_OSADA = 'jp_male_osada' 97 | 98 | # SINGING VOICES 99 | SING_FEMALE_ALTO = 'en_female_f08_salut_damour' 100 | SING_MALE_TENOR = 'en_male_m03_lobby' 101 | SING_FEMALE_WARMY_BREEZE = 'en_female_f08_warmy_breeze' 102 | SING_MALE_SUNSHINE_SOON = 'en_male_m03_sunshine_soon' 103 | SING_FEMALE_GLORIOUS = 'en_female_ht_f08_glorious' 104 | SING_MALE_IT_GOES_UP = 'en_male_sing_funny_it_goes_up' 105 | SING_MALE_CHIPMUNK = 'en_male_m2_xhxs_m03_silly' 106 | SING_FEMALE_WONDERFUL_WORLD = 'en_female_ht_f08_wonderful_world' 107 | SING_MALE_FUNNY_THANKSGIVING = 'en_male_sing_funny_thanksgiving' 108 | 109 | # OTHER 110 | MALE_NARRATION = 'en_male_narration' 111 | MALE_FUNNY = 'en_male_funny' 112 | FEMALE_EMOTIONAL = 'en_female_emotional' 113 | 114 | # Function to check if a string matches any enum member name 115 | @staticmethod 116 | def from_string(input_string: str): 117 | # Iterate over all enum members 118 | for voice in Voice: 119 | if voice.name == input_string: 120 | return voice 121 | return None 122 | -------------------------------------------------------------------------------- /tiktok_voice/src/text_to_speech.py: -------------------------------------------------------------------------------- 1 | # Python standard modules 2 | import asyncio 3 | import os 4 | import base64 5 | import re 6 | from json import load 7 | from threading import Thread 8 | from typing import Dict, List, Optional 9 | 10 | # Downloaded modules 11 | import aiohttp 12 | from playsound import playsound 13 | 14 | # Local files 15 | from .voice import Voice 16 | 17 | def tts( 18 | text: str, 19 | voice: Voice, 20 | output_file_path: str = "output.mp3", 21 | play_sound: bool = False 22 | ): 23 | """Main function to convert text to speech and save to a file.""" 24 | 25 | # Validate input arguments 26 | _validate_args(text, voice) 27 | 28 | # Load endpoint data from the endpoints.json file 29 | endpoint_data: List[Dict[str, str]] = _load_endpoints() 30 | success: bool = False 31 | 32 | # Iterate over endpoints to find a working one 33 | for endpoint in endpoint_data: 34 | # Generate audio bytes from the current endpoint 35 | audio_bytes: bytes = asyncio.run(_fetch_audio_bytes_async(endpoint, text, voice)) 36 | 37 | if audio_bytes: 38 | # Save the generated audio to a file 39 | _save_audio_file(output_file_path, audio_bytes) 40 | 41 | # Optionally play the audio file 42 | if play_sound: 43 | playsound(output_file_path) 44 | 45 | success = True 46 | # Stop after processing a valid endpoint 47 | break 48 | 49 | if not success: 50 | raise Exception("failed to generate audio") 51 | 52 | def _save_audio_file(output_file_path: str, audio_bytes: bytes): 53 | """Write the audio bytes to a file.""" 54 | if os.path.exists(output_file_path): 55 | os.remove(output_file_path) 56 | 57 | with open(output_file_path, "wb") as file: 58 | file.write(audio_bytes) 59 | 60 | async def _fetch_audio_bytes_async( 61 | endpoint: Dict[str, str], 62 | text: str, 63 | voice: Voice 64 | ) -> Optional[bytes]: 65 | text_chunks: List[str] = _split_text(text) 66 | audio_chunks: List[str] = [""] * len(text_chunks) 67 | 68 | async def fetch_chunk( 69 | session: aiohttp.ClientSession, 70 | index: int, 71 | text_chunk: str 72 | ): 73 | try: 74 | async with session.post( 75 | endpoint["url"], 76 | json={"text": text_chunk, "voice": voice.value}, 77 | ) as response: 78 | response.raise_for_status() 79 | data = await response.json() 80 | audio_chunks[index] = data[endpoint["response"]] 81 | except (aiohttp.ClientError, KeyError): 82 | pass 83 | 84 | async with aiohttp.ClientSession() as session: 85 | tasks = [ 86 | fetch_chunk(session, i, chunk) 87 | for i, chunk in enumerate(text_chunks) 88 | ] 89 | await asyncio.gather(*tasks) 90 | 91 | if any(not chunk for chunk in audio_chunks): 92 | return None 93 | 94 | return base64.b64decode("".join(audio_chunks)) 95 | 96 | def _load_endpoints() -> List[Dict[str, str]]: 97 | """Load endpoint configurations from a JSON file.""" 98 | script_dir = os.path.dirname(__file__) 99 | json_file_path = os.path.join(script_dir, '../data', 'config.json') 100 | with open(json_file_path, 'r') as file: 101 | return load(file) 102 | 103 | def _validate_args(text: str, voice: Voice): 104 | """Validate the input arguments.""" 105 | 106 | # Check if the voice is of the correct type 107 | if not isinstance(voice, Voice): 108 | raise TypeError("'voice' must be of type Voice") 109 | 110 | # Check if the text is not empty 111 | if not text: 112 | raise ValueError("text must not be empty") 113 | 114 | def _split_text(text: str) -> List[str]: 115 | """Split text into chunks of 300 characters or less.""" 116 | 117 | # Split text into chunks based on punctuation marks 118 | merged_chunks: List[str] = [] 119 | separated_chunks: List[str] = re.findall(r'.*?[.,!?:;-]|.+', text) 120 | character_limit: int = 300 121 | # Further split any chunks longer than 300 characters 122 | for i, chunk in enumerate(separated_chunks): 123 | if len(chunk.encode("utf-8")) > character_limit: 124 | separated_chunks[i:i+1] = re.findall(r'.*?[ ]|.+', chunk) 125 | 126 | # Combine chunks into segments of 300 characters or less 127 | current_chunk: str = "" 128 | for separated_chunk in separated_chunks: 129 | if len(current_chunk.encode("utf-8")) + len(separated_chunk.encode("utf-8")) <= character_limit: 130 | current_chunk += separated_chunk 131 | else: 132 | merged_chunks.append(current_chunk) 133 | current_chunk = separated_chunk 134 | 135 | # Append the last chunk 136 | merged_chunks.append(current_chunk) 137 | return merged_chunks 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TikTok Voice TTS 2 | 3 | This is a simple Python program that gives you an `.mp3` file including the given text input which is spoken by one of the TikTok voices. 4 | 5 | I thank all people that use this for their project. I love to contribute to the community. However, please credit me by using the GitHub project link. 6 | 7 | ## Usage 8 | To use this program, you need an internet connection, python 3.6+ and all of the required packages installed. 9 | To install the required packages, run: `pip3 install -r requirements.txt` 10 | 11 | ### Create audio from file 12 | 1. Make sure you have your text in the file plaintext. 13 | 2. Run `py main.py -txt FILENAME.txt -v VOICENAME` (see voices below) 14 | 15 | Only latin characters are supported. 16 | 17 | ### Create audio from argument 18 | 1. Run `py main.py -t TEXT -v VOICENAME` (see voices below) 19 | 20 | You can have non-latin characters (as long as it has a TTS supported voice). 21 | 22 | ### Create audio in python script 23 | 1. Put the file folder/package `tiktok_voice` into your directory. 24 | 2. Import the text-to-speech function and the voices with `from tiktok_voice import tts, Voice`. 25 | 3. Execute `tts(TEXT, VOICE, OUTPUTFILENAME, PLAYSOUND)` in your code. 26 | 27 | I provided an [example script](https://github.com/GiorDior/TikTok-Voice-TTS/blob/main/example_script.py) which shows how the tts function could be used in a script. 28 | 29 | ## Voices 30 | List of every voice and its designation: 31 | 32 | | Name | 33 | | ----------------------------- | 34 | | GHOSTFACE | 35 | | CHEWBACCA | 36 | | C3PO | 37 | | STITCH | 38 | | STORMTROOPER | 39 | | ROCKET | 40 | | MADAME_LEOTA | 41 | | GHOST_HOST | 42 | | PIRATE | 43 | | AU_FEMALE_1 | 44 | | AU_MALE_1 | 45 | | UK_MALE_1 | 46 | | UK_MALE_2 | 47 | | US_FEMALE_1 | 48 | | US_FEMALE_2 | 49 | | US_MALE_1 | 50 | | US_MALE_2 | 51 | | US_MALE_3 | 52 | | US_MALE_4 | 53 | | MALE_JOMBOY | 54 | | MALE_CODY | 55 | | FEMALE_SAMC | 56 | | FEMALE_MAKEUP | 57 | | FEMALE_RICHGIRL | 58 | | MALE_GRINCH | 59 | | MALE_DEADPOOL | 60 | | MALE_JARVIS | 61 | | MALE_ASHMAGIC | 62 | | MALE_OLANTERKKERS | 63 | | MALE_UKNEIGHBOR | 64 | | MALE_UKBUTLER | 65 | | FEMALE_SHENNA | 66 | | FEMALE_PANSINO | 67 | | MALE_TREVOR | 68 | | FEMALE_BETTY | 69 | | MALE_CUPID | 70 | | FEMALE_GRANDMA | 71 | | MALE_XMXS_CHRISTMAS | 72 | | MALE_SANTA_NARRATION | 73 | | MALE_SING_DEEP_JINGLE | 74 | | MALE_SANTA_EFFECT | 75 | | FEMALE_HT_NEYEAR | 76 | | MALE_WIZARD | 77 | | FEMALE_HT_HALLOWEEN | 78 | | FR_MALE_1 | 79 | | FR_MALE_2 | 80 | | DE_FEMALE | 81 | | DE_MALE | 82 | | ES_MALE | 83 | | ES_MX_MALE | 84 | | BR_FEMALE_1 | 85 | | BR_FEMALE_2 | 86 | | BR_FEMALE_3 | 87 | | BR_MALE | 88 | | BP_FEMALE_IVETE | 89 | | BP_FEMALE_LUDMILLA | 90 | | PT_FEMALE_LHAYS | 91 | | PT_FEMALE_LAIZZA | 92 | | PT_MALE_BUENO | 93 | | ID_FEMALE | 94 | | JP_FEMALE_1 | 95 | | JP_FEMALE_2 | 96 | | JP_FEMALE_3 | 97 | | JP_MALE | 98 | | KR_MALE_1 | 99 | | KR_FEMALE | 100 | | KR_MALE_2 | 101 | | JP_FEMALE_FUJICOCHAN | 102 | | JP_FEMALE_HASEGAWARIONA | 103 | | JP_MALE_KEIICHINAKANO | 104 | | JP_FEMALE_OOMAEAIIKA | 105 | | JP_MALE_YUJINCHIGUSA | 106 | | JP_FEMALE_SHIROU | 107 | | JP_MALE_TAMAWAKAZUKI | 108 | | JP_FEMALE_KAORISHOJI | 109 | | JP_FEMALE_YAGISHAKI | 110 | | JP_MALE_HIKAKIN | 111 | | JP_FEMALE_REI | 112 | | JP_MALE_SHUICHIRO | 113 | | JP_MALE_MATSUDAKE | 114 | | JP_FEMALE_MACHIKORIIITA | 115 | | JP_MALE_MATSUO | 116 | | JP_MALE_OSADA | 117 | | SING_FEMALE_ALTO | 118 | | SING_MALE_TENOR | 119 | | SING_FEMALE_WARMY_BREEZE | 120 | | SING_MALE_SUNSHINE_SOON | 121 | | SING_FEMALE_GLORIOUS | 122 | | SING_MALE_IT_GOES_UP | 123 | | SING_MALE_CHIPMUNK | 124 | | SING_FEMALE_WONDERFUL_WORLD | 125 | | SING_MALE_FUNNY_THANKSGIVING | 126 | | MALE_NARRATION | 127 | | MALE_FUNNY | 128 | | FEMALE_EMOTIONAL | 129 | 130 | ## Samples 131 | 132 | You can find samples of all voices in [/samples/](https://github.com/GiorDior/TikTok-Voice-TTS/tree/main/samples) 133 | 134 | ## Credits 135 | - [oscie](https://github.com/oscie57/tiktok-voice) for giving me the idea for this project 136 | 137 | ## License 138 | ``` 139 | MIT License 140 | 141 | Copyright (c) 2024 Mark Reznikov 142 | 143 | Permission is hereby granted, free of charge, to any person obtaining a copy 144 | of this software and associated documentation files (the "Software"), to deal 145 | in the Software without restriction, including without limitation the rights 146 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 147 | copies of the Software, and to permit persons to whom the Software is 148 | furnished to do so, subject to the following conditions: 149 | 150 | The above copyright notice and this permission notice shall be included in all 151 | copies or substantial portions of the Software. 152 | 153 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 154 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 155 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 156 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 157 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 158 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 159 | SOFTWARE. 160 | ``` --------------------------------------------------------------------------------