diff --git a/TTS/bin/frogie.py b/TTS/bin/frogie.py new file mode 100644 index 0000000000..1b92c18391 --- /dev/null +++ b/TTS/bin/frogie.py @@ -0,0 +1,25 @@ +def ascii_art_printer(): + print(r""" + + # ,*++++++*, ,*++++++*, + # *++. .+++ *++. .++* + # *+* ,++++* *+* *+* ,++++, *+* + # ,+, .++++++++++* ,++,,,,*+, ,++++++++++. *+, + # *+. .++++++++++++..++ *+.,++++++++++++. .+* + # .+* ++++++++++++.*+, .+*.++++++++++++ *+, + # .++ *++++++++* ++, .++.*++++++++* ++, + # ,+++*. . .*++, ,++*. .*+++* + # *+, .,*++**. .**++**. ,+* + # .+* *+, + # *+. Coqui .+* + # *+* +++ TTS +++ *+* + # .+++*. . . *+++. + # ,+* *+++*... ...*+++* *+, + # .++. .---+++++++****+++++++----. ++. + # ,++. .++, + # .++* *++. + # *+++, ,+++* + # .,*++++::::::++++*,. + # `````` + + """) \ No newline at end of file diff --git a/TTS/bin/inquire.py b/TTS/bin/inquire.py new file mode 100644 index 0000000000..0a2c182074 --- /dev/null +++ b/TTS/bin/inquire.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# pylint: disable=redefined-outer-name, unused-argument +from pathlib import Path + +from TTS.utils.manage import ModelManager +from TTS.utils.synthesizer import Synthesizer + +import inquirer +from inquirer.themes import GreenPassion + +# TODO add inquirer in requirements.txt + +path = Path(__file__).parent / "../.models.json" +manager = ModelManager(path) + +str2none = lambda i: i or None # converter for default None (''->None) + + +def official_zoo_inquirer(): + model_list = {} + model_list["tts_models"] = manager.list_tts_models(print_list=False) + model_list["vocoder_models"] = manager.list_vocoder_models(print_list=False) + model_list["vocoder_models"].insert(0, "default_vocoder") + model_load_questions = [ + inquirer.List( + "tts_choose", + message="Choose a tts model to load", + choices=model_list["tts_models"], + default="tts_models/en/ljspeech/tacotron2-DDC", + ), + inquirer.List( + "vocoder_choose", + message="Choose a vocoder model to load", + choices=model_list["vocoder_models"], + ), + inquirer.List( + "use_cuda", + message="Run model on CUDA?", + choices=[True, False], + default=True, + ), + ] + answers_model_load = inquirer.prompt(model_load_questions, theme=GreenPassion()) + return answers_model_load + + +def official_zoo_info_inquirer(): + joint_model_list = manager.list_tts_models(print_list=False) + manager.list_vocoder_models(print_list=False) + model_info_questions = [ + inquirer.List( + "model_choose_for_info", + message="Choose a tts model for info", + choices=joint_model_list, + ), + ] + answers_model_info_request = inquirer.prompt(model_info_questions, theme=GreenPassion()) + return answers_model_info_request + + +def custom_model_inquirer(): + custom_model_load_questions = [ + inquirer.Text( + "model_path", + message="Path to TTS model path", + default=None, + ), + inquirer.Text( + "model_config_path", + message="Path to TTS model config path", + default=None, + ), + inquirer.Text( + "vocoder_path", + message="Path to vocoder model file.", + default=None, + ), + inquirer.Text( + "vocoder_config_path", + message="Path to vocoder model config file.", + default=None, + ), + inquirer.Text( + "encoder_path", + message="Path to speaker encoder model file.", + default=None, + ), + inquirer.Text( + "encoder_config_path", + message="Path to speaker encoder config file.", + default=None, + ), + inquirer.List( + "use_cuda", + message="Run model on CUDA?", + choices=[True, False], + default=True, + ), + ] + answers_custom_model_load = inquirer.prompt(custom_model_load_questions, theme=GreenPassion()) + return answers_custom_model_load + + +def multispeaker_inquirer(): + multispeaker_questions = [ + inquirer.Text( + "speakers_file_path", + message="JSON file for multi-speaker model.", + default=None, + ), + inquirer.Text( + "language_ids_file_path", + message="JSON file for multi-lingual model.", + default=None, + ), + inquirer.Text("speaker_idx", message="Enter speaker idx", default=None), + inquirer.Text("language_idx", message="Enter language idx", default=None), + inquirer.Text("speaker_wav", message="Enter speaker wav file path", default=None), + inquirer.Text("reference_wav", message="Enter ref wav file path", default=None), + inquirer.Text("reference_speaker_idx", message="Enter reference speaker idx", default=None), + ] + answers_multispeaker = inquirer.prompt(multispeaker_questions, theme=GreenPassion()) + return answers_multispeaker + + +def capacitron_inquirer(): + capacitron_questions = [ + inquirer.Text("capacitron_style_wav", message="Enter capacitron style wav path", default=None), + inquirer.Text("capacitron_style_text", message="Enter capacitron style text", default=None), + ] + answers_capacitron = inquirer.prompt(capacitron_questions, theme=GreenPassion()) + return answers_capacitron + + +def continue_inquirer(): + continue_questions = [ + inquirer.List( + "to_do", message="What to do next?", choices=["try another text-input", "restart tts", "exit tts"] + ), + ] + + continue_answers = inquirer.prompt(continue_questions, theme=GreenPassion()) + return continue_answers + + +def tts_inquirer( + synthesizer, + speaker_idx, + language_idx, + speaker_wav, + reference_wav, + reference_speaker_idx, + capacitron_style_wav, + capacitron_style_text, +): + tts_questions = [ + inquirer.Text( + "text_input", + message="Type text to convert to speech", + ), + inquirer.Text("out_path", message="Enter output wav path", default="tts_output.wav"), + ] + answers_tts = inquirer.prompt(tts_questions, theme=GreenPassion()) + text = str2none(answers_tts["text_input"]) + outpath = answers_tts["out_path"] + text = text if text is not None else "Enter random text." + print(f" > Text: {text}") + # kick it + wav = synthesizer.tts( + text, + speaker_idx, + language_idx, + speaker_wav, + reference_wav=reference_wav, + reference_speaker_name=reference_speaker_idx, + style_wav=capacitron_style_wav, + style_text=capacitron_style_text, + ) + + # save the results + print(f" > Saving output to {outpath}") + synthesizer.save_wav(wav, outpath) + + continue_answers = continue_inquirer() + return continue_answers + + +def block_prompt(synthesizer, isCapacitron=False): + speaker_idx = None + language_idx = None + speaker_wav = None + reference_wav = reference_wav = None + reference_speaker_idx = None + capacitron_style_wav = None + capacitron_style_text = None + + if synthesizer.tts_speakers_file or hasattr(synthesizer.tts_model.speaker_manager, "ids"): + answers_multispeaker = multispeaker_inquirer() + for key, item in answers_multispeaker.items(): + answers_multispeaker[key] = str2none(item) + speaker_idx = answers_multispeaker["speaker_idx"] + language_idx = answers_multispeaker["language_idx"] + speaker_wav = answers_multispeaker["speaker_wav"] + reference_wav = reference_wav = answers_multispeaker["reference_wav"] + reference_speaker_idx = answers_multispeaker["reference_speaker_idx"] + + if isCapacitron: + answers_capacitron = capacitron_inquirer() + for key, item in answers_capacitron.items(): + answers_capacitron[key] = str2none(item) + capacitron_style_wav = answers_capacitron["capacitron_style_wav"] + capacitron_style_text = answers_capacitron["capacitron_style_text"] + + continue_answers = tts_inquirer( + synthesizer, + speaker_idx, + language_idx, + speaker_wav, + reference_wav, + reference_speaker_idx, + capacitron_style_wav, + capacitron_style_text, + ) + + if continue_answers["to_do"] == "exit tts": + return + if continue_answers["to_do"] == "restart tts": + print("restart") + init_prompt() + if continue_answers["to_do"] == "try another text-input": + block_prompt(synthesizer, isCapacitron=isCapacitron) + + +def init_prompt(): + model_path = None + config_path = None + speakers_file_path = None + language_ids_file_path = None + vocoder_path = None + vocoder_config_path = None + encoder_path = None + encoder_config_path = None + use_cuda = True + isCapacitron = False + + init_questions = [ + inquirer.List( + "to_do", + message="What do you need?", + choices=[ + "get info from official model zoo", + "play with official model zoo", + "play with your own model", + "exit tts", + ], + ), + ] + + init_answers = inquirer.prompt(init_questions, theme=GreenPassion()) + + if init_answers["to_do"] == "get info from official model zoo": + answers_model_load = official_zoo_info_inquirer() + manager.model_info_by_full_name(answers_model_load["model_choose_for_info"]) + + if init_answers["to_do"] == "exit tts": + return + + if init_answers["to_do"] == "play with official model zoo": + answers_model_load = official_zoo_inquirer() + tts_model_name = answers_model_load["tts_choose"] + model_path, config_path, model_item = manager.download_model(tts_model_name) + vocoder_name = ( + answers_model_load["vocoder_choose"] + if answers_model_load["vocoder_choose"] != "default_vocoder" + else model_item["default_vocoder"] + ) + if vocoder_name is not None: + vocoder_path, vocoder_config_path, _ = manager.download_model(vocoder_name) + use_cuda = answers_model_load["use_cuda"] + + synthesizer = Synthesizer( + model_path, + config_path, + speakers_file_path, + language_ids_file_path, + vocoder_path, + vocoder_config_path, + encoder_path, + encoder_config_path, + use_cuda, + ) + + if "capacitron" in tts_model_name: + isCapacitron = True + + block_prompt(synthesizer, isCapacitron=isCapacitron) + + if init_answers["to_do"] == "play with your own model": + answers_custom_model_load = custom_model_inquirer() + for key, item in answers_custom_model_load.items(): + answers_custom_model_load[key] = str2none(item) + + model_path = answers_custom_model_load["model_path"] + config_path = answers_custom_model_load["model_config_path"] + vocoder_path = answers_custom_model_load["vocoder_path"] + vocoder_config_path = answers_custom_model_load["vocoder_config_path"] + encoder_path = answers_custom_model_load["encoder_path"] + encoder_config_path = answers_custom_model_load["encoder_config_path"] + use_cuda = answers_custom_model_load["use_cuda"] + + synthesizer = Synthesizer( + model_path, + config_path, + speakers_file_path, + language_ids_file_path, + vocoder_path, + vocoder_config_path, + encoder_path, + encoder_config_path, + use_cuda, + ) + + if "use_capacitron_vae" in synthesizer.tts_config: # find if model is capacitron + print("yes cap") + isCapacitron = True + + block_prompt(synthesizer, isCapacitron=isCapacitron) + + +def main(): + from TTS.bin.frogie import ascii_art_printer + + ascii_art_printer() + print("welcome to COQUI TTS") + init_prompt() + + +if __name__ == "__main__": + main() diff --git a/TTS/utils/manage.py b/TTS/utils/manage.py index 281e5af02a..99a66735f8 100644 --- a/TTS/utils/manage.py +++ b/TTS/utils/manage.py @@ -5,6 +5,7 @@ from pathlib import Path from shutil import copyfile, rmtree from typing import Dict, Tuple +from tqdm import tqdm import requests @@ -58,35 +59,37 @@ def read_models_file(self, file_path): with open(file_path, "r", encoding="utf-8") as json_file: self.models_dict = json.load(json_file) - def _list_models(self, model_type, model_count=0): + def _list_models(self, model_type, model_count=0, print_list=False): model_list = [] for lang in self.models_dict[model_type]: for dataset in self.models_dict[model_type][lang]: for model in self.models_dict[model_type][lang][dataset]: model_full_name = f"{model_type}--{lang}--{dataset}--{model}" output_path = os.path.join(self.output_prefix, model_full_name) - if os.path.exists(output_path): - print(f" {model_count}: {model_type}/{lang}/{dataset}/{model} [already downloaded]") - else: - print(f" {model_count}: {model_type}/{lang}/{dataset}/{model}") + if print_list: + if os.path.exists(output_path): + print(f" {model_count}: {model_type}/{lang}/{dataset}/{model} [already downloaded]") + else: + print(f" {model_count}: {model_type}/{lang}/{dataset}/{model}") model_list.append(f"{model_type}/{lang}/{dataset}/{model}") model_count += 1 return model_list - def _list_for_model_type(self, model_type): - print(" Name format: language/dataset/model") + def _list_for_model_type(self, model_type, print_list=False): + if print_list: + print(" Name format: model_type/language/dataset/model") models_name_list = [] model_count = 1 - model_type = "tts_models" - models_name_list.extend(self._list_models(model_type, model_count)) - return [name.replace(model_type + "/", "") for name in models_name_list] + models_name_list.extend(self._list_models(model_type=model_type, model_count=model_count,print_list=print_list)) + return models_name_list - def list_models(self): - print(" Name format: type/language/dataset/model") + def list_models(self, print_list=True): + if print_list: + print(" Name format: type/language/dataset/model") models_name_list = [] model_count = 1 for model_type in self.models_dict: - model_list = self._list_models(model_type, model_count) + model_list = self._list_models(model_type, model_count, print_list=print_list) models_name_list.extend(model_list) return models_name_list @@ -165,17 +168,17 @@ def model_info_by_full_name(self, model_query_name): else: print(f"> model_type {model_type} does not exist in the list.") - def list_tts_models(self): + def list_tts_models(self, print_list=True): """Print all `TTS` models and return a list of model names - Format is `language/dataset/model` + Format is `tts_models/language/dataset/model` """ return self._list_for_model_type("tts_models") - def list_vocoder_models(self): + def list_vocoder_models(self, print_list=True): """Print all the `vocoder` models and return a list of model names - Format is `language/dataset/model` + Format is `vocoder_models/language/dataset/model` """ return self._list_for_model_type("vocoder_models") @@ -337,11 +340,20 @@ def _update_path(field_name, new_path, config_path): def _download_zip_file(file_url, output_folder): """Download the github releases""" # download the file - r = requests.get(file_url) + r = requests.get(file_url, stream=True) # extract the file try: - with zipfile.ZipFile(io.BytesIO(r.content)) as z: + total_size_in_bytes= int(r.headers.get('content-length', 0)) + block_size = 1024 #1 Kibibyte + progress_bar = tqdm(total=total_size_in_bytes, unit='iB', unit_scale=True) + temp_zip_name = os.path.join(output_folder, file_url.split('/')[-1]) + with open(temp_zip_name, 'wb') as file: + for data in r.iter_content(block_size): + progress_bar.update(len(data)) + file.write(data) + with zipfile.ZipFile(temp_zip_name) as z: z.extractall(output_folder) + os.remove(temp_zip_name) #delete zip after extract except zipfile.BadZipFile: print(f" > Error: Bad zip file - {file_url}") raise zipfile.BadZipFile # pylint: disable=raise-missing-from diff --git a/requirements.txt b/requirements.txt index b3acfeca4e..5cd04ca2d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,19 @@ # core deps -numpy==1.21.6 +numpy>=1.21.6 cython==0.29.28 scipy>=1.4.0 torch>=1.7 torchaudio soundfile librosa==0.8.0 -numba==0.55.1 +numba>=0.55.1 inflect tqdm anyascii pyyaml fsspec>=2021.04.0 +#deps for CLI +inquirer # deps for examples flask # deps for inference diff --git a/setup.py b/setup.py index 3c8609499d..81d672ef25 100644 --- a/setup.py +++ b/setup.py @@ -114,7 +114,7 @@ def pip_install(package_name): "notebooks": requirements_notebooks, }, python_requires=">=3.7.0, <3.11", - entry_points={"console_scripts": ["tts=TTS.bin.synthesize:main", "tts-server = TTS.server.server:main"]}, + entry_points={"console_scripts": ["tts=TTS.bin.synthesize:main", "tts-server = TTS.server.server:main", "tts-cli = TTS.bin.inquire:main"]}, classifiers=[ "Programming Language :: Python", "Programming Language :: Python :: 3",