Merge branch 'staging' into xtts-more-controls

This commit is contained in:
Cohee
2024-01-03 21:15:56 +02:00
276 changed files with 104084 additions and 17625 deletions

View File

@@ -4,17 +4,15 @@ TODO:
- Delete useless call
*/
import { doExtrasFetch, extension_settings, getApiUrl, getContext, modules, ModuleWorkerWrapper } from "../../extensions.js"
import { callPopup } from "../../../script.js"
import { initVoiceMap } from "./index.js"
import { doExtrasFetch, extension_settings, getApiUrl, modules } from '../../extensions.js';
import { callPopup } from '../../../script.js';
import { initVoiceMap } from './index.js';
export { CoquiTtsProvider }
export { CoquiTtsProvider };
const DEBUG_PREFIX = "<Coqui TTS module> ";
const UPDATE_INTERVAL = 1000;
const DEBUG_PREFIX = '<Coqui TTS module> ';
let inApiCall = false;
let voiceIdList = []; // Updated with module worker
let coquiApiModels = {}; // Initialized only once
let coquiApiModelsFull = {}; // Initialized only once
let coquiLocalModels = []; // Initialized only once
@@ -35,24 +33,24 @@ coquiApiModels format [language][dataset][name]:coqui-api-model-id, example:
}
*/
const languageLabels = {
"multilingual": "Multilingual",
"en": "English",
"fr": "French",
"es": "Spanish",
"ja": "Japanese"
}
'multilingual': 'Multilingual',
'en': 'English',
'fr': 'French',
'es': 'Spanish',
'ja': 'Japanese',
};
function throwIfModuleMissing() {
if (!modules.includes('coqui-tts')) {
const message = `Coqui TTS module not loaded. Add coqui-tts to enable-modules and restart the Extras API.`
const message = 'Coqui TTS module not loaded. Add coqui-tts to enable-modules and restart the Extras API.';
// toastr.error(message, { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
throw new Error(DEBUG_PREFIX, message);
}
}
function resetModelSettings() {
$("#coqui_api_model_settings_language").val("none");
$("#coqui_api_model_settings_speaker").val("none");
$('#coqui_api_model_settings_language').val('none');
$('#coqui_api_model_settings_speaker').val('none');
}
class CoquiTtsProvider {
@@ -60,14 +58,14 @@ class CoquiTtsProvider {
// Extension UI and Settings //
//#############################//
settings
settings;
defaultSettings = {
voiceMap: {},
customVoices: {},
voiceIds: [],
voiceMapDict: {}
}
voiceMapDict: {},
};
get settingsHtml() {
let html = `
@@ -121,48 +119,48 @@ class CoquiTtsProvider {
</div>
</div>
</div>
`
return html
`;
return html;
}
async loadSettings(settings) {
// Only accept keys defined in defaultSettings
this.settings = this.defaultSettings
this.settings = this.defaultSettings;
for (const key in settings) {
if (key in this.settings) {
this.settings[key] = settings[key]
this.settings[key] = settings[key];
} else {
throw DEBUG_PREFIX + `Invalid setting passed to extension: ${key}`
throw DEBUG_PREFIX + `Invalid setting passed to extension: ${key}`;
}
}
await initLocalModels();
this.updateCustomVoices(); // Overide any manual modification
$("#coqui_api_model_div").hide();
$("#coqui_local_model_div").hide();
$('#coqui_api_model_div').hide();
$('#coqui_local_model_div').hide();
$("#coqui_api_language").show();
$("#coqui_api_model_name").hide();
$("#coqui_api_model_settings").hide();
$("#coqui_api_model_install_status").hide();
$("#coqui_api_model_install_button").hide();
$('#coqui_api_language').show();
$('#coqui_api_model_name').hide();
$('#coqui_api_model_settings').hide();
$('#coqui_api_model_install_status').hide();
$('#coqui_api_model_install_button').hide();
let that = this
$("#coqui_model_origin").on("change", function () { that.onModelOriginChange() });
$("#coqui_api_language").on("change", function () { that.onModelLanguageChange() });
$("#coqui_api_model_name").on("change", function () { that.onModelNameChange() });
let that = this;
$('#coqui_model_origin').on('change', function () { that.onModelOriginChange(); });
$('#coqui_api_language').on('change', function () { that.onModelLanguageChange(); });
$('#coqui_api_model_name').on('change', function () { that.onModelNameChange(); });
$("#coqui_remove_voiceId_mapping").on("click", function () { that.onRemoveClick() });
$("#coqui_add_voiceId_mapping").on("click", function () { that.onAddClick() });
$('#coqui_remove_voiceId_mapping').on('click', function () { that.onRemoveClick(); });
$('#coqui_add_voiceId_mapping').on('click', function () { that.onAddClick(); });
// Load coqui-api settings from json file
await 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);
await 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')
@@ -175,14 +173,14 @@ class CoquiTtsProvider {
$("#coqui_api_language").append(new Option(languageLabels[language],language));
console.log(DEBUG_PREFIX,"added language",language);
}*/
});
});
// Load coqui-api FULL settings from json file
await fetch("/scripts/extensions/tts/coqui_api_models_settings_full.json")
.then(response => response.json())
.then(json => {
coquiApiModelsFull = json;
console.debug(DEBUG_PREFIX,"initialized coqui-api full model list to", coquiApiModelsFull);
await fetch('/scripts/extensions/tts/coqui_api_models_settings_full.json')
.then(response => response.json())
.then(json => {
coquiApiModelsFull = json;
console.debug(DEBUG_PREFIX,'initialized coqui-api full model list to', coquiApiModelsFull);
/*
$('#coqui_api_full_language')
.find('option')
@@ -195,13 +193,13 @@ class CoquiTtsProvider {
$("#coqui_api_full_language").append(new Option(languageLabels[language],language));
console.log(DEBUG_PREFIX,"added language",language);
}*/
});
});
}
// Perform a simple readiness check by trying to fetch voiceIds
async checkReady(){
throwIfModuleMissing()
await this.fetchTtsVoiceObjects()
throwIfModuleMissing();
await this.fetchTtsVoiceObjects();
}
updateCustomVoices() {
@@ -209,37 +207,37 @@ class CoquiTtsProvider {
this.settings.customVoices = {};
for (let voiceName in this.settings.voiceMapDict) {
const voiceId = this.settings.voiceMapDict[voiceName];
this.settings.customVoices[voiceName] = voiceId["model_id"];
this.settings.customVoices[voiceName] = voiceId['model_id'];
if (voiceId["model_language"] != null)
this.settings.customVoices[voiceName] += "[" + voiceId["model_language"] + "]";
if (voiceId['model_language'] != null)
this.settings.customVoices[voiceName] += '[' + voiceId['model_language'] + ']';
if (voiceId["model_speaker"] != null)
this.settings.customVoices[voiceName] += "[" + voiceId["model_speaker"] + "]";
if (voiceId['model_speaker'] != null)
this.settings.customVoices[voiceName] += '[' + voiceId['model_speaker'] + ']';
}
// Update UI select list with voices
$("#coqui_voicename_select").empty()
$('#coqui_voicename_select').empty();
$('#coqui_voicename_select')
.find('option')
.remove()
.end()
.append('<option value="none">Select Voice</option>')
.val('none')
.val('none');
for (const voiceName in this.settings.voiceMapDict) {
$("#coqui_voicename_select").append(new Option(voiceName, voiceName));
$('#coqui_voicename_select').append(new Option(voiceName, voiceName));
}
this.onSettingsChange()
this.onSettingsChange();
}
onSettingsChange() {
console.debug(DEBUG_PREFIX, "Settings changes", this.settings);
console.debug(DEBUG_PREFIX, 'Settings changes', this.settings);
extension_settings.tts.Coqui = this.settings;
}
async onRefreshClick() {
this.checkReady()
this.checkReady();
}
async onAddClick() {
@@ -248,136 +246,136 @@ class CoquiTtsProvider {
}
// Ask user for voiceId name to save voice
const voiceName = await callPopup('<h3>Name of Coqui voice to add to voice select dropdown:</h3>', 'input')
const voiceName = await callPopup('<h3>Name of Coqui voice to add to voice select dropdown:</h3>', 'input');
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();
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 (!voiceName) {
toastr.error(`Voice name empty, please enter one.`, DEBUG_PREFIX + " voice mapping voice name", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
toastr.error('Voice name empty, please enter one.', DEBUG_PREFIX + ' voice mapping voice name', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
this.updateCustomVoices(); // 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 });
if (model_origin == 'none') {
toastr.error('Origin not selected, please select one.', DEBUG_PREFIX + ' voice mapping origin', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
this.updateCustomVoices(); // Overide any manual modification
return;
}
if (model_origin == "local") {
const model_id = $("#coqui_local_model_name").val();
if (model_origin == 'local') {
const model_id = $('#coqui_local_model_name').val();
if (model_name == "none") {
toastr.error(`Model not selected, please select one.`, DEBUG_PREFIX + " voice mapping model", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
if (model_name == 'none') {
toastr.error('Model not selected, please select one.', DEBUG_PREFIX + ' voice mapping model', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
this.updateCustomVoices(); // Overide any manual modification
return;
}
this.settings.voiceMapDict[voiceName] = { model_type: "local", model_id: "local/" + model_id };
console.debug(DEBUG_PREFIX, "Registered new voice map: ", voiceName, ":", this.settings.voiceMapDict[voiceName]);
this.settings.voiceMapDict[voiceName] = { model_type: 'local', model_id: 'local/' + model_id };
console.debug(DEBUG_PREFIX, 'Registered new voice map: ', voiceName, ':', this.settings.voiceMapDict[voiceName]);
this.updateCustomVoices(); // 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 });
if (model_language == 'none') {
toastr.error('Language not selected, please select one.', DEBUG_PREFIX + ' voice mapping language', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
this.updateCustomVoices(); // 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 });
if (model_name == 'none') {
toastr.error('Model not selected, please select one.', DEBUG_PREFIX + ' voice mapping model', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
this.updateCustomVoices(); // Overide any manual modification
return;
}
if (model_setting_language == "none")
if (model_setting_language == 'none')
model_setting_language = null;
if (model_setting_speaker == "none")
if (model_setting_speaker == 'none')
model_setting_speaker = null;
const tokens = $('#coqui_api_model_name').val().split("/");
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
const model_id = 'tts_models/' + model_language + '/' + model_dataset + '/' + model_label;
let modelDict = coquiApiModels
if (model_origin == "coqui-api-full")
modelDict = coquiApiModelsFull
let modelDict = coquiApiModels;
if (model_origin == 'coqui-api-full')
modelDict = coquiApiModelsFull;
if (model_setting_language == null & "languages" in modelDict[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 });
if (model_setting_language == null & 'languages' in modelDict[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 modelDict[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 });
if (model_setting_speaker == null & 'speakers' in modelDict[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 custom voices: ", this.settings.customVoices);
console.debug(DEBUG_PREFIX, 'Current custom voices: ', this.settings.customVoices);
this.settings.voiceMapDict[voiceName] = { model_type: "coqui-api", model_id: model_id, model_language: model_setting_language, model_speaker: model_setting_speaker };
this.settings.voiceMapDict[voiceName] = { model_type: 'coqui-api', model_id: model_id, model_language: model_setting_language, model_speaker: model_setting_speaker };
console.debug(DEBUG_PREFIX, "Registered new voice map: ", voiceName, ":", this.settings.voiceMapDict[voiceName]);
console.debug(DEBUG_PREFIX, 'Registered new voice map: ', voiceName, ':', this.settings.voiceMapDict[voiceName]);
this.updateCustomVoices();
initVoiceMap() // Update TTS extension voiceMap
initVoiceMap(); // Update TTS extension voiceMap
let successMsg = voiceName + ":" + model_id;
let successMsg = voiceName + ':' + model_id;
if (model_setting_language != null)
successMsg += "[" + model_setting_language + "]";
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 });
successMsg += '[' + model_setting_speaker + ']';
toastr.info(successMsg, DEBUG_PREFIX + ' voice map updated', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
return
return;
}
async getVoice(voiceName) {
let match = await this.fetchTtsVoiceObjects()
let match = await this.fetchTtsVoiceObjects();
match = match.filter(
voice => voice.name == voiceName
)[0]
voice => voice.name == voiceName,
)[0];
if (!match) {
throw `TTS Voice name ${voiceName} not found in CoquiTTS Provider voice list`
throw `TTS Voice name ${voiceName} not found in CoquiTTS Provider voice list`;
}
return match;
}
async onRemoveClick() {
const voiceName = $("#coqui_voicename_select").val();
const voiceName = $('#coqui_voicename_select').val();
if (voiceName === "none") {
toastr.error(`Voice not selected, please select one.`, DEBUG_PREFIX + " voice mapping voiceId", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
if (voiceName === 'none') {
toastr.error('Voice not selected, please select one.', DEBUG_PREFIX + ' voice mapping voiceId', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
return;
}
// Todo erase from voicemap
delete (this.settings.voiceMapDict[voiceName]);
this.updateCustomVoices();
initVoiceMap() // Update TTS extension voiceMap
initVoiceMap(); // Update TTS extension voiceMap
}
async onModelOriginChange() {
throwIfModuleMissing()
throwIfModuleMissing();
resetModelSettings();
const model_origin = $('#coqui_model_origin').val();
if (model_origin == "none") {
$("#coqui_local_model_div").hide();
$("#coqui_api_model_div").hide();
if (model_origin == 'none') {
$('#coqui_local_model_div').hide();
$('#coqui_api_model_div').hide();
}
// show coqui model selected list (SAFE)
if (model_origin == "coqui-api") {
$("#coqui_local_model_div").hide();
if (model_origin == 'coqui-api') {
$('#coqui_local_model_div').hide();
$('#coqui_api_language')
.find('option')
@@ -387,19 +385,19 @@ class CoquiTtsProvider {
.val('none');
for(let language in coquiApiModels) {
let languageLabel = language
let languageLabel = language;
if (language in languageLabels)
languageLabel = languageLabels[language]
$("#coqui_api_language").append(new Option(languageLabel,language));
console.log(DEBUG_PREFIX,"added language",languageLabel,"(",language,")");
languageLabel = languageLabels[language];
$('#coqui_api_language').append(new Option(languageLabel,language));
console.log(DEBUG_PREFIX,'added language',languageLabel,'(',language,')');
}
$("#coqui_api_model_div").show();
$('#coqui_api_model_div').show();
}
// show coqui model full list (UNSAFE)
if (model_origin == "coqui-api-full") {
$("#coqui_local_model_div").hide();
if (model_origin == 'coqui-api-full') {
$('#coqui_local_model_div').hide();
$('#coqui_api_language')
.find('option')
@@ -409,38 +407,38 @@ class CoquiTtsProvider {
.val('none');
for(let language in coquiApiModelsFull) {
let languageLabel = language
let languageLabel = language;
if (language in languageLabels)
languageLabel = languageLabels[language]
$("#coqui_api_language").append(new Option(languageLabel,language));
console.log(DEBUG_PREFIX,"added language",languageLabel,"(",language,")");
languageLabel = languageLabels[language];
$('#coqui_api_language').append(new Option(languageLabel,language));
console.log(DEBUG_PREFIX,'added language',languageLabel,'(',language,')');
}
$("#coqui_api_model_div").show();
$('#coqui_api_model_div').show();
}
// show local model list
if (model_origin == "local") {
$("#coqui_api_model_div").hide();
$("#coqui_local_model_div").show();
if (model_origin == 'local') {
$('#coqui_api_model_div').hide();
$('#coqui_local_model_div').show();
}
}
async onModelLanguageChange() {
throwIfModuleMissing();
resetModelSettings();
$("#coqui_api_model_settings").hide();
$('#coqui_api_model_settings').hide();
const model_origin = $('#coqui_model_origin').val();
const model_language = $('#coqui_api_language').val();
console.debug(model_language);
if (model_language == "none") {
$("#coqui_api_model_name").hide();
if (model_language == 'none') {
$('#coqui_api_model_name').hide();
return;
}
$("#coqui_api_model_name").show();
$('#coqui_api_model_name').show();
$('#coqui_api_model_name')
.find('option')
.remove()
@@ -448,46 +446,46 @@ class CoquiTtsProvider {
.append('<option value="none">Select model</option>')
.val('none');
let modelDict = coquiApiModels
if (model_origin == "coqui-api-full")
modelDict = coquiApiModelsFull
let modelDict = coquiApiModels;
if (model_origin == 'coqui-api-full')
modelDict = coquiApiModelsFull;
for(let model_dataset in modelDict[model_language])
for(let model_name in modelDict[model_language][model_dataset]) {
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));
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));
}
}
async onModelNameChange() {
throwIfModuleMissing();
resetModelSettings();
$("#coqui_api_model_settings").hide();
$('#coqui_api_model_settings').hide();
const model_origin = $('#coqui_model_origin').val();
// No model selected
if ($('#coqui_api_model_name').val() == "none") {
$("#coqui_api_model_install_button").off('click');
$("#coqui_api_model_install_button").hide();
if ($('#coqui_api_model_name').val() == 'none') {
$('#coqui_api_model_install_button').off('click');
$('#coqui_api_model_install_button').hide();
return;
}
// Get languages and speakers options
const model_language = $('#coqui_api_language').val();
const tokens = $('#coqui_api_model_name').val().split("/");
const tokens = $('#coqui_api_model_name').val().split('/');
const model_dataset = tokens[0];
const model_name = tokens[1];
let modelDict = coquiApiModels
if (model_origin == "coqui-api-full")
modelDict = coquiApiModelsFull
let modelDict = coquiApiModels;
if (model_origin == 'coqui-api-full')
modelDict = coquiApiModelsFull;
const model_settings = modelDict[model_language][model_dataset][model_name]
const model_settings = modelDict[model_language][model_dataset][model_name];
if ("languages" in model_settings) {
$("#coqui_api_model_settings").show();
$("#coqui_api_model_settings_language").show();
if ('languages' in model_settings) {
$('#coqui_api_model_settings').show();
$('#coqui_api_model_settings_language').show();
$('#coqui_api_model_settings_language')
.find('option')
.remove()
@@ -495,18 +493,18 @@ class CoquiTtsProvider {
.append('<option value="none">Select language</option>')
.val('none');
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));
for (let 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();
$('#coqui_api_model_settings_language').hide();
}
if ("speakers" in model_settings) {
$("#coqui_api_model_settings").show();
$("#coqui_api_model_settings_speaker").show();
if ('speakers' in model_settings) {
$('#coqui_api_model_settings').show();
$('#coqui_api_model_settings_speaker').show();
$('#coqui_api_model_settings_speaker')
.find('option')
.remove()
@@ -514,75 +512,75 @@ class CoquiTtsProvider {
.append('<option value="none">Select speaker</option>')
.val('none');
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));
for (let 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_settings_speaker').hide();
}
$("#coqui_api_model_install_status").text("Requesting model to extras server...");
$("#coqui_api_model_install_status").show();
$('#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 = modelDict[model_language][model_dataset][model_name]["id"]
console.debug(DEBUG_PREFIX,"Check if model is already installed",model_id);
const model_id = modelDict[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"];
const model_state = result['model_state'];
console.debug(DEBUG_PREFIX, " Model state:", 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();
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"
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)
$('#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");
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 () {
$('#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();
$('#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);
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();
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();
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 });
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();
$('#coqui_api_model_install_button').show();
return;
}
@@ -597,7 +595,7 @@ class CoquiTtsProvider {
Check model installation state, return one of ["installed", "corrupted", "absent"]
*/
static async checkmodel_state(model_id) {
throwIfModuleMissing()
throwIfModuleMissing();
const url = new URL(getApiUrl());
url.pathname = '/api/text-to-speech/coqui/coqui-api/check-model-state';
@@ -605,11 +603,11 @@ class CoquiTtsProvider {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
'Cache-Control': 'no-cache',
},
body: JSON.stringify({
"model_id": model_id,
})
'model_id': model_id,
}),
});
if (!apiResult.ok) {
@@ -617,11 +615,11 @@ class CoquiTtsProvider {
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
return apiResult
return apiResult;
}
static async installModel(model_id, action) {
throwIfModuleMissing()
throwIfModuleMissing();
const url = new URL(getApiUrl());
url.pathname = '/api/text-to-speech/coqui/coqui-api/install-model';
@@ -629,12 +627,12 @@ class CoquiTtsProvider {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
'Cache-Control': 'no-cache',
},
body: JSON.stringify({
"model_id": model_id,
"action": action
})
'model_id': model_id,
'action': action,
}),
});
if (!apiResult.ok) {
@@ -642,14 +640,14 @@ class CoquiTtsProvider {
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
return apiResult
return apiResult;
}
/*
Retrieve user custom models
*/
static async getLocalModelList() {
throwIfModuleMissing()
throwIfModuleMissing();
const url = new URL(getApiUrl());
url.pathname = '/api/text-to-speech/coqui/local/get-models';
@@ -657,20 +655,20 @@ class CoquiTtsProvider {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
'Cache-Control': 'no-cache',
},
body: JSON.stringify({
"model_id": "model_id",
"action": "action"
})
})
'model_id': 'model_id',
'action': 'action',
}),
});
if (!apiResult.ok) {
toastr.error(apiResult.statusText, DEBUG_PREFIX + ' Get local model list request failed');
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
return apiResult
return apiResult;
}
@@ -679,27 +677,27 @@ class CoquiTtsProvider {
// tts_models/en/ljspeech/glow-tts
// ts_models/ja/kokoro/tacotron2-DDC
async generateTts(text, voiceId) {
throwIfModuleMissing()
voiceId = this.settings.customVoices[voiceId]
throwIfModuleMissing();
voiceId = this.settings.customVoices[voiceId];
const url = new URL(getApiUrl());
url.pathname = '/api/text-to-speech/coqui/generate-tts';
let language = "none"
let speaker = "none"
const tokens = voiceId.replaceAll("]", "").replaceAll("\"", "").split("[");
const model_id = tokens[0]
let language = 'none';
let speaker = 'none';
const tokens = voiceId.replaceAll(']', '').replaceAll('"', '').split('[');
const model_id = tokens[0];
console.debug(DEBUG_PREFIX, "Preparing TTS request for", tokens)
console.debug(DEBUG_PREFIX, 'Preparing TTS request for', tokens);
// First option
if (tokens.length > 1) {
const option1 = tokens[1]
const option1 = tokens[1];
if (model_id.includes("multilingual"))
language = option1
if (model_id.includes('multilingual'))
language = option1;
else
speaker = option1
speaker = option1;
}
// Second option
@@ -710,14 +708,14 @@ class CoquiTtsProvider {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
'Cache-Control': 'no-cache',
},
body: JSON.stringify({
"text": text,
"model_id": model_id,
"language_id": parseInt(language),
"speaker_id": parseInt(speaker)
})
'text': text,
'model_id': model_id,
'language_id': parseInt(language),
'speaker_id': parseInt(speaker),
}),
});
if (!apiResult.ok) {
@@ -725,7 +723,7 @@ class CoquiTtsProvider {
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
return apiResult
return apiResult;
}
// Dirty hack to say not implemented
@@ -733,12 +731,12 @@ class CoquiTtsProvider {
const voiceIds = Object
.keys(this.settings.voiceMapDict)
.map(voice => ({ name: voice, voice_id: voice, preview_url: false }));
return voiceIds
return voiceIds;
}
// Do nothing
previewTtsVoice(id) {
return
return;
}
async fetchTtsFromHistory(history_item_id) {
@@ -748,16 +746,16 @@ class CoquiTtsProvider {
async function initLocalModels() {
if (!modules.includes('coqui-tts'))
return
return;
// Initialized local model once
if (!coquiLocalModelsReceived) {
let result = await CoquiTtsProvider.getLocalModelList();
result = await result.json();
coquiLocalModels = result["models_list"];
coquiLocalModels = result['models_list'];
$("#coqui_local_model_name").show();
$('#coqui_local_model_name').show();
$('#coqui_local_model_name')
.find('option')
.remove()
@@ -766,7 +764,7 @@ async function initLocalModels() {
.val('none');
for (const model_dataset of coquiLocalModels)
$("#coqui_local_model_name").append(new Option(model_dataset, model_dataset));
$('#coqui_local_model_name').append(new Option(model_dataset, model_dataset));
coquiLocalModelsReceived = true;
}

View File

@@ -1,73 +1,73 @@
import { getRequestHeaders } from "../../../script.js"
import { getApiUrl } from "../../extensions.js"
import { doExtrasFetch, modules } from "../../extensions.js"
import { getPreviewString } from "./index.js"
import { saveTtsProviderSettings } from "./index.js"
import { getRequestHeaders } from '../../../script.js';
import { getApiUrl } from '../../extensions.js';
import { doExtrasFetch, modules } from '../../extensions.js';
import { getPreviewString } from './index.js';
import { saveTtsProviderSettings } from './index.js';
export { EdgeTtsProvider }
export { EdgeTtsProvider };
class EdgeTtsProvider {
//########//
// Config //
//########//
settings
voices = []
separator = ' . '
audioElement = document.createElement('audio')
settings;
voices = [];
separator = ' . ';
audioElement = document.createElement('audio');
defaultSettings = {
voiceMap: {},
rate: 0,
}
};
get settingsHtml() {
let html = `Microsoft Edge TTS Provider<br>
<label for="edge_tts_rate">Rate: <span id="edge_tts_rate_output"></span></label>
<input id="edge_tts_rate" type="range" value="${this.defaultSettings.rate}" min="-100" max="100" step="1" />`
return html
<input id="edge_tts_rate" type="range" value="${this.defaultSettings.rate}" min="-100" max="100" step="1" />`;
return html;
}
onSettingsChange() {
this.settings.rate = Number($('#edge_tts_rate').val());
$('#edge_tts_rate_output').text(this.settings.rate);
saveTtsProviderSettings()
saveTtsProviderSettings();
}
async loadSettings(settings) {
// Pupulate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.info("Using default TTS Provider settings")
console.info('Using default TTS Provider settings');
}
// Only accept keys defined in defaultSettings
this.settings = this.defaultSettings
this.settings = this.defaultSettings;
for (const key in settings) {
if (key in this.settings) {
this.settings[key] = settings[key]
this.settings[key] = settings[key];
} else {
throw `Invalid setting passed to TTS Provider: ${key}`
throw `Invalid setting passed to TTS Provider: ${key}`;
}
}
$('#edge_tts_rate').val(this.settings.rate || 0);
$('#edge_tts_rate_output').text(this.settings.rate || 0);
$('#edge_tts_rate').on("input", () => {this.onSettingsChange()})
await this.checkReady()
$('#edge_tts_rate').on('input', () => {this.onSettingsChange();});
await this.checkReady();
console.debug("EdgeTTS: Settings loaded")
console.debug('EdgeTTS: Settings loaded');
}
// Perform a simple readiness check by trying to fetch voiceIds
async checkReady(){
throwIfModuleMissing()
await this.fetchTtsVoiceObjects()
throwIfModuleMissing();
await this.fetchTtsVoiceObjects();
}
async onRefreshClick() {
return
return;
}
//#################//
@@ -76,39 +76,39 @@ class EdgeTtsProvider {
async getVoice(voiceName) {
if (this.voices.length == 0) {
this.voices = await this.fetchTtsVoiceObjects()
this.voices = await this.fetchTtsVoiceObjects();
}
const match = this.voices.filter(
voice => voice.name == voiceName
)[0]
voice => voice.name == voiceName,
)[0];
if (!match) {
throw `TTS Voice name ${voiceName} not found`
throw `TTS Voice name ${voiceName} not found`;
}
return match
return match;
}
async generateTts(text, voiceId) {
const response = await this.fetchTtsGeneration(text, voiceId)
return response
const response = await this.fetchTtsGeneration(text, voiceId);
return response;
}
//###########//
// API CALLS //
//###########//
async fetchTtsVoiceObjects() {
throwIfModuleMissing()
throwIfModuleMissing();
const url = new URL(getApiUrl());
url.pathname = `/api/edge-tts/list`
const response = await doExtrasFetch(url)
url.pathname = '/api/edge-tts/list';
const response = await doExtrasFetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`)
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
let responseJson = await response.json()
let responseJson = await response.json();
responseJson = responseJson
.sort((a, b) => a.Locale.localeCompare(b.Locale) || a.ShortName.localeCompare(b.ShortName))
.map(x => ({ name: x.ShortName, voice_id: x.ShortName, preview_url: false, lang: x.Locale }));
return responseJson
return responseJson;
}
@@ -117,9 +117,9 @@ class EdgeTtsProvider {
this.audioElement.currentTime = 0;
const voice = await this.getVoice(id);
const text = getPreviewString(voice.lang);
const response = await this.fetchTtsGeneration(text, id)
const response = await this.fetchTtsGeneration(text, id);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`)
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const audio = await response.blob();
@@ -129,34 +129,34 @@ class EdgeTtsProvider {
}
async fetchTtsGeneration(inputText, voiceId) {
throwIfModuleMissing()
throwIfModuleMissing();
console.info(`Generating new TTS for voice_id ${voiceId}`)
console.info(`Generating new TTS for voice_id ${voiceId}`);
const url = new URL(getApiUrl());
url.pathname = `/api/edge-tts/generate`;
url.pathname = '/api/edge-tts/generate';
const response = await doExtrasFetch(url,
{
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({
"text": inputText,
"voice": voiceId,
"rate": Number(this.settings.rate),
})
}
)
'text': inputText,
'voice': voiceId,
'rate': Number(this.settings.rate),
}),
},
);
if (!response.ok) {
toastr.error(response.statusText, 'TTS Generation Failed');
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response
return response;
}
}
function throwIfModuleMissing() {
if (!modules.includes('edge-tts')) {
const message = `Edge TTS module not loaded. Add edge-tts to enable-modules and restart the Extras API.`
const message = 'Edge TTS module not loaded. Add edge-tts to enable-modules and restart the Extras API.';
// toastr.error(message)
throw new Error(message)
throw new Error(message);
}
}

