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'); // 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); } }