""" Coqui module for SillyTavern Extras Authors: - Pyrater (https://github.com/pyrater) - Tony Ribeiro (https://github.com/Tony-sama) Models are saved into user cache folder: "C:/Users//AppData/Local/tts" References: - Code adapted from: - Coqui TTS https://tts.readthedocs.io/en/latest/ - Audio-webui: https://github.com/gitmylo/audio-webui """ import json import os import io import shutil from flask import abort, request, send_file, jsonify from TTS.api import TTS from TTS.utils.manage import ModelManager from modules.utils import silence_log DEBUG_PREFIX = "" COQUI_MODELS_PATH = "data/models/coqui/" IGNORED_FILES = [".placeholder"] COQUI_LOCAL_MODEL_FILE_NAME = "model.pth" COQUI_LOCAL_CONFIG_FILE_NAME = "config.json" gpu_mode = False is_downloading = False def install_model(model_id): global gpu_mode audio_buffer = io.BytesIO() speaker_id = None language_id = None print(DEBUG_PREFIX,"Loading model",model_id) try: tts = TTS(model_name=model_id, progress_bar=True, gpu=gpu_mode) if tts.is_multi_lingual: language_id = tts.languages[0] if tts.is_multi_speaker: speaker_id =tts.speakers[0] tts.tts_to_file(text="this is a test message", file_path=audio_buffer, speaker=speaker_id, language=language_id) except Exception as e: print(DEBUG_PREFIX,"ERROR:", e) print("Model", model_id, "cannot be loaded, maybe wrong model name? Must be one of") for i in TTS.list_models(): print(i) return False print(DEBUG_PREFIX,"Success") return True def coqui_check_model_state(): """ Check if the requested model is installed on the server machine """ try: model_state = "absent" request_json = request.get_json() model_id = request_json["model_id"] print(DEBUG_PREFIX,"Search for model", model_id) coqui_models_folder = ModelManager().output_prefix # models location # Check if tts folder exist if os.path.isdir(coqui_models_folder): installed_models = os.listdir(coqui_models_folder) model_folder_exists = False model_folder = None for i in installed_models: if model_id == i.replace("--","/",3): # Error with model wrong name model_folder_exists = True model_folder = i print(DEBUG_PREFIX,"Folder found:",model_folder) # Check failed download if model_folder_exists: content = os.listdir(os.path.join(coqui_models_folder,model_folder)) print(DEBUG_PREFIX,"Checking content:",content) for i in content: if i == model_folder+".zip": print("Corrupt installed found, model download must have failed previously") model_state = "corrupted" break if model_state != "corrupted": model_state = "installed" response = json.dumps({"model_state":model_state}) return response except Exception as e: print(e) abort(500, DEBUG_PREFIX + " Exception occurs while trying to search for installed model") def coqui_install_model(): """ Install requested model is installed on the server machine """ global gpu_mode global is_downloading try: model_installed = False request_json = request.get_json() model_id = request_json["model_id"] action = request_json["action"] print(DEBUG_PREFIX,"Received request",action,"for model",model_id) if (is_downloading): print(DEBUG_PREFIX,"Rejected, already downloading a model") return json.dumps({"status":"downloading"}) coqui_models_folder = ModelManager().output_prefix # models location # Check if tts folder exist if os.path.isdir(coqui_models_folder): installed_models = os.listdir(coqui_models_folder) model_path = None print(DEBUG_PREFIX,"Found",len(installed_models),"models in",coqui_models_folder) for i in installed_models: if model_id == i.replace("--","/"): model_installed = True model_path = os.path.join(coqui_models_folder,i) if model_installed: print(DEBUG_PREFIX,"model found:", model_id) else: print(DEBUG_PREFIX,"model not found") if action == "download": if model_installed: abort(500, DEBUG_PREFIX + "Bad request, model already installed.") is_downloading = True TTS(model_name=model_id, progress_bar=True, gpu=gpu_mode) is_downloading = False if action == "repare": if not model_installed: abort(500, DEBUG_PREFIX + " bad request: requesting repare of model not installed") print(DEBUG_PREFIX,"Deleting corrupted model folder:",model_path) shutil.rmtree(model_path, ignore_errors=True) is_downloading = True TTS(model_name=model_id, progress_bar=True, gpu=gpu_mode) is_downloading = False response = json.dumps({"status":"done"}) return response except Exception as e: is_downloading = False print(e) abort(500, DEBUG_PREFIX + " Exception occurs while trying to search for installed model") def coqui_get_local_models(): """ Return user local models list in the following format: [language][dataset][name] = TTS_string_id """ try: print(DEBUG_PREFIX, "Received request for list of RVC models") folder_names = os.listdir(COQUI_MODELS_PATH) print(DEBUG_PREFIX,"Searching model in",COQUI_MODELS_PATH) model_list = [] for folder_name in folder_names: folder_path = COQUI_MODELS_PATH+folder_name if folder_name in IGNORED_FILES: continue # Must be a folder if not os.path.isdir(folder_path): print("> WARNING:",folder_name,"is not a folder, it should not be there, ignored") continue print("> Found model folder",folder_name) # Check pth valid_folder = False for file_name in os.listdir(folder_path): if file_name.endswith(".pth"): print(" > pth:",file_name) valid_folder = True if file_name.endswith(".config"): print(" > config:",file_name) if valid_folder: print(" > Valid folder added to list") model_list.append(folder_name) else: print(" > WARNING: Missing pth or config file, ignored folder") # Return the list of valid folders response = json.dumps({"models_list":model_list}) return response except Exception as e: print(e) abort(500, DEBUG_PREFIX + " Exception occurs while searching for Coqui models.") def coqui_generate_tts(): """ Process request text with the loaded RVC model - expected request: { "text": text, "model_id": voiceId, "language_id": language, "speaker_id": speaker } - model_id formats: - model_type/language/dataset/model_name - model_type/language/dataset/model_name[spearker_id] - model_type/language/dataset/model_name[spearker_id][language_id] - examples: - tts_models/ja/kokoro/tacotron2-DDC - tts_models/en/vctk/vits[0] - tts_models/multilingual/multi-dataset/your_tts[2][1] """ global gpu_mode global is_downloading audio_buffer = io.BytesIO() try: request_json = request.get_json() #print(request_json) print(DEBUG_PREFIX,"Received TTS request for ", request_json) if (is_downloading): print(DEBUG_PREFIX,"Rejected, currently downloading a model, cannot perform TTS") abort(500, DEBUG_PREFIX + " Requested TTS while downloading a model") text = request_json["text"] model_name = request_json["model_id"] language_id = None speaker_id = None # Local model model_type = model_name.split("/")[0] if model_type == "local": return generate_tts_local(model_name.split("/")[1], text) if request_json["language_id"] != "none": language_id = request_json["language_id"] if request_json["speaker_id"] != "none": speaker_id = request_json["speaker_id"] print(DEBUG_PREFIX,"Loading tts \n- model", model_name, "\n - speaker_id: ",speaker_id,"\n - language_id: ",language_id, "\n - using",("GPU" if gpu_mode else "CPU")) is_downloading = True tts = TTS(model_name=model_name, progress_bar=True, gpu=gpu_mode) is_downloading = False if tts.is_multi_lingual: if language_id is None: abort(400, DEBUG_PREFIX + " Requested model "+model_name+" is multi-lingual but no language id provided") language_id = tts.languages[int(language_id)] if tts.is_multi_speaker: if speaker_id is None: abort(400, DEBUG_PREFIX + " Requested model "+model_name+" is multi-speaker but no speaker id provided") speaker_id =tts.speakers[int(speaker_id)] tts.tts_to_file(text=text, file_path=audio_buffer, speaker=speaker_id, language=language_id) print(DEBUG_PREFIX, "Success, saved to",audio_buffer) # Return the output_audio_path object as a response response = send_file(audio_buffer, mimetype="audio/x-wav") audio_buffer = io.BytesIO() return response except Exception as e: print(e) abort(500, DEBUG_PREFIX + " Exception occurs while trying to process request "+str(request_json)) def generate_tts_local(model_folder, text): """ Generate tts using local coqui model """ audio_buffer = io.BytesIO() print(DEBUG_PREFIX,"Request for tts from local coqui model",model_folder) model_path = os.path.join(COQUI_MODELS_PATH,model_folder,COQUI_LOCAL_MODEL_FILE_NAME) config_path = os.path.join(COQUI_MODELS_PATH,model_folder,COQUI_LOCAL_CONFIG_FILE_NAME) if not os.path.exists(model_path): raise ValueError("File does not exists:",model_path) if not os.path.exists(config_path): raise ValueError("File does not exists:",config_path) print(DEBUG_PREFIX,"Loading local tts model", model_path,"using",("GPU" if gpu_mode else "CPU")) tts = TTS(model_path=model_path, config_path=config_path, progress_bar=True, gpu=gpu_mode) tts.tts_to_file(text=text, file_path=audio_buffer) print(DEBUG_PREFIX, "Success, saved to",audio_buffer) # Return the output_audio_path object as a response response = send_file(audio_buffer, mimetype="audio/x-wav") audio_buffer = io.BytesIO() return response