View File

@@ -1,23 +1,23 @@
import { saveTtsProviderSettings } from "./index.js"
export { ElevenLabsTtsProvider }
import { saveTtsProviderSettings } from './index.js';
export { ElevenLabsTtsProvider };
class ElevenLabsTtsProvider {
//########//
// Config //
//########//
settings
voices = []
separator = ' ... ... ... '
settings;
voices = [];
separator = ' ... ... ... ';
defaultSettings = {
stability: 0.75,
similarity_boost: 0.75,
apiKey: "",
apiKey: '',
model: 'eleven_monolingual_v1',
voiceMap: {}
}
voiceMap: {},
};
get settingsHtml() {
let html = `
@@ -36,28 +36,28 @@ class ElevenLabsTtsProvider {
<label for="elevenlabs_tts_similarity_boost">Similarity Boost: <span id="elevenlabs_tts_similarity_boost_output"></span></label>
<input id="elevenlabs_tts_similarity_boost" type="range" value="${this.defaultSettings.similarity_boost}" min="0" max="1" step="0.05" />
</div>
`
return html
`;
return html;
}
onSettingsChange() {
// Update dynamically
this.settings.stability = $('#elevenlabs_tts_stability').val()
this.settings.similarity_boost = $('#elevenlabs_tts_similarity_boost').val()
this.settings.model = $('#elevenlabs_tts_model').find(':selected').val()
this.settings.stability = $('#elevenlabs_tts_stability').val();
this.settings.similarity_boost = $('#elevenlabs_tts_similarity_boost').val();
this.settings.model = $('#elevenlabs_tts_model').find(':selected').val();
$('#elevenlabs_tts_stability_output').text(this.settings.stability);
$('#elevenlabs_tts_similarity_boost_output').text(this.settings.similarity_boost);
saveTtsProviderSettings()
saveTtsProviderSettings();
}
async loadSettings(settings) {
// Pupulate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.info("Using default TTS Provider settings")
console.info('Using default TTS Provider settings');
}
// Only accept keys defined in defaultSettings
this.settings = this.defaultSettings
this.settings = this.defaultSettings;
// Migrate old settings
if (settings['multilingual'] !== undefined) {
@@ -67,34 +67,34 @@ class ElevenLabsTtsProvider {
for (const key in settings) {
if (key in this.settings) {
this.settings[key] = settings[key]
this.settings[key] = settings[key];
} else {
throw `Invalid setting passed to TTS Provider: ${key}`
throw `Invalid setting passed to TTS Provider: ${key}`;
}
}
$('#elevenlabs_tts_stability').val(this.settings.stability)
$('#elevenlabs_tts_similarity_boost').val(this.settings.similarity_boost)
$('#elevenlabs_tts_api_key').val(this.settings.apiKey)
$('#elevenlabs_tts_stability').val(this.settings.stability);
$('#elevenlabs_tts_similarity_boost').val(this.settings.similarity_boost);
$('#elevenlabs_tts_api_key').val(this.settings.apiKey);
$('#elevenlabs_tts_model').val(this.settings.model);
$('#eleven_labs_connect').on('click', () => { this.onConnectClick() })
$('#elevenlabs_tts_similarity_boost').on('input', this.onSettingsChange.bind(this))
$('#elevenlabs_tts_stability').on('input', this.onSettingsChange.bind(this))
$('#elevenlabs_tts_model').on('change', this.onSettingsChange.bind(this))
$('#eleven_labs_connect').on('click', () => { this.onConnectClick(); });
$('#elevenlabs_tts_similarity_boost').on('input', this.onSettingsChange.bind(this));
$('#elevenlabs_tts_stability').on('input', this.onSettingsChange.bind(this));
$('#elevenlabs_tts_model').on('change', this.onSettingsChange.bind(this));
$('#elevenlabs_tts_stability_output').text(this.settings.stability);
$('#elevenlabs_tts_similarity_boost_output').text(this.settings.similarity_boost);
try {
await this.checkReady()
console.debug("ElevenLabs: Settings loaded")
await this.checkReady();
console.debug('ElevenLabs: Settings loaded');
} catch {
console.debug("ElevenLabs: Settings loaded, but not ready")
console.debug('ElevenLabs: Settings loaded, but not ready');
}
}
// Perform a simple readiness check by trying to fetch voiceIds
async checkReady() {
await this.fetchTtsVoiceObjects()
await this.fetchTtsVoiceObjects();
}
async onRefreshClick() {
@@ -103,22 +103,21 @@ class ElevenLabsTtsProvider {
async onConnectClick() {
// Update on Apply click
return await this.updateApiKey().catch((error) => {
toastr.error(`ElevenLabs: ${error}`)
})
toastr.error(`ElevenLabs: ${error}`);
});
}
async updateApiKey() {
// Using this call to validate API key
this.settings.apiKey = $('#elevenlabs_tts_api_key').val()
this.settings.apiKey = $('#elevenlabs_tts_api_key').val();
await this.fetchTtsVoiceObjects().catch(error => {
throw `TTS API key validation failed`
})
this.settings.apiKey = this.settings.apiKey
console.debug(`Saved new API_KEY: ${this.settings.apiKey}`)
$('#tts_status').text('')
this.onSettingsChange()
throw 'TTS API key validation failed';
});
console.debug(`Saved new API_KEY: ${this.settings.apiKey}`);
$('#tts_status').text('');
this.onSettingsChange();
}
//#################//
@@ -127,30 +126,30 @@ class ElevenLabsTtsProvider {
async getVoice(voiceName) {
if (this.voices.length == 0) {
this.voices = await this.fetchTtsVoiceObjects()
this.voices = await this.fetchTtsVoiceObjects();
}
const match = this.voices.filter(
elevenVoice => elevenVoice.name == voiceName
)[0]
elevenVoice => elevenVoice.name == voiceName,
)[0];
if (!match) {
throw `TTS Voice name ${voiceName} not found in ElevenLabs account`
throw `TTS Voice name ${voiceName} not found in ElevenLabs account`;
}
return match
return match;
}
async generateTts(text, voiceId) {
const historyId = await this.findTtsGenerationInHistory(text, voiceId)
const historyId = await this.findTtsGenerationInHistory(text, voiceId);
let response
let response;
if (historyId) {
console.debug(`Found existing TTS generation with id ${historyId}`)
response = await this.fetchTtsFromHistory(historyId)
console.debug(`Found existing TTS generation with id ${historyId}`);
response = await this.fetchTtsFromHistory(historyId);
} else {
console.debug(`No existing TTS generation found, requesting new generation`)
response = await this.fetchTtsGeneration(text, voiceId)
console.debug('No existing TTS generation found, requesting new generation');
response = await this.fetchTtsGeneration(text, voiceId);
}
return response
return response;
}
//###################//
@@ -158,16 +157,16 @@ class ElevenLabsTtsProvider {
//###################//
async findTtsGenerationInHistory(message, voiceId) {
const ttsHistory = await this.fetchTtsHistory()
const ttsHistory = await this.fetchTtsHistory();
for (const history of ttsHistory) {
const text = history.text
const itemId = history.history_item_id
const text = history.text;
const itemId = history.history_item_id;
if (message === text && history.voice_id == voiceId) {
console.info(`Existing TTS history item ${itemId} found: ${text} `)
return itemId
console.info(`Existing TTS history item ${itemId} found: ${text} `);
return itemId;
}
}
return ''
return '';
}
@@ -176,44 +175,44 @@ class ElevenLabsTtsProvider {
//###########//
async fetchTtsVoiceObjects() {
const headers = {
'xi-api-key': this.settings.apiKey
}
const response = await fetch(`https://api.elevenlabs.io/v1/voices`, {
headers: headers
})
'xi-api-key': this.settings.apiKey,
};
const response = await fetch('https://api.elevenlabs.io/v1/voices', {
headers: headers,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`)
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const responseJson = await response.json()
return responseJson.voices
const responseJson = await response.json();
return responseJson.voices;
}
async fetchTtsVoiceSettings() {
const headers = {
'xi-api-key': this.settings.apiKey
}
'xi-api-key': this.settings.apiKey,
};
const response = await fetch(
`https://api.elevenlabs.io/v1/voices/settings/default`,
'https://api.elevenlabs.io/v1/voices/settings/default',
{
headers: headers
}
)
headers: headers,
},
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`)
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response.json()
return response.json();
}
async fetchTtsGeneration(text, voiceId) {
let model = this.settings.model ?? "eleven_monolingual_v1";
console.info(`Generating new TTS for voice_id ${voiceId}, model ${model}`)
let model = this.settings.model ?? 'eleven_monolingual_v1';
console.info(`Generating new TTS for voice_id ${voiceId}, model ${model}`);
const response = await fetch(
`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,
{
method: 'POST',
headers: {
'xi-api-key': this.settings.apiKey,
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
body: JSON.stringify({
model_id: model,
@@ -222,43 +221,43 @@ class ElevenLabsTtsProvider {
stability: Number(this.settings.stability),
similarity_boost: Number(this.settings.similarity_boost),
},
})
}
)
}),
},
);
if (!response.ok) {
toastr.error(response.statusText, 'TTS Generation Failed');
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response
return response;
}
async fetchTtsFromHistory(history_item_id) {
console.info(`Fetched existing TTS with history_item_id ${history_item_id}`)
console.info(`Fetched existing TTS with history_item_id ${history_item_id}`);
const response = await fetch(
`https://api.elevenlabs.io/v1/history/${history_item_id}/audio`,
{
headers: {
'xi-api-key': this.settings.apiKey
}
}
)
'xi-api-key': this.settings.apiKey,
},
},
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`)
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response
return response;
}
async fetchTtsHistory() {
const headers = {
'xi-api-key': this.settings.apiKey
}
const response = await fetch(`https://api.elevenlabs.io/v1/history`, {
headers: headers
})
'xi-api-key': this.settings.apiKey,
};
const response = await fetch('https://api.elevenlabs.io/v1/history', {
headers: headers,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`)
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const responseJson = await response.json()
return responseJson.history
const responseJson = await response.json();
return responseJson.history;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,34 @@
import { getRequestHeaders, callPopup } from "../../../script.js"
import { getPreviewString, saveTtsProviderSettings } from "./index.js"
import { initVoiceMap } from "./index.js"
import { getRequestHeaders, callPopup } from '../../../script.js';
import { splitRecursive } from '../../utils.js';
import { getPreviewString, saveTtsProviderSettings } from './index.js';
import { initVoiceMap } from './index.js';
export { NovelTtsProvider }
export { NovelTtsProvider };
class NovelTtsProvider {
//########//
// Config //
//########//
settings
voices = []
separator = ' . '
audioElement = document.createElement('audio')
settings;
voices = [];
separator = ' . ';
audioElement = document.createElement('audio');
defaultSettings = {
voiceMap: {},
customVoices: []
customVoices: [],
};
/**
* Perform any text processing before passing to TTS engine.
* @param {string} text Input text
* @returns {string} Processed text
*/
processText(text) {
// Novel reads tilde as a word. Replace with full stop
text = text.replace(/~/g, '.');
return text;
}
get settingsHtml() {
@@ -41,68 +53,68 @@ class NovelTtsProvider {
// Add a new Novel custom voice to provider
async addCustomVoice(){
const voiceName = await callPopup('<h3>Custom Voice name:</h3>', 'input')
this.settings.customVoices.push(voiceName)
this.populateCustomVoices()
initVoiceMap() // Update TTS extension voiceMap
saveTtsProviderSettings()
async addCustomVoice() {
const voiceName = await callPopup('<h3>Custom Voice name:</h3>', 'input');
this.settings.customVoices.push(voiceName);
this.populateCustomVoices();
initVoiceMap(); // Update TTS extension voiceMap
saveTtsProviderSettings();
}
// Delete selected custom voice from provider
deleteCustomVoice() {
const selected = $("#tts-novel-custom-voices-select").find(':selected').val();
const selected = $('#tts-novel-custom-voices-select').find(':selected').val();
const voiceIndex = this.settings.customVoices.indexOf(selected);
if (voiceIndex !== -1) {
this.settings.customVoices.splice(voiceIndex, 1);
}
this.populateCustomVoices()
initVoiceMap() // Update TTS extension voiceMap
saveTtsProviderSettings()
this.populateCustomVoices();
initVoiceMap(); // Update TTS extension voiceMap
saveTtsProviderSettings();
}
// Create the UI dropdown list of voices in provider
populateCustomVoices(){
let voiceSelect = $("#tts-novel-custom-voices-select")
voiceSelect.empty()
populateCustomVoices() {
let voiceSelect = $('#tts-novel-custom-voices-select');
voiceSelect.empty();
this.settings.customVoices.forEach(voice => {
voiceSelect.append(`<option>${voice}</option>`)
})
voiceSelect.append(`<option>${voice}</option>`);
});
}
async loadSettings(settings) {
// Populate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.info("Using default TTS Provider settings")
console.info('Using default TTS Provider settings');
}
$("#tts-novel-custom-voices-add").on('click', () => (this.addCustomVoice()))
$("#tts-novel-custom-voices-delete").on('click',() => (this.deleteCustomVoice()))
$('#tts-novel-custom-voices-add').on('click', () => (this.addCustomVoice()));
$('#tts-novel-custom-voices-delete').on('click', () => (this.deleteCustomVoice()));
// Only accept keys defined in defaultSettings
this.settings = this.defaultSettings
this.settings = this.defaultSettings;
for (const key in settings) {
if (key in this.settings) {
this.settings[key] = settings[key]
this.settings[key] = settings[key];
} else {
throw `Invalid setting passed to TTS Provider: ${key}`
throw `Invalid setting passed to TTS Provider: ${key}`;
}
}
this.populateCustomVoices()
await this.checkReady()
console.debug("NovelTTS: Settings loaded")
this.populateCustomVoices();
await this.checkReady();
console.debug('NovelTTS: Settings loaded');
}
// Perform a simple readiness check by trying to fetch voiceIds
// Doesnt really do much for Novel, not seeing a good way to test this at the moment.
async checkReady(){
await this.fetchTtsVoiceObjects()
async checkReady() {
await this.fetchTtsVoiceObjects();
}
async onRefreshClick() {
return
return;
}
//#################//
@@ -111,15 +123,15 @@ class NovelTtsProvider {
async getVoice(voiceName) {
if (!voiceName) {
throw `TTS Voice name not provided`
throw 'TTS Voice name not provided';
}
return { name: voiceName, voice_id: voiceName, lang: 'en-US', preview_url: false}
return { name: voiceName, voice_id: voiceName, lang: 'en-US', preview_url: false };
}
async generateTts(text, voiceId) {
const response = await this.fetchTtsGeneration(text, voiceId)
return response
const response = await this.fetchTtsGeneration(text, voiceId);
return response;
}
//###########//
@@ -144,9 +156,9 @@ class NovelTtsProvider {
// Add in custom voices to the map
let addVoices = this.settings.customVoices.map(voice =>
({ name: voice, voice_id: voice, lang: 'en-US', preview_url: false })
)
voices = voices.concat(addVoices)
({ name: voice, voice_id: voice, lang: 'en-US', preview_url: false }),
);
voices = voices.concat(addVoices);
return voices;
}
@@ -156,10 +168,10 @@ class NovelTtsProvider {
this.audioElement.pause();
this.audioElement.currentTime = 0;
const text = getPreviewString('en-US')
const response = await this.fetchTtsGeneration(text, id)
const text = getPreviewString('en-US');
const response = await this.fetchTtsGeneration(text, id);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
throw new Error(`HTTP ${response.status}`);
}
const audio = await response.blob();
@@ -168,22 +180,26 @@ class NovelTtsProvider {
this.audioElement.play();
}
async fetchTtsGeneration(inputText, voiceId) {
console.info(`Generating new TTS for voice_id ${voiceId}`)
const response = await fetch(`/api/novelai/generate-voice`,
{
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({
"text": inputText,
"voice": voiceId,
})
async* fetchTtsGeneration(inputText, voiceId) {
const MAX_LENGTH = 1000;
console.info(`Generating new TTS for voice_id ${voiceId}`);
const chunks = splitRecursive(inputText, MAX_LENGTH);
for (const chunk of chunks) {
const response = await fetch('/api/novelai/generate-voice',
{
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({
'text': chunk,
'voice': 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()}`);
yield response;
}
return response
}
}

