From 395de0fab8d382a0502b3d3998ccefaa0b0f22dd Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Sat, 12 Aug 2023 06:05:39 +0200 Subject: [PATCH 1/6] Started refactoring of Coqui-tts extension. --- public/scripts/extensions/tts/coqui.js | 200 +++++++++++++++++++++++++ public/scripts/extensions/tts/index.js | 3 +- 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 public/scripts/extensions/tts/coqui.js diff --git a/public/scripts/extensions/tts/coqui.js b/public/scripts/extensions/tts/coqui.js new file mode 100644 index 000000000..46fe6bb47 --- /dev/null +++ b/public/scripts/extensions/tts/coqui.js @@ -0,0 +1,200 @@ +/* +TODO: + - load/download models + - load assign voice ids + - handle apply/available voices button +*/ + +import { eventSource, event_types } from "../../../script.js" +import { doExtrasFetch, getApiUrl, modules } from "../../extensions.js" + +export { CoquiTtsProvider } + +const DEBUG_PREFIX = " " + +function throwIfModuleMissing() { + if (!modules.includes('coqui-tts')) { + toastr.error(`Coqui TTS module not loaded. Add coqui-tts to enable-modules and restart the Extras API.`) + throw new Error(`Coqui TTS module not loaded.`) + } +} + +class CoquiTtsProvider { + //#############################// + // Extension UI and Settings // + //#############################// + + settings + voices = [] + separator = ' .. ' + + defaultSettings = { + voiceMap: {} + } + + get settingsHtml() { + let html = ` +
+
+
+ + + + + +
+ + + +
+
+
+
+ ` + return html + } + + loadSettings(settings) { + $("#coqui_api_model_div").hide(); + $("#coqui_api_model_name").hide(); + $("#coqui_model_origin").on("change",this.onModelOriginChange); + $("#coqui_api_language").on("change",this.onModelLanguageChange); + + } + + onSettingsChange() { + } + + async onApplyClick() { + return + } + + // DBG: assume voiceName is correct + // TODO: check voice is correct + async getVoice(voiceName) { + console.log(DEBUG_PREFIX,"getVoice",voiceName); + const output = {voice_id: voiceName}; + return output; + } + + async onModelOriginChange() { + const model_origin = $('#coqui_model_origin').val(); + console.debug(model_origin); + + // TODO: show coqui model list + if (model_origin == "coquiApi") { + $("#coqui_api_model_div").show(); + } + else + $("#coqui_api_model_div").hide(); + + // TODO show local model list + if (model_origin == "local") { + toastr.info("coming soon, ready when ready, etc", DEBUG_PREFIX+' Custom models not supported yet'); + } + + } + + async onModelLanguageChange() { + const model_language = $('#coqui_api_language').val(); + console.debug(model_language); + + if (model_language == "none") { + $("#coqui_api_model_name").hide(); + return; + } + + $("#coqui_api_model_name").show(); + + // TODO: if model list not already load, request it from extras + const result = await CoquiTtsProvider.getCoquiApiModels(); + const models = await result.json(); + console.debug(models,typeof(models)); + console.debug("models lists:", models[model_language]); + + $('#coqui_api_model_name') + .find('option') + .remove() + .end() + .append('') + .val('whatever') + + for(let k in models[model_language]) { + let model_name = k.split("/") + model_name = model_name[0] + " ("+model_name[1]+" dataset)" + $("#coqui_api_model_name").append(new Option(model_name,models[model_language][k])); + } + + // TODO: populate with corresponding dataset/model pairs got from initialization request + + } + + + //#############################// + // API Calls // + //#############################// + + static async getCoquiApiModels() { + const url = new URL(getApiUrl()); + url.pathname = '/api/text-to-speech/coqui/supported-models'; + + const apiResult = await doExtrasFetch(url, {method: 'POST'}) + + if (!apiResult.ok) { + toastr.error(apiResult.statusText, DEBUG_PREFIX+' Get models list failed'); + throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`); + } + + return apiResult + } + + + // Expect voiceId format to be like: + // tts_models/multilingual/multi-dataset/your_tts[2][1] + // tts_models/en/ljspeech/glow-tts + // ts_models/ja/kokoro/tacotron2-DDC + async generateTts(text, voiceId) { + const url = new URL(getApiUrl()); + url.pathname = '/api/text-to-speech/coqui/process-text'; + + const apiResult = await doExtrasFetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + }, + body: JSON.stringify({ + "text": text, + "voiceId": voiceId, + }) + }) + + if (!apiResult.ok) { + toastr.error(apiResult.statusText, 'TTS Generation Failed'); + throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`); + } + + return apiResult + } + + //------------------------------------------------------------------------------------ + + async fetchTtsFromHistory(history_item_id) { + return Promise.resolve(history_item_id); + } +} diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index d1a4d8bef..bb4e6418c 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -4,7 +4,8 @@ import { escapeRegex, getStringHash } from '../../utils.js' import { EdgeTtsProvider } from './edge.js' import { ElevenLabsTtsProvider } from './elevenlabs.js' import { SileroTtsProvider } from './silerotts.js' -import { CoquiTtsProvider } from './coquitts.js' +//import { CoquiTtsProvider } from './coquitts.js' +import { CoquiTtsProvider } from './coqui.js' // TODO: rename once done import { SystemTtsProvider } from './system.js' import { NovelTtsProvider } from './novel.js' import { power_user } from '../../power-user.js' From e60f91ce424abb7eb7b55efd36130f2f5b7d0b8f Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Sun, 13 Aug 2023 02:18:46 +0200 Subject: [PATCH 2/6] Finished unefficient full coqui pipeline, UI generated through request to extras. Need to be pruned --- public/scripts/extensions/tts/coqui.js | 455 +++++++++++++++++++++++-- 1 file changed, 421 insertions(+), 34 deletions(-) diff --git a/public/scripts/extensions/tts/coqui.js b/public/scripts/extensions/tts/coqui.js index 46fe6bb47..564f34cef 100644 --- a/public/scripts/extensions/tts/coqui.js +++ b/public/scripts/extensions/tts/coqui.js @@ -6,30 +6,66 @@ TODO: */ import { eventSource, event_types } from "../../../script.js" -import { doExtrasFetch, getApiUrl, modules } from "../../extensions.js" +import { doExtrasFetch, extension_settings, getApiUrl, getContext, modules, ModuleWorkerWrapper } from "../../extensions.js" export { CoquiTtsProvider } -const DEBUG_PREFIX = " " +const DEBUG_PREFIX = " " +const UPDATE_INTERVAL = 1000 + +let inApiCall = false +let charactersList = [] // Updated with module worker +let coquiApiModels = {} // Initialized only once +/* +coquiApiModels format [language][dataset][name]:coqui-api-model-id, example: +{ + "en": { + "vctk": { + "vits": "tts_models/en/vctk/vits" + } + }, + "ja": { + "kokoro": { + "tacotron2-DDC": "tts_models/ja/kokoro/tacotron2-DDC" + } + } +} +*/ +const languageLabels = { + "multilingual": "Multilingual", + "en" : "English", + "fr" : "French", + "es" : "Spanish", + "ja" : "Japanese" +} function throwIfModuleMissing() { if (!modules.includes('coqui-tts')) { - toastr.error(`Coqui TTS module not loaded. Add coqui-tts to enable-modules and restart the Extras API.`) - throw new Error(`Coqui TTS module not loaded.`) + toastr.error(`Add coqui-tts to enable-modules and restart the Extras API.`, "Coqui TTS module not loaded.", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + throw new Error(DEBUG_PREFIX,`Coqui TTS module not loaded.`); } } +function throwLocalOrigin() { + toastr.info("coming soon, ready when ready, etc", DEBUG_PREFIX+' Custom models not supported yet', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + throw new Error(DEBUG_PREFIX,`requesting feature not implemented yet.`); +} + +function resetModelSettings() { + $("#coqui_api_model_settings_language").val("none"); + $("#coqui_api_model_settings_speaker").val("none"); +} + class CoquiTtsProvider { //#############################// // Extension UI and Settings // //#############################// settings - voices = [] - separator = ' .. ' defaultSettings = { - voiceMap: {} + voiceMap: "", + voiceMapDict : {} } get settingsHtml() { @@ -39,28 +75,36 @@ class CoquiTtsProvider {
+ + + + Loading... + +
+ + +
@@ -70,17 +114,95 @@ class CoquiTtsProvider { } loadSettings(settings) { + // Only accept keys defined in defaultSettings + this.settings = this.defaultSettings + + for (const key in settings){ + if (key in this.settings){ + this.settings[key] = settings[key] + } else { + throw DEBUG_PREFIX+`Invalid setting passed to extension: ${key}` + } + } + $("#coqui_api_model_div").hide(); $("#coqui_api_model_name").hide(); + $("#coqui_api_model_settings").hide(); + $("#coqui_api_model_loading").hide(); + $("#coqui_api_model_install_button").hide(); $("#coqui_model_origin").on("change",this.onModelOriginChange); $("#coqui_api_language").on("change",this.onModelLanguageChange); - + $("#coqui_api_model_name").on("change",this.onModelNameChange); } onSettingsChange() { } async onApplyClick() { + if (inApiCall) { + return; // TOdo block dropdown + } + + const character = $("#coqui_character_select").val(); + const model_origin = $("#coqui_model_origin").val(); + const model_language = $("#coqui_api_language").val(); + const model_name = $("#coqui_api_model_name").val(); + let model_setting_language = $("#coqui_api_model_settings_language").val(); + let model_setting_speaker = $("#coqui_api_model_settings_speaker").val(); + + + if (character === "none") { + toastr.error(`Character not selected, please select one.`, DEBUG_PREFIX+" voice mapping character", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + return; + } + + if (model_origin == "none") { + toastr.error(`Origin not selected, please select one.`, DEBUG_PREFIX+" voice mapping origin", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + return; + } + + if (model_origin == "local") { + throwLocalOrigin(); + return; + } + + if (model_language == "none") { + toastr.error(`Language not selected, please select one.`, DEBUG_PREFIX+" voice mapping language", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + return; + } + + if (model_name == "none") { + toastr.error(`Model not selected, please select one.`, DEBUG_PREFIX+" voice mapping model", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + return; + } + + if (model_setting_language == "none") + model_setting_language = null; + + if (model_setting_speaker == "none") + model_setting_speaker = null; + + console.debug(DEBUG_PREFIX,"Current voice map: ",this.settings.voiceMap); + + this.settings.voiceMapDict[character] = {model_id: model_name, model_language:model_setting_language, model_speaker:model_setting_speaker}; + + console.debug(DEBUG_PREFIX,"Registered new voice map: ",character,":",this.settings.voiceMapDict[character]); + + this.settings.voiceMap = ""; + for(let i in this.settings.voiceMapDict) { + const voice_settings = this.settings.voiceMapDict[i]; + this.settings.voiceMap += i + ":" + voice_settings["model_id"]; + + if (model_setting_language != null) + this.settings.voiceMap += "[" + voice_settings["model_language"] + "]"; + + if (model_setting_speaker != null) + this.settings.voiceMap += "[" + voice_settings["model_speaker"] + "]"; + + this.settings.voiceMap += ","; + } + $("#tts_voice_map").val(this.settings.voiceMap); + return } @@ -93,11 +215,13 @@ class CoquiTtsProvider { } async onModelOriginChange() { + throwIfModuleMissing() + resetModelSettings(); const model_origin = $('#coqui_model_origin').val(); console.debug(model_origin); // TODO: show coqui model list - if (model_origin == "coquiApi") { + if (model_origin == "coqui-api") { $("#coqui_api_model_div").show(); } else @@ -105,12 +229,15 @@ class CoquiTtsProvider { // TODO show local model list if (model_origin == "local") { - toastr.info("coming soon, ready when ready, etc", DEBUG_PREFIX+' Custom models not supported yet'); + throwLocalOrigin(); } - } async onModelLanguageChange() { + throwIfModuleMissing(); + resetModelSettings(); + $("#coqui_api_model_settings").hide(); + const model_origin = $('#coqui_model_origin').val(); const model_language = $('#coqui_api_language').val(); console.debug(model_language); @@ -120,58 +247,254 @@ class CoquiTtsProvider { } $("#coqui_api_model_name").show(); - - // TODO: if model list not already load, request it from extras - const result = await CoquiTtsProvider.getCoquiApiModels(); - const models = await result.json(); - console.debug(models,typeof(models)); - console.debug("models lists:", models[model_language]); - $('#coqui_api_model_name') .find('option') .remove() .end() .append('') - .val('whatever') + .val('none'); - for(let k in models[model_language]) { - let model_name = k.split("/") - model_name = model_name[0] + " ("+model_name[1]+" dataset)" - $("#coqui_api_model_name").append(new Option(model_name,models[model_language][k])); + for(let model_dataset in coquiApiModels[model_language]) + for(let model_name in coquiApiModels[model_language][model_dataset]) { + const model_id = coquiApiModels[model_language][model_dataset][model_name] + const model_label = model_name + " ("+model_dataset+" dataset)" + $("#coqui_api_model_name").append(new Option(model_label,model_id)); } // TODO: populate with corresponding dataset/model pairs got from initialization request } + + async onModelNameChange() { + throwIfModuleMissing(); + resetModelSettings(); + $("#coqui_api_model_settings").hide(); + + const modelId = $("#coqui_api_model_name").val(); + + if (modelId == "none") { + $("#coqui_api_model_install_button").off('click'); + $("#coqui_api_model_install_button").show(); + return; + } + + $("#coqui_api_model_loading").show(); + + // Check if already download and propose to do it + console.debug(DEBUG_PREFIX,"Check if model is already installed",modelId); + let result = await CoquiTtsProvider.checkModelState(modelId); + result = await result.json(); + const modelState = result["model_state"]; + + console.debug(DEBUG_PREFIX," Model state:", modelState) + + if (modelState != "installed") { + $("#coqui_api_model_install_button").show(); + $("#coqui_api_model_install_button").on("click", function (){ + CoquiTtsProvider.installModel(modelId); + }); + + if (modelState == "corrupted") { + toastr.error("Click repare button to reinstall the model "+$("#coqui_api_model_name").find(":selected").text(), DEBUG_PREFIX+" corrupted model install", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + $("#coqui_api_model_install_button").val("Repare"); + } + else { + toastr.info("Click download button to install the model "+$("#coqui_api_model_name").find(":selected").text(), DEBUG_PREFIX+" model not installed", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + $("#coqui_api_model_install_button").val("Download"); + } + return; + } + + console.debug(DEBUG_PREFIX,"Request model settings for",modelId); + + result = await CoquiTtsProvider.getModelSettings(modelId); + const modelSettings = await result.json(); + $("#coqui_api_model_loading").hide(); + + console.debug(DEBUG_PREFIX,modelSettings) + + + if (modelSettings["languages"].length > 0) { + $("#coqui_api_model_settings").show(); + $("#coqui_api_model_settings_language").show(); + $('#coqui_api_model_settings_language') + .find('option') + .remove() + .end() + .append('') + .val('none'); + + for(const language of modelSettings["languages"]) { + $("#coqui_api_model_settings_language").append(new Option(language,language)); + } + } + else { + $("#coqui_api_model_settings_language").hide(); + } + + if (modelSettings["speakers"].length > 0) { + $("#coqui_api_model_settings").show(); + $("#coqui_api_model_settings_speaker").show(); + $('#coqui_api_model_settings_speaker') + .find('option') + .remove() + .end() + .append('') + .val('none'); + + for(const speaker of modelSettings["speakers"]) { + $("#coqui_api_model_settings_speaker").append(new Option(speaker,speaker)); + } + } + else { + $("#coqui_api_model_settings_speaker").hide(); + } + + } //#############################// // API Calls // //#############################// - static async getCoquiApiModels() { + static async getModelList(origin) { + throwIfModuleMissing() const url = new URL(getApiUrl()); - url.pathname = '/api/text-to-speech/coqui/supported-models'; + + if (origin == "coqui-api") + url.pathname = '/api/text-to-speech/coqui/coqui-api/get-models'; + else + url.pathname = '/api/text-to-speech/coqui/local/get-models'; const apiResult = await doExtrasFetch(url, {method: 'POST'}) if (!apiResult.ok) { - toastr.error(apiResult.statusText, DEBUG_PREFIX+' Get models list failed'); + toastr.error(apiResult.statusText, DEBUG_PREFIX+' Get models list request failed'); throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`); } return apiResult } + /* + Check model installation state, return one of ["installed", "corrupted", "absent"] + */ + static async checkModelState(modelId) { + throwIfModuleMissing() + const url = new URL(getApiUrl()); + url.pathname = '/api/text-to-speech/coqui/coqui-api/check-model-state'; + + const apiResult = await doExtrasFetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + }, + body: JSON.stringify({ + "model_id": modelId, + }) + }); + + if (!apiResult.ok) { + toastr.error(apiResult.statusText, DEBUG_PREFIX+' Check model state request failed'); + throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`); + } + + return apiResult + } + + static async installModel(modelId) { + throwIfModuleMissing() + const url = new URL(getApiUrl()); + url.pathname = '/api/text-to-speech/coqui/coqui-api/install-model'; + + const apiResult = await doExtrasFetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + }, + body: JSON.stringify({ + "model_id": modelId, + }) + }); + + if (!apiResult.ok) { + toastr.error(apiResult.statusText, DEBUG_PREFIX+' Install model '+modelId+' request failed'); + throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`); + } + + return apiResult + } + + static async getModelSettings(modelId) { + throwIfModuleMissing() + // API is busy + inApiCall = true; + + try { + const url = new URL(getApiUrl()); + url.pathname = '/api/text-to-speech/coqui/coqui-api/get-model-settings'; + + const apiResult = await doExtrasFetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + }, + body: JSON.stringify({ + "model_id": modelId, + }) + }); + + if (!apiResult.ok) { + toastr.error(apiResult.statusText, DEBUG_PREFIX+' Get model setting request failed for '+modelId); + throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`); + } + + return apiResult + } + catch (error) { + console.debug(error); + } + finally { + inApiCall = false; + } + } + + // Get speakers + // Expect voiceId format to be like: // tts_models/multilingual/multi-dataset/your_tts[2][1] // tts_models/en/ljspeech/glow-tts // ts_models/ja/kokoro/tacotron2-DDC async generateTts(text, voiceId) { + throwIfModuleMissing() const url = new URL(getApiUrl()); url.pathname = '/api/text-to-speech/coqui/process-text'; + let language = "none" + let speaker = "none" + const tokens = voiceId.replaceAll("]","").split("["); + const modelId = tokens[0] + + console.debug(DEBUG_PREFIX,"Preparing TTS request for",tokens) + + // First option + if (tokens.length > 1) { + const option1 = tokens[1] + + if (modelId.includes("multilingual")) + language = option1 + else + speaker = option1 + } + + // Second option + if (tokens.length > 2) + speaker = tokens[2]; + const apiResult = await doExtrasFetch(url, { method: 'POST', headers: { @@ -180,9 +503,11 @@ class CoquiTtsProvider { }, body: JSON.stringify({ "text": text, - "voiceId": voiceId, + "model_id": modelId, + "language": language, + "speaker": speaker }) - }) + }); if (!apiResult.ok) { toastr.error(apiResult.statusText, 'TTS Generation Failed'); @@ -198,3 +523,65 @@ class CoquiTtsProvider { return Promise.resolve(history_item_id); } } + +//#############################// +// Module Worker // +//#############################// + +async function moduleWorker() { + // Skip if module not loaded + if (!modules.includes('coqui-tts')) + return; + + // 1) Update characters list + //---------------------------- + let currentcharacters = new Set(); + for (const i of getContext().characters) { + currentcharacters.add(i.name); + } + + currentcharacters = Array.from(currentcharacters) + + if (JSON.stringify(charactersList) !== JSON.stringify(currentcharacters)) { + charactersList = currentcharacters + + $('#coqui_character_select') + .find('option') + .remove() + .end() + .append('') + .val('none') + + for(const charName of charactersList) { + $("#coqui_character_select").append(new Option(charName,charName)); + } + + console.debug(DEBUG_PREFIX,"Updated character list to:", charactersList); + } + + // 2) Initialize TTS models lists + //--------------------------------- + if (Object.keys(coquiApiModels).length === 0) { + const result = await CoquiTtsProvider.getModelList("coqui-api"); + coquiApiModels = await result.json(); + console.debug(DEBUG_PREFIX,"initialized coqui-api model list to", coquiApiModels); + + $('#coqui_api_language') + .find('option') + .remove() + .end() + .append('') + .val('none'); + + for(let language in coquiApiModels) { + $("#coqui_api_language").append(new Option(languageLabels[language],language)); + console.log(DEBUG_PREFIX,"added language",language); + } + } +} + +$(document).ready(function () { + const wrapper = new ModuleWorkerWrapper(moduleWorker); + setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); + moduleWorker(); +}) \ No newline at end of file From c414606c812d7e34978d1183d2defaee2f3822a8 Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Mon, 14 Aug 2023 04:03:28 +0200 Subject: [PATCH 3/6] Finished new version of coqui TTS UI. --- public/scripts/extensions/tts/coqui.js | 407 ++++++++++-------- .../tts/coqui_api_models_settings.json | 187 ++++++++ public/scripts/extensions/tts/coquitts.js | 403 ----------------- public/scripts/extensions/tts/index.js | 3 +- 4 files changed, 409 insertions(+), 591 deletions(-) create mode 100644 public/scripts/extensions/tts/coqui_api_models_settings.json delete mode 100644 public/scripts/extensions/tts/coquitts.js diff --git a/public/scripts/extensions/tts/coqui.js b/public/scripts/extensions/tts/coqui.js index 564f34cef..f1222d568 100644 --- a/public/scripts/extensions/tts/coqui.js +++ b/public/scripts/extensions/tts/coqui.js @@ -1,11 +1,10 @@ /* TODO: - - load/download models - - load assign voice ids - - handle apply/available voices button + - Hide voice map its just confusing + - Delete useless call */ -import { eventSource, event_types } from "../../../script.js" +import { saveSettingsDebounced } from "../../../script.js" import { doExtrasFetch, extension_settings, getApiUrl, getContext, modules, ModuleWorkerWrapper } from "../../extensions.js" export { CoquiTtsProvider } @@ -56,6 +55,32 @@ function resetModelSettings() { $("#coqui_api_model_settings_speaker").val("none"); } +function updateCharactersList() { + let currentcharacters = new Set(); + for (const i of getContext().characters) { + currentcharacters.add(i.name); + } + + currentcharacters = Array.from(currentcharacters) + + if (JSON.stringify(charactersList) !== JSON.stringify(currentcharacters)) { + charactersList = currentcharacters + + $('#coqui_character_select') + .find('option') + .remove() + .end() + .append('') + .val('none') + + for(const charName of charactersList) { + $("#coqui_character_select").append(new Option(charName,charName)); + } + + console.debug(DEBUG_PREFIX,"Updated character list to:", charactersList); + } +} + class CoquiTtsProvider { //#############################// // Extension UI and Settings // @@ -78,7 +103,7 @@ class CoquiTtsProvider { - gpu_mode @@ -93,10 +118,6 @@ class CoquiTtsProvider { - - - Loading... -
+ Model installed on extras server + @@ -125,17 +148,72 @@ class CoquiTtsProvider { } } + this.updateVoiceMap(); // Overide any manual modification + $("#coqui_api_model_div").hide(); $("#coqui_api_model_name").hide(); $("#coqui_api_model_settings").hide(); - $("#coqui_api_model_loading").hide(); + $("#coqui_api_model_install_status").hide(); $("#coqui_api_model_install_button").hide(); + $("#coqui_model_origin").on("change",this.onModelOriginChange); $("#coqui_api_language").on("change",this.onModelLanguageChange); $("#coqui_api_model_name").on("change",this.onModelNameChange); + + // Load characters list + $('#coqui_character_select') + .find('option') + .remove() + .end() + .append('') + .val('none') + + for(const charName of charactersList) { + $("#coqui_character_select").append(new Option(charName,charName)); + } + + // Load coqui-api settings from json file + fetch("/scripts/extensions/tts/coqui_api_models_settings.json") + .then(response => response.json()) + .then(json => { + coquiApiModels = json; + console.debug(DEBUG_PREFIX,"initialized coqui-api model list to", coquiApiModels); + + $('#coqui_api_language') + .find('option') + .remove() + .end() + .append('') + .val('none'); + + for(let language in coquiApiModels) { + $("#coqui_api_language").append(new Option(languageLabels[language],language)); + console.log(DEBUG_PREFIX,"added language",language); + } + }); + } + + updateVoiceMap(){ + this.settings.voiceMap = ""; + for(let i in this.settings.voiceMapDict) { + const voice_settings = this.settings.voiceMapDict[i]; + this.settings.voiceMap += i + ":" + voice_settings["model_id"]; + + if (voice_settings["model_language"] != null) + this.settings.voiceMap += "[" + voice_settings["model_language"] + "]"; + + if (voice_settings["model_speaker"] != null) + this.settings.voiceMap += "[" + voice_settings["model_speaker"] + "]"; + + this.settings.voiceMap += ","; + } + $("#tts_voice_map").val(this.settings.voiceMap); + extension_settings.tts.Coqui = this.settings; } onSettingsChange() { + console.debug(DEBUG_PREFIX,"Settings changes",this.settings); + extension_settings.tts.Coqui = this.settings; } async onApplyClick() { @@ -153,26 +231,31 @@ class CoquiTtsProvider { if (character === "none") { toastr.error(`Character not selected, please select one.`, DEBUG_PREFIX+" voice mapping character", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + this.updateVoiceMap(); // Overide any manual modification return; } if (model_origin == "none") { toastr.error(`Origin not selected, please select one.`, DEBUG_PREFIX+" voice mapping origin", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + this.updateVoiceMap(); // Overide any manual modification return; } if (model_origin == "local") { throwLocalOrigin(); + this.updateVoiceMap(); // Overide any manual modification return; } if (model_language == "none") { toastr.error(`Language not selected, please select one.`, DEBUG_PREFIX+" voice mapping language", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + this.updateVoiceMap(); // Overide any manual modification return; } if (model_name == "none") { toastr.error(`Model not selected, please select one.`, DEBUG_PREFIX+" voice mapping model", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + this.updateVoiceMap(); // Overide any manual modification return; } @@ -181,28 +264,36 @@ class CoquiTtsProvider { if (model_setting_speaker == "none") model_setting_speaker = null; + + const tokens = $('#coqui_api_model_name').val().split("/"); + const model_dataset = tokens[0]; + const model_label = tokens[1]; + const model_id = "tts_models/" + model_language + "/" + model_dataset + "/" + model_label + + if (model_setting_language == null & "languages" in coquiApiModels[model_language][model_dataset][model_label]) { + toastr.error(`Model language not selected, please select one.`, DEBUG_PREFIX+" voice mapping model language", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + return; + } + + if (model_setting_speaker == null & "speakers" in coquiApiModels[model_language][model_dataset][model_label]) { + toastr.error(`Model speaker not selected, please select one.`, DEBUG_PREFIX+" voice mapping model speaker", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + return; + } console.debug(DEBUG_PREFIX,"Current voice map: ",this.settings.voiceMap); - this.settings.voiceMapDict[character] = {model_id: model_name, model_language:model_setting_language, model_speaker:model_setting_speaker}; - + this.settings.voiceMapDict[character] = {model_id: model_id, model_language:model_setting_language, model_speaker:model_setting_speaker}; + console.debug(DEBUG_PREFIX,"Registered new voice map: ",character,":",this.settings.voiceMapDict[character]); - this.settings.voiceMap = ""; - for(let i in this.settings.voiceMapDict) { - const voice_settings = this.settings.voiceMapDict[i]; - this.settings.voiceMap += i + ":" + voice_settings["model_id"]; - - if (model_setting_language != null) - this.settings.voiceMap += "[" + voice_settings["model_language"] + "]"; - - if (model_setting_speaker != null) - this.settings.voiceMap += "[" + voice_settings["model_speaker"] + "]"; - - this.settings.voiceMap += ","; - } - $("#tts_voice_map").val(this.settings.voiceMap); + this.updateVoiceMap(); + let successMsg = character+":"+model_id; + if (model_setting_language != null) + successMsg += "[" + model_setting_language + "]"; + if (model_setting_speaker != null) + successMsg += "[" + model_setting_speaker + "]"; + toastr.info(successMsg, DEBUG_PREFIX+" voice map updated", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); return } @@ -256,13 +347,10 @@ class CoquiTtsProvider { for(let model_dataset in coquiApiModels[model_language]) for(let model_name in coquiApiModels[model_language][model_dataset]) { - const model_id = coquiApiModels[model_language][model_dataset][model_name] + const model_id = model_dataset + "/" + model_name const model_label = model_name + " ("+model_dataset+" dataset)" $("#coqui_api_model_name").append(new Option(model_label,model_id)); } - - // TODO: populate with corresponding dataset/model pairs got from initialization request - } async onModelNameChange() { @@ -270,51 +358,22 @@ class CoquiTtsProvider { resetModelSettings(); $("#coqui_api_model_settings").hide(); - const modelId = $("#coqui_api_model_name").val(); - - if (modelId == "none") { + // No model selected + if ($('#coqui_api_model_name').val() == "none") { $("#coqui_api_model_install_button").off('click'); - $("#coqui_api_model_install_button").show(); + $("#coqui_api_model_install_button").hide(); return; } - $("#coqui_api_model_loading").show(); - - // Check if already download and propose to do it - console.debug(DEBUG_PREFIX,"Check if model is already installed",modelId); - let result = await CoquiTtsProvider.checkModelState(modelId); - result = await result.json(); - const modelState = result["model_state"]; + // Get languages and speakers options + const model_language = $('#coqui_api_language').val(); + const tokens = $('#coqui_api_model_name').val().split("/"); + const model_dataset = tokens[0]; + const model_name = tokens[1]; - console.debug(DEBUG_PREFIX," Model state:", modelState) + const model_settings = coquiApiModels[model_language][model_dataset][model_name] - if (modelState != "installed") { - $("#coqui_api_model_install_button").show(); - $("#coqui_api_model_install_button").on("click", function (){ - CoquiTtsProvider.installModel(modelId); - }); - - if (modelState == "corrupted") { - toastr.error("Click repare button to reinstall the model "+$("#coqui_api_model_name").find(":selected").text(), DEBUG_PREFIX+" corrupted model install", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); - $("#coqui_api_model_install_button").val("Repare"); - } - else { - toastr.info("Click download button to install the model "+$("#coqui_api_model_name").find(":selected").text(), DEBUG_PREFIX+" model not installed", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); - $("#coqui_api_model_install_button").val("Download"); - } - return; - } - - console.debug(DEBUG_PREFIX,"Request model settings for",modelId); - - result = await CoquiTtsProvider.getModelSettings(modelId); - const modelSettings = await result.json(); - $("#coqui_api_model_loading").hide(); - - console.debug(DEBUG_PREFIX,modelSettings) - - - if (modelSettings["languages"].length > 0) { + if ("languages" in model_settings) { $("#coqui_api_model_settings").show(); $("#coqui_api_model_settings_language").show(); $('#coqui_api_model_settings_language') @@ -324,15 +383,16 @@ class CoquiTtsProvider { .append('') .val('none'); - for(const language of modelSettings["languages"]) { - $("#coqui_api_model_settings_language").append(new Option(language,language)); + for(var i = 0; i < model_settings["languages"].length; i++) { + const language_label = JSON.stringify(model_settings["languages"][i]).replaceAll("\"",""); + $("#coqui_api_model_settings_language").append(new Option(language_label,i)); } } else { $("#coqui_api_model_settings_language").hide(); } - if (modelSettings["speakers"].length > 0) { + if ("speakers" in model_settings) { $("#coqui_api_model_settings").show(); $("#coqui_api_model_settings_speaker").show(); $('#coqui_api_model_settings_speaker') @@ -342,14 +402,78 @@ class CoquiTtsProvider { .append('') .val('none'); - for(const speaker of modelSettings["speakers"]) { - $("#coqui_api_model_settings_speaker").append(new Option(speaker,speaker)); + for(var i = 0; i < model_settings["speakers"].length;i++) { + const speaker_label = JSON.stringify(model_settings["speakers"][i]).replaceAll("\"",""); + $("#coqui_api_model_settings_speaker").append(new Option(speaker_label,i)); } } else { $("#coqui_api_model_settings_speaker").hide(); } + $("#coqui_api_model_install_status").text("Requesting model to extras server..."); + $("#coqui_api_model_install_status").show(); + + // Check if already installed and propose to do it otherwise + const model_id = coquiApiModels[model_language][model_dataset][model_name]["id"] + console.debug(DEBUG_PREFIX,"Check if model is already installed",model_id); + let result = await CoquiTtsProvider.checkmodel_state(model_id); + result = await result.json(); + const model_state = result["model_state"]; + + console.debug(DEBUG_PREFIX," Model state:", model_state) + + if (model_state == "installed") { + $("#coqui_api_model_install_status").text("Model already installed on extras server"); + $("#coqui_api_model_install_button").hide(); + } + else { + let action = "download" + if (model_state == "corrupted") { + action = "repare" + //toastr.error("Click install button to reinstall the model "+$("#coqui_api_model_name").find(":selected").text(), DEBUG_PREFIX+" corrupted model install", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + $("#coqui_api_model_install_status").text("Model found but incomplete try install again (maybe still downloading)"); // (remove and download again) + } + else { + toastr.info("Click download button to install the model "+$("#coqui_api_model_name").find(":selected").text(), DEBUG_PREFIX+" model not installed", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + $("#coqui_api_model_install_status").text("Model not found on extras server"); + } + + const onModelNameChange_pointer = this.onModelNameChange; + + $("#coqui_api_model_install_button").off("click").on("click", async function (){ + try { + $("#coqui_api_model_install_status").text("Downloading model..."); + $("#coqui_api_model_install_button").hide(); + //toastr.info("For model "+model_id, DEBUG_PREFIX+" Started "+action, { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + let apiResult = await CoquiTtsProvider.installModel(model_id, action); + apiResult = await apiResult.json(); + + console.debug(DEBUG_PREFIX,"Response:",apiResult); + + if (apiResult["status"] == "done") { + $("#coqui_api_model_install_status").text("Model installed and ready to use!"); + $("#coqui_api_model_install_button").hide(); + onModelNameChange_pointer(); + } + + if (apiResult["status"] == "downloading") { + toastr.error("Check extras console for progress", DEBUG_PREFIX+" already downloading", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + $("#coqui_api_model_install_status").text("Already downloading a model, check extras console!"); + $("#coqui_api_model_install_button").show(); + } + } catch (error) { + console.error(error) + toastr.error(error, DEBUG_PREFIX+" error with model download", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }); + onModelNameChange_pointer(); + } + // will refresh model status + }); + + $("#coqui_api_model_install_button").show(); + return; + } + } @@ -357,29 +481,10 @@ class CoquiTtsProvider { // API Calls // //#############################// - static async getModelList(origin) { - throwIfModuleMissing() - const url = new URL(getApiUrl()); - - if (origin == "coqui-api") - url.pathname = '/api/text-to-speech/coqui/coqui-api/get-models'; - else - url.pathname = '/api/text-to-speech/coqui/local/get-models'; - - const apiResult = await doExtrasFetch(url, {method: 'POST'}) - - if (!apiResult.ok) { - toastr.error(apiResult.statusText, DEBUG_PREFIX+' Get models list request failed'); - throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`); - } - - return apiResult - } - /* Check model installation state, return one of ["installed", "corrupted", "absent"] */ - static async checkModelState(modelId) { + static async checkmodel_state(model_id) { throwIfModuleMissing() const url = new URL(getApiUrl()); url.pathname = '/api/text-to-speech/coqui/coqui-api/check-model-state'; @@ -391,7 +496,7 @@ class CoquiTtsProvider { 'Cache-Control': 'no-cache' }, body: JSON.stringify({ - "model_id": modelId, + "model_id": model_id, }) }); @@ -403,7 +508,7 @@ class CoquiTtsProvider { return apiResult } - static async installModel(modelId) { + static async installModel(model_id, action) { throwIfModuleMissing() const url = new URL(getApiUrl()); url.pathname = '/api/text-to-speech/coqui/coqui-api/install-model'; @@ -415,53 +520,19 @@ class CoquiTtsProvider { 'Cache-Control': 'no-cache' }, body: JSON.stringify({ - "model_id": modelId, + "model_id": model_id, + "action": action }) }); if (!apiResult.ok) { - toastr.error(apiResult.statusText, DEBUG_PREFIX+' Install model '+modelId+' request failed'); + toastr.error(apiResult.statusText, DEBUG_PREFIX+' Install model '+model_id+' request failed'); throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`); } return apiResult } - static async getModelSettings(modelId) { - throwIfModuleMissing() - // API is busy - inApiCall = true; - - try { - const url = new URL(getApiUrl()); - url.pathname = '/api/text-to-speech/coqui/coqui-api/get-model-settings'; - - const apiResult = await doExtrasFetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Cache-Control': 'no-cache' - }, - body: JSON.stringify({ - "model_id": modelId, - }) - }); - - if (!apiResult.ok) { - toastr.error(apiResult.statusText, DEBUG_PREFIX+' Get model setting request failed for '+modelId); - throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`); - } - - return apiResult - } - catch (error) { - console.debug(error); - } - finally { - inApiCall = false; - } - } - // Get speakers @@ -472,12 +543,12 @@ class CoquiTtsProvider { async generateTts(text, voiceId) { throwIfModuleMissing() const url = new URL(getApiUrl()); - url.pathname = '/api/text-to-speech/coqui/process-text'; + url.pathname = '/api/text-to-speech/coqui/generate-tts'; let language = "none" let speaker = "none" - const tokens = voiceId.replaceAll("]","").split("["); - const modelId = tokens[0] + const tokens = voiceId.replaceAll("]","").replaceAll("\"","").split("["); + const model_id = tokens[0] console.debug(DEBUG_PREFIX,"Preparing TTS request for",tokens) @@ -485,7 +556,7 @@ class CoquiTtsProvider { if (tokens.length > 1) { const option1 = tokens[1] - if (modelId.includes("multilingual")) + if (model_id.includes("multilingual")) language = option1 else speaker = option1 @@ -503,9 +574,9 @@ class CoquiTtsProvider { }, body: JSON.stringify({ "text": text, - "model_id": modelId, - "language": language, - "speaker": speaker + "model_id": model_id, + "language_id": parseInt(language), + "speaker_id": parseInt(speaker) }) }); @@ -517,7 +588,15 @@ class CoquiTtsProvider { return apiResult } - //------------------------------------------------------------------------------------ + // Dirty hack to say not implemented + async fetchTtsVoiceIds() { + return [{name:"Voice samples not implemented for coqui TTS yet, search for the model samples online", voice_id:"",lang:"",}] + } + + // Do nothing + previewTtsVoice(id) { + return + } async fetchTtsFromHistory(history_item_id) { return Promise.resolve(history_item_id); @@ -532,52 +611,8 @@ async function moduleWorker() { // Skip if module not loaded if (!modules.includes('coqui-tts')) return; - - // 1) Update characters list - //---------------------------- - let currentcharacters = new Set(); - for (const i of getContext().characters) { - currentcharacters.add(i.name); - } - currentcharacters = Array.from(currentcharacters) - - if (JSON.stringify(charactersList) !== JSON.stringify(currentcharacters)) { - charactersList = currentcharacters - - $('#coqui_character_select') - .find('option') - .remove() - .end() - .append('') - .val('none') - - for(const charName of charactersList) { - $("#coqui_character_select").append(new Option(charName,charName)); - } - - console.debug(DEBUG_PREFIX,"Updated character list to:", charactersList); - } - - // 2) Initialize TTS models lists - //--------------------------------- - if (Object.keys(coquiApiModels).length === 0) { - const result = await CoquiTtsProvider.getModelList("coqui-api"); - coquiApiModels = await result.json(); - console.debug(DEBUG_PREFIX,"initialized coqui-api model list to", coquiApiModels); - - $('#coqui_api_language') - .find('option') - .remove() - .end() - .append('') - .val('none'); - - for(let language in coquiApiModels) { - $("#coqui_api_language").append(new Option(languageLabels[language],language)); - console.log(DEBUG_PREFIX,"added language",language); - } - } + updateCharactersList(); } $(document).ready(function () { diff --git a/public/scripts/extensions/tts/coqui_api_models_settings.json b/public/scripts/extensions/tts/coqui_api_models_settings.json new file mode 100644 index 000000000..afd30a8a9 --- /dev/null +++ b/public/scripts/extensions/tts/coqui_api_models_settings.json @@ -0,0 +1,187 @@ +{ + "multilingual": { + "multi-dataset": { + "your_tts": { + "id": "tts_models/multilingual/multi-dataset/your_tts", + "languages": [ + "en", + "fr-fr", + "pt-br" + ], + "speakers": [ + "female-en-5", + "female-en-5\n", + "female-pt-4\n", + "male-en-2", + "male-en-2\n", + "male-pt-3\n" + ] + } + } + }, + "en": { + "ljspeech": { + "tacotron2-DDC": { + "id": "tts_models/en/ljspeech/tacotron2-DDC" + }, + "glow-tts": { + "id": "tts_models/en/ljspeech/glow-tts" + }, + "vits": { + "id": "tts_models/en/ljspeech/vits" + } + }, + "vctk": { + "vits": { + "id": "tts_models/en/vctk/vits", + "speakers": [ + "ED\n", + "p225", + "p226", + "p227", + "p228", + "p229", + "p230", + "p231", + "p232", + "p233", + "p234", + "p236", + "p237", + "p238", + "p239", + "p240", + "p241", + "p243", + "p244", + "p245", + "p246", + "p247", + "p248", + "p249", + "p250", + "p251", + "p252", + "p253", + "p254", + "p255", + "p256", + "p257", + "p258", + "p259", + "p260", + "p261", + "p262", + "p263", + "p264", + "p265", + "p266", + "p267", + "p268", + "p269", + "p270", + "p271", + "p272", + "p273", + "p274", + "p275", + "p276", + "p277", + "p278", + "p279", + "p280", + "p281", + "p282", + "p283", + "p284", + "p285", + "p286", + "p287", + "p288", + "p292", + "p293", + "p294", + "p295", + "p297", + "p298", + "p299", + "p300", + "p301", + "p302", + "p303", + "p304", + "p305", + "p306", + "p307", + "p308", + "p310", + "p311", + "p312", + "p313", + "p314", + "p316", + "p317", + "p318", + "p323", + "p326", + "p329", + "p330", + "p333", + "p334", + "p335", + "p336", + "p339", + "p340", + "p341", + "p343", + "p345", + "p347", + "p351", + "p360", + "p361", + "p362", + "p363", + "p364", + "p374", + "p376" + ] + } + }, + "jenny": { + "jenny": { + "id": "tts_models/en/jenny/jenny" + } + } + }, + "es": { + "mai": { + "tacotron2-DDC": { + "id": "tts_models/es/mai/tacotron2-DDC" + } + }, + "css10": { + "vits": { + "id": "tts_models/es/css10/vits" + } + } + }, + "fr": { + "mai": { + "tacotron2-DDC": { + "id": "tts_models/fr/mai/tacotron2-DDC" + } + }, + "css10": { + "vits": { + "id": "tts_models/fr/css10/vits" + } + } + }, + "ja": { + "kokoro": { + "tacotron2-DDC": { + "id": "tts_models/ja/kokoro/tacotron2-DDC" + } + } + } +} \ No newline at end of file diff --git a/public/scripts/extensions/tts/coquitts.js b/public/scripts/extensions/tts/coquitts.js deleted file mode 100644 index 14bcc07dd..000000000 --- a/public/scripts/extensions/tts/coquitts.js +++ /dev/null @@ -1,403 +0,0 @@ -import { eventSource, event_types } from "../../../script.js" -import { doExtrasFetch, getApiUrl, modules } from "../../extensions.js" - -export { CoquiTtsProvider } - -function throwIfModuleMissing() { - if (!modules.includes('coqui-tts')) { - toastr.error(`Coqui TTS module not loaded. Add coqui-tts to enable-modules and restart the Extras API.`) - throw new Error(`Coqui TTS module not loaded.`) - } -} - -class CoquiTtsProvider { - //########// - // Config // - //########// - - settings - voices = [] - separator = ' .. ' - - defaultSettings = { - voiceMap: {} - } - - - get settingsHtml() { - let html = ` -
-
- - -
-
- -
-
- -
-
- - -
-
- - -
-
- ` - return html - } - - onSettingsChange() { - } - - loadSettings(settings) { - // Pupulate Provider UI given input settings - if (Object.keys(settings).length == 0) { - console.info("Using default TTS Provider settings") - } - - const modelSelect = document.getElementById('coqui_model'); - const previewButton = document.getElementById('coqui_preview'); - previewButton.addEventListener('click', () => { - const selectedModel = modelSelect.value; - this.sampleTtsVoice(selectedModel); - });//add event listener to button - - previewButton.disabled = true; - previewButton.innerText = "Select Model"; - - // Only accept keys defined in defaultSettings - this.settings = this.defaultSettings - - for (const key in settings) { - if (key in this.settings) { - this.settings[key] = settings[key] - } else { - throw `Invalid setting passed to TTS Provider: ${key}` - } - } - - const textexample = document.getElementById('tts_voice_map'); - textexample.placeholder = 'Enter comma separated map of charName:ttsName[speakerID][langID]. Example: \nAqua:tts_models--en--ljspeech--glow-tts\model_file.pth,\nDarkness:tts_models--multilingual--multi-dataset--your_tts\model_file.pth[2][3]'; - - //Load models function - eventSource.on(event_types.EXTRAS_CONNECTED, () => { - this.getModels(); - }); - this.onttsCoquiHideButtons(); - console.info("Settings loaded") - } - - async onttsCoquiHideButtons() { - // Get references to the select element and the two input elements - const ttsProviderSelect = document.getElementById('tts_provider'); - const ttsVoicesInput = document.getElementById('tts_voices'); - const ttsPreviewInput = document.getElementById('tts_preview'); - - ttsProviderSelect.addEventListener('click', () => { - this.getModels(); - }); - - // Add an event listener to the 'change' event of the tts_provider select element - ttsProviderSelect.addEventListener('change', () => { - // Check if the selected value is 'Coqui' - if (ttsProviderSelect.value === 'Coqui') { - ttsVoicesInput.style.display = 'none'; // Hide the tts_voices input - ttsPreviewInput.style.display = ''; // Show the tts_preview input - } else { - ttsVoicesInput.style.display = ''; // Show the tts_voices input - ttsPreviewInput.style.display = 'none'; // Hide the tts_preview input - } - }); - } - - async onApplyClick() { - return - } - - async getLang() { - try { - const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/multlang`); - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - const voiceData = await response.json(); - - const languageSelect = document.getElementById('coqui_language'); - languageSelect.innerHTML = ''; // Clear existing options - - if (Object.keys(voiceData).length === 0) { - const option = document.createElement('option'); - option.value = 'none'; - option.textContent = 'None'; - languageSelect.appendChild(option); - } else { - for (const [key, value] of Object.entries(voiceData)) { - const option = document.createElement('option'); - option.value = key; - option.textContent = key + ": " + value; - languageSelect.appendChild(option); - } - } - } catch (error) { - //console.error('Error fetching voice data:', error); - - // Remove all options except "None" - const languageSelect = document.getElementById('coqui_language'); - languageSelect.innerHTML = ''; - - const option = document.createElement('option'); - option.value = 'none'; - option.textContent = 'None'; - languageSelect.appendChild(option); - } - } - - - async getSpeakers() { - try { - const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/multspeaker`); - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - const voiceData = await response.json(); - - const speakerSelect = document.getElementById('coqui_speaker'); - speakerSelect.innerHTML = ''; // Clear existing options - - if (Object.keys(voiceData).length === 0) { - const option = document.createElement('option'); - option.value = 'none'; - option.textContent = 'None'; - speakerSelect.appendChild(option); - } else { - for (const [index, name] of Object.entries(voiceData)) { - const option = document.createElement('option'); - option.value = index; - option.textContent = index + ": " + name; - speakerSelect.appendChild(option); - } - } - } catch (error) { - //console.error('Error fetching voice data:', error); - - // Remove all options except "None" - const speakerSelect = document.getElementById('coqui_speaker'); - speakerSelect.innerHTML = ''; - - const option = document.createElement('option'); - option.value = 'none'; - option.textContent = 'None'; - speakerSelect.appendChild(option); - } - } - - async getModels() { - try { - throwIfModuleMissing(); - const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/list`); - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - const voiceIds = await response.json(); - - const modelSelect = document.getElementById('coqui_model'); - if (voiceIds.length === 0) { - const option = document.createElement('option'); - option.value = 'none'; - option.textContent = 'Select Model'; - modelSelect.appendChild(option); - } else { - voiceIds.forEach(voiceId => { - const option = document.createElement('option'); - option.value = voiceId; - option.textContent = voiceId; - modelSelect.appendChild(option); - }); - } - - // Update provider endpoint on model selection change - modelSelect.addEventListener('change', () => { - const selectedModel = modelSelect.value; - this.LoadModel(selectedModel); - }); - } catch (error) { - console.error('Error fetching voice IDs:', error); - - // Add "None" option when the request fails or the response is empty - const modelSelect = document.getElementById('coqui_model'); - const option = document.createElement('option'); - option.value = 'none'; - option.textContent = 'None'; - modelSelect.appendChild(option); - } - } - - async LoadModel(selectedModel) { - const previewButton = document.getElementById('coqui_preview'); - previewButton.disabled = true; - previewButton.innerText = "Loading"; - try { - throwIfModuleMissing(); - const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/load?_model=${selectedModel}`); - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - this.getSpeakers(); - this.getLang(); - - const previewButton = document.getElementById('coqui_preview'); - previewButton.disabled = false; - previewButton.innerText = "Play"; - - } catch (error) { - console.error('Error updating provider endpoint:', error); - } - } - - async getVoice(voiceName) { - //tts_models--multilingual--multi-dataset--your_tts\model_file.pth[2][1] - //tts_models--en--ljspeech--glow-tts\model_file.pth - - let _voiceNameOrg = voiceName; // Store the original voiceName in a variable _voiceNameOrg - voiceName = voiceName.replace(/(\[\d+\])+$/, ''); // For example, converts 'model[2][1]' to 'model' - - this.voices = []; //reset for follow up runs - - if (this.voices.length === 0) { this.voices = await this.fetchCheckMap(); } - - // Search for a voice object in the 'this.voices' array where the 'name' property matches the provided 'voiceName' - - //const match = this.voices.find((CoquiVoice) => CoquiVoice.name === voiceName); - const match = this.voices.find((CoquiVoice) => CoquiVoice.name === voiceName); - - // If no match is found, throw an error indicating that the TTS Voice name was not found - if (!match) { - throw new Error(`TTS Voice name ${voiceName} not found`); - } else { - match.name = _voiceNameOrg; - match.voice_id = _voiceNameOrg; - } - // Return the matched voice object (with the 'name' property updated if a match was found) - return match; - } - - async fetchCheckMap() { - const endpoint = `${getApiUrl()}/api/coqui-tts/checkmap`; - const response = await doExtrasFetch(endpoint); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${await response.json()}`); - } - const voiceData = await response.json(); - const voices = voiceData.map((voice) => ({ - id: voice.name, - name: voice.id, // this is the issue!!! - voice_id: voice.id, // this is the issue!!! - //preview_url: false, - lang: voice.lang, - })); - return voices; - } - - async fetchTtsVoiceIds() { - throwIfModuleMissing(); - const endpoint = `${getApiUrl()}/api/coqui-tts/speaker_id`; - const response = await doExtrasFetch(endpoint); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${await response.json()}`); - } - const voiceData = await response.json(); - const voices = voiceData.map((voice) => ({ - id: voice.name, - name: voice.id, //add filename here - voice_id: voice.id, - //preview_url: false, - //preview_url: `${getApiUrl()}/api/coqui-tts/download?model=${voice.id}`, - //http://localhost:5100/api/coqui-tts/speaker_id/tts_models/en/ljspeech/speedy-speech - lang: voice.lang, - })); - return voices; - } - - sampleTtsVoice(voiceId) { - // Get the selected values of speaker and language - const speakerSelect = document.getElementById('coqui_speaker'); - const languageSelect = document.getElementById('coqui_language'); - const selectedSpeaker = speakerSelect.value; - const selectedLanguage = languageSelect.value; - - // Construct the URL with the selected values - const url = `${getApiUrl()}/api/coqui-tts/tts?text=The%20Quick%20Brown%20Fox%20Jumps%20Over%20the%20Lazy%20Dog.&speaker_id=${voiceId}&style_wav=&language_id=${selectedLanguage}&mspker=${selectedSpeaker}`; - - doExtrasFetch(url) - .then(response => response.blob()) - .then(blob => { - const audioUrl = URL.createObjectURL(blob); - // Play the audio - const audio = new Audio(audioUrl); - audio.play(); - }) - .catch(error => { - console.error('Error performing TTS request:', error); - }); - } - - previewTtsVoice(voiceId) { //button on avail voices - throwIfModuleMissing(); - const url = `${getApiUrl()}/api/coqui-tts/download?model=${voiceId}`; - - doExtrasFetch(url) - .then(response => response.text()) // Expecting a text response - .then(responseText => { - const isResponseTrue = responseText.trim().toLowerCase() === 'true'; - - if (isResponseTrue) { - console.log("Downloading Model") //if true - } else { - console.error('Already Installed'); //if false - } - }) - .catch(error => { - console.error('Error performing download:', error); - }); - } - - - async generateTts(text, voiceId) { - const response = await this.fetchTtsGeneration(text, voiceId) - return response - } - - async fetchTtsGeneration(inputText, voiceId) { - throwIfModuleMissing(); - console.info(`Generating new TTS for voice_id ${voiceId}`); - const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/tts?text=${encodeURIComponent(inputText)}&speaker_id=${voiceId}`); - if (!response.ok) { - toastr.error(response.statusText, 'TTS Generation Failed'); - throw new Error(`HTTP ${response.status}: ${await response.text()}`); - } - if (!response.ok) { - toastr.error(response.statusText, 'TTS Generation Failed'); - throw new Error(`HTTP ${response.status}: ${await response.text()}`); - } - return response - } - - async fetchTtsFromHistory(history_item_id) { - return Promise.resolve(history_item_id); - } - -} diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index bb4e6418c..13c8be308 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -4,8 +4,7 @@ import { escapeRegex, getStringHash } from '../../utils.js' import { EdgeTtsProvider } from './edge.js' import { ElevenLabsTtsProvider } from './elevenlabs.js' import { SileroTtsProvider } from './silerotts.js' -//import { CoquiTtsProvider } from './coquitts.js' -import { CoquiTtsProvider } from './coqui.js' // TODO: rename once done +import { CoquiTtsProvider } from './coqui.js' import { SystemTtsProvider } from './system.js' import { NovelTtsProvider } from './novel.js' import { power_user } from '../../power-user.js' From 5816d2d6a678ac2c1a8f58791870dcc59cc3157b Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Mon, 14 Aug 2023 05:03:42 +0200 Subject: [PATCH 4/6] Correct typo in tts index file that cause tts provider settings to be destroyed. --- public/scripts/extensions/tts/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 6eeb1cf84..814c38646 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -650,8 +650,9 @@ function onTtsProviderSettingsInput() { ttsProvider.onSettingsChange() // Persist changes to SillyTavern tts extension settings - - extension_settings.tts[ttsProviderName] = ttsProvider.setttings + console.debug(ttsProvider) // DBG + console.debug(ttsProvider.setttings) // DBG + extension_settings.tts[ttsProviderName] = ttsProvider.settings saveSettingsDebounced() console.info(`Saved settings ${ttsProviderName} ${JSON.stringify(ttsProvider.settings)}`) } From 213099dd6d62b4dc27fe39491d38f0d28ff30d34 Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Mon, 14 Aug 2023 05:05:10 +0200 Subject: [PATCH 5/6] removed debug messages --- public/scripts/extensions/tts/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 814c38646..37bfd15b1 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -650,8 +650,6 @@ function onTtsProviderSettingsInput() { ttsProvider.onSettingsChange() // Persist changes to SillyTavern tts extension settings - console.debug(ttsProvider) // DBG - console.debug(ttsProvider.setttings) // DBG extension_settings.tts[ttsProviderName] = ttsProvider.settings saveSettingsDebounced() console.info(`Saved settings ${ttsProviderName} ${JSON.stringify(ttsProvider.settings)}`) From f97cc5694ddda8f754b876ea1a43c471c889f31c Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Mon, 14 Aug 2023 05:14:21 +0200 Subject: [PATCH 6/6] refresh character even when module is not detected --- public/scripts/extensions/tts/coqui.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/public/scripts/extensions/tts/coqui.js b/public/scripts/extensions/tts/coqui.js index f1222d568..325668cca 100644 --- a/public/scripts/extensions/tts/coqui.js +++ b/public/scripts/extensions/tts/coqui.js @@ -608,10 +608,6 @@ class CoquiTtsProvider { //#############################// async function moduleWorker() { - // Skip if module not loaded - if (!modules.includes('coqui-tts')) - return; - updateCharactersList(); }