|
""" |
|
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/<username>/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-TTS module>" |
|
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 |
|
|
|
|
|
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): |
|
model_folder_exists = True |
|
model_folder = i |
|
print(DEBUG_PREFIX,"Folder found:",model_folder) |
|
|
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
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) |
|
|
|
|
|
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") |
|
|
|
|
|
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(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 |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
response = send_file(audio_buffer, mimetype="audio/x-wav") |
|
audio_buffer = io.BytesIO() |
|
|
|
return response |