├── .gitignore ├── requirements.txt ├── .gitattributes ├── samples ├── indonesian │ └── id_001.mp3 ├── korean (korea) │ ├── kr_002.mp3 │ ├── kr_003.mp3 │ └── kr_004.mp3 ├── french (france) │ ├── fr_001.mp3 │ └── fr_002.mp3 ├── german (germany) │ ├── de_001.mp3 │ └── de_002.mp3 ├── japanese (japan) │ ├── jp_001.mp3 │ ├── jp_003.mp3 │ ├── jp_005.mp3 │ └── jp_006.mp3 ├── spanish (spain) │ └── es_002.mp3 ├── portuguese (brazil) │ ├── br_001.mp3 │ ├── br_003.mp3 │ ├── br_004.mp3 │ └── br_005.mp3 ├── spanish (mexico) │ └── es_mx_002.mp3 ├── english (australia) │ ├── en_au_001.mp3 │ └── en_au_002.mp3 ├── english (disney) │ ├── en_us_c3po.mp3 │ ├── en_us_rocket.mp3 │ ├── en_us_stitch.mp3 │ ├── en_us_chewbacca.mp3 │ ├── en_us_ghostface.mp3 │ └── en_us_stormtrooper.mp3 ├── english (united kingdom) │ ├── en_uk_001.mp3 │ └── en_uk_003.mp3 └── english (united states) │ ├── en_us_001.mp3 │ ├── en_us_002.mp3 │ ├── en_us_006.mp3 │ ├── en_us_007.mp3 │ ├── en_us_009.mp3 │ └── en_us_010.mp3 ├── __pycache__ └── constants.cpython-311.pyc ├── .github └── FUNDING.yml ├── constants.py ├── readme.md └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | voice.mp3 2 | text.txt 3 | batch 4 | .venv -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | playsound==1.3.0 2 | requests==2.25.1 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /samples/indonesian/id_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/indonesian/id_001.mp3 -------------------------------------------------------------------------------- /samples/korean (korea)/kr_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/korean (korea)/kr_002.mp3 -------------------------------------------------------------------------------- /samples/korean (korea)/kr_003.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/korean (korea)/kr_003.mp3 -------------------------------------------------------------------------------- /samples/korean (korea)/kr_004.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/korean (korea)/kr_004.mp3 -------------------------------------------------------------------------------- /samples/french (france)/fr_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/french (france)/fr_001.mp3 -------------------------------------------------------------------------------- /samples/french (france)/fr_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/french (france)/fr_002.mp3 -------------------------------------------------------------------------------- /samples/german (germany)/de_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/german (germany)/de_001.mp3 -------------------------------------------------------------------------------- /samples/german (germany)/de_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/german (germany)/de_002.mp3 -------------------------------------------------------------------------------- /samples/japanese (japan)/jp_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/japanese (japan)/jp_001.mp3 -------------------------------------------------------------------------------- /samples/japanese (japan)/jp_003.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/japanese (japan)/jp_003.mp3 -------------------------------------------------------------------------------- /samples/japanese (japan)/jp_005.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/japanese (japan)/jp_005.mp3 -------------------------------------------------------------------------------- /samples/japanese (japan)/jp_006.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/japanese (japan)/jp_006.mp3 -------------------------------------------------------------------------------- /samples/spanish (spain)/es_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/spanish (spain)/es_002.mp3 -------------------------------------------------------------------------------- /__pycache__/constants.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/__pycache__/constants.cpython-311.pyc -------------------------------------------------------------------------------- /samples/portuguese (brazil)/br_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/portuguese (brazil)/br_001.mp3 -------------------------------------------------------------------------------- /samples/portuguese (brazil)/br_003.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/portuguese (brazil)/br_003.mp3 -------------------------------------------------------------------------------- /samples/portuguese (brazil)/br_004.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/portuguese (brazil)/br_004.mp3 -------------------------------------------------------------------------------- /samples/portuguese (brazil)/br_005.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/portuguese (brazil)/br_005.mp3 -------------------------------------------------------------------------------- /samples/spanish (mexico)/es_mx_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/spanish (mexico)/es_mx_002.mp3 -------------------------------------------------------------------------------- /samples/english (australia)/en_au_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (australia)/en_au_001.mp3 -------------------------------------------------------------------------------- /samples/english (australia)/en_au_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (australia)/en_au_002.mp3 -------------------------------------------------------------------------------- /samples/english (disney)/en_us_c3po.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (disney)/en_us_c3po.mp3 -------------------------------------------------------------------------------- /samples/english (disney)/en_us_rocket.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (disney)/en_us_rocket.mp3 -------------------------------------------------------------------------------- /samples/english (disney)/en_us_stitch.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (disney)/en_us_stitch.mp3 -------------------------------------------------------------------------------- /samples/english (disney)/en_us_chewbacca.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (disney)/en_us_chewbacca.mp3 -------------------------------------------------------------------------------- /samples/english (disney)/en_us_ghostface.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (disney)/en_us_ghostface.mp3 -------------------------------------------------------------------------------- /samples/english (united kingdom)/en_uk_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (united kingdom)/en_uk_001.mp3 -------------------------------------------------------------------------------- /samples/english (united kingdom)/en_uk_003.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (united kingdom)/en_uk_003.mp3 -------------------------------------------------------------------------------- /samples/english (united states)/en_us_001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (united states)/en_us_001.mp3 -------------------------------------------------------------------------------- /samples/english (united states)/en_us_002.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (united states)/en_us_002.mp3 -------------------------------------------------------------------------------- /samples/english (united states)/en_us_006.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (united states)/en_us_006.mp3 -------------------------------------------------------------------------------- /samples/english (united states)/en_us_007.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (united states)/en_us_007.mp3 -------------------------------------------------------------------------------- /samples/english (united states)/en_us_009.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (united states)/en_us_009.mp3 -------------------------------------------------------------------------------- /samples/english (united states)/en_us_010.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (united states)/en_us_010.mp3 -------------------------------------------------------------------------------- /samples/english (disney)/en_us_stormtrooper.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oscie57/tiktok-voice/HEAD/samples/english (disney)/en_us_stormtrooper.mp3 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: oscie 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: https://buymeacoffee.com/oscie # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /constants.py: -------------------------------------------------------------------------------- 1 | voices = [ 2 | # DISNEY VOICES 3 | 'en_us_ghostface', # Ghost Face 4 | 'en_us_chewbacca', # Chewbacca 5 | 'en_us_c3po', # C3PO 6 | 'en_us_stitch', # Stitch 7 | 'en_us_stormtrooper', # Stormtrooper 8 | 'en_us_rocket', # Rocket 9 | 10 | # ENGLISH VOICES 11 | 'en_au_001', # English AU - Female 12 | 'en_au_002', # English AU - Male 13 | 'en_uk_001', # English UK - Male 1 14 | 'en_uk_003', # English UK - Male 2 15 | 'en_us_001', # English US - Female (Int. 1) 16 | 'en_us_002', # English US - Female (Int. 2) 17 | 'en_us_006', # English US - Male 1 18 | 'en_us_007', # English US - Male 2 19 | 'en_us_009', # English US - Male 3 20 | 'en_us_010', # English US - Male 4 21 | 22 | # EUROPE VOICES 23 | 'fr_001', # French - Male 1 24 | 'fr_002', # French - Male 2 25 | 'de_001', # German - Female 26 | 'de_002', # German - Male 27 | 'es_002', # Spanish - Male 28 | 29 | # AMERICA VOICES 30 | 'es_mx_002', # Spanish MX - Male 31 | 'br_001', # Portuguese BR - Female 1 32 | 'br_003', # Portuguese BR - Female 2 33 | 'br_004', # Portuguese BR - Female 3 34 | 'br_005', # Portuguese BR - Male 35 | 36 | # ASIA VOICES 37 | 'id_001', # Indonesian - Female 38 | 'jp_001', # Japanese - Female 1 39 | 'jp_003', # Japanese - Female 2 40 | 'jp_005', # Japanese - Female 3 41 | 'jp_006', # Japanese - Male 42 | 'kr_002', # Korean - Male 1 43 | 'kr_003', # Korean - Female 44 | 'kr_004', # Korean - Male 2 45 | 46 | # SINGING VOICES 47 | 'en_female_f08_salut_damour' # Alto 48 | 'en_male_m03_lobby' # Tenor 49 | 'en_female_f08_warmy_breeze' # Warmy Breeze 50 | 'en_male_m03_sunshine_soon' # Sunshine Soon 51 | 52 | # OTHER 53 | 'en_male_narration' # narrator 54 | 'en_male_funny' # wacky 55 | 'en_female_emotional' # peaceful 56 | ] -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # TikTok Text-to-speech API 2 | 3 | ## FOR DOCUMENTATION, VISIT THE [WIKI](https://github.com/oscie57/tiktok-voice/wiki) 4 | 5 | This is a simple Python program that accesses the TikTok API and gives you an `.mp3` file with what it says in the specified voice. 6 | 7 | If you are stuck and need assistance, please ask me in my [Discord server](https://discord.gg/ymb84qM54A) in [#tiktok-voice](https://discord.com/channels/804449200921509913/963871023252533288) (quickest response) or via the Issues tab. 8 | 9 | If you like this project, feel free to support me via [Ko-Fi]([https://oscie.net/support](https://ko-fi.com/oscie))! 10 | 11 | If you're a creator using this for a project, thank you! I'm glad my project can help with your own. All I ask is that you credit this repository in your README file. 12 | 13 | If you're creating a project relating to a content farm (i.e. Reddit story readers, TikTok/Reels/YouTube Shorts generators), please do not use my project (or any other projects that use/are based off of this), nor any of the documentation included. **I do not support content farms.** 14 | 15 | ## Usage 16 | 17 | To use this, you need Python 3.8+ and all of the required packages installed. 18 | 19 | To install required packages, run `pip3 install playsound requests` or `pip3 install -r requirements.txt` 20 | 21 | ### Read from file 22 | 1. Make sure you have your text in plaintext. You can name it anything 23 | 2. Run `py main.py -v VOICE -f FILENAME.txt --session SESSION_ID` (see voices below) 24 | 25 | There is no character limit, though only latin characters are supported. 26 | 27 | ### Read from argument 28 | 1. Run `py main.py -v VOICE -t TEXT -n FILENAME.mp3 --session SESSION_ID` (see voices below) 29 | 30 | This has a 200 character limit, but you can have non-latin characters (as long as it has a TTS supported voice) 31 | 32 | ### Play from text 33 | Optionally, if you want to listen to the file instead of saving to a file, you can use the `-p` argument to play directly and then delete. If you get error `263`, ignore it, it doesn't affect the program itself. 34 | 35 | ### Session ID 36 | [Get session id](https://github.com/oscie57/tiktok-voice/wiki/Obtaining-SessionID) 37 | 38 | ## Voice Options 39 | 40 | **Since the list has gotten quite large, I have moved it to [the wiki](https://github.com/oscie57/tiktok-voice/wiki/Voice-Codes)** 41 | 42 | Languages Supported: 43 | - Portuguese (Brazil) 44 | - German 45 | - English (Australia) 46 | - English (United Kingdom) 47 | - English (United States) 48 | - English (Disney) 49 | - Spanish 50 | - Spanish (Mexico) 51 | - French 52 | - Indonesian 53 | - Japanese 54 | - Korean 55 | 56 | ## Samples 57 | 58 | You can find samples of all the voices in [/samples/](https://github.com/oscie57/tiktok-voice/blob/main/samples/) 59 | 60 | ## Credits 61 | - [Spotlight](https://twitter.com/xibwrangler) for giving me the idea for this program 62 | - [Myself](https://oscie.net) for creating this 63 | - [scanlime](https://twitter.com/scanlime) for giving the voice options 64 | - [Komfudo](https://github.com/Komfudo/) for translating the sample text to German 65 | - [Philemax](https://twitter.com/Philemax1) for translating the sample text to French 66 | - [Ash](https://github.com/ashmonty) for adding command line arguments 67 | - [BigJ64](https://github.com/BigJ64/tiktok-voice) for adding a play argument 68 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import requests, base64, random, argparse, os, playsound, time, re, textwrap 2 | from constants import voices 3 | 4 | API_BASE_URL = f"https://api16-normal-v6.tiktokv.com/media/api/text/speech/invoke/" 5 | USER_AGENT = f"com.zhiliaoapp.musically/2022600030 (Linux; U; Android 7.1.2; es_ES; SM-G988N; Build/NRD90M;tt-ok/3.12.13.1)" 6 | 7 | 8 | def tts(session_id: str, text_speaker: str = "en_us_002", req_text: str = "TikTok Text To Speech", 9 | filename: str = 'voice.mp3', play: bool = False): 10 | req_text = req_text.replace("+", "plus") 11 | req_text = req_text.replace(" ", "+") 12 | req_text = req_text.replace("&", "and") 13 | req_text = req_text.replace("ä", "ae") 14 | req_text = req_text.replace("ö", "oe") 15 | req_text = req_text.replace("ü", "ue") 16 | req_text = req_text.replace("ß", "ss") 17 | 18 | r = requests.post( 19 | f"{API_BASE_URL}?text_speaker={text_speaker}&req_text={req_text}&speaker_map_type=0&aid=1233", 20 | headers={ 21 | 'User-Agent': USER_AGENT, 22 | 'Cookie': f'sessionid={session_id}' 23 | } 24 | ) 25 | 26 | if r.json()["message"] == "Couldn't load speech. Try again.": 27 | output_data = {"status": "Session ID is invalid", "status_code": 5} 28 | print(output_data) 29 | return output_data 30 | 31 | vstr = [r.json()["data"]["v_str"]][0] 32 | msg = [r.json()["message"]][0] 33 | scode = [r.json()["status_code"]][0] 34 | log = [r.json()["extra"]["log_id"]][0] 35 | 36 | dur = [r.json()["data"]["duration"]][0] 37 | spkr = [r.json()["data"]["speaker"]][0] 38 | 39 | b64d = base64.b64decode(vstr) 40 | 41 | with open(filename, "wb") as out: 42 | out.write(b64d) 43 | 44 | output_data = { 45 | "status": msg.capitalize(), 46 | "status_code": scode, 47 | "duration": dur, 48 | "speaker": spkr, 49 | "log": log 50 | } 51 | 52 | print(output_data) 53 | 54 | if play is True: 55 | playsound.playsound(filename) 56 | os.remove(filename) 57 | 58 | return output_data 59 | 60 | 61 | def batch_create(filename: str = 'voice.mp3'): 62 | out = open(filename, 'wb') 63 | 64 | def sorted_alphanumeric(data): 65 | convert = lambda text: int(text) if text.isdigit() else text.lower() 66 | alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] 67 | return sorted(data, key=alphanum_key) 68 | 69 | for item in sorted_alphanumeric(os.listdir('./batch/')): 70 | filestuff = open('./batch/' + item, 'rb').read() 71 | out.write(filestuff) 72 | 73 | out.close() 74 | 75 | 76 | def main(): 77 | parser = argparse.ArgumentParser(description="Simple Python script to interact with the TikTok TTS API") 78 | parser.add_argument("-v", "--voice", help="the code of the desired voice") 79 | parser.add_argument("-t", "--text", help="the text to be read") 80 | parser.add_argument("-s", "--session", help="account session id") 81 | parser.add_argument("-f", "--file", help="use this if you wanna use 'text.txt'") 82 | parser.add_argument("-n", "--name", help="The name for the output file (.mp3)") 83 | parser.add_argument("-p", "--play", action='store_true', help="use this if you want to play your output") 84 | args = parser.parse_args() 85 | 86 | text_speaker = args.voice 87 | 88 | if args.file is not None: 89 | req_text = open(args.file, 'r', errors='ignore', encoding='utf-8').read() 90 | else: 91 | if args.text == None: 92 | req_text = 'TikTok Text To Speech' 93 | print('You need to have one form of text! (See README.md)') 94 | else: 95 | req_text = args.text 96 | 97 | if args.play is not None: 98 | play = args.play 99 | 100 | if args.voice == None: 101 | text_speaker = 'en_us_002' 102 | print('You need to have a voice! (See README.md)') 103 | 104 | if text_speaker == "random": 105 | text_speaker = randomvoice() 106 | 107 | if args.name is not None: 108 | filename = args.name 109 | else: 110 | filename = 'voice.mp3' 111 | 112 | if args.session is None: 113 | print('FATAL: You need to have a TikTok session ID!') 114 | exit(1) 115 | 116 | if args.file is not None: 117 | chunk_size = 200 118 | textlist = textwrap.wrap(req_text, width=chunk_size, break_long_words=True, break_on_hyphens=False) 119 | 120 | batch_dir = './batch/' 121 | 122 | if not os.path.exists(batch_dir): 123 | os.makedirs(batch_dir) 124 | 125 | for i, item in enumerate(textlist): 126 | tts(args.session, text_speaker, item, f'{batch_dir}{i}.mp3', False) 127 | 128 | batch_create(filename) 129 | 130 | for item in os.listdir(batch_dir): 131 | os.remove(batch_dir + item) 132 | 133 | if os.path.exists: 134 | os.removedirs(batch_dir) 135 | 136 | return 137 | 138 | tts(args.session, text_speaker, req_text, filename, play) 139 | 140 | 141 | def randomvoice(): 142 | count = random.randint(0, len(voices)) 143 | text_speaker = voices[count] 144 | 145 | return text_speaker 146 | 147 | 148 | def sampler(): 149 | for item in voices: 150 | text_speaker = item 151 | filename = item 152 | print(item) 153 | req_text = 'TikTok Text To Speech Sample' 154 | tts(text_speaker, req_text, filename) 155 | 156 | 157 | if __name__ == "__main__": 158 | main() 159 | --------------------------------------------------------------------------------