From e857db40fb49bcd9b2f6d784276988c32d893ec2 Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:14:38 +0000 Subject: [PATCH] Updated AllTalk Extension to support AllTalk V2 1. Core Feature Addition: - Added AllTalk V1/V2 server version selection - Added RVC (Realistic Voice Conversion) support for V2 - RVC features are automatically disabled when V1 is selected 2. Code Improvements: - Replaced custom debounce implementation with shared utils.js debounce utility - Fixed linting issues: - Converted HTML attribute quotes in template literals to single quotes - Added trailing commas where required - Fixed console.error message formatting 3. New Settings/Properties Added: ```javascript server_version: 'v2' (default) rvc_character_voice: 'Disabled' rvc_character_pitch: '0' rvc_narrator_voice: 'Disabled' rvc_narrator_pitch: '0' ``` 4. Bug Fixes/Improvements: - Better error handling for RVC voice fetching - Improved URL handling for V1/V2 differences in API responses - Enhanced settings initialization and validation 5. Structural Changes: - Added RVC-specific UI elements and controls - Added version-specific logic for API endpoints - Improved settings synchronization between UI and backend **NOTE** On line 70 there is an eslint bypass: ```javascript // HTML template literals can trigger ESLint quotes warnings when quotes are used in HTML attributes. // Disabling quotes rule for this one line as it's a false positive with HTML template literals. // eslint-disable-next-line quotes let html = `
AllTalk V2 Settings
`; ``` The reason is: 1. ESLint's quotes rule wants all strings to use single quotes 2. However, this is a template literal containing HTML, where double quotes are standard for attributes 3. I tried various solutions: - Using single quotes: `
` - Using double quotes: `
` - Even tried escaping quotes But ESLint just kept flagging it as an error --- public/scripts/extensions/tts/alltalk.js | 383 ++++++++++------------- 1 file changed, 171 insertions(+), 212 deletions(-) diff --git a/public/scripts/extensions/tts/alltalk.js b/public/scripts/extensions/tts/alltalk.js index 2d4bc00d4..3392390e7 100644 --- a/public/scripts/extensions/tts/alltalk.js +++ b/public/scripts/extensions/tts/alltalk.js @@ -1,4 +1,5 @@ -import { doExtrasFetch, getApiUrl, modules } from '../../extensions.js'; +import { doExtrasFetch } from '../../extensions.js'; +import { debounce } from '../../utils.js'; import { saveTtsProviderSettings } from './index.js'; export { AllTalkTtsProvider }; @@ -13,7 +14,7 @@ class AllTalkTtsProvider { // Initialize with default settings if they are not already set this.settings = { provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851', - server_version: this.settings.server_version || 'v2', + server_version: this.settings.server_version || 'v2', language: this.settings.language || 'en', voiceMap: this.settings.voiceMap || {}, at_generation_method: this.settings.at_generation_method || 'standard_generation', @@ -24,7 +25,7 @@ class AllTalkTtsProvider { rvc_character_pitch: this.settings.rvc_character_pitch || '0', rvc_narrator_voice: this.settings.rvc_narrator_voice || 'Disabled', rvc_narrator_pitch: this.settings.rvc_narrator_pitch || '0', - finetuned_model: this.settings.finetuned_model || 'false' + finetuned_model: this.settings.finetuned_model || 'false', }; // Separate property for dynamically updated settings from the server this.dynamicSettings = { @@ -63,161 +64,159 @@ class AllTalkTtsProvider { }; get settingsHtml() { + // HTML template literals can trigger ESLint quotes warnings when quotes are used in HTML attributes. + // Disabling quotes rule for this one line as it's a false positive with HTML template literals. + // eslint-disable-next-line quotes let html = `
AllTalk V2 Settings
`; - html += `
- -
- - + +
`; - html += `
- -
- - + + +
-
- - + + +
-
`; - html += `
-
- - `; if (this.voices) { for (let voice of this.voices) { - html += ``; + html += ``; } } html += `
-
- - `; for (let language in this.languageLabels) { - html += ``; + html += ``; } html += `
`; - html += `
-
- - `; if (this.rvcVoices) { for (let rvccharvoice of this.rvcVoices) { - html += ``; + html += ``; } } html += `
-
- - `; if (this.rvcVoices) { for (let rvcnarrvoice of this.rvcVoices) { - html += ``; + html += ``; } } html += `
`; - // Add the RVC pitch control row - html += `
-
- - `; for (let i = -24; i <= 24; i++) { const selected = i === 0 ? 'selected="selected"' : ''; - html += ``; + html += ``; } html += `
-
- - `; for (let i = -24; i <= 24; i++) { const selected = i === 0 ? 'selected="selected"' : ''; - html += ``; + html += ``; } html += `
`; - html += `
-
- -
-
- - +
+ +
`; - html += `
-
- - + +
`; - html += `
-
- - + html += `
+
+ +
-
- - +
+ +
-
- Status: Ready +
+ Status: Ready
-
+
`; - - html += `
-
- AllTalk V2Config & Docs. + html += `
+
+ AllTalk V2Config & Docs.
-
- AllTalk Website. +
+ AllTalk Website.
`; - html += `
-
+ html += `
+
- If you change your TTS engine in AllTalk, you will need to Reload (button above) and re-select your voices.

- Assuming the server is Status: Ready, most problems will be resolved by hitting Reload and selecting voices that match the loaded TTS engine.

- Text-generation-webui users - Uncheck Enable TTS in the TGWUI interface, or you will hear 2x voices and file names being generated. @@ -258,7 +257,7 @@ class AllTalkTtsProvider { $('#rvc_character_voice').val(this.settings.rvc_character_voice); $('#rvc_narrator_voice').val(this.settings.rvc_narrator_voice); $('#rvc_character_pitch').val(this.settings.rvc_character_pitch); - $('#rvc_narrator_pitch').val(this.settings.rvc_narrator_pitch); + $('#rvc_narrator_pitch').val(this.settings.rvc_narrator_pitch); console.debug('AllTalkTTS: Settings loaded'); try { @@ -273,7 +272,7 @@ class AllTalkTtsProvider { this.applySettingsToHTML(); updateStatus('Ready'); } catch (error) { - console.error("Error loading settings:", error); + console.error('Error loading settings:', error); updateStatus('Offline'); } } @@ -381,7 +380,7 @@ class AllTalkTtsProvider { name: filename, voice_id: filename, preview_url: null, // Preview URL will be dynamically generated - lang: 'en' // Default language + lang: 'en', // Default language }; }); this.voices = voices; // Assign to the class property @@ -393,7 +392,7 @@ class AllTalkTtsProvider { console.log('Skipping RVC voices fetch for V1 server'); return []; } - + console.log('Fetching RVC Voices'); try { const response = await fetch(`${this.settings.provider_endpoint}/api/rvcvoices`); @@ -402,20 +401,20 @@ class AllTalkTtsProvider { console.error('Error text:', errorText); throw new Error(`HTTP ${response.status}: ${errorText}`); } - + const data = await response.json(); if (!data || !data.rvcvoices) { console.error('Invalid data format:', data); throw new Error('Invalid data format received from /api/rvcvoices'); } - + const voices = data.rvcvoices.map(filename => { return { name: filename, voice_id: filename, }; }); - + console.log('RVC voices:', voices); this.rvcVoices = voices; // Assign to the class property this.updateRvcVoiceDropdowns(); // Update UI after fetching voices @@ -467,11 +466,11 @@ class AllTalkTtsProvider { // Handle all RVC-related elements const rvcElements = document.querySelectorAll('.rvc-setting'); const isV2 = this.settings.server_version === 'v2'; - + rvcElements.forEach(element => { element.style.display = isV2 ? 'block' : 'none'; }); - + // Update and disable/enable character voice dropdown const rvcCharacterVoiceSelect = document.getElementById('rvc_character_voice'); if (rvcCharacterVoiceSelect) { @@ -489,7 +488,7 @@ class AllTalkTtsProvider { } } } - + // Update and disable/enable narrator voice dropdown const rvcNarratorVoiceSelect = document.getElementById('rvc_narrator_voice'); if (rvcNarratorVoiceSelect) { @@ -507,13 +506,13 @@ class AllTalkTtsProvider { } } } - + // Update pitch inputs const characterPitch = document.getElementById('rvc_character_pitch'); if (characterPitch) { characterPitch.disabled = !isV2; } - + const narratorPitch = document.getElementById('rvc_narrator_pitch'); if (narratorPitch) { narratorPitch.disabled = !isV2; @@ -602,7 +601,7 @@ class AllTalkTtsProvider { const languageSelect = document.getElementById('language_options'); if (languageSelect) { // Ensure default language is set - this.settings.language = this.settings.language; + this.settings.language = this.settings.language || 'en'; languageSelect.innerHTML = ''; for (let language in this.languageLabels) { @@ -622,56 +621,54 @@ class AllTalkTtsProvider { //########################################// setupEventListeners() { - - let debounceTimeout; - const debounceDelay = 1400; // Milliseconds - // Define the event handler function const onModelSelectChange = async (event) => { - console.log("Model select change event triggered"); // Debugging statement + console.log('Model select change event triggered'); const selectedModel = event.target.value; - console.log(`Selected model: ${selectedModel}`); // Debugging statement - // Set status to Processing + console.log(`Selected model: ${selectedModel}`); updateStatus('Processing'); try { const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, { - method: 'POST' + method: 'POST', }); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } const data = await response.json(); - console.log("POST response data:", data); // Debugging statement - // Set status to Ready if successful + console.log('POST response data:', data); updateStatus('Ready'); } catch (error) { - console.error("POST request error:", error); // Debugging statement - // Set status to Error in case of failure + console.error('POST request error:', error); updateStatus('Error'); } - - // Handle response or error }; + // Switch Model Listener with debounce + const modelSelect = document.getElementById('switch_model'); + if (modelSelect) { + const debouncedModelSelectChange = debounce(onModelSelectChange, 1400); + modelSelect.addEventListener('change', debouncedModelSelectChange); + } + // AllTalk Server version change listener const serverVersionSelect = document.getElementById('server_version'); if (serverVersionSelect) { serverVersionSelect.addEventListener('change', async (event) => { this.settings.server_version = event.target.value; - // Clear and refetch voices if needed if (event.target.value === 'v2') { await this.fetchRvcVoiceObjects(); } this.updateRvcVoiceDropdowns(); this.onSettingsChange(); }); - } + } + // RVC Voice and Pitch listeners const rvcCharacterVoiceSelect = document.getElementById('rvc_character_voice'); if (rvcCharacterVoiceSelect) { rvcCharacterVoiceSelect.addEventListener('change', (event) => { this.settings.rvccharacter_voice_gen = event.target.value; - this.onSettingsChange(); // Save the settings after change + this.onSettingsChange(); }); } @@ -679,7 +676,7 @@ class AllTalkTtsProvider { if (rvcNarratorVoiceSelect) { rvcNarratorVoiceSelect.addEventListener('change', (event) => { this.settings.rvcnarrator_voice_gen = event.target.value; - this.onSettingsChange(); // Save the settings after change + this.onSettingsChange(); }); } @@ -687,7 +684,7 @@ class AllTalkTtsProvider { if (rvcCharacterPitchSelect) { rvcCharacterPitchSelect.addEventListener('change', (event) => { this.settings.rvc_character_pitch = event.target.value; - this.onSettingsChange(); // Save the settings after change + this.onSettingsChange(); }); } @@ -695,59 +692,34 @@ class AllTalkTtsProvider { if (rvcNarratorPitchSelect) { rvcNarratorPitchSelect.addEventListener('change', (event) => { this.settings.rvc_narrator_pitch = event.target.value; - this.onSettingsChange(); // Save the settings after change + this.onSettingsChange(); }); - } - - const debouncedModelSelectChange = (event) => { - clearTimeout(debounceTimeout); - debounceTimeout = setTimeout(() => { - onModelSelectChange(event); - }, debounceDelay); - }; - - // Switch Model Listener - const modelSelect = document.getElementById('switch_model'); - if (modelSelect) { - // Remove the event listener if it was previously added - modelSelect.removeEventListener('change', debouncedModelSelectChange); - // Add the debounced event listener - modelSelect.addEventListener('change', debouncedModelSelectChange); } // DeepSpeed Listener const deepspeedCheckbox = document.getElementById('deepspeed'); if (deepspeedCheckbox) { - deepspeedCheckbox.addEventListener('change', async (event) => { + const handleDeepSpeedChange = async (event) => { const deepSpeedValue = event.target.checked ? 'True' : 'False'; - // Set status to Processing updateStatus('Processing'); try { const response = await fetch(`${this.settings.provider_endpoint}/api/deepspeed?new_deepspeed_value=${deepSpeedValue}`, { - method: 'POST' + method: 'POST', }); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } const data = await response.json(); - console.log("POST response data:", data); // Debugging statement - // Set status to Ready if successful + console.log('POST response data:', data); updateStatus('Ready'); } catch (error) { - console.error("POST request error:", error); // Debugging statement - // Set status to Error in case of failure + console.error('POST request error:', error); updateStatus('Error'); } - }); - } - - function lowvramdebounce(func, delay) { - let debounceTimer; - return function (...args) { - const context = this; - clearTimeout(debounceTimer); - debounceTimer = setTimeout(() => func.apply(context, args), delay); }; + + const debouncedHandleDeepSpeedChange = debounce(handleDeepSpeedChange, 300); + deepspeedCheckbox.addEventListener('change', debouncedHandleDeepSpeedChange); } // Low VRAM Listener @@ -755,38 +727,33 @@ class AllTalkTtsProvider { if (lowVramCheckbox) { const handleLowVramChange = async (event) => { const lowVramValue = event.target.checked ? 'True' : 'False'; - // Set status to Processing updateStatus('Processing'); try { const response = await fetch(`${this.settings.provider_endpoint}/api/lowvramsetting?new_low_vram_value=${lowVramValue}`, { - method: 'POST' + method: 'POST', }); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } const data = await response.json(); - console.log("POST response data:", data); // Debugging statement - // Set status to Ready if successful + console.log('POST response data:', data); updateStatus('Ready'); } catch (error) { - console.error("POST request error:", error); // Debugging statement - // Set status to Error in case of failure + console.error('POST request error:', error); updateStatus('Error'); } }; - const debouncedHandleLowVramChange = lowvramdebounce(handleLowVramChange, 300); // Adjust delay as needed - + const debouncedHandleLowVramChange = debounce(handleLowVramChange, 300); lowVramCheckbox.addEventListener('change', debouncedHandleLowVramChange); } - - // Narrator Voice Dropdown Listener + // Other listeners without debounce since they don't need it const narratorVoiceSelect = document.getElementById('narrator_voice'); if (narratorVoiceSelect) { narratorVoiceSelect.addEventListener('change', (event) => { this.settings.narrator_voice_gen = `${event.target.value}`; - this.onSettingsChange(); // Save the settings after change + this.onSettingsChange(); }); } @@ -794,7 +761,7 @@ class AllTalkTtsProvider { if (textNotInsideSelect) { textNotInsideSelect.addEventListener('change', (event) => { this.settings.text_not_inside = event.target.value; - this.onSettingsChange(); // Save the settings after change + this.onSettingsChange(); }); } @@ -809,15 +776,10 @@ class AllTalkTtsProvider { const narratorOption = event.target.value; this.settings.narrator_enabled = narratorOption; - // Check if narrator is disabled const isNarratorDisabled = narratorOption === 'false'; textNotInsideSelect.disabled = isNarratorDisabled; narratorVoiceSelect.disabled = isNarratorDisabled; - console.log(`Narrator option: ${narratorOption}`); - console.log(`textNotInsideSelect disabled: ${textNotInsideSelect.disabled}`); - console.log(`narratorVoiceSelect disabled: ${narratorVoiceSelect.disabled}`); - if (narratorOption === 'true') { ttsPassAsterisksCheckbox.checked = false; $('#tts_pass_asterisks').click(); @@ -842,47 +804,44 @@ class AllTalkTtsProvider { }); } - // Event Listener for AT Generation Method Dropdown const atGenerationMethodSelect = document.getElementById('at_generation_method'); - const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled'); if (atGenerationMethodSelect) { atGenerationMethodSelect.addEventListener('change', (event) => { const selectedMethod = event.target.value; if (selectedMethod === 'streaming_enabled') { // Disable and unselect AT Narrator - atNarratorEnabledSelect.disabled = true; - atNarratorEnabledSelect.value = 'false'; + atNarratorSelect.disabled = true; + atNarratorSelect.value = 'false'; textNotInsideSelect.disabled = true; narratorVoiceSelect.disabled = true; } else if (selectedMethod === 'standard_generation') { // Enable AT Narrator - atNarratorEnabledSelect.disabled = false; + atNarratorSelect.disabled = false; } - this.settings.at_generation_method = selectedMethod; // Update the setting here - this.onSettingsChange(); // Save the settings after change + this.settings.at_generation_method = selectedMethod; + this.onSettingsChange(); }); } - // Listener for Language Dropdown + // Language Dropdown Listener const languageSelect = document.getElementById('language_options'); if (languageSelect) { languageSelect.addEventListener('change', (event) => { this.settings.language = event.target.value; - this.onSettingsChange(); // Save the settings after change + this.onSettingsChange(); }); } - // Listener for AllTalk Endpoint Input + // AllTalk Endpoint Input Listener const atServerInput = document.getElementById('at_server'); if (atServerInput) { atServerInput.addEventListener('input', (event) => { this.settings.provider_endpoint = event.target.value; - this.onSettingsChange(); // Save the settings after change + this.onSettingsChange(); }); } - } //#############################// @@ -900,7 +859,7 @@ class AllTalkTtsProvider { this.settings.rvc_character_voice = $('#rvc_character_voice').val(); this.settings.rvc_narrator_voice = $('#rvc_narrator_voice').val(); this.settings.rvc_character_pitch = $('#rvc_character_pitch').val(); - this.settings.rvc_narrator_pitch = $('#rvc_narrator_pitch').val(); + this.settings.rvc_narrator_pitch = $('#rvc_narrator_pitch').val(); this.settings.narrator_voice_gen = $('#narrator_voice').val(); // Save the updated settings saveTtsProviderSettings(); @@ -931,44 +890,44 @@ class AllTalkTtsProvider { try { // Prepare data for POST request const postData = new URLSearchParams(); - postData.append("voice", `${voiceName}`); - + postData.append('voice', `${voiceName}`); + // Add RVC parameters for V2 if applicable if (this.settings.server_version === 'v2' && this.settings.rvc_character_voice !== 'Disabled') { - postData.append("rvccharacter_voice_gen", this.settings.rvc_character_voice); - postData.append("rvccharacter_pitch", this.settings.rvc_character_pitch || "0"); + postData.append('rvccharacter_voice_gen', this.settings.rvc_character_voice); + postData.append('rvccharacter_pitch', this.settings.rvc_character_pitch || '0'); } - + // Making the POST request const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, { - method: "POST", + method: 'POST', headers: { - 'Content-Type': 'application/x-www-form-urlencoded' + 'Content-Type': 'application/x-www-form-urlencoded', }, body: postData, }); - + if (!response.ok) { const errorText = await response.text(); - console.error(`[previewTtsVoice] Error Response Text:`, errorText); + console.error('previewTtsVoice Error Response Text:', errorText); throw new Error(`HTTP ${response.status}: ${errorText}`); } - + const data = await response.json(); if (data.output_file_url) { // Handle V1/V2 URL differences const fullUrl = this.settings.server_version === 'v1' ? data.output_file_url : `${this.settings.provider_endpoint}${data.output_file_url}`; - + const audioElement = new Audio(fullUrl); - audioElement.play().catch(e => console.error("Error playing audio:", e)); + audioElement.play().catch(e => console.error('Error playing audio:', e)); } else { - console.warn("[previewTtsVoice] No output file URL received in the response"); - throw new Error("No output file URL received in the response"); + console.warn('previewTtsVoice No output file URL received in the response'); + throw new Error('No output file URL received in the response'); } } catch (error) { - console.error("[previewTtsVoice] Exception caught during preview generation:", error); + console.error('previewTtsVoice Exception caught during preview generation:', error); throw error; } } @@ -1003,7 +962,7 @@ class AllTalkTtsProvider { if (this.settings.at_generation_method === 'streaming_enabled') { // Construct the streaming URL const streamingUrl = `${this.settings.provider_endpoint}/api/tts-generate-streaming?text=${encodeURIComponent(inputText)}&voice=${encodeURIComponent(voiceId)}&language=${encodeURIComponent(this.settings.language)}&output_file=stream_output.wav`; - console.log("Streaming URL:", streamingUrl); + console.log('Streaming URL:', streamingUrl); // Return the streaming URL directly return streamingUrl; @@ -1017,7 +976,7 @@ class AllTalkTtsProvider { return audioResponse; // Return the fetch response directly } } catch (error) { - console.error("Error in generateTts:", error); + console.error('Error in generateTts:', error); throw error; } } @@ -1030,30 +989,30 @@ class AllTalkTtsProvider { async fetchTtsGeneration(inputText, voiceId) { const requestBody = new URLSearchParams({ 'text_input': inputText, - 'text_filtering': "standard", + 'text_filtering': 'standard', 'character_voice_gen': voiceId, 'narrator_enabled': this.settings.narrator_enabled, 'narrator_voice_gen': this.settings.narrator_voice_gen, 'text_not_inside': this.settings.at_narrator_text_not_inside, 'language': this.settings.language, - 'output_file_name': "st_output", - 'output_file_timestamp': "true", - 'autoplay': "false", - 'autoplay_volume': "0.8" + 'output_file_name': 'st_output', + 'output_file_timestamp': 'true', + 'autoplay': 'false', + 'autoplay_volume': '0.8', }); - + // Add RVC parameters only for V2 if (this.settings.server_version === 'v2') { if (this.settings.rvc_character_voice !== 'Disabled') { requestBody.append('rvccharacter_voice_gen', this.settings.rvc_character_voice); - requestBody.append('rvccharacter_pitch', this.settings.rvc_character_pitch || "0"); + requestBody.append('rvccharacter_pitch', this.settings.rvc_character_pitch || '0'); } if (this.settings.rvc_narrator_voice !== 'Disabled') { requestBody.append('rvcnarrator_voice_gen', this.settings.rvc_narrator_voice); - requestBody.append('rvcnarrator_pitch', this.settings.rvc_narrator_pitch || "0"); + requestBody.append('rvcnarrator_pitch', this.settings.rvc_narrator_pitch || '0'); } } - + try { const response = await doExtrasFetch( `${this.settings.provider_endpoint}/api/tts-generate`, @@ -1063,25 +1022,25 @@ class AllTalkTtsProvider { 'Content-Type': 'application/x-www-form-urlencoded', 'Cache-Control': 'no-cache', }, - body: requestBody - } + body: requestBody, + }, ); - + if (!response.ok) { const errorText = await response.text(); - console.error(`[fetchTtsGeneration] Error Response Text:`, errorText); + console.error('fetchTtsGeneration Error Response Text:', errorText); throw new Error(`HTTP ${response.status}: ${errorText}`); } const data = await response.json(); - + // Handle V1/V2 URL differences const outputUrl = this.settings.server_version === 'v1' ? data.output_file_url // V1 returns full URL : `${this.settings.provider_endpoint}${data.output_file_url}`; // V2 returns relative path - + return outputUrl; } catch (error) { - console.error("[fetchTtsGeneration] Exception caught:", error); + console.error('[fetchTtsGeneration] Exception caught:', error); throw error; } }