View File

@@ -1,7 +1,7 @@
import { getRequestHeaders } from "../../../script.js"
import { saveTtsProviderSettings } from "./index.js";
import { getRequestHeaders } from '../../../script.js';
import { saveTtsProviderSettings } from './index.js';
export { OpenAITtsProvider }
export { OpenAITtsProvider };
class OpenAITtsProvider {
static voices = [
@@ -13,17 +13,17 @@ class OpenAITtsProvider {
{ name: 'Shimmer', voice_id: 'shimmer', lang: 'en-US', preview_url: 'https://cdn.openai.com/API/docs/audio/shimmer.wav' },
];
settings
voices = []
separator = ' . '
audioElement = document.createElement('audio')
settings;
voices = [];
separator = ' . ';
audioElement = document.createElement('audio');
defaultSettings = {
voiceMap: {},
customVoices: [],
model: 'tts-1',
speed: 1,
}
};
get settingsHtml() {
let html = `
@@ -44,7 +44,7 @@ class OpenAITtsProvider {
</div>
<div>
<label for="openai-tts-speed">Speed: <span id="openai-tts-speed-output"></span></label>
<input type="range" id="openai-tts-speed" value="1" min="0.25" max="4" step="0.25">
<input type="range" id="openai-tts-speed" value="1" min="0.25" max="4" step="0.05">
</div>`;
return html;
}
@@ -52,7 +52,7 @@ class OpenAITtsProvider {
async loadSettings(settings) {
// Populate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.info("Using default TTS Provider settings")
console.info('Using default TTS Provider settings');
}
// Only accept keys defined in defaultSettings
@@ -79,7 +79,7 @@ class OpenAITtsProvider {
$('#openai-tts-speed-output').text(this.settings.speed);
await this.checkReady();
console.debug("OpenAI TTS: Settings loaded");
console.debug('OpenAI TTS: Settings loaded');
}
onSettingsChange() {
@@ -100,21 +100,21 @@ class OpenAITtsProvider {
async getVoice(voiceName) {
if (!voiceName) {
throw `TTS Voice name not provided`
throw 'TTS Voice name not provided';
}
const voice = OpenAITtsProvider.voices.find(voice => voice.voice_id === voiceName || voice.name === voiceName);
if (!voice) {
throw `TTS Voice not found: ${voiceName}`
throw `TTS Voice not found: ${voiceName}`;
}
return voice;
}
async generateTts(text, voiceId) {
const response = await this.fetchTtsGeneration(text, voiceId)
return response
const response = await this.fetchTtsGeneration(text, voiceId);
return response;
}
async fetchTtsVoiceObjects() {
@@ -126,15 +126,15 @@ class OpenAITtsProvider {
}
async fetchTtsGeneration(inputText, voiceId) {
console.info(`Generating new TTS for voice_id ${voiceId}`)
const response = await fetch(`/api/openai/generate-voice`, {
console.info(`Generating new TTS for voice_id ${voiceId}`);
const response = await fetch('/api/openai/generate-voice', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({
"text": inputText,
"voice": voiceId,
"model": this.settings.model,
"speed": this.settings.speed,
'text': inputText,
'voice': voiceId,
'model': this.settings.model,
'speed': this.settings.speed,
}),
});

View File

@@ -1,8 +1,8 @@
# Provider Requirements.
# Provider Requirements.
Because I don't know how, or if you can, and/or maybe I am just too lazy to implement interfaces in JS, here's the requirements of a provider that the extension needs to operate.
### class YourTtsProvider
#### Required
#### Required
Exported for use in extension index.js, and added to providers list in index.js
1. generateTts(text, voiceId)
2. fetchTtsVoiceObjects()
@@ -13,8 +13,9 @@ Exported for use in extension index.js, and added to providers list in index.js
7. settingsHtml field
#### Optional
1. previewTtsVoice()
1. previewTtsVoice()
2. separator field
3. processText(text)
# Requirement Descriptions
### generateTts(text, voiceId)
@@ -49,14 +50,14 @@ Return without error to let TTS extension know that the provider is ready.
Return an error to block the main TTS extension for initializing the provider and UI. The error will be put in the TTS extension UI directly.
### loadSettings(settingsObject)
Required.
Required.
Handle the input settings from the TTS extension on provider load.
Put code in here to load your provider settings.
### settings field
Required, used for storing any provider state that needs to be saved.
Anything stored in this field is automatically persisted under extension_settings[providerName] by the main extension in `saveTtsProviderSettings()`, as well as loaded when the provider is selected in `loadTtsProvider(provider)`.
TTS extension doesn't expect any specific contents.
TTS extension doesn't expect any specific contents.
### settingsHtml field
Required, injected into the TTS extension UI. Besides adding it, not relied on by TTS extension directly.
@@ -68,4 +69,8 @@ Function to handle playing previews of voice samples if no direct preview_url is
### separator field
Optional.
Used when narrate quoted text is enabled.
Defines the string of characters used to introduce separation between between the groups of extracted quoted text sent to the provider. The provider will use this to introduce pauses by default using `...`
Defines the string of characters used to introduce separation between between the groups of extracted quoted text sent to the provider. The provider will use this to introduce pauses by default using `...`
### processText(text)
Optional.
A function applied to the input text before passing it to the TTS generator. Can be async.

View File

@@ -1,22 +1,22 @@
import { doExtrasFetch, getApiUrl, modules } from "../../extensions.js"
import { saveTtsProviderSettings } from "./index.js"
import { doExtrasFetch, getApiUrl, modules } from '../../extensions.js';
import { saveTtsProviderSettings } from './index.js';
export { SileroTtsProvider }
export { SileroTtsProvider };
class SileroTtsProvider {
//########//
// Config //
//########//
settings
ready = false
voices = []
separator = ' .. '
settings;
ready = false;
voices = [];
separator = ' .. ';
defaultSettings = {
provider_endpoint: "http://localhost:8001/tts",
voiceMap: {}
}
provider_endpoint: 'http://localhost:8001/tts',
voiceMap: {},
};
get settingsHtml() {
let html = `
@@ -24,30 +24,31 @@ class SileroTtsProvider {
<input id="silero_tts_endpoint" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.provider_endpoint}"/>
<span>
<span>Use <a target="_blank" href="https://github.com/SillyTavern/SillyTavern-extras">SillyTavern Extras API</a> or <a target="_blank" href="https://github.com/ouoertheo/silero-api-server">Silero TTS Server</a>.</span>
`
return html
`;
return html;
}
onSettingsChange() {
// Used when provider settings are updated from UI
this.settings.provider_endpoint = $('#silero_tts_endpoint').val()
saveTtsProviderSettings()
this.settings.provider_endpoint = $('#silero_tts_endpoint').val();
saveTtsProviderSettings();
this.refreshSession();
}
async loadSettings(settings) {
// Pupulate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.info("Using default TTS Provider settings")
console.info('Using default TTS Provider settings');
}
// Only accept keys defined in defaultSettings
this.settings = this.defaultSettings
this.settings = this.defaultSettings;
for (const key in settings){
if (key in this.settings){
this.settings[key] = settings[key]
for (const key in settings) {
if (key in this.settings) {
this.settings[key] = settings[key];
} else {
throw `Invalid setting passed to TTS Provider: ${key}`
throw `Invalid setting passed to TTS Provider: ${key}`;
}
}
@@ -62,21 +63,26 @@ class SileroTtsProvider {
}
}, 2000);
$('#silero_tts_endpoint').val(this.settings.provider_endpoint)
$('#silero_tts_endpoint').on("input", () => {this.onSettingsChange()})
$('#silero_tts_endpoint').val(this.settings.provider_endpoint);
$('#silero_tts_endpoint').on('input', () => { this.onSettingsChange(); });
this.refreshSession();
await this.checkReady()
await this.checkReady();
console.debug("SileroTTS: Settings loaded")
console.debug('SileroTTS: Settings loaded');
}
// Perform a simple readiness check by trying to fetch voiceIds
async checkReady(){
await this.fetchTtsVoiceObjects()
async checkReady() {
await this.fetchTtsVoiceObjects();
}
async onRefreshClick() {
return
return;
}
async refreshSession() {
await this.initSession();
}
//#################//
@@ -85,55 +91,81 @@ class SileroTtsProvider {
async getVoice(voiceName) {
if (this.voices.length == 0) {
this.voices = await this.fetchTtsVoiceObjects()
this.voices = await this.fetchTtsVoiceObjects();
}
const match = this.voices.filter(
sileroVoice => sileroVoice.name == voiceName
)[0]
sileroVoice => sileroVoice.name == voiceName,
)[0];
if (!match) {
throw `TTS Voice name ${voiceName} not found`
throw `TTS Voice name ${voiceName} not found`;
}
return match
return match;
}
async generateTts(text, voiceId){
const response = await this.fetchTtsGeneration(text, voiceId)
return response
async generateTts(text, voiceId) {
const response = await this.fetchTtsGeneration(text, voiceId);
return response;
}
//###########//
// API CALLS //
//###########//
async fetchTtsVoiceObjects() {
const response = await doExtrasFetch(`${this.settings.provider_endpoint}/speakers`)
const response = await doExtrasFetch(`${this.settings.provider_endpoint}/speakers`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`)
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
}
const responseJson = await response.json()
return responseJson
const responseJson = await response.json();
return responseJson;
}
async fetchTtsGeneration(inputText, voiceId) {
console.info(`Generating new TTS for voice_id ${voiceId}`)
console.info(`Generating new TTS for voice_id ${voiceId}`);
const response = await doExtrasFetch(
`${this.settings.provider_endpoint}/generate`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache' // Added this line to disable caching of file so new files are always played - Rolyat 7/7/23
'Cache-Control': 'no-cache', // Added this line to disable caching of file so new files are always played - Rolyat 7/7/23
},
body: JSON.stringify({
"text": inputText,
"speaker": voiceId
})
}
)
'text': inputText,
'speaker': voiceId,
'session': 'sillytavern',
}),
},
);
if (!response.ok) {
toastr.error(response.statusText, 'TTS Generation Failed');
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response
return response;
}
async initSession() {
console.info('Silero TTS: requesting new session');
try {
const response = await doExtrasFetch(
`${this.settings.provider_endpoint}/session`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
},
body: JSON.stringify({
'path': 'sillytavern',
}),
},
);
if (!response.ok && response.status !== 404) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
} catch (error) {
console.info('Silero TTS: endpoint not available', error);
}
}
// Interface not used by Silero TTS

