├── assets └── icon.png ├── .gitignore ├── README.md └── edge_tts_gui.py /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schr-0dinger/edge_tts_gui/HEAD/assets/icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gui.py 2 | gui.spec 3 | modern.spec 4 | build/ 5 | dist/ 6 | edge 7 | EDGE\ TTS\ GUI\ 0.1-alpha.spec 8 | *.MP3 9 | *.WAV -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EDGE TTS GUI 2 | 3 | ![Screenshot](https://i.ibb.co/R9CDMLn/edge-tts-gui.png) 4 | 5 | EDGE TTS GUI is a graphical user interface (GUI) application built with [CustomTkinter](https://github.com/tomschimansky/customtkinter) that utilizes the `edge-tts` library to convert text to speech using Microsoft's online text-to-speech service. This application allows users to input text, select a voice, and adjust speech parameters such as rate, pitch, and volume. It also provides options to preview the generated speech and save it as an audio file. 6 | 7 | ## Features 8 | 9 | - **Voice Selection:** Choose from a wide range of available voices. 10 | - **Adjustable Parameters:** Customize the rate, pitch, and volume of the speech. 11 | - **Preview:** Listen to the generated speech before saving. 12 | - **Save Options:** Save the generated speech with different naming conventions. 13 | - **Internet Connectivity Check:** Ensures that the application is connected to the internet before performing any text-to-speech operations. 14 | 15 | ## Installation 16 | 17 | #### Method 1 (Windows Executable): 18 | 19 | 1. **Download the Standalone Executable:** 20 | - Go to the [Releases](https://github.com/schr-0dinger/edge_tts_gui/releases) page. 21 | - Download the latest version of `edge_tts_gui.exe`. 22 | 23 | 2. **Run the Application:** 24 | - Double-click the downloaded `edge_tts_gui.exe` file to start the application. 25 | 26 | 27 | #### Method 2: 28 | 29 | ###### Prerequisites: 30 | 31 | - Python 3.x 32 | - Dependencies 33 | 34 | 0. Install dependencies: 35 | 36 | pip install edge-tts CTkMessageBox customtkinter pydub 37 | 38 | 39 | 1. ** Clone the repo ** 40 | 41 | git clone https://github.com/schr-0dinger/edge_tts_gui.git 42 | 43 | 2. ** Run edge_tts_gui.py ** 44 | 45 | python edge_tts_gui.py 46 | 47 | 48 | ## Usage 49 | 50 | 1. **Interface Overview:** 51 | - **Text Input:** Enter the text you want to convert to speech. 52 | - **Voice Selection:** Select a voice from the dropdown menu. 53 | - **Adjust Parameters:** Use the sliders to adjust rate, pitch, and volume. 54 | - **Generate:** Click the "GENERATE" button to save the speech as an audio file. 55 | - **Preview:** Click the "PREVIEW" button to listen to the speech before saving. 56 | - **Save Options:** Choose how you want to name the output file. 57 | 58 | ## To-do List 59 | 60 | - ~~Add option to switch between MP3/WAV format~~ 61 | - Fix faulty preview function 62 | - Fix 0-100 volume slider 63 | 64 | ## Dependencies 65 | 66 | All necessary dependencies are bundled within the standalone executable, so you don't need to install anything else if you using the executable. 67 | 68 | ## Project Description 69 | 70 | This project is essentially a GUI version of the `edge-tts` library, which allows users to use Microsoft Edge's online text-to-speech service from Python WITHOUT needing Microsoft Edge, Windows, or an API key. 71 | 72 | ## Contributing 73 | 74 | Contributions are welcome! Please open an issue or submit a pull request if you have any improvements or bug fixes. 75 | 76 | ## Acknowledgements 77 | 78 | Special thanks to the developers of the `edge-tts` library for making this project possible. 79 | 80 | -------------------------------------------------------------------------------- /edge_tts_gui.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os, sys 3 | import tkinter 4 | from datetime import datetime 5 | from CTkMessagebox import CTkMessagebox 6 | import customtkinter as ctk 7 | import edge_tts 8 | import requests 9 | from PIL import ImageTk 10 | from pydub import AudioSegment 11 | from pydub.playback import play 12 | import tempfile 13 | import threading 14 | import webbrowser 15 | 16 | class App(ctk.CTk): 17 | def __init__(self): 18 | super().__init__() 19 | self.geometry("800x440") 20 | self.resizable(False, False) 21 | self.grid_rowconfigure(0, weight=1) 22 | self.grid_columnconfigure(0, weight=1) 23 | self.title("EDGE TTS GUI") 24 | # Determine if running in a PyInstaller bundle 25 | if getattr(sys, 'frozen', False): 26 | # Running in a bundle 27 | base_path = sys._MEIPASS 28 | else: 29 | # Running in normal Python environment 30 | base_path = os.path.abspath(".") 31 | 32 | icon_path = os.path.join(base_path, "assets", "icon.png") 33 | 34 | if not os.path.exists(icon_path): 35 | raise FileNotFoundError(f"Icon file not found: {icon_path}") 36 | 37 | self.iconpath = ImageTk.PhotoImage(file=icon_path) 38 | # self.wm_iconbitmap(icon_path) 39 | self.iconphoto(False, self.iconpath) 40 | 41 | # Voice Names 42 | self.default_voice = "en-GB-RyanNeural" 43 | self.voice_names = ['af-ZA-AdriNeural', 'af-ZA-WillemNeural', 'am-ET-AmehaNeural', 'am-ET-MekdesNeural', 'ar-AE-FatimaNeural', 'ar-AE-HamdanNeural', 'ar-BH-AliNeural', 'ar-BH-LailaNeural', 'ar-DZ-AminaNeural', 'ar-DZ-IsmaelNeural', 'ar-EG-SalmaNeural', 'ar-EG-ShakirNeural', 'ar-IQ-BasselNeural', 'ar-IQ-RanaNeural', 'ar-JO-SanaNeural', 'ar-JO-TaimNeural', 'ar-KW-FahedNeural', 'ar-KW-NouraNeural', 'ar-LB-LaylaNeural', 'ar-LB-RamiNeural', 'ar-LY-ImanNeural', 'ar-LY-OmarNeural', 'ar-MA-JamalNeural', 'ar-MA-MounaNeural', 'ar-OM-AbdullahNeural', 'ar-OM-AyshaNeural', 'ar-QA-AmalNeural', 'ar-QA-MoazNeural', 'ar-SA-HamedNeural', 'ar-SA-ZariyahNeural', 'ar-SY-AmanyNeural', 'ar-SY-LaithNeural', 'ar-TN-HediNeural', 'ar-TN-ReemNeural', 'ar-YE-MaryamNeural', 'ar-YE-SalehNeural', 'az-AZ-BabekNeural', 'az-AZ-BanuNeural', 'bg-BG-BorislavNeural', 'bg-BG-KalinaNeural', 'bn-BD-NabanitaNeural', 'bn-BD-PradeepNeural', 'bn-IN-BashkarNeural', 'bn-IN-TanishaaNeural', 'bs-BA-GoranNeural', 'bs-BA-VesnaNeural', 'ca-ES-EnricNeural', 'ca-ES-JoanaNeural', 'cs-CZ-AntoninNeural', 'cs-CZ-VlastaNeural', 'cy-GB-AledNeural', 'cy-GB-NiaNeural', 'da-DK-ChristelNeural', 'da-DK-JeppeNeural', 'de-AT-IngridNeural', 'de-AT-JonasNeural', 'de-CH-JanNeural', 'de-CH-LeniNeural', 'de-DE-AmalaNeural', 'de-DE-ConradNeural', 'de-DE-FlorianMultilingualNeural', 'de-DE-KatjaNeural', 'de-DE-KillianNeural', 'de-DE-SeraphinaMultilingualNeural', 'el-GR-AthinaNeural', 'el-GR-NestorasNeural', 'en-AU-NatashaNeural', 'en-AU-WilliamNeural', 'en-CA-ClaraNeural', 'en-CA-LiamNeural', 'en-GB-LibbyNeural', 'en-GB-MaisieNeural', 'en-GB-RyanNeural', 'en-GB-SoniaNeural', 'en-GB-ThomasNeural', 'en-HK-SamNeural', 'en-HK-YanNeural', 'en-IE-ConnorNeural', 'en-IE-EmilyNeural', 'en-IN-NeerjaExpressiveNeural', 'en-IN-NeerjaNeural', 'en-IN-PrabhatNeural', 'en-KE-AsiliaNeural', 'en-KE-ChilembaNeural', 'en-NG-AbeoNeural', 'en-NG-EzinneNeural', 'en-NZ-MitchellNeural', 'en-NZ-MollyNeural', 'en-PH-JamesNeural', 'en-PH-RosaNeural', 'en-SG-LunaNeural', 'en-SG-WayneNeural', 'en-TZ-ElimuNeural', 'en-TZ-ImaniNeural', 'en-US-AnaNeural', 'en-US-AndrewMultilingualNeural', 'en-US-AndrewNeural', 'en-US-AriaNeural', 'en-US-AvaMultilingualNeural', 'en-US-AvaNeural', 'en-US-BrianMultilingualNeural', 'en-US-BrianNeural', 'en-US-ChristopherNeural', 'en-US-EmmaMultilingualNeural', 'en-US-EmmaNeural', 'en-US-EricNeural', 'en-US-GuyNeural', 'en-US-JennyNeural', 'en-US-MichelleNeural', 'en-US-RogerNeural', 'en-US-SteffanNeural', 'en-ZA-LeahNeural', 'en-ZA-LukeNeural', 'es-AR-ElenaNeural', 'es-AR-TomasNeural', 'es-BO-MarceloNeural', 'es-BO-SofiaNeural', 'es-CL-CatalinaNeural', 'es-CL-LorenzoNeural', 'es-CO-GonzaloNeural', 'es-CO-SalomeNeural', 'es-CR-JuanNeural', 'es-CR-MariaNeural', 'es-CU-BelkysNeural', 'es-CU-ManuelNeural', 'es-DO-EmilioNeural', 'es-DO-RamonaNeural', 'es-EC-AndreaNeural', 'es-EC-LuisNeural', 'es-ES-AlvaroNeural', 'es-ES-ElviraNeural', 'es-ES-XimenaNeural', 'es-GQ-JavierNeural', 'es-GQ-TeresaNeural', 'es-GT-AndresNeural', 'es-GT-MartaNeural', 'es-HN-CarlosNeural', 'es-HN-KarlaNeural', 'es-MX-DaliaNeural', 'es-MX-JorgeNeural', 'es-NI-FedericoNeural', 'es-NI-YolandaNeural', 'es-PA-MargaritaNeural', 'es-PA-RobertoNeural', 'es-PE-AlexNeural', 'es-PE-CamilaNeural', 'es-PR-KarinaNeural', 'es-PR-VictorNeural', 'es-PY-MarioNeural', 'es-PY-TaniaNeural', 'es-SV-LorenaNeural', 'es-SV-RodrigoNeural', 'es-US-AlonsoNeural', 'es-US-PalomaNeural', 'es-UY-MateoNeural', 'es-UY-ValentinaNeural', 'es-VE-PaolaNeural', 'es-VE-SebastianNeural', 'et-EE-AnuNeural', 'et-EE-KertNeural', 'fa-IR-DilaraNeural', 'fa-IR-FaridNeural', 'fi-FI-HarriNeural', 'fi-FI-NooraNeural', 'fil-PH-AngeloNeural', 'fil-PH-BlessicaNeural', 'fr-BE-CharlineNeural', 'fr-BE-GerardNeural', 'fr-CA-AntoineNeural', 'fr-CA-JeanNeural', 'fr-CA-SylvieNeural', 'fr-CA-ThierryNeural', 'fr-CH-ArianeNeural', 'fr-CH-FabriceNeural', 'fr-FR-DeniseNeural', 'fr-FR-EloiseNeural', 'fr-FR-HenriNeural', 'fr-FR-RemyMultilingualNeural', 'fr-FR-VivienneMultilingualNeural', 'ga-IE-ColmNeural', 'ga-IE-OrlaNeural', 'gl-ES-RoiNeural', 'gl-ES-SabelaNeural', 'gu-IN-DhwaniNeural', 'gu-IN-NiranjanNeural', 'he-IL-AvriNeural', 'he-IL-HilaNeural', 'hi-IN-MadhurNeural', 'hi-IN-SwaraNeural', 'hr-HR-GabrijelaNeural', 'hr-HR-SreckoNeural', 'hu-HU-NoemiNeural', 'hu-HU-TamasNeural', 'id-ID-ArdiNeural', 'id-ID-GadisNeural', 'is-IS-GudrunNeural', 'is-IS-GunnarNeural', 'it-IT-DiegoNeural', 'it-IT-ElsaNeural', 'it-IT-GiuseppeNeural', 'it-IT-IsabellaNeural', 'ja-JP-KeitaNeural', 'ja-JP-NanamiNeural', 'jv-ID-DimasNeural', 'jv-ID-SitiNeural', 'ka-GE-EkaNeural', 'ka-GE-GiorgiNeural', 'kk-KZ-AigulNeural', 'kk-KZ-DauletNeural', 'km-KH-PisethNeural', 'km-KH-SreymomNeural', 'kn-IN-GaganNeural', 'kn-IN-SapnaNeural', 'ko-KR-HyunsuNeural', 'ko-KR-InJoonNeural', 'ko-KR-SunHiNeural', 'lo-LA-ChanthavongNeural', 'lo-LA-KeomanyNeural', 'lt-LT-LeonasNeural', 'lt-LT-OnaNeural', 'lv-LV-EveritaNeural', 'lv-LV-NilsNeural', 'mk-MK-AleksandarNeural', 'mk-MK-MarijaNeural', 'ml-IN-MidhunNeural', 'ml-IN-SobhanaNeural', 'mn-MN-BataaNeural', 'mn-MN-YesuiNeural', 'mr-IN-AarohiNeural', 'mr-IN-ManoharNeural', 'ms-MY-OsmanNeural', 'ms-MY-YasminNeural', 'mt-MT-GraceNeural', 'mt-MT-JosephNeural', 'my-MM-NilarNeural', 'my-MM-ThihaNeural', 'nb-NO-FinnNeural', 'nb-NO-PernilleNeural', 'ne-NP-HemkalaNeural', 'ne-NP-SagarNeural', 'nl-BE-ArnaudNeural', 'nl-BE-DenaNeural', 'nl-NL-ColetteNeural', 'nl-NL-FennaNeural', 'nl-NL-MaartenNeural', 'pl-PL-MarekNeural', 'pl-PL-ZofiaNeural', 'ps-AF-GulNawazNeural', 'ps-AF-LatifaNeural', 'pt-BR-AntonioNeural', 'pt-BR-FranciscaNeural', 'pt-BR-ThalitaNeural', 'pt-PT-DuarteNeural', 'pt-PT-RaquelNeural', 'ro-RO-AlinaNeural', 'ro-RO-EmilNeural', 'ru-RU-DmitryNeural', 'ru-RU-SvetlanaNeural', 'si-LK-SameeraNeural', 'si-LK-ThiliniNeural', 'sk-SK-LukasNeural', 'sk-SK-ViktoriaNeural', 'sl-SI-PetraNeural', 'sl-SI-RokNeural', 'so-SO-MuuseNeural', 'so-SO-UbaxNeural', 'sq-AL-AnilaNeural', 'sq-AL-IlirNeural', 'sr-RS-NicholasNeural', 'sr-RS-SophieNeural', 'su-ID-JajangNeural', 'su-ID-TutiNeural', 'sv-SE-MattiasNeural', 'sv-SE-SofieNeural', 'sw-KE-RafikiNeural', 'sw-KE-ZuriNeural', 'sw-TZ-DaudiNeural', 'sw-TZ-RehemaNeural', 'ta-IN-PallaviNeural', 'ta-IN-ValluvarNeural', 'ta-LK-KumarNeural', 'ta-LK-SaranyaNeural', 'ta-MY-KaniNeural', 'ta-MY-SuryaNeural', 'ta-SG-AnbuNeural', 'ta-SG-VenbaNeural', 'te-IN-MohanNeural', 'te-IN-ShrutiNeural', 'th-TH-NiwatNeural', 'th-TH-PremwadeeNeural', 'tr-TR-AhmetNeural', 'tr-TR-EmelNeural', 'uk-UA-OstapNeural', 'uk-UA-PolinaNeural', 'ur-IN-GulNeural', 'ur-IN-SalmanNeural', 'ur-PK-AsadNeural', 'ur-PK-UzmaNeural', 'uz-UZ-MadinaNeural', 'uz-UZ-SardorNeural', 'vi-VN-HoaiMyNeural', 'vi-VN-NamMinhNeural', 'zh-CN-XiaoxiaoNeural', 'zh-CN-XiaoyiNeural', 'zh-CN-YunjianNeural', 'zh-CN-YunxiNeural', 'zh-CN-YunxiaNeural', 'zh-CN-YunyangNeural', 'zh-CN-liaoning-XiaobeiNeural', 'zh-CN-shaanxi-XiaoniNeural', 'zh-HK-HiuGaaiNeural', 'zh-HK-HiuMaanNeural', 'zh-HK-WanLungNeural', 'zh-TW-HsiaoChenNeural', 'zh-TW-HsiaoYuNeural', 'zh-TW-YunJheNeural', 'zu-ZA-ThandoNeural', 'zu-ZA-ThembaNeural'] 44 | self.radio_var = tkinter.IntVar(value=1) 45 | 46 | self.check_internet_conn() 47 | self.create_widgets() 48 | 49 | # Check if connected to internet 50 | def check_internet_conn(self): 51 | try: 52 | response = requests.get('https://www.google.com', timeout=3) 53 | if response.status_code != 200: 54 | self.show_warning("Unable to connect!") 55 | except requests.RequestException: 56 | self.show_warning("Unable to connect!") 57 | exit() 58 | 59 | # Show Warning message at startup if not connected to internet 60 | def show_warning(self, message): 61 | msg = CTkMessagebox(title="Warning Message!", message=message, icon="warning", option_1="Cancel", option_2="Retry") 62 | if msg.get() == "Retry": 63 | self.check_internet_conn() 64 | else: 65 | self.exit_app() 66 | 67 | # About 68 | def show_info(self): 69 | about = CTkMessagebox(title="About", message="𝙀𝘿𝙂𝙀 𝙏𝙏𝙎 𝙂𝙐𝙄\nversion:0.1-alpha\n𝘽𝙮 𝙨𝙘𝙝𝙧_𝙤𝙙𝙞𝙣𝙜𝙚𝙧", icon="info", option_1="OK", option_2="Github") 70 | response = about.get() 71 | 72 | if response == "Github": 73 | url = "https://github.com/schr-0dinger/edge_tts_gui" 74 | webbrowser.open(url, new=0, autoraise=True) 75 | 76 | # Messagebox for empty Textbox 77 | def show_empty(self): 78 | CTkMessagebox(title="Warning Message!", message="Text box cannot be empty", icon="warning", option_1="OK") 79 | self.btn_preview.configure(state="normal") 80 | 81 | async def convert(self, TEXT, VOICE, OUTPUT_FILE, RATE, PITCH, VOLUME, FORMAT): # async function 82 | # Yet, by default, edge-tts has no WAV output 83 | communicate = edge_tts.Communicate(TEXT, VOICE, rate=RATE, pitch=PITCH, volume=VOLUME) 84 | await communicate.save(OUTPUT_FILE) # save output file as MP3 (default) 85 | if FORMAT == "WAV": # if the format selected is WAV, 86 | sound = AudioSegment.from_mp3(OUTPUT_FILE) # convert MP3 to WAV 87 | sound.export(f"{OUTPUT_FILE}.wav", format="wav") 88 | os.remove(OUTPUT_FILE) 89 | 90 | async def preview(self, TEXT, VOICE, OUTPUT_FILE, RATE, PITCH, VOLUME): # async function 91 | communicate = edge_tts.Communicate(TEXT, VOICE, rate=RATE, pitch=PITCH, volume=VOLUME) 92 | with open(OUTPUT_FILE, "wb") as file: 93 | async for chunk in communicate.stream(): 94 | if chunk["type"] == "audio": 95 | file.write(chunk["data"]) 96 | 97 | def play_audio(self, file_path, playback_event): 98 | audio = AudioSegment.from_file(file_path) 99 | self.change_preview_button_text("PLAYING", "GREEN") 100 | play(audio) 101 | self.change_preview_button_text("PREVIEW", "#41436A") 102 | self.btn_preview.configure(state="normal") 103 | playback_event.set() 104 | 105 | def change_preview_button_text(self, text, color): 106 | self.btn_preview.configure(text=text, fg_color=color) 107 | 108 | def on_convert(self): 109 | TEXT = self.ent.get("1.0", "end-1c") 110 | VOICE = self.combox.get() 111 | FORMAT = self.format_option.get() 112 | RATE, PITCH, VOLUME = self.get_audio_properties() 113 | OUTPUT_FILE = self.get_output_filename(TEXT, VOICE, FORMAT) 114 | if len(TEXT) != 0 : 115 | if isinstance(OUTPUT_FILE, str): 116 | asyncio.run(self.convert(TEXT, VOICE, OUTPUT_FILE, RATE, PITCH, VOLUME,FORMAT)) 117 | else: 118 | self.show_empty() 119 | 120 | def on_preview(self): 121 | TEXT = self.ent.get("1.0", "end-1c") 122 | VOICE = self.combox.get() 123 | RATE, PITCH, VOLUME = self.get_audio_properties() 124 | with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_file: 125 | OUTPUT_FILE = temp_file.name 126 | if len(TEXT) != 0: 127 | asyncio.run(self.preview(TEXT, VOICE, OUTPUT_FILE, RATE, PITCH, VOLUME)) 128 | playback_event = threading.Event() 129 | threading.Thread(target=self.play_audio, args=(OUTPUT_FILE, playback_event)).start() 130 | playback_event.wait() 131 | os.remove(OUTPUT_FILE) 132 | else: 133 | self.show_empty() 134 | 135 | """ Starting conversion and preview on separate threads """ 136 | def start_convert_thread(self): 137 | threading.Thread(target=self.on_convert).start() 138 | 139 | def start_preview_thread(self): 140 | self.btn_preview.configure(state="disabled", text="PROCESSING") 141 | threading.Thread(target=self.on_preview).start() 142 | 143 | def get_audio_properties(self): 144 | RATE = f"{'+' if int(self.rate_scale.get()) >= 0 else ''}{int(self.rate_scale.get())}%" 145 | PITCH = f"{'+' if int(self.pitch_scale.get()) >= 0 else ''}{int(self.pitch_scale.get())}Hz" 146 | VOLUME = f"{'+' if int(self.vol_scale.get()) >= 0 else ''}{int(self.vol_scale.get())}%" 147 | return RATE, PITCH, VOLUME 148 | 149 | def get_output_filename(self, TEXT, VOICE, FORMAT): 150 | now_str = datetime.now().strftime("%B %d %Y %H-%M-%S") 151 | FORMAT = 'MP3' 152 | if self.format_option.get() == 'WAV': FORMAT = '' 153 | if self.radio_var.get() == 1: 154 | return f"{VOICE}{now_str}.{FORMAT}" 155 | elif self.radio_var.get() == 2: 156 | return f"{TEXT[:11].replace(' ', '_')}.{FORMAT}" 157 | else: 158 | return ctk.CTkInputDialog(text="Enter Output file name", title="Save As").get_input() + FORMAT 159 | 160 | def get_output_format(self): 161 | FORMAT = self.format_option.get() 162 | return FORMAT 163 | 164 | def update_scales(self, event=None): 165 | self.rate_scale_lbl.configure(text="RATE " + str(int(self.rate_scale.get()))) 166 | self.pitch_scale_lbl.configure(text="PITCH " + str(int(self.pitch_scale.get()))) 167 | self.vol_scale_lbl.configure(text="VOLUME " + str(int(self.vol_scale.get()))) 168 | 169 | # Reset to default values of the scales 170 | def reset_scales(self): 171 | self.rate_scale.set(0) 172 | self.pitch_scale.set(0) 173 | self.vol_scale.set(0) 174 | self.update_scales() 175 | 176 | def create_widgets(self): 177 | self.label = ctk.CTkLabel(self, text="EDGE TTS GUI", font=("Helvetica", 24, "bold"), corner_radius=10) 178 | self.label.place(x=120, y=10) 179 | self.combox = ctk.CTkComboBox(self, values=self.voice_names, width=260, hover=True) 180 | self.combox.set(self.default_voice) 181 | self.combox.place(x=60, y=50) 182 | self.ent = ctk.CTkTextbox(self, width=300, height=300, border_color="#41436A", border_width=1) 183 | self.ent.place(x=40, y=90) 184 | self.create_scale_frame() 185 | self.btn = ctk.CTkButton(self, text="GENERATE", command=self.start_convert_thread, width=100, height=50, fg_color="#F64668") 186 | self.btn.place(x=400, y=250) 187 | self.btn_preview = ctk.CTkButton(self, text="PREVIEW", command=self.start_preview_thread, width=100, height=50, fg_color="#41436A") 188 | self.btn_preview.place(x=400, y=310) 189 | self.create_rename_frame() 190 | self.format_option = ctk.CTkComboBox(self, values=["MP3","WAV"], width=150, hover=True) 191 | self.format_option.place(x=400, y=380) 192 | self.reset_scale_btn = ctk.CTkButton(self, text="RESET", command=self.reset_scales, width=10) 193 | self.reset_scale_btn.place(x=640, y=30) 194 | self.info = ctk.CTkButton(self, text="About", width=100, command=self.show_info) 195 | self.info.place(x=650, y=400) 196 | 197 | def create_scale_frame(self): 198 | self.scale_frame = ctk.CTkFrame(self, border_color="#41436A", border_width=1) 199 | self.scale_frame.place(x=400, y=20) 200 | self.rate_scale = ctk.CTkSlider(self.scale_frame, from_=-100, to=100, width=300) 201 | self.rate_scale.grid(column=0, row=1, pady=10, padx=6, sticky='nsw') 202 | self.pitch_scale = ctk.CTkSlider(self.scale_frame, from_=-100, to=100, width=300) 203 | self.pitch_scale.grid(column=0, row=3, pady=10, padx=6, sticky='nsw') 204 | self.vol_scale = ctk.CTkSlider(self.scale_frame, from_=-100, to=100, width=300) 205 | self.vol_scale.grid(column=0, row=5, pady=10, padx=6, sticky='nsw') 206 | self.rate_scale_lbl = ctk.CTkLabel(self.scale_frame, text="RATE " + str(int(self.rate_scale.get()))) 207 | self.rate_scale_lbl.grid(column=0, row=0, pady=4) 208 | self.pitch_scale_lbl = ctk.CTkLabel(self.scale_frame, text="PITCH " + str(int(self.pitch_scale.get()))) 209 | self.pitch_scale_lbl.grid(column=0, row=2) 210 | self.vol_scale_lbl = ctk.CTkLabel(self.scale_frame, text="VOLUME " + str(int(self.vol_scale.get()))) 211 | self.vol_scale_lbl.grid(column=0, row=4) 212 | for scale in [self.rate_scale, self.pitch_scale, self.vol_scale]: 213 | scale.bind("", self.update_scales) 214 | scale.bind("", self.update_scales) 215 | 216 | def create_rename_frame(self): 217 | self.rename_frame = ctk.CTkFrame(self) 218 | self.rename_frame.place(x=560, y=250) 219 | self.rename_label = ctk.CTkLabel(self.rename_frame, text="Save As options:", font=("Helvetica", 12, "bold")) 220 | self.rename_label.grid(column=0, row=0, padx=6, pady=2, sticky='w') 221 | for idx, text in enumerate(["Voice + Timestamp", "First 10 chars of text entered", "Enter manual name"], start=1): 222 | ctk.CTkRadioButton(self.rename_frame, text=text, variable=self.radio_var, value=idx).grid(column=0, row=idx, padx=6, pady=2, sticky='w') 223 | 224 | if __name__ == "__main__": 225 | app = App() 226 | app.mainloop() 227 | 228 | 229 | --------------------------------------------------------------------------------