View File

@@ -1,8 +1,8 @@
import { isMobile } from "../../RossAscends-mods.js";
import { getPreviewString } from "./index.js";
import { isMobile } from '../../RossAscends-mods.js';
import { getPreviewString } from './index.js';
import { talkingAnimation } from './index.js';
import { saveTtsProviderSettings } from "./index.js"
export { SystemTtsProvider }
import { saveTtsProviderSettings } from './index.js';
export { SystemTtsProvider };
/**
* Chunkify
@@ -46,7 +46,7 @@ var speechUtteranceChunker = function (utt, settings, callback) {
newUtt = new SpeechSynthesisUtterance(chunk);
var x;
for (x in utt) {
if (utt.hasOwnProperty(x) && x !== 'text') {
if (Object.hasOwn(utt, x) && x !== 'text') {
newUtt[x] = utt[x];
}
}
@@ -79,20 +79,20 @@ class SystemTtsProvider {
// Config //
//########//
settings
ready = false
voices = []
separator = ' ... '
settings;
ready = false;
voices = [];
separator = ' ... ';
defaultSettings = {
voiceMap: {},
rate: 1,
pitch: 1,
}
};
get settingsHtml() {
if (!('speechSynthesis' in window)) {
return "Your browser or operating system doesn't support speech synthesis";
return 'Your browser or operating system doesn\'t support speech synthesis';
}
return `<p>Uses the voices provided by your operating system</p>
@@ -107,13 +107,13 @@ class SystemTtsProvider {
this.settings.pitch = Number($('#system_tts_pitch').val());
$('#system_tts_pitch_output').text(this.settings.pitch);
$('#system_tts_rate_output').text(this.settings.rate);
saveTtsProviderSettings()
saveTtsProviderSettings();
}
async loadSettings(settings) {
// Populate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.info("Using default TTS Provider settings");
console.info('Using default TTS Provider settings');
}
// iOS should only allows speech synthesis trigged by user interaction
@@ -146,21 +146,21 @@ class SystemTtsProvider {
$('#system_tts_pitch').val(this.settings.pitch || this.defaultSettings.pitch);
// Trigger updates
$('#system_tts_rate').on("input", () => { this.onSettingsChange() })
$('#system_tts_rate').on("input", () => { this.onSettingsChange() })
$('#system_tts_rate').on('input', () => { this.onSettingsChange(); });
$('#system_tts_rate').on('input', () => { this.onSettingsChange(); });
$('#system_tts_pitch_output').text(this.settings.pitch);
$('#system_tts_rate_output').text(this.settings.rate);
console.debug("SystemTTS: Settings loaded");
console.debug('SystemTTS: Settings loaded');
}
// Perform a simple readiness check by trying to fetch voiceIds
async checkReady() {
await this.fetchTtsVoiceObjects()
await this.fetchTtsVoiceObjects();
}
async onRefreshClick() {
return
return;
}
//#################//
@@ -191,7 +191,7 @@ class SystemTtsProvider {
const voice = speechSynthesis.getVoices().find(x => x.voiceURI === voiceId);
if (!voice) {
throw `TTS Voice name ${voiceName} not found`
throw `TTS Voice id ${voiceId} not found`;
}
speechSynthesis.cancel();
@@ -205,14 +205,14 @@ class SystemTtsProvider {
async getVoice(voiceName) {
if (!('speechSynthesis' in window)) {
return { voice_id: null }
return { voice_id: null };
}
const voices = speechSynthesis.getVoices();
const match = voices.find(x => x.name == voiceName);
if (!match) {
throw `TTS Voice name ${voiceName} not found`
throw `TTS Voice name ${voiceName} not found`;
}
return { voice_id: match.voiceURI, name: match.name };

View File

@@ -1,17 +1,32 @@
import { doExtrasFetch, getApiUrl, modules } from "../../extensions.js"
import { saveTtsProviderSettings } from "./index.js"
import { doExtrasFetch, getApiUrl, modules } from '../../extensions.js';
import { saveTtsProviderSettings } from './index.js';
export { XTTSTtsProvider }
export { XTTSTtsProvider };
class XTTSTtsProvider {
//########//
// Config //
//########//
settings
ready = false
voices = []
separator = '. '
settings;
ready = false;
voices = [];
separator = '. ';
/**
* Perform any text processing before passing to TTS engine.
* @param {string} text Input text
* @returns {string} Processed text
*/
processText(text) {
// Replace fancy ellipsis with "..."
text = text.replace(/…/g, '...');
// Remove quotes
text = text.replace(/["“”‘’]/g, '');
// Replace multiple "." with single "."
text = text.replace(/\.+/g, '.');
return text;
}
languageLabels = {
"Arabic": "ar",
@@ -32,7 +47,7 @@ class XTTSTtsProvider {
"Hungarian": "hu",
"Hindi": "hi",
}
defaultSettings = {
provider_endpoint: "http://localhost:8020",
language: "en",
@@ -58,7 +73,7 @@ class XTTSTtsProvider {
if (this.languageLabels[language] == this.settings?.language) {
html += `<option value="${this.languageLabels[language]}" selected="selected">${language}</option>`;
continue
continue;
}
html += `<option value="${this.languageLabels[language]}">${language}</option>`;
@@ -94,7 +109,6 @@ class XTTSTtsProvider {
<label for="xtts_tts_endpoint">Provider Endpoint:</label>
<input id="xtts_tts_endpoint" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.provider_endpoint}"/>
<label for="xtts_tts_streaming" class="checkbox_label">
<input id="xtts_tts_streaming" type="checkbox" />
<span>Streaming <small>(RVC not supported)</small></span>
@@ -124,7 +138,7 @@ class XTTSTtsProvider {
this.settings.stream_chunk_size = $('#xtts_stream_chunk_size').val();
this.settings.enable_text_splitting = $('#xtts_enable_text_splitting').is(':checked');
this.settings.streaming = $('#xtts_tts_streaming').is(':checked');
// Update the UI to reflect changes
$('#xtts_tts_speed_output').text(this.settings.speed);
$('#xtts_tts_temperature_output').text(this.settings.temperature);
@@ -133,7 +147,7 @@ class XTTSTtsProvider {
$('#xtts_top_k_output').text(this.settings.top_k);
$('#xtts_top_p_output').text(this.settings.top_p);
$('#xtts_stream_chunk_size_output').text(this.settings.stream_chunk_size);
saveTtsProviderSettings()
this.changeTTSSetting()
}
@@ -142,17 +156,17 @@ class XTTSTtsProvider {
async loadSettings(settings) {
// Pupulate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.info("Using default TTS Provider settings")
console.info('Using default TTS Provider settings');
}
// Only accept keys defined in defaultSettings
this.settings = this.defaultSettings
this.settings = this.defaultSettings;
for (const key in settings) {
if (key in this.settings) {
this.settings[key] = settings[key]
this.settings[key] = settings[key];
} else {
throw `Invalid setting passed to TTS Provider: ${key}`
throw `Invalid setting passed to TTS Provider: ${key}`;
}
}
@@ -167,10 +181,12 @@ class XTTSTtsProvider {
}
}, 2000);
$('#xtts_tts_endpoint').val(this.settings.provider_endpoint)
$('#xtts_tts_endpoint').on("input", () => { this.onSettingsChange() })
$('#xtts_api_language').val(this.settings.language)
$('#xtts_api_language').on("change", () => { this.onSettingsChange() })
$('#xtts_tts_endpoint').val(this.settings.provider_endpoint);
$('#xtts_tts_endpoint').on('input', () => { this.onSettingsChange(); });
$('#xtts_api_language').val(this.settings.language);
$('#xtts_api_language').on('change', () => { this.onSettingsChange(); });
$('#xtts_tts_streaming').prop('checked', this.settings.streaming);
$('#xtts_tts_streaming').on('change', () => { this.onSettingsChange(); });
// Set initial values from the settings
$('#xtts_speed').val(this.settings.speed);
@@ -194,19 +210,18 @@ class XTTSTtsProvider {
$('#xtts_tts_streaming').prop('checked', this.settings.streaming);
$('#xtts_tts_streaming').on('change', () => { this.onSettingsChange(); });
await this.checkReady()
await this.checkReady();
console.debug("XTTS: Settings loaded")
console.debug('XTTS: Settings loaded');
}
// Perform a simple readiness check by trying to fetch voiceIds
async checkReady() {
const response = await this.fetchTtsVoiceObjects()
await this.fetchTtsVoiceObjects();
}
async onRefreshClick() {
return
return;
}
//#################//
@@ -215,32 +230,32 @@ class XTTSTtsProvider {
async getVoice(voiceName) {
if (this.voices.length == 0) {
this.voices = await this.fetchTtsVoiceObjects()
this.voices = await this.fetchTtsVoiceObjects();
}
const match = this.voices.filter(
XTTSVoice => XTTSVoice.name == voiceName
)[0]
XTTSVoice => XTTSVoice.name == voiceName,
)[0];
if (!match) {
throw `TTS Voice name ${voiceName} not found`
throw `TTS Voice name ${voiceName} not found`;
}
return match
return match;
}
async generateTts(text, voiceId) {
const response = await this.fetchTtsGeneration(text, voiceId)
return response
const response = await this.fetchTtsGeneration(text, voiceId);
return response;
}
//###########//
// API CALLS //
//###########//
async fetchTtsVoiceObjects() {
const response = await doExtrasFetch(`${this.settings.provider_endpoint}/speakers`)
const response = await doExtrasFetch(`${this.settings.provider_endpoint}/speakers`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`)
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
}
const responseJson = await response.json()
return responseJson
const responseJson = await response.json();
return responseJson;
}
// Each time a parameter is changed, we change the configuration
@@ -285,20 +300,20 @@ class XTTSTtsProvider {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache' // Added this line to disable caching of file so new files are always played - Rolyat 7/7/23
'Cache-Control': 'no-cache', // Added this line to disable caching of file so new files are always played - Rolyat 7/7/23
},
body: JSON.stringify({
"text": inputText,
"speaker_wav": voiceId,
"language": this.settings.language
})
}
)
'text': inputText,
'speaker_wav': voiceId,
'language': this.settings.language,
}),
},
);
if (!response.ok) {
toastr.error(response.statusText, 'TTS Generation Failed');
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response
return response;
}
// Interface not used by XTTS